目录
一、何为数据库事务
二、数据库并发问题
三、事务的隔离级别
四、事务类型
五、Spring事务管理
六、使用XML配置JDBC事务
七、tx:method标签设置
八、配置一个CRUD通用的事务配置
九、使用注解配置JDBC事务
事务是一系列操作组成的工作单元,该单元内的操作是不可分割的,即要么所有操作都做,要么所有操作都不做
事务必需满足ACID特性,缺一不可:
1.原子性(Atomicity):事务不可分割的最小工作单元,事务内的操作要么全做,要么全不做
2.一致性(Consistency):在事务执行前数据库的数据处于正确的状态,需事务执行完后数据库的数据依然处于正确的状态,即数据完整性约束没有被破坏,如A给B转帐,不论转帐是否成功,转帐之后的A和B的帐户总额和转帐之前是相同的
3.隔离性(Isolation):当多个事务处于并发访问同一个数据库资源时,事务之间相互影响响度,不同的隔离级别决定了各个事务对数据资源访问的不同行为
4.持久性(Durability):事务一旦执行成功,它对数据库的数据的改变是不可逆的
为了解决这些并发问题,需要通过数据库隔离级别来解决,在标准SQL规范中定义了四种隔离级别:
READ UNCOMMITED < READ COMMITED < REPEATABLE READ < SERIALIZABLE
Oracle支持READ COMMITED(缺省)和SERIALIZABLE
MySql支持四种隔离级别,缺省为REPEATABLE READ
SQL92推荐使用REPEATABLE READ以保证数据的读一致性,不过用户可以根据具体的需求选择适合的事务隔离级别。
默认性况下:Mysql不会出现幻读,除非(select * from 表名 lock in share mode);MySql中锁基于索引机制,也不会出现第一类丢失更新
如何选择:
隔离级别越高,数据库事务并发执行能力越差,能处理的操作越少。因此在实际项目开发中为了考虑并发性能一般使用READ COMMITED,它能避免丢失更新和脏读,尽管不可重复和幻读不能避免,更多的情况下使用悲观锁或乐观锁来解决来解决.
1.悲观锁
悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度(悲观),因此,在整个数据处理过程中,将数据处于锁定状态。 悲观锁的实现,往往依靠数据库提供的锁机制 (也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)
在数据库中,悲观锁的流程如下:
在对任意记录进行修改前,先尝试为该记录加上排他锁(exclusive locking)。
如果加锁失败,说明该记录正在被修改,那么当前查询可能要等待或者抛出异常。 具体响应方式由开发者根据实际需要决定。
如果成功加锁,那么就可以对记录做修改,事务完成后就会解锁了。
其间如果有其他对该记录做修改或加排他锁的操作,都会等待我们解锁或直接抛出异常。
MySQL InnoDB中使用悲观锁:
要使用悲观锁,我们必须关闭mysql数据库的自动提交属性,因为MySQL默认使用autocommit模式,也就是说,当你执行一个更新操作后,MySQL会立刻将结果进行提交。
set autocommit=0;
//0.开始事务
begin;/begin work;/start transaction; (三者选一就可以)
//1.查询出商品信息
select status from t_goods where id=1 for update;
//2.根据商品信息生成订单
insert into t_orders (id,goods_id) values (null,1);
//3.修改商品status为2
update t_goods set status=2;
//4.提交事务
commit;/commit work;
上面的查询语句中,我们使用了select…for update
的方式,这样就通过开启排他锁的方式实现了悲观锁。此时在t_goods表中,id为1的 那条数据就被我们锁定了,其它的事务必须等本次事务提交之后才能执行。这样我们可以保证当前的数据不会被其它事务修改
上面我们提到,使用
select…for update
会把数据给锁住,不过我们需要注意一些锁的级别,MySQL InnoDB默认行级锁。行级锁都是基于索引的,如果一条SQL语句用不到索引是不会使用行级锁的,会使用表级锁把整张表锁住,这点需要注意。
优点:悲观并发控制实际上是“先取锁再访问”的保守策略,为数据处理的安全提供了保证。
不足:但是在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会;另外,在只读型事务处理中由于不会产生冲突,也没必要使用锁,这样做只能增加系统负载;还有会降低了并行性,一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理那行数
2.乐观锁
乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做.
使用乐观锁
数据版本,为数据增加的一个版本标识。当读取数据时,将版本标识的值一同读出,数据每更新一次,同时对版本标识进行更新。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的版本标识进行比对,如果数据库表当前版本号与第一次取出来的版本标识值相等,则予以更新,否则认为是过期数据
1.查询出商品信息
select (status,status,version) from t_goods where id=#{id}
2.根据商品信息生成订单
3.修改商品status为2
update t_goods
set status=2,version=version+1
where id=#{id} and version=#{version};
优点:乐观并发控制相信事务之间的数据竞争(data race)的概率是比较小的,因此尽可能直接做下去,直到提交的时候才去锁定,所以不会产生任何锁和死锁。
不足:但如果直接简单这么做,还是有可能会遇到不可预期的结果,例如两个事务都读取了数据库的某一行,经过修改以后写回数据库,这时就遇到了问题。
1、划分本地事务和分布式事务:
1)本地事务:就是普通事务,能保证单台数据库上的操作的ACID,被限定在一台数据库上
2)分布式事务:涉及多个数据源的事务,即跨越多台同类或异类数据库的事务(由每台数据库的本地事务组务),分布式事务旨在保证这些本地事务的所有操作的ACID,使事务可以跨越多台数据库;
2、划分JDBC事务和JTA事务:
1)JDBC事务:就是数据库事务中的本地事务。通过Connection对象的控制来管理事务
2)JTA指(Java Transaction API),是Java EE数据库事务规范,JTA只提供了事务管理接口,由应用程序服务器厂商提供实现 ,JTA事务比JDBC更强大,支持分布式事务
3、按是否通过编程实现事务:
1)编程式事务:通过编写代码来管理事务
2)通过注解或XML配置来管理事务
Spring的事务管理主要包括3个接口:
1)PlatformTransactionManager:根据TransactionDefinition提供的事务属性配置信息,创建事务.
2)TransactionDefinition:封状事务的隔离级别、超时时间、是否只读事务和传播规则等事务属性.
3)TransactionStatus:封装了事务的具体运行状态,如是否是新事务,是否已经提交事务,设置当前事务为rollback-only等;
1.PlatformTransactionManager
接口统一抽象处理事务操作相关的方法,是其他事务的规范,方法解析:
1)TransactionStatus getTransaction(@Nullable TransactionDefinition definition):根据事务定义信息从事事务环境返回一个已存在的事务,或者创建一个新的事务。
2)void commit(TransactionStatus status):根据事务的状态提交事务,如果事务状态已经标识为rollback-only,该方法执行回滚事务的操作
3)void rollback(TransactionStatus status):将事务回滚,当commit方法抛出异常时,rollback会被隐式调用
常用的事务管理器:
DataSourceTransactionManager:支持JDBC,MyBatis等;
HibernateTransactionManager:支持Hibernate
2.TransactionDefinition
事务隔离级别:用来解决并发事务出现的问题
1)ISOLATION_DEFAULT:默认隔离级别,即使用底层数据库默认的隔离级别;
2)ISOLATION_READ_UNCOMMITTED:未提交读
3)ISOLATION_READ_COMMITTED :提交读,一般情况我们使用这个
4)ISOLATION_REPEATABLE_READ :可重复读
5)ISOLATION_SERIALIZABLE : 序列化
注:除第一个外,后面四个都是spring通过java代码模拟出来的
传播规则:在一个事务中调用其他事务方法,此时事务该如何传播,按照什么规则传播,用谁的事务,还是都不用等
Spring共支持7种传播行为:
情况一:遵从当前事务
1)REQUIRED:必须存在事务,如果当前存在一个事务,则加入该事务,否则将新建一个事务(缺省)
2)SUPPORTS:支持当前事务,指如果当前存在逻辑事务,就加入到该事务,如果当前没有事务,就以非事务方式执行
3)MANDATORY:必须有事务,使用当前事务执行,如果当前没有事务,则抛出异常IllegalTransactionStateException
情况二:不遵从当前事务
1)REQUIRES_NEW:不管当前是否存在事务,每次都创建新事务
2)NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,就把当前事务暂停,以非事务方式执行
3)NEVER:不支持事务,如果当前存在事务,则抛出异常:IllegalTransactionStateException
情况三:寄生事务(外部事务和寄生事务)
NESTED:如果当前存在事务,则在内部事务内执行,如果当前不存在事务,则创建一个新的事务,嵌套事务使用数据库中的保存点来实现,即嵌套事务回滚不影响外部事务,但外部事务回滚将导致嵌套事务回滚。
1.表account结构
2.domain类
@Data
public class Account {
private Long id;
private int balance;
}
3.dao接口及实现类
public interface IAccountDAO {
/**
* 从指定帐户转出多少钱
* @param outId
* @param money
*/
void transOut(Long outId,int money);
/**
* 从指定帐户转入多少钱
* @param inId
* @param money
*/
void transIn(Long inId,int money);
}
public class AccountDAOImpl implements IAccountDAO {
private JdbcTemplate jdbcTemplate;
public void setDataSource(DataSource ds) {
this.jdbcTemplate = new JdbcTemplate(ds);
}
@Override
public void transOut(Long outId, int money) {
System.out.println("outId:"+outId+",money:"+money);
this.jdbcTemplate.update("update account set balance = balance - ? where id=?", money,outId);
}
@Override
public void transIn(Long inId, int money) {
System.out.println("inId:"+inId+",money:"+money);
this.jdbcTemplate.update("update account set balance = balance + ? where id=?", money,inId);
}
}
3.service接口及实现类
public interface IAccountService {
/**
* 从指定帐户转出另一个帐户多少钱
* @param outId
* @param inId
* @param money
*/
void trans(Long outId,Long inId,int money);
}
public class AccountServiceImpl implements IAccountService {
private IAccountDAO dao;
public void setDao(IAccountDAO dao) {
this.dao = dao;
}
@Override
public void trans(Long outId, Long inId, int money) {
//转出
this.dao.transOut(outId, money);
//转入
int a = 1/0;//模拟异常
this.dao.transIn(inId, money);
}
}
4.XML配置
注意以上关联关系
5.测试代码
@SpringJUnitConfig
public class App {
@Autowired
private IAccountService service;
@Test
void testTrans() {
service.trans(10002L, 10010L, 100);
}
}
1.name:匹配到的方法模拟,必须配置;
2.read-only:如果为true,开启一个只读事务,只读事务的性能较高,但是不能只读事务中操作DML;
3.isolation:代表数据库事务隔离级别(就使用默认),DEFAULT:让Spring使用数据库默认的事务隔离级别;其他:Spring模拟
4.no-rollback-for:如果遇到的异常是匹配的异常类型,就不回滚事务
5.rollback-for:如果遇到的异常是指定匹配的异常类型,才回滚事务;
6.propagation:事务的传播方式(当一个方法已在一个开启的事务当中了,应该怎么处理自身的事务);
1.XML配置
2.domain类似上面JDBC的方式
3.dao接口同上,实现类如下,添加注解:@Repository
@Repository
public class AccountDAOImpl implements IAccountDAO {
private JdbcTemplate jdbcTemplate;
@Autowired
public void setDataSource(DataSource ds) {
this.jdbcTemplate = new JdbcTemplate(ds);
}
@Override
public void transOut(Long outId, int money) {
System.out.println("outId:"+outId+",money:"+money);
this.jdbcTemplate.update("update account set balance = balance - ? where id=?", money,outId);
}
@Override
public void transIn(Long inId, int money) {
System.out.println("inId:"+inId+",money:"+money);
this.jdbcTemplate.update("update account set balance = balance + ? where id=?", money,inId);
}
}
4.service接口同上,实现类如下,添加注解:@Service@Transactional
@Service
@Transactional
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountDAO dao;
@Override
public void trans(Long outId, Long inId, int money) {
//转出
this.dao.transOut(outId, money);
//转入
int a = 1/0;//模拟异常
this.dao.transIn(inId, money);
}
@Transactional(readOnly=true)
public void listAcount() {
}
}
可以在指定方法配置指定规则,如上述: @Transactional(readOnly=true)
5.测试类同上
上一篇:spring5整理:(八)DAO
下一篇:spring5整理:(十)JavaConfig配置