#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就是一个类,在开发中常用语封装数据。具有如下特性
-
需要实现接口: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,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语句提交一次事务。
- 手动提交:先开启,再提交
-
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 窗口?–等待结束,可以进行操作