一直在用spring管理hibernate的事务,但是一直没太搞清楚,今天梳理一下
开始使用的spring 4.2.6.release和hibernate 5.2.5.final结果启动就报错
java.lang.NoSuchMethodError: org.hibernate.Session.getFlushMode()Lorg/hibernate/FlushMode;
有两种方案一种是把hibernate降到5.1,另一种是把spring升到5.3,最后我使用的是spring4.3.2.release和hibernate5.2.5.final.
使用注解@Transactional
前,需要在xml配置
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
bean>
<tx:annotation-driven transaction-manager="transactionManager" />
然后在需要启用事务的类或方法上加上@Transactional,在类上加对所有方法都有效,在单独方法上加只对这个方法有效.
定义回滚的抛出类型,默认是RuntimeException.class,设置成@Transactional(rollbackFor = Exception.class)
后,Exception和RuntimeException都会回滚,总之就是触发回滚的异常类型.在源码里是这样定义的Class extends Throwable>[] noRollbackFor() default {};
所以范围限制在Throwable的子类.
和rollbackFor相反,定义之后,该种异常不会触发回滚
事务隔离级别,默认是Isolation.DEFAULT,这里需要先介绍一下三种现象
1、脏读(dirty read):一个事务可以读取另一个尚未提交事务的修改数据。
2、非重复读(nonrepeatable read):在同一个事务中,同一个查询在T1时间读取某一行,在T2时间重新读取这一行时候,这一行的数据已经发生修改,可能被更新了(update),也可能被删除了(delete)。
3、幻像读(phantom read):在同一事务中,同一查询多次进行时候,由于其他插入操作(insert)的事务提交,导致每次返回不同的结果集。
不同级别对应的情况
级别 | 说明 |
---|---|
DEFAULT | 使用底层数据存储的默认隔离级别,所有的其他级别与jdbc隔离级别一致 |
READ_UNCOMMITTED | 这种等级允许一个事务改变了一行,在提交之前,另一个事务能够读到这种改变.如果改变被回滚了,第二个事务会获得一行非法的数据 |
READ_COMMITTED | 禁止一个事务读取其他一行数据的未提交的内容 |
REPEATABLE_READ | 禁止事务读取未提交的内容;禁止一个事务读取一行时另一个事务修改这行,然后第一个事务重新读这行,第二次获得一个不一样的值(不可重复读) |
SERIALIZABLE | 除了REPEATABLE_READ禁止的情况外,禁止当一个事务通过where条件读查出的所有行,另一个事务插入一行满足条件的数据,然后第一个事务重新用相同的条件读,在第二次读的时候获得额外的幻像行 |
级别和现象的关系
隔离级别 | 脏读 | 非重复读 | 幻像读 |
---|---|---|---|
READ_UNCOMMITTED | 允许 | 允许 | 允许 |
READ_COMMITTED | 允许 | 允许 | |
REPEATABLE_READ | 允许 | ||
SERIALIZABLE |
事务的传播类型
类型 | 说明 |
---|---|
REQUIRED(默认值) | 在有transaction状态下执行;如当前没有transaction,则创建新的transaction. |
SUPPORTS | 如当前有transaction,则在transaction状态下执行;如果当前没有transaction,在无transaction状态下执行 |
MANDATORY | 必须在有transaction状态下执行,如果当前没有transaction,则抛出异常IllegalTransactionStateException. |
REQUIRES_NEW | 创建新的transaction并执行;如果当前已有transaction,则将当前transaction挂起. |
NOT_SUPPORTED | 在无transaction状态下执行;如果当前已有transaction,则将当前transaction挂起. |
NEVER | 在无transaction状态下执行;如果当前已有transaction,则抛出异常IllegalTransactionStateException. |
如果readonly设置成true.当事务里有增删改操作时,会直接抛异常java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
事务的超时时间,当为默认值 TransactionDefinition.TIMEOUT_DEFAULT
时,会使用底层系统的默认超时时间.
一个特定事务的标识值,可以用来觉得目标transaction manager,匹配一个特定的 {@link org.springframework.transaction.PlatformTransactionManager}的bean definition.
<bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory " />
bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="add*" propagation="REQUIRED" />
<tx:method name="get*" propagation="REQUIRED" />
<tx:method name="del*" propagation="REQUIRED" />
<tx:method name="do*" propagation="REQUIRED" />
<tx:method name="*" read-only="true" />
tx:attributes>
tx:advice>
<aop:config expose-proxy="true">
<aop:pointcut id="txPointcut" expression="execution(* com.xxx.ws.service..*.*(..))" />
<aop:advisor pointcut-ref="txPointcut" advice-ref="txAdvice"/>
aop:config>
这样配置后所有service包下的类和方法都加上事务了.
在把事务交给spring管理后,dao里的查询方法也要采用一定的写法
1.采用sessionFactory.getCurrentSession();
的方式获取session
2.操作完成后不要关闭session
3.不要把异常catch掉,要往外抛,否则spring检测不到异常,就无法回滚
4.不要显示的调用Transaction的操作,会与spring的冲突
public int updateSql(String sql,Object...params)
{
Session session = sf.getCurrentSession();
try
{
NativeQuery nativeQuery = session.createNativeQuery(sql);
for (int i = 0; i <params.length ; i++)
{
nativeQuery.setParameter(i,params[i]);
}
int num = nativeQuery.executeUpdate();
return num;
} catch (Exception e)
{
throw new RuntimeException(e);
}finally
{
}
}
不要在dao里显示的调用tx的操作,会出现上面的报错移除tx.commit()后修复.
有时候业务逻辑必须要把异常catch掉,这时候spring因为检测不到异常无法回滚,可以通过调用
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
来手动回滚
没有配置spring管理的事务,或者配置没生效