DBUtils&事务

  • Post author:
  • Post category:其他


#DBUtils&事务


  • 掌握DBUtils实现增删改

  • 掌握DBUtils实现查询

  • 理解事务的概念

  • 理解脏读,不可重复读,幻读的概念及解决办法

  • 能够在MySQL中使用事务

  • 能够在JDBC中使用事务

  • 能够在DBUtils中使用事务



第一章 DBUtils

如果只使用JDBC进行开发,我们会发现冗余代码过多,为了简化JDBC开发,本案例我们讲采用apache commons组件一个成员:DBUtils。

DBUtils就是JDBC的简化开发工具包。需要项目导入commons-dbutils-1.6.jar才能够正常使用DBUtils工具。



1.1 概述

DBUtils是java编程中的数据库操作实用工具,小巧简单实用。DBUtils封装了对JDBC的操作,简化了JDBC操作,可以少写代码。

Dbutils三个核心功能介绍

  • QueryRunner中提供对sql语句操作的API.

  • ResultSetHandler接口,用于定义select操作后,怎样封装结果集.

  • DbUtils类,它就是一个工具类,定义了关闭资源与事务处理的方法



1.2 准备数据

  • 创建表:
create table product(
	pid int primary key,
	pname varchar(20),
	price double,
	category_id varchar(32)
);
  • 插入表记录
INSERT INTO product(pid,pname,price,category_id) VALUES(1,'联想',5000,'c001');
INSERT INTO product(pid,pname,price,category_id) VALUES(2,'海尔',3000,'c001');
INSERT INTO product(pid,pname,price,category_id) VALUES(3,'雷神',5000,'c001');

INSERT INTO product(pid,pname,price,category_id) VALUES(4,'JACK JONES',800,'c002');
INSERT INTO product(pid,pname,price,category_id) VALUES(5,'真维斯',200,'c002');
INSERT INTO product(pid,pname,price,category_id) VALUES(6,'花花公子',440,'c002');
INSERT INTO product(pid,pname,price,category_id) VALUES(7,'劲霸',2000,'c002');

INSERT INTO product(pid,pname,price,category_id) VALUES(8,'香奈儿',800,'c003');
INSERT INTO product(pid,pname,price,category_id) VALUES(9,'相宜本草',200,'c003');
INSERT INTO product(pid,pname,price,category_id) VALUES(10,'面霸',5,'c003');

INSERT INTO product(pid,pname,price,category_id) VALUES(11,'好想你枣',56,'c004');
INSERT INTO product(pid,pname,price,category_id) VALUES(12,'香飘飘奶茶',1,'c005');

INSERT INTO product(pid,pname,price,category_id) VALUES(13,'果9',1,NULL);



1.3 QueryRunner核心类介绍



1.3.1 提供数据源

  • 构造方法


    • QueryRunner(DataSource)

      创建核心类,并提供数据源,内部自己维护Connection
  • 普通方法


    • update(String sql , Object ... params)

      执行DML语句

    • query(String sql , ResultSetHandler , Object ... params)

      执行DQL语句,并将查询结果封装到对象中。



1.3.2 提供连接

  • 构造方法


    • QueryRunner()

      创建核心类,

      没有

      提供数据源,在进行具体操作时,需要手动提供Connection
  • 普通方法


    • update(Connection conn , String sql , Object ... params)

      使用提供的Connection,完成DML语句

    • query(Connection conn , String sql , ResultSetHandler , Object ... params)

      使用提供的Connection,执行DQL语句,并将查询结果封装到对象中。



1.4 QueryRunner实现添加、更新、删除操作


  • update(String sql, Object... params)

    用来完成表数据的增加、删除、更新操作



1.4.1 添加

public void insert() throws SQLException{
	//获取一个用来执行SQL语句的对象   QueryRunner
	QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource());
		
	String sql = " INSERT INTO product(pid,pname,price,category_id) VALUES(?,?,?,?);";
	Object[] params = {100,"百岁山", 5500, "c005"};
	int line = qr.update(sql,params);// 用来完成表数据的增加、删除、更新操作
	//结果集处理
	System.out.println("line = " + line);
}



1.4.2 更新

@Test
public void update() throws SQLException{
	//1 核心类
	QueryRunner queryRunner = new QueryRunner(C3P0Utils.getDataSource());
	//2 准备sql语句
	String sql = "update product set pname=?,price=?,category_id=? where pid=?";
	
	//3 准备实际参数
	Object[] params = {"芒果99","998","c009",13};
	
	//4 执行
	int r = queryRunner.update(sql, params);
	System.out.println(r);
	
}



1.4.3 删除

@Test
public void delete() throws SQLException{
	
	QueryRunner queryRunner = new QueryRunner(C3P0Utils.getDataSource());
	String sql = "delete from product where pid = ?";
	Object[] params = {99};
	int r = queryRunner.update(sql, params);
	System.out.println(r);
			
}



1.5 QueryRunner实现查询操作


  • query(String sql, ResultSetHandler<T> rsh, Object... params)

    用来完成表数据的查询操作



1.5.1ResultSetHandler 结果集

  • BeanHandler:将结果集中第一条记录封装到一个指定的javaBean中。
  • BeanListHandler:将结果集中每一条记录封装到指定的javaBean中,将这些javaBean在封装到List集合中
  • ScalarHandler:它是用于单数据。例如select count(*) from 表操作。
  • ColumnListHandler:将结果集中指定的列的字段值,封装到一个List集合中



JavaBean

JavaBean就是一个类,在开发中常用语封装数据。具有如下特性

  1. 需要实现接口:java.io.Serializable ,通常实现接口这步骤省略了,不会影响程序。

  2. 提供私有字段:private 类型 字段名;

  3. 提供getter/setter方法:

  4. 提供无参构造

public class Product {
	
	private String pid;
	private String pname;
	private Double price;
	private String category_id;

	//省略 getter和setter方法
}



BeanHandler

/*
 * 查询数据表结果集处理其中一种方式:
 * BeanHandler处理方式
 *     将数据表的结果集第一行数据,封装成JavaBean类的对象
 * 构造方法:
 *     BeanHandler(Class<T> type) 
 *     传递一个Class类型对象,将结果封装到哪个类的对象呢
 *     ZhangWu类的Class对象
 */
@Test
public void demo01() throws SQLException{
	// 通过id查询详情,将查询结果封装到JavaBean product
	
	//1核心类 
	QueryRunner queryRunner = new QueryRunner(C3P0Utils.getDataSource());
	//2 sql语句
	String sql = "select * from product where pid = ?";
	//3 实际参数
	Object[] params = {6};
	//4 查询并封装
	Product product = queryRunner.query(sql, new BeanHandler<Product>(Product.class), params);
	
	System.out.println(product);
}



BeanListHandler

/*
 * 查询数据表结果集处理其中一种方式:
 * BeanListHandler处理方式
 *     将数据表的每一行数据,封装成JavaBean类对象
 *     多行数据了,多个JavaBean对象,存储List集合
 */
@Test 
public void demo02() throws SQLException{
	//查询所有,将每一条记录封装到一个JavaBean,然后将JavaBean添加到List中,最后返回List,BeanListHandler
	QueryRunner queryRunner = new QueryRunner(C3P0Utils.getDataSource());
	String sql = "select * from product";
	Object[] params = {};
	List<Product> list = queryRunner.query(sql, new BeanListHandler<Product>(Product.class), params);
	
	for(Product product : list){
		System.out.println(product);
	}
}



ScalarHander

/*
 * 查询数据表结果集处理其中一种方式:
 * ScalarHandler处理方式
 *     处理单值查询结果,执行的select语句后,结果集只有1个
 */
@Test 
public void demo03() throws SQLException{
	// ScalarHandler : 用于处理聚合函数执行结果(一行一列)
	// * 查询总记录数
	QueryRunner queryRunner = new QueryRunner(C3P0Utils.getDataSource());
	String sql = "select count(*) from product";
	
	Long obj = queryRunner.query(sql, new ScalarHandler<Long>());
	
	//System.out.println(obj.getClass());
	System.out.println(obj);
	
}



ColumnListHandler

/*
* 查询数据表结果集处理其中一种方式:
* ColumnListHandler处理方式
*     将查询数据表结果集中的某一列数据,存储到List集合
*     哪个列不清楚,数据类型也不清楚, List<Object>
* ColumnListHandler构造方法
*     空参数: 获取就是数据表的第一列
*     int参数: 传递列的顺序编号
*     String参数: 传递列名
*    
*  创建对象,可以加入泛型,但是加入的数据类型,要和查询的列类型一致
*/
@Test 
public void demo04() throws SQLException{
	// ColumnListHandler : 查询指定一列数据
	QueryRunner queryRunner = new QueryRunner(C3P0Utils.getDataSource());
	String sql = "select * from product";
	List<String> list = queryRunner.query(sql, new ColumnListHandler<String>("pname"));

	System.out.println(list);
}



1.6 小结

DBUtils工具

  • 作用:简化JDBC的操作

DBUtils常用类与方法

  • QueryRunner 用来执行SQL语句对象

    • update(Connection conn, String sql, Object… params) 插入表记录、更新表记录、删除表记录
    • query(Connection conn, String sql, ResultSetHandler handler, Object… params) 查询表记录
  • ResultSetHandler 处理结果集的对象

    • BeanHandler:将结果集中第一条记录封装到一个指定的javaBean中。
    • BeanListHandler:将结果集中每一条记录封装到指定的javaBean中,将这些javaBean在封装到List集合中
    • ScalarHandler:它是用于单数据。例如select count(*) from 表操作。
    • ColumnListHandler:将结果集中指定的列的字段值,封装到一个List集合中



第二章 事务操作


事务概述

  • 事务指的是逻辑上的一组操作,组成这组操作的各个单元要么全都成功,要么全都失败.
  • 事务作用:保证在一个事务中多次SQL操作要么全都成功,要么全都失败.



2.1 mysql事务操作

sql语句 描述
start transaction 开启事务
commit 提交事务
rollback 回滚事务
  • 准备数据
# 创建一个表:账户表.
create database webdb;
# 使用数据库
use webdb;
# 创建账号表
create table account(
	id int primary key auto_increment,
	name varchar(20),
	money double
);
# 初始化数据
insert into account values (null,'jack',10000);
insert into account values (null,'rose',10000);
insert into account values (null,'tom',10000);
  • 操作

    • MYSQL中可以有两种方式进行事务的管理:

      • 自动提交:MySql默认自动提交。及执行一条sql语句提交一次事务。
      • 手动提交:先开启,再提交
  • 方式1:手动提交
start transaction;
update account set money=money-1000 where name='jack';
update account set money=money+1000 where name='rose';
commit;
#或者
rollback;
  • 方式2:自动提交,通过修改mysql全局变量“autocommit”进行控制
show variables like '%commit%';
* 设置自动提交的参数为OFF:
set autocommit = 0;  -- 0:OFF  1:ON



2.2 jdbc事务操作

Connection 对象的方法名 描述
conn.setAutoCommit(false) 开启事务
conn.commit() 提交事务
conn.rollback() 回滚事务

代码演示

//事务模板代码
public void demo01() throws SQLException{
	// 获得连接
	Connection conn = null;
	
	try {
		//#1 开始事务
		conn.setAutoCommit(false);
		
		//.... 加钱 ,减钱
		
		//#2 提交事务
		conn.commit();
	} catch (Exception e) {
		//#3 回滚事务
		conn.rollback();
	} finally{
		// 释放资源
		conn.close();
	}
}



2.3 DBUtils事务操作

Connection对象的方法名 描述
conn.setAutoCommit(false) 开启事务
new QueryRunner() 创建核心类,不设置数据源(手动管理连接)
query(conn , sql , handler, params ) 或

update(conn, sql , params)
手动传递连接, 执行SQL语句CRUD
DbUtils.commitAndCloseQuietly(conn) 提交并关闭连接,不抛异常
DbUtils.rollbackAndCloseQuietly(conn) 回滚并关闭连接,不抛异常

代码演示

//事务模板代码
public void demo02() throws SQLException{
	// 获得连接
	Connection conn = null;
	
	try {
		//#1 开始事务
		conn.setAutoCommit(false);
		
		//.... 加钱 ,减钱
		
		//#2 提交事务
		DbUtils.ommitAndCloseQuietly(conn); 
	} catch (Exception e) {
		//#3 回滚事务
		DbUtils.rollbackAndCloseQuietly(conn);
        e.printStackTrace();
	} 
}



2.4 案例:JDBC事务分层(dao、service)传递Connection



分析


在这里插入图片描述

  • 开发中,常使用分层思想

    • 不同的层次结构分配不同的解决过程,各个层次间组成严密的封闭系统
  • 不同层级结构彼此平等

  • 分层的目的是:

    • 解耦
    • 可维护性
    • 可扩展性
    • 可重用性
  • 不同层次,使用不同的包表示

    • com.itheima 公司域名倒写
    • com.itheima.dao dao层
    • com.itheima.service service层
    • com.itheima.domain javabean
    • com.itheima.utils 工具



代码实现

  • 工具类C3P0Utils
public class C3P0Utils {
    //创建一个C3P0的连接池对象(使用c3p0-config.xml中default-config标签中对应的参数)
    public static DataSource ds = new ComboPooledDataSource();

    //从池中获得一个连接
    public static Connection getConnection() throws SQLException {
        return ds.getConnection();
    }
}
  • c3p0-config.xml
<?xml version="1.0" encoding="UTF-8"?>

<c3p0-config>
    <!-- 使用默认的配置读取连接池对象 -->
    <default-config>
        <!--  连接参数 -->
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/webdb</property>
        <property name="user">root</property>
        <property name="password">root</property>

        <!-- 连接池参数 -->
        <property name="initialPoolSize">5</property>
        <property name="maxPoolSize">10</property>
        <property name="checkoutTimeout">2000</property>
        <property name="maxIdleTime">1000</property>
    </default-config>
</c3p0-config>
  • 步骤1:编写入口程序
public class App {
    public static void main(String[] args) {
        try {
            String outUser = "jack";
            String inUser = "rose";
            Integer money = 100;
            //2 转账
            AccountService accountService = new AccountService();
            accountService.transfer(outUser, inUser, money);
            //3 提示
            System.out.println("转账成功");
        } catch (Exception e) {
            //3 提示
            System.out.println("转账失败");
            e.printStackTrace();
        }
    }
}
  • service层
public class AccountService {

    /**
     * 事务管理方式:向下传递Connection。有侵入性。使用DBUtils
     * 业务层事务管理转账的方法
     * @param from
     * @param to
     * @param money
     */
    public void transfer(String from, String to, double money) {
        //调用dao层
        AccountDao accountDao = new AccountDao();

        //DBUtils进行事务处理的原理,是在Service层获得连接,以保证事务处理过程中的Connection对象为同一个Connection。
        //因为必须保证连接为同一个连接,所以在业务层获得连接,再将连接传递到持久层,代码具有侵入性。
        //DBUtils使用的方法
        Connection conn = null;

        try {
            //获得连接
            conn = C3P0Utils.getConnection();
            //设置事务不自动提交
            conn.setAutoCommit(false);
            //调用持久层
            accountDao.outMoney(conn,from,money);

            //如果有异常
            //int a = 1 / 0 ;

            accountDao.inMoney(conn,to,money);
            //提交事务,并安静的关闭连接
            DbUtils.commitAndCloseQuietly(conn);
        } catch (SQLException e) {
            //有异常出现时,回滚事务,并安静的关闭连接
            DbUtils.rollbackAndCloseQuietly(conn);
            e.printStackTrace();
        }
    }
}
  • dao层
public class AccountDao {

    /**
     * 付款方法
     * @param conn 连接对象
     * @param from 付款人
     * @param money 金额
     */
    public void outMoney(Connection conn, String from, double money) {
        QueryRunner qr = new QueryRunner();
        try {
            String sql = "update account set money = money - ? where name = ?";
            qr.update(conn, sql, money,from);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 收款方法
     * @param conn 连接对象
     * @param to 收款人
     * @param money 金额
     */
    public void inMoney(Connection conn, String to, double money) {
        QueryRunner qr = new QueryRunner();
        try {
            String sql = "update account set money = money + ? where name = ?";
            qr.update(conn, sql, money,to);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}



第三章 事务总结



3.1 事务特性:ACID

  • 原子性(Atomicity)原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。

  • 一致性(Consistency)事务前后数据的完整性必须保持一致。

  • 隔离性(Isolation)事务的隔离性是指多个用户并发访问数据库时,一个用户的事务不能被其它用户的事务所干扰,多个并发事务之间数据要相互隔离。

  • 持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。



3.2 并发访问问题

如果不考虑隔离性,事务存在3中并发访问问题。

  1. 脏读:一个事务读到了另一个事务未提交的数据.

  2. 不可重复读:一个事务读到了另一个事务已经提交(update)的数据。引发另一个事务,在事务中的多次查询结果不一致。

  3. 虚读 /幻读:一个事务读到了另一个事务已经提交(insert)的数据。导致另一个事务,在事务中多次查询的结果不一致。



3.3 隔离级别:解决问题

  • 数据库规范规定了4种隔离级别,分别用于描述两个事务并发的所有情况。

  1. read uncommitted

    读未提交,一个事务读到另一个事务没有提交的数据。

    a)存在:3个问题(脏读、不可重复读、虚读)。

    b)解决:0个问题


  2. read committed

    读已提交,一个事务读到另一个事务已经提交的数据。

    a)存在:2个问题(不可重复读、虚读)。

    b)解决:1个问题(脏读)


  3. repeatable read

    :可重复读,在一个事务中读到的数据始终保持一致,无论另一个事务是否提交。

    a)存在:1个问题(虚读)。

    b)解决:2个问题(脏读、不可重复读)


  4. serializable 串行化

    ,同时只能执行一个事务,相当于事务中的单线程。

    a)存在:0个问题。

    b)解决:3个问题(脏读、不可重复读、虚读)

  • 安全和性能对比

    • 安全性:

      serializable > repeatable read > read committed > read uncommitted
    • 性能 :

      serializable < repeatable read < read committed < read uncommitted
  • 常见数据库的默认隔离级别:

    • MySql:

      repeatable read
    • Oracle:

      read committed



3.4 演示演示

  • 隔离级别演示参考:资料/隔离级别操作过程.doc【增强内容,了解】

  • 查询数据库的隔离级别

show variables like '%isolation%';
或
select @@tx_isolation;


在这里插入图片描述

  • 设置数据库的隔离级别


    • set session transactionisolation level

      级别字符串
    • 级别字符串:

      readuncommitted



      read committed



      repeatable read



      serializable
    • 例如:

      set session transaction isolation level read uncommitted;
  • 读未提交:readuncommitted

    • A窗口设置隔离级别

      • AB同时开始事务
      • A 查询
      • B 更新,但不提交
      • A 再查询?– 查询到了未提交的数据
      • B 回滚
      • A 再查询?– 查询到事务开始前数据
  • 读已提交:read committed

    • A窗口设置隔离级别

      • AB同时开启事务
      • A查询
      • B更新、但不提交
      • A再查询?–数据不变,解决问题【脏读】
      • B提交
      • A再查询?–数据改变,存在问题【不可重复读】
  • 可重复读:repeatable read

    • A窗口设置隔离级别

      • AB 同时开启事务
      • A查询
      • B更新, 但不提交
      • A再查询?–数据不变,解决问题【脏读】
      • B提交
      • A再查询?–数据不变,解决问题【不可重复读】
      • A提交或回滚
      • A再查询?–数据改变,另一个事务
  • 串行化:serializable

    • A窗口设置隔离级别
    • AB同时开启事务
    • A查询

      • B更新?–等待(如果A没有进一步操作,B将等待超时)
      • A回滚
      • B 窗口?–等待结束,可以进行操作



版权声明:本文为weixin_44654375原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。