前言:
事务回滚我们总是在用到,但是有可能不太了解具体的细节,接下来我会通过源码解读以及真实的案例测试,来说明。
接下来我会从三方面来讲述事务的运用:
- 源码解读
- 事务使用
- 事务的失效场景
1.源码解读
/**
描述事务的属性在一个方法或者类上(个人觉得应该是使用事务的属性在方法或者类上)
*<p>
这种注释类型通常可以直接与Spring的注释类型进行比较,
实际上将直接将数据转换为后者,因此Spring的事务支持代码不必知道注释。 如果没有规则与异常相关,则将其视为回滚运行时异常
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
/**
* 主要是spring不提供事务管理器,可以通过这个去定义自己想要的事务管理器,例如hibernate的、JTA的、JDBC的
* 大致上有以下几种
* org.springframework.jdbc.datasource.DataSourceTransactionManager DBC及iBATIS、MyBatis框架事务管理器
* org.springframework.orm.jdo.JdoTransactionManager Jdo事务管理器
* org.springframework.orm.jpa.JpaTransactionManager Jpa事务管理器
* org.springframework.orm.hibernate3.HibernateTransactionManager hibernate事务管理器
* org.springframework.transaction.jta.JtaTransactionManager Jta事务管理器
*/
String value() default "";
/**
* * 事务传播类型 默认是required 具体解释如下:
* @Transactional(propagation=Propagation.REQUIRED) :如果有事务, 那么加入事务, 没有的话新建一个(默认情况下)
* @Transactional(propagation=Propagation.NOT_SUPPORTED) :容器不为这个方法开启事务
* @Transactional(propagation=Propagation.REQUIRES_NEW) :不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务
* @Transactional(propagation=Propagation.MANDATORY) :必须在一个已有的事务中执行,否则抛出异常
* @Transactional(propagation=Propagation.NEVER) :必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反)
* @Transactional(propagation=Propagation.SUPPORTS) :如果其他bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务.
* <p>
*/
Propagation propagation() default Propagation.REQUIRED;
/**
* 事务的隔离机制 默认是数据库的隔离机制 需要是innodb引擎数据库才可以
* READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED), 读取未提交数据(会出现脏读, 不可重复读) 基本不使用
* READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED), 读取已提交数据(会出现不可重复读和幻读)
* REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ), 可重复读(会出现幻读)
* SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE); 串行化
*/
Isolation isolation() default Isolation.DEFAULT;
/*
* 事务超时时间 默认-1 永远不超时 如果设置之后 达到时间自动回滚
*/
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
/**
* 设置是否只读 默认是false 如果设置之后会锁住库 如果有插入的话 会报异常 java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
*/
boolean readOnly() default false;
/**
* 回滚的异常 默认只能是回滚runtimeExceptional
如果想全部回滚需要加上 rollback=exceptional.class
* CheckedException不回滚:
* Java认为Checked异常都是可以被处理的异常,所以Java程序必须显式的处理Checked异常,如果程序没有处理checked异常,程序在编译时候将发生错误。
* 我们比较熟悉的Checked异常有
* Java.lang.ClassNotFoundException
* Java.lang.NoSuchMetodException
* java.io.IOException
* <p>
* RunTimeException 回滚:
* Runtime如除数是0和数组下标越界等,其产生频繁,处理麻烦,若显示申明或者捕获将会对程序的可读性和运行效率影响很大。所以由系统自动检测并将它们交给缺省的异常处理程序。当然如果你有处理要求也可以显示捕获它们。
* 我们比较熟悉的RumtimeException类的子类有
* Java.lang.ArithmeticException
* Java.lang.ArrayStoreExcetpion
* Java.lang.ClassCastException
* Java.lang.IndexOutOfBoundsException
* Java.lang.NullPointerException
*
* @Transactional(rollbackFor=RuntimeException.class)
* 指定多个异常类:@Transactional(rollbackFor={RuntimeException.class, Exception.class})
*/
Class<? extends Throwable>[] rollbackFor() default {};
/**
* 这个是支持输入的类名称 详细功能同上
* @Transactional(rollbackForClassName="RuntimeException")
* 指定多个异常类名称:@Transactional(rollbackForClassName={"RuntimeException","Exception"})
*/
String[] rollbackForClassName() default {};
/**
* 不会滚的异常可以在这设置 ,也可以自己实现异常 但是必须要继承runtimeExceptional
*/
Class<? extends Throwable>[] noRollbackFor() default {};
/**
* 同上 这个是支持输入的类名称 不会滚的异常可以在这设置 ,也可以自己实现异常 但是必须要继承runtimeExceptional
*/
String[] noRollbackForClassName() default {};
}
2.事务使用
(1). 一个方法中涉及两次及以上重要插入,用事务来保证完整性 原子性
(2). 事务只能用在public 方法上 ,其他方法不起作用
(3). 可以放在ServiceImpl类上。全局实现事务
下面举一个简单的例子:
@Override
@Transactional(rollbackFor = Exception.class)
public int insert(DneWechat record) {
record.setId(1);
record.setName("hj12334");
updateByPrimaryKey(record);
dneWechatMapper.insert(record);
insertMemberStudent();
return 1;
}
//该方法是被第二次调用的方法 如果抛出异常 则全部回滚
//该方法可以不用写@transectional 因为事务是可以继承的
@Transactional(rollbackFor = Exception.class)
public void insertMemberStudent() {
DneMemberStudent dneMemberStudent = DneMemberStudent.builder()
.receivedTime(new Date())
.expireTime(new Date())
.userId(123)
.build();
dneMemberStudentService.insert(dneMemberStudent);
throw new RuntimeException();
}
3.事务失效的几种场景
一、try catch之后没有抛出对应的异常,这样是不会回滚的,错误例子如下:
@Override
@Transactional(rollbackFor = Exception.class)
public int insert(DneWechat record) {
record.setId(1);
record.setName("hj12334");
updateByPrimaryKey(record);
dneWechatMapper.insert(record);
insertMemberStudent();
return 1;
}
@Transactional(rollbackFor = Exception.class)
public void insertMemberStudent() {
DneMemberStudent dneMemberStudent = DneMemberStudent.builder()
.receivedTime(new Date())
.expireTime(new Date())
.userId(123)
.build();
dneMemberStudentService.insert(dneMemberStudent);
try {
int a = 1 / 0;
} catch (Exception e) {
e.printStackTrace();
}
}
二、 private 方法 不会回滚,错误例子如下:
@Override
@Transactional
public int insert(DneWechat record) throws IOException {
record.setId(1);
record.setName("hj12334");
updateByPrimaryKey(record);
dneWechatMapper.insert(record);
insertMemberStudent();
return 1;
}
@Transactional(rollbackFor = Exception.class)
private void insertMemberStudent() {
DneMemberStudent dneMemberStudent = DneMemberStudent.builder()
.receivedTime(new Date())
.expireTime(new Date())
.userId(123)
.build();
dneMemberStudentService.insert(dneMemberStudent);
}
三、 检查异常 例如ioexception 不会回滚,例子如下:
@Override
@Transactional
public int insert(DneWechat record) throws IOException {
record.setId(1);
record.setName("hj12334");
updateByPrimaryKey(record);
dneWechatMapper.insert(record);
insertMemberStudent();
throw new IOException();
}
@Transactional(rollbackFor = Exception.class)
public void insertMemberStudent() {
DneMemberStudent dneMemberStudent = DneMemberStudent.builder()
.receivedTime(new Date())
.expireTime(new Date())
.userId(123)
.build();
dneMemberStudentService.insert(dneMemberStudent);
}
四、 被调用的方法有注解,Override 方法没有注解,不会回滚,因为注解注入是通过aop实现的,例子如下:
@Override
public int insert(DneWechat record) {
record.setId(1);
record.setName("hj12334");
updateByPrimaryKey(record);
dneWechatMapper.insert(record);
insertMemberStudent();
return 1;
}
@Transactional(rollbackFor = Exception.class)
public void insertMemberStudent() {
DneMemberStudent dneMemberStudent = DneMemberStudent.builder()
.receivedTime(new Date())
.expireTime(new Date())
.userId(123)
.build();
dneMemberStudentService.insert(dneMemberStudent);
throw new RuntimeException();
}
我等采石之人,当心怀大教堂之愿景!