数据库事务 : 一组逻辑操作单元,使数据从一种形态到另一种形态
**事务处理:**保证所有的事务作为一个工作单位来执行,即使出现了故障都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提交,永久的保存下来,要么所有的事务全部回滚到最初事务开启之前的状态 ( 事务要不全部成功,要不全部失败)
注意点: 事务一旦提交,便无法回滚
什么操作会导致事务提交
- DDL(数据定义语言 如CREATE、ALTER、DROP) 操作会导致事务提交
- DML(数据操纵语言 如SELECT、UPDATE、INSERT、DELETE,)默认执行就提交,不过可以进行修改(通过 set autocommit = false进行修改)
- 默认关闭数据库连接时也会提交
事务代码(错误写法)
// 通用增删改
public static void update(String sql, Object ... args) {
Connection connection = null ;
PreparedStatement ps = null;
try
{
// 1.获取数据库连接
connection = getConnection();
// 2.获取PreparedStatement
ps = connection.prepareStatement(sql);
// 3.填充占位符,
for (int i = 0; i <args.length ; i++)
{
ps.setObject((i+1), args[i]);
}
// 4.执行
ps.execute();
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
// 5.关闭
closes(connection , ps);
}
}
@Test
public void test2(){
String sql1 = "update dome4 set balance = balance -100 where name = ?";
JdbcUtil.update(sql1,"zs");
// 在此过程中模拟中途发送了错误
System.out.println(10/ 0);
String sql2 = "update dome4 set balance = balance +100 where name = ?";
JdbcUtil.update(sql2,"ls");
}
执行前
执行后
正确写法
// 和事务有关的通用增删改
public static void updateTx( Connection connection ,String sql, Object ... args) {
PreparedStatement ps = null;
try
{
// 1.获取PreparedStatement
ps = connection.prepareStatement(sql);
// 2.填充占位符,
for (int i = 0; i <args.length ; i++)
{
ps.setObject((i+1), args[i]);
}
// 3.执行
ps.execute();
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
// 4.关闭
closes(null , ps);
}
}
@Test
public void test1 (){
Connection connection = null;
try
{
// 1.获取连接
connection = JdbcUtil.getConnection();
// 事务 : zs 用户余额减100, ls用户余额加100
// 2.设置事务DML事务不自动提交
connection.setAutoCommit(false);
// 3.开启事务操作
String sql1 = "update dome4 set balance = balance -100 where name = ?";
JdbcUtil.updateTx(connection,sql1,"zs");
// 在此过程中模拟中途发送了错误
System.out.println(10/ 0);
String sql2 = "update dome4 set balance = balance +100 where name = ?";
JdbcUtil.updateTx(connection,sql2,"ls");
System.out.println("转账成功");
// 4.事务处理完成提交事务
connection.commit();
}
catch (Exception e)
{
// 如果出现异常回滚操作
try
{
connection.rollback();
}
catch (SQLException ex)
{
ex.printStackTrace();
}
}
finally
{
JdbcUtil.closes(connection,null);
}
}
执行前
执行后
原因:设置了DML操作数据库的自动提交事务,因为前一种方法我们在每一次执行sql语句的时候就把连接关闭了,关闭连接会自动提交,当是第二种方法,我们在事务全部操作完毕之后再关闭连接,出现错误就回滚事务,所以在出现错误的时候,下面那一条sql语句并没有执行,也并没有提交,并跑到catch里面进行事务的回滚。
事务的ACID属性
- 原子性(Atomicity):指的是事务是一个不可分割的单位,事务中的操作要不都发生,要不都不发生
- 一致性(Consistency):事务必须使数据库从一个一致性到另一个的一致性状态
- 隔离性(Isolation):事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
- 持久性(Durability):持久性是指一个事务一旦被提交 。它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。
数据库并发问题
概念:对于同时运行的多个事务,当这些事务访问数据库中相同的数据时,如果没有采取必要的隔离机制,就会导致各种并发问题。
数据库并发问题:
- 脏读:二个事务T1,T2如果事务T1读取了事务T2更新了并且没有提交的数据,如果T2事务在此时回滚了事务,那么T1读取到T2更新的数据是无效的
- 不可重复读:二个事务T1,T2,当T1得到了T2的数据之后,T2更新了数据,当是T1在去获取相同的字段,获取的值就会有所不同
- 幻读:对于二个事务T1,T2,T1从一个表中读取了一个字段,然后T2在该表中插入了一些新的行,之后,如果T1在去读取那个表就会多出现几行
数据库的四种隔离级别
- 读未提交(Read uncommitted):最低级别的隔离,允许事务读取未被其他事物提交的变更。脏读,不可重复读和幻读的问题都会出现
- 读取已提交(Read committed,Oracle、PostgreSQL、SQL Server默认模式):只允许事务读取已经被其它事务提交的变更.可以避免脏读,但不可重复读和幻读问题仍然可能出现
- 可重复读(Repeatable read,MySQL默认模式):确保事务可以多次从一个字段中读取相同的值在这个事务持续期间,禁止其他事物对这个字段进行更新可以避免脏读和不可重复读,但幻读的问题仍然存在
- 串行化(Serializable,SQLite默认模式):最高级别的隔离,确保事务可以从一个表中读取相同的行在这个事务持续期间,禁止其他事务对该表执行插入,更新和删除操作,所有并发问题都可以避免,但性能十分低下。
注意点 :隔离级别越高,数据一致性就越好, 但并发性越弱。
Java代码层面控制数据库的隔离级别
connection.setTransactionIsolation(Connection.TRANSACTION_NONE);
Connection的五种取值
- TRANSACTION_NONE (0) :表示不支持事务的常量。
- TRANSACTION_READ_UNCOMMITTED (1):读未提交
- TRANSACTION_READ_COMMITTED (2):读取已提交
- TRANSACTION_REPEATABLE_READ (4):可重复读
- TRANSACTION_SERIALIZABLE (8): 串行化