1. 事务管理的基本原理
spring事务管理实际上数据库对事务的支持,在java中使用的是JDBC的事务管理机制,就是利用原生jdbc的java.sql.Connection来完成对事务的管理(提交,回滚),使用原生jdbc的事务管理一般来说的操作是:
Connection connection = DriverManager.getConnection();
try {
connection .setAutoCommit(false); //取消自动提交
// 执行CRUD操作 ...
connection .commit(); //事务完成后手动提交
} catch (Exception e) {
connection .rollback(); //事务期间发生异常,回滚事务
e.printStackTrace();
} finally {
connection .colse(); //关闭连接
}
事务,简单来说就是一些操作或者说一组sql,他们执行的特点就是要么全部都执行,要么全部都不执行,当然jdbc事务的管理可以设置savePoint,也就是回滚点,不一定要全部回滚,但是一般来说都是要么全部执行,要么全部不执行,回滚到事务开始的状态,这样就能够保数据的一致性和晚完整性,避免了因为数据不一致带来的后续错误。例如转账扣钱成功,加钱失败,导致系统逻辑上出现问题,数据也不一致。
2. 和数据库事务相关理论
ACID
A(Atomicity):原子性,事务是一个原子操作,由一系列操作组成,这一系列操作组合起来应该是一个原子的,要么都不完成,要么都完成。
C(Consistency):一致性 ,事务在执行完成后,数据的一致性应该可以保证的。
I(Isolation):隔离性,多个事务并发期间他们之间应该是隔离的,即一个事务执行期间不会 对其他事务产生影响,这个需要事务的隔离级别来保证,
D(Durability):持久性,事务一旦完成提交后,数据的改变就持久化到数据库。
在应用中,对数据库的并发访问时必然存在的,那么事务的并发可能带来的问题就包括:
脏读:一个事务读取了另外一个事务未提交的数据。
不可重复读:一个事务在读取同一数据期间,被其他事务更新
(update,delete)了数据,导致多次读取不一致。
幻读:一个事务在读取同一数据期间,被其他事务了插入
(insert)了新的数据,导致多次读取不一致
丢失更新问题:回滚事务时,把其他事务提交的更新覆盖了。
隔离性级别
既然事务的并发会产生诸多问题,那么事务的隔离级别就很重要了,jdcb定义了五种事务的隔离级别来结局事务并发产生的问题:
TRANSACTION_NONE JDBC 驱动不支持事务
TRANSACTION_READ_UNCOMMITTED 读未提交,允许脏读、不可重复读和幻读
TRANSACTION_READ_COMMITTED 读已提交,禁止脏读,但允许不可重复读和幻读。
TRANSACTION_REPEATABLE_READ 重复读,禁止脏读和不可重复读,但是幻读。
TRANSACTION_SERIALIZABLE 串行化,禁止脏读、不可重复读和幻读。
*********************************************
msyql默认REPEATABLE-READ
oracle支持READ COMMITTED 和 SERIALIZABLE,默认READ COMMITTED
sql server 默认READ COMMITTED
事务的隔离性越高,也就意味着并发性越差,性能也就越低,原生jdbc中可通过connection.setTransactionLevel去设置需要的隔离级别。
JDBC虽然定义了统一的事务支持以上隔离级别,但是具体的数据库厂商的支持可能不尽相同,处于性能的考虑一般设置为TRANSACTION_READ_COMMITTED就可以了,剩下的问题就通过数据库的锁来解决。
对于丢失更新问题,不管读取会产生一致性问题,写入数据也存在这样的问题,例如以下操作:
事务A和事务B都同时获取了同一数据,做修改,
事务A完成后提交,
事务B完成后提交,那么事务A的更新就丢失了,反之,事务B的更新就丢失了。
解决这样的问题,主要采取悲观锁或者乐观锁的方式
悲观锁:
悲观锁采用的是数据库的一种锁机制,在sql语句后面添加 for update子句,当一个事务操作该条记录的时候,这条记录就被锁定了,只有当这个事务提交后,锁才被释放,其他事务才能操作这条记录。
乐观锁: 乐观锁是人为的采用添加版本号的机制来解决的。
原理:为表添加一个version字段,默认值为0,当事务t1在修改完后,提交事务的时候会验证数据库中version字段是否和先前查询出来的version一致,一致才可以提交事务,并且修改版本号为1。接下来t2事务同样这样做,更新版本。。。。。。
spring事务管理
对于基本jdbc事差不多就涉及这些,那么spring事务管理做的是什么?
有了spring事务管理,再也不需要去获取连接,关闭连接,异常回滚,隔离级别设置等,这也是spring的一个特点(对于这样模板化的操作,提供统一的方式去操作,或者说叫又做了一层封装,简化开发)。
实际上spring并不是直接管理事务,而是提供了一个事务管理器,将事务管理的职责交给jdbc事务管理,Hibernate或者更强大的JTA这样的事务管理的平台框架的事务管理。
spring环境下统一的事务管理器平台,对于使用不同持久化技术,平台统一了事务管理。
*在spring JdbcTemplate,原生JDBC,或者mybatis这样的半自动化的工具持久化数据的时候,使用org.springframework.jdbc.datasource.DataSourceTransactionManager,事务管理器。
* 在hibernate这样全自动orm框架下,使用org.springframework.orm.hibernate5.HibernateTransactionManager作为事务管理器。
例如,使用hibernate的时候配置的org.springframework.orm.hibernate5.HibernateTransactionManager
使用mybatis的时候配置的org.springframework.jdbc.datasource.DataSourceTransactionManager
spring提供了事务管理核心接口PlatformTransactionManager
事务管理器通过过getTransaction(TransactionDefinition definition)方法返回当前的事务负责创建一个事务,参数definition就定义了基本的事务属性,例如事务的传播行为和隔离级别。
事务的传播属性
前面提到,spring的对于事务的支持其实只是充当一代理的角色,都是借助其他具体的事务管理来支持的,但是的事务的传播行为是spring凭借自身框架提供的功能,显得非常棒。
spring的事务传播属性包括
PROPAGATION_REQUIRED 0 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择,也是Spring默认的事务的传播。
PROPAGATION_SUPPORTS 1 支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY 2 支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW 3 新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED 4 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER 5 以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED 6 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。
具体的说, 假设事务传播属性为PROPAGATION_REQUIRED,一般来说将事务设置在service层,那么service层需要调用其他service的时候,那么就可以保证这两个操作在同一个事务中。
事务的隔离级别
和jdbc事务里面定义的差不多。
ISOLATION_DEFAULT -1 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别。另外四个与JDBC的隔离级别相对应
ISOLATION_READ_UNCOMMITTED 1 这是事务最低的隔离级别,它充许另外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻读。
ISOLATION_READ_COMMITTED 2 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。
ISOLATION_REPEATABLE_READ 4 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻读。
ISOLATION_SERIALIZABLE 8 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读外,还避免了幻读。
spring事务的配置
配置事务管理器
例如使用hibernate的时候
<!--配置一个事务管理器-->
<bean id="hibernateTransactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
使用mybatis-spring的时候
<!-- 事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 数据源 -->
<property name="dataSource" ref="dataSource"/>
</bean>
这个配置不是唯一的,可以根据项目具体选择的orn框架来配置事务管理器。
配置好事务管理器外,事务的具体管理操作还需要手动去做,spring提供了两种事务管理的方式:编程式事务和声明式事务。
编程式事务
编程式事务也就是同通过PlatformTransactionManager来实现对事务的管理,当然,spring也提供了模板类来管理事务,只需要在ioc容器中配置一个bean,就可以实现采用模板类来操作事务。
配置一个事务模板:
<!--配置事务模板,使用事务模板执行事务中的操作-->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="dataSourceTransactionManager"/>
</bean>
在事务的地方(service)通过模板来编程式的控制事务。
public void transfer(String from, String to, Double money) {
/*
编程式事务:在service层注入一个事务模板,将事务中的多个操作放置到模板中进行。
spring的事务模板对象会根据配置的事务模板执行事务。
*/
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
//转账过程中,加钱和扣钱应该是一个事务,才能保证数据库数据的完整性。
//扣钱
accountDao.outMoney(from, money);
int i = 1 / 0;
//加钱
accountDao.inMoney(to, money);
}
});
}
声明式事务
声明式事务管理有两种常用的方式,一种是基于tx和aop命名空间的xml配置文件,一种是基于@Transactional注解
XML配置方式
例如:
<!-- 通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- 传播行为 -->
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="insert*" propagation="REQUIRED"/>
<tx:method name="add*" propagation="REQUIRED"/>
<tx:method name="create*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="select*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="get*" propagation="SUPPORTS" read-only="true"/>
</tx:attributes>
</tx:advice>
<!-- 切面 -->
<aop:config>
<!-- 切入点表达式 -->
<aop:pointcut id="pointCut" expression="execution(* com.abc.service.*.*(..))"/>
<!-- 配置事务增强 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointCut"/>
</aop:config>
@Transaction注解配置方式
这种方式显得更加简洁,
第一步,只需要在配置文件中开启对注解事务管理的支持:
<!-- 声明式事务管理 配置事务的注解方式-->
<tx:annotation-driven transaction-manager="transactionManager"/>
第二步,在需要事务管理的地方加上@Transactional注解:
@Transactional(rollbackFor=Exception.class)
public void transfer(String from, String to, Double money) {
//转账过程中,加钱和扣钱应该是一个事务,才能保证数据库数据的完整性。
//扣钱
accountDao.outMoney(from, money);
int i = 1 / 0;
//加钱
accountDao.inMoney(to, money);
}
网友评论