#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语句的对象 QueryRunnerQueryRunner 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就是一个类,在开发中常用语封装数据。具有如下特性
-
需要实现接口:java.io.Serializable ,通常实现接口这步骤省略了,不会影响程序。
-
提供私有字段:private 类型 字段名;
-
提供getter/setter方法:
-
提供无参构造
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,BeanListHandlerQueryRunner 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语句提交一次事务。
- 手动提交:先开启,再提交
- MYSQL中可以有两种方式进行事务的管理:
- 方式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中并发访问问题。
-
脏读:一个事务读到了另一个事务未提交的数据.
-
不可重复读:一个事务读到了另一个事务已经提交(update)的数据。引发另一个事务,在事务中的多次查询结果不一致。
-
虚读 /幻读:一个事务读到了另一个事务已经提交(insert)的数据。导致另一个事务,在事务中多次查询的结果不一致。
3.3 隔离级别:解决问题
- 数据库规范规定了4种隔离级别,分别用于描述两个事务并发的所有情况。
-
read uncommitted 读未提交,一个事务读到另一个事务没有提交的数据。
a)存在:3个问题(脏读、不可重复读、虚读)。
b)解决:0个问题
-
read committed 读已提交,一个事务读到另一个事务已经提交的数据。
a)存在:2个问题(不可重复读、虚读)。
b)解决:1个问题(脏读)
-
repeatable read:可重复读,在一个事务中读到的数据始终保持一致,无论另一个事务是否提交。
a)存在:1个问题(虚读)。
b)解决:2个问题(脏读、不可重复读)
-
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
- MySql:
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 再查询?-- 查询到事务开始前数据
- A窗口设置隔离级别
-
读已提交:read committed
- A窗口设置隔离级别
- AB同时开启事务
- A查询
- B更新、但不提交
- A再查询?–数据不变,解决问题【脏读】
- B提交
- A再查询?–数据改变,存在问题【不可重复读】
- A窗口设置隔离级别
-
可重复读:repeatable read
- A窗口设置隔离级别
- AB 同时开启事务
- A查询
- B更新, 但不提交
- A再查询?–数据不变,解决问题【脏读】
- B提交
- A再查询?–数据不变,解决问题【不可重复读】
- A提交或回滚
- A再查询?–数据改变,另一个事务
- A窗口设置隔离级别
-
串行化:serializable
- A窗口设置隔离级别
- AB同时开启事务
- A查询
- B更新?–等待(如果A没有进一步操作,B将等待超时)
- A回滚
- B 窗口?–等待结束,可以进行操作