后程事务
数据库事务
DAO及其实现类
数据库连接池
Apache-DButils实现CRUD操作
QueryRunner类的使用
数据库事务
try...catch...
和throws
使用的选择:
-
单独的方法出现的异常使用
throws
-
当多个方法组合使用的时候使用
try...catch...
,当方法一抛出异常的时候捕获异常执行方法二
经典模拟场景--->转账
AA给BB转账--->数据库事务
/*
1、针对于数据表账户表
2、用户AA给用户BB转账100
3、sql语句
1、update user_table set balance = balance - 100 where user = 'AA';
2、update user_table set balance = balance + 100 where user = 'BB';
*/
如果发生了异常,那么就会导致数据的最终不一致
/*
1、针对于数据表账户表
2、用户AA给用户BB转账100
3、sql语句
1、update user_table set balance = balance - 100 where user = 'AA';
2、update user_table set balance = balance + 100 where user = 'BB';
*/
事务的概念:
-
一组逻辑操作单元,使数据从一种状态变换到另一种状态--->称为一个数据库事务
事务处理(事务操作)的特点:
-
一个事务执行多个操作时,要么所有的事务都被提交(Commit)
-
有一个事务提交失败整个事务就进行回滚(Rollback)
确保数据的一致性的特点:
-
数据的操纵应当是
离散的成组的
逻辑单元 -
整个单元当中有一部分失败应当视为事务的失败,数据全部回滚
/*
什么是数据库事务?
1、一组逻辑操作单元:一个或多个DML操作
1、刚才的AA给BB转账:涉及两个DML操作。是一个事务
2、AA给BB转账,CC给DD转账这是两个事务
3、AA给BB转账,CC给BB转账这也是两个事务。
2、事务处理的原则:
1、事务作为一个工作单元执行,出现了故障也不能改变这种执行方式--->整体提交or事务回滚
事务回滚机制:
1、事务回滚只回滚到最近的commit操作--->数据一旦提交就不可回滚
2、需要关注哪些操作会导致数据的自动提交
1、DDL操作一旦执行都会自动提交--->set autocommit = false对于DDL操作失效(DDL操作--->创建一个表、修改一个表中的字段、删除一张表独立的就相当于是一个事务(DDL操作)
2、DML(增删改查)默认的情况下,一旦执行,自动提交--->可以通过set autocommit = false的方式取消DML操作的自动提交
3、关闭连接的时候也会将没有提交的数据进行自动提交--->关闭连接会进行一次commit--->在执行(增删改查)的操作的时候不需要关闭数据库连接。保证多个DML操作作为一个事务进行提交
1、获取链接
2、进行第一个DML操作
3、不关闭连接进行第二个DML操作
调用方法的时候外部传入一个数据库连接
防止事务的自动提交就要避免上诉的三件事
*/
示例代码:
统一的驱动连接调用
//外部传入连接
//通用的增删改查方法--->version 1.0
public int update(Connection conn,String sql, Object ...args) {
PreparedStatement ps = null;
try {
//获取链接
conn = JDBCUtils.getConnection();
//预编译sql
ps = conn.prepareStatement(sql);
//填充占位符--->使用流的形式
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
//执行语句
return ps.executeUpdate();
}catch (Exception e) {
e.printStackTrace();
}finally {
//关闭资源
/*
1、由于是外部传入的连接,所以不需要关闭Connection连接
*/
JDBCUtils.closeResource(null, ps);
}
return 0;
}
操作过程:
//#########考虑数据库事务后的转账操作##########
注意:
-
如果
Connection
没有被关闭,需要恢复自动提交状态。使用setAutoCommit(true)
方法。 -
使用数据库连接池的时候执行
close()
方法前需恢复自动提交状态
数据库事务的ACID属性
原子性(Atomicity)
一致性(Consistency)
隔离性(Isolation)
持久性(Durability)
原子性
概念:
事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生
一致性
概念:
事务必须使数据库从一个一致性状态变换到另一个一致性状态
隔离性
概念:
一个事务的执行不能被其他事务干扰。
-
一个事务内部的操作及使用的数据并对并发的其他事务是隔离的
-
并发执行的各个事务之间不能互相干扰
特点:
-
数据库系统必须具有隔离并发运行各个事务的能力,不会互相影响。避免各种并发问题
-
隔离级别越高,数据一致性越好,并发性越弱
持久性
数据库的并发问题:
发生场景:
同时运行多个事务,事务访问数据库的相同数据。没有采取隔离机制就会导致并发问题。
并发问题分类:
脏读:两个事务T1和T2。T1读取已经被T2更新但还没有被提交的字段。此时T2进行回滚操作。T1读取到的数据就是临时且无效的。
不可重复读:两个事务T1和T2。T1读取了一个字段,然后T2更新了该字段,T1再次读取同一个字段,值就不同了--->针对update操作
幻读:两个事务T1和T2。T1从一个表中读取了一个字段,然后T2在该表中插入了一些新的行,如果T1再次读同一个表,会多出几行。--->针对insert操作
四种隔离级别
隔离级别 | 描述 |
---|---|
Read Uncommitted |
允许事务读取未被其他事务提交的变更。脏读、不可重复读、幻读问题都会出现 |
Read Commited |
只允许事务读取已经被其他事务提交的变更。可避免脏读但不可重复读和幻读问题仍可能出现 |
Repeatable Read (可重复读) |
确保事务可以多次从一个字段中读取相同的值。在该事务持续期间禁止其他事物对该字段进行更新。避免脏读、不可重复读但幻读仍然存在 |
Serializable (串行化) |
确保事务可以从一个表中读取相同的行,事务持续期间禁止其他事物对该表执行插入、更新、删除操作所。所有并发问题都可以避免,但性能十分低下 |
MySql默认的事务隔离级别:
Repeatable Read
--->并发性较差,一致性较好
持久性
概念:
一个事务一旦被提交,对数据库中数据的改变是永久性的。接下来的其他操作和数据库故障不会对其有任何影响
MySql中设置隔离级别
启动mysql的特点:
-
每启动一个
mysql
程序就会获得一个单独的数据库连接 -
每个数据库连接都有一个全局变量
@@tx_isolation
表示当前的事务隔离级别
查看当前的隔离级别:
SELECT @@tx_isolation;
设置当前mysql
链接的隔离级别:
set transaction isolation level read committed;
/*
set + 事务单词(transaction) + 隔离级别单词(isolation level) + 要设置成的隔离级别单词(read committed)
*/
设置数据库系统的全局的隔离级别:
set global transaction isolation level read committed;
/*
原有的语法上在事务前面加上adj.全面的
此时需要退出客户端在重新进入客户端
*/
创建数据库用户:
create user xxx identified by aaa;
#设置用户名:xxx,密码:aaa
#这时候只会默认提供一个数据库的权限给到该用户--->information_schema
授予权限:
#授权通过网络方式登录xxx账户,有所有库所有表的全部权限,设置密码
grant all privileges on *.* to xxx@'%' identified by '密码';
#给xxx用户使用本地命令行方式,授予test这个库下的所有表的增删改查权限
grant select,insert,delete,update on xxx.* to xxx@localhost identified by '密码'
/*
grant关键字 + 要赋予的权限 + 数据库.表名(*代表全部表) + to(表示给到哪个用户) + 用户名 + identified by + 密码
*/
先设置事务不自动提交:
set autocommit = false;
在进行查询(此时的事务并没有自动提交):
#使用root用户进行查询--->已经设置autocommit = false;
select * from user_table where `user` = 'cc';
#在使用junkingboy用户进行修改--->已经设置autocommit = false;其目的是为了测试mysql的默认事务隔离级别
update user_table set `balance` = 3000 where `user` = 'cc';
#此时事务未曾提交。root用户查出来的结果还是2000,junkingboy用户可以查出结果为3000。
#因为mysql默认的隔离级别是可重复读。所以即便junkingboy提交了事务但是root没有提交事务root用户查询的结果依旧不会是3000而是2000.
#使用root用户提交事务,这样就可以查询出junkingboy用户修改以后的数据
修改全局事务的隔离级别:
set global transaction isolation level read committed;
#重新进入两个用户的客户端
#设置不自动提交事务
#在junkingboy用户中提交update事务,然后再root用户中再进行读取
#root用户未提交事务,junkingboy用户提交了事务。那么再root得事务当中可以读取到junkingboy提交以后得数据。
#没有解决不可重复读得问题
再次修改全局事务隔离级别:
set global transaction isolation level read uncommitted;
#重新进入客户端
#设置不自动提交事务
#在junkingboy用户中提交update事务,然后再root用户中再进行读取
#root用户未提交事务,junkingboy用户也未提交了事务。那么再root得事务当中可以读取到junkingboy提交以后得数据。
#没有解决脏读的问题
Java
代码层面设置事务隔离级别:--->该设置的隔离级别是针对连接的全局隔离级别
统一的查询方法:
/*
1、将其设置为事务的处理方式。--->将事务作为参数传入函数当中
注意:
1、不要再次创建链接Connection
2、关闭的时候不要关闭连接
通用查询操作,用于返回数据表中的一条数据(考虑事务操作)
*/
public <T> T getInstance(Connection conn, Class<T> clazz, String sql, Object ...args) {
// Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// //获取数据库连接
// conn = JDBCUtils.getConnection();
//预编译sql
ps = conn.prepareStatement(sql);
//填充占位符
for (int i=0; i<args.length; i++) {
ps.setObject(i+1, args[i]);
}
//执行sql保存为结果集对象
rs = ps.executeQuery();
//获取结果集元数据
ResultSetMetaData rsmd = rs.getMetaData();
//获取列数
int columnCount = rsmd.getColumnCount();
//获取结果集
if (rs.next()) {
//通过反射获取运行时加载类建立对象的引用--->反射+泛型
T t = clazz.newInstance(); //--->任何一个类在提供一个JavaBean对象的时候要提供一个空参的public权限的构造器,在这里使用
/*
方法当中返回一个t
t由当前类决定的
*/
//动态的获取列,列的数量为列数
for (int j=0; j<columnCount; j++) {
//动态的获取列值--->结果集当中获取列值
Object columnValue = rs.getObject(j+1);
//获取每列的列名
String columnLabel = rsmd.getColumnLabel(j+1);
//动态获取加载的类的属性--->获取到域(T类型的)
Field field = clazz.getField(columnLabel);
//设置私有属性可访问
field.setAccessible(true);
//将对象属性设置成列值
field.set(t, columnValue);
}
return t;
}
}catch (Exception e) {
e.printStackTrace();
}finally {
//关闭资源--->注意不要关闭连接
JDBCUtils.closeResource(null, ps, rs);
}
return null;
}
查询操作:
//针对同一张表的同一个数据进行操作。演示事务隔离级别
@Test
public void testTransactionSelect() throws Exception {
Connection conn = JDBCUtils.getConnection();
//查看数据库隔离级别
System.out.println(conn.getTransactionIsolation());
//设置数据库的隔离级别
conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
//取消自动提交事务
conn.setAutoCommit(false);
//调用查询方法。因为返回的是一个对象。所以需要新建一个JavaBean的类--->返回的是一个User类对象
//sql语句
String sql = "select user, password, balance from user_table where user = ?;";
//注意填充占位符
User user = getInstance(conn, User.class, sql, "cc");
System.out.println(user);
}
修改操作:
@Test
public void testTransactionUpdate() throws Exception {
//获取连接
Connection conn = JDBCUtils.getConnection();
//调用update方法
TransactionTest tt = new TransactionTest();
//取消自动提交事务
conn.setAutoCommit(false);
//sql
String sql = "update user_table set balance = ? where user = ?;";
tt.update(conn, sql, 5000,"CC");
//线程等待15s
Thread.sleep(15000);
System.out.println("修改结束!");
}
理解脏读、不可重复读、幻读
脏读
脏读是指:
两个事务A和B,同时设置不自动提交。A读取表中字段1,未提交事务,同时B修改表中字段1,未提交事务。此时A再去查询字段1(还在当前事务内)查询到的结果是B修改后的结果。
幻读
幻读是指:
两个事务A和B,同时设置不自动提交。A读取表中字段1,未提交事务,同时B修改表中字段1并且提交事务。此时A再去查询字段1(还在当前事务内)查询到的结果是B修改后的结果。
不可重复读
不可重复读是指:
两个事务A和B,同时设置不自动提交。A读取表中字段1,未提交事务,同时B修改表中字段1未曾提交事务。此时A再去查询字段1(还在当前事务内)查询到的结果是未修改的结果。此时B提交事务,A再去查询字段1(还在当前事务内)查询到的结果还是未修改的结果。