事物是一种概念,可以将一些分散的操作划分成一组原子性操作,要么全部成功,要么全部失败。常见的例子有银行转账操作。
事物具有4种性质(ACID)
原子性(atomicity):强调事务的不可分割.一致性(consistency):事务的执行的前后数据的完整性保持一致.隔离性(isolation):一个事务执行的过程中,不应该受到其他事务的干扰.持久性(durability) :事务一旦结束,数据就被修改.日常只知事物完成的工作却不知其原理,本文详细介绍下spring事物的原理。
spring中也有事物,通常我们使用@Transaction注解来标注我们需要事物的方法
/** * @author Mcj * @date 2020-01-15 20:11 */ @RestController @RequestMapping("/student") public class StudentController { @Autowired StudentService studentService; /** * 添加一个学生 * @return 200 */ @GetMapping public ResponseEntity addStudent() { Student student = new Student(); student.setName("孙亚龙"); student.setAge(666); student.setNum(0); studentService.addStudent(student); return ResponseEntity.ok(200); } } @Service public class StudentServiceImpl implements StudentService { @Autowired private StudentDao studentDao; /** * 添加一个学生 * * @param student 学生 */ @Override @Transactional(rollbackFor = IOException.class) public void addStudent(Student student) { studentDao.save(student); } @Override public Student findStudentByName(String name) { return studentDao.findAllByNameEquals(name).get(0); } } @Data @AllArgsConstructor @NoArgsConstructor @Entity public class Student { @Id @GeneratedValue private Integer id; /** * 学生姓名 */ private String name; /** * 学生年龄 */ private Integer age; /** * 学生完成的工作数量 */ private Integer num; public Student(String name) { this.name = name; } public Student(Integer age) { this.age = age; } }当我们访问http接口之后会在数据库中保存一个学生,但是我们在这里打印一个1/0,这里应该会报出by zero的异常并且数据库不会保存
@Service @Transactional public class StudentServiceImpl implements StudentService { @Autowired private StudentDao studentDao; /** * 添加一个学生 * @param student 学生 */ @Override public void addStudent(Student student) { studentDao.save(student); System.out.println(1/0); } }本章讲解了日常使用spring事物时遇到的现象,当出现错误时,@Transaction事物配置发生了作用,如果没有配置声明式事物则数据库内会新增一条数据然后再报出异常。
spring声明式事物原理是利用aop、动态代理技术在目标方法被执行之前拦截住,执行一些增强逻辑。
利用与数据库的Connection对象进行提交或者会滚操作
同时利用了ThreadLocal对象,将当前数据库连接绑定到当前线程中来保证每次对数据库的操作都是同一条连接。
数据库实现了事物功能 #当student表存储引擎为myisam时,执行以下语句则会直接保存,但是当存储引擎为innodb时,并不会保存。所以是否有事物功能与数据库表有关。 BEGIN; INSERT INTO student (id,age,name) VALUES(10,666,'吴奇隆'); SELECT * from student;innodb与myisam引擎区别,对事物的支持
spring事物管理并不直接管理事物,而是定义接口供相关平台实现,spring事物核心接口是PlatformTransactionManager,接口定义了commit()和rollback()方法,PlatformTransactionManager接口实现类中有JpaTransactionManager类。同时跟踪JpaTransactionManager的doCommit()方法,最终是jdbc操作connection来完成事物提交。自定义的dao接口继承的JpaRepository接口,此接口的默认实现SimpleJpaRepository类也有声明式事物注解@Transaction
public interface StudentDao extends JpaRepository<Student,Integer> @Repository @Transactional(readOnly = true) public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {在studentservice中也写了这个注解
@Service @Transactional public class StudentServiceImpl implements StudentService {在第一章的例子中也是写了这个注解而让sql语句没有提交。
注解只是一个标志,在项目启动时SpringTransactionAnnotationParser#parseTransactionAnnotation()类已经扫描并且获取注解上配置的属性并且注册事物拦截器。
@Override @Nullable public TransactionAttribute parseTransactionAnnotation(AnnotatedElement element) { //获取配置的属性 AnnotationAttributes attributes = AnnotatedElementUtils.findMergedAnnotationAttributes( element, Transactional.class, false, false); if (attributes != null) { return parseTransactionAnnotation(attributes); } else { return null; } }spring为标记了@Transactional注解的类或者方法利用aop创建动态代理对象在目标方法调用前后创建提交事物.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0cs3W8Bt-1602061137147)(拦截器.jpg)]
当某个目标类中有@Transaction注解声明过的方法时,cglib就会为目标类生成一个代理对象,StudentController中的studentservice在项目启动时是一个cglib生成的代理对象,这个代理对象有许多回调函数,DynamicAdvisedInterceptor会获取方法的拦截器链并且执行。只有代理对象调用目标方法才会执行拦截器逻辑。但是在调用目标方法时不使用代理对象而是调用使用原始bean对象调用。
步骤:StudentController中我调用了studentservice#addStudent()会进入以下逻辑
###createTransactionIfNecessary
@SuppressWarnings("serial") protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm, @Nullable TransactionAttribute txAttr, final String joinpointIdentification) { // If no name specified, apply method identification as transaction name. if (txAttr != null && txAttr.getName() == null) { txAttr = new DelegatingTransactionAttribute(txAttr) { @Override public String getName() { return joinpointIdentification; } }; } TransactionStatus status = null; if (txAttr != null) { if (tm != null) { //开启事物并且获取事物状态 status = tm.getTransaction(txAttr); } else { if (logger.isDebugEnabled()) { logger.debug("Skipping transactional joinpoint [" + joinpointIdentification + "] because no transaction manager has been configured"); } } } //封装事物对象属性,并且将事物绑定到本地线程中 return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status); }执行拦截的方法了,回到TransactionAspectSupport方法中
try { // This is an around advice: Invoke the next interceptor in the chain. // This will normally result in a target object being invoked. //执行动态代理的目标方法 retVal = invocation.proceedWithInvocation(); }由于只有一个transactionintercept拦截器
public Object proceed() throws Throwable { // We start with an index of -1 and increment early. //查看当前是否还有拦截器需要执行,如果已经是最后一个拦截器则执行目标方法 if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) { //执行addstudent方法 return invokeJoinpoint(); }所以执行joinpoint,就是写上注解的那个方法addStudent();
此时不调用jpa的话,执行commitTransactionAfterReturning方法,事物就结束了。
但是addStudent()方法内调用了jpa的save()方法。
jpa内又配置了10个拦截器,并且jpa是jdk动态代理的
JdkDynamicAopProxy @Override @Nullable public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object oldProxy = null; boolean setProxyContext = false; TargetSource targetSource = this.advised.targetSource; Object target = null; try { if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) { // The target does not implement the equals(Object) method itself. return equals(args[0]); } else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) { // The target does not implement the hashCode() method itself. return hashCode(); } else if (method.getDeclaringClass() == DecoratingProxy.class) { // There is only getDecoratedClass() declared -> dispatch to proxy config. return AopProxyUtils.ultimateTargetClass(this.advised); } else if (!this.advised.opaque && method.getDeclaringClass().isInterface() && method.getDeclaringClass().isAssignableFrom(Advised.class)) { // Service invocations on ProxyConfig with the proxy config... return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args); } Object retVal; if (this.advised.exposeProxy) { // Make invocation available if necessary. oldProxy = AopContext.setCurrentProxy(proxy); setProxyContext = true; } // Get as late as possible to minimize the time we "own" the target, // in case it comes from a pool. target = targetSource.getTarget(); Class<?> targetClass = (target != null ? target.getClass() : null); // Get the interception chain for this method. //获取拦截器链 List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); // Check whether we have any advice. If we don't, we can fallback on direct // reflective invocation of the target, and avoid creating a MethodInvocation. if (chain.isEmpty()) { // We can skip creating a MethodInvocation: just invoke the target directly // Note that the final invoker must be an InvokerInterceptor so we know it does // nothing but a reflective operation on the target, and no hot swapping or fancy proxying. Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args); retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse); } else { // We need to create a method invocation... MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); // Proceed to the joinpoint through the interceptor chain. //调用拦截器逻辑,并且跳到下一个拦截器 retVal = invocation.proceed(); }10个拦截器按照上面的方式顺序调用。
jpa配置的拦截器中同样有transactioninterceptor,同样会createTransactionIfNecessary创建事物,这时候方法返回值是TransactionInfo对象,这个对象保存着外层的事物信息,如addstudent的事物信息,这个对象是用链表结构保存嵌套事物的。
jpa拦截器执行完毕需要提交数据时会调用commitTransactionAfterReturning方法,
/** * Execute after successful completion of call, but not after an exception was handled. * Do nothing if we didn't create a transaction. * @param txInfo information about the current transaction */ protected void commitTransactionAfterReturning(@Nullable TransactionInfo txInfo) { if (txInfo != null && txInfo.getTransactionStatus() != null) { if (logger.isTraceEnabled()) { logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() + "]"); } //配置的是jpatransactionmanager方法的commit。最终跟踪下去是connection的commit方法 txInfo.getTransactionManager().commit(txInfo.getTransactionStatus()); } }AbstractPlatformTransactionManager
//提交时会判断是否是新事物,jpa的save方法不是最外层事物,addStudent方法是最外层事物 else if (status.isNewTransaction()) { if (status.isDebug()) { logger.debug("Initiating transaction commit"); } unexpectedRollback = status.isGlobalRollbackOnly(); //执行提交方法 doCommit(status); }内部jpa的事物结束后,再提交addStudent的事物,提交完成后事物才算结束。
至此事物提交成功。
根据动态代理的方法自定义一个事物处理机制 定义注解
/** * @author mcj */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface McjTransaction { }定义注解解释类
@Aspect @Configuration @Slf4j public class CglibConfig { @Autowired PlatformTransactionManager jpaTransactionManager; @Autowired TransactionDefinition transactionDefinition; /* 定义一个切入点 */ @Pointcut("@annotation(com.springboot.transactiondemo.annotation.McjTransaction)") public void doPointCut() { } @Before("doPointCut()") public void before(JoinPoint joinPoint) { log.info("PersonAspect ==> before method : {}", joinPoint.getSignature().getName()); log.info("注解的类型名称为{}",joinPoint.getSignature().getDeclaringTypeName()); log.info("方法修饰符个数为{}",joinPoint.getSignature().getModifiers()); log.info("方法名称为{}",joinPoint.getSignature().getName()); } @After("doPointCut()") public void after(JoinPoint joinPoint) throws FileNotFoundException { log.info("PersonAspect ==> after method : {}", joinPoint.getSignature().getName()); } @Around("doPointCut()") public void aroundMethod(ProceedingJoinPoint point) { TransactionStatus transaction = jpaTransactionManager.getTransaction(new DefaultTransactionAttribute()); point.proceed(); jpaTransactionManager.commit(transaction); } }最后将注解替换
/** * 添加一个学生 * * @param student 学生 */ @Override @McjTransaction public void addStudent(Student student) { studentDao.save(student); }还可以自定义一个transactionManager
@Component("mcjtrans") public class McjTransactionManager implements PlatformTransactionManager { @Autowired JdbcUtil jdbcUtil; @Override public TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException { return new SimpleTransactionStatus(); } @Override public void commit(TransactionStatus status) throws TransactionException { try { jdbcUtil.getConnectionThreadLocal().commit(); } catch (SQLException e) { e.printStackTrace(); } } @Override public void rollback(TransactionStatus status) throws TransactionException { try { jdbcUtil.getConnectionThreadLocal().rollback(); } catch (SQLException e) { e.printStackTrace(); } } }不用jpa后我选择使用jdbc
@Component public class JdbcUtil { @Autowired private JdbcTemplate jdbcTemplate; private ThreadLocal<Connection> t = new ThreadLocal<Connection>(); public Connection getConnectionThreadLocal() { if (t.get() == null) { try { Connection connection = jdbcTemplate.getDataSource().getConnection(); connection.setAutoCommit(false); t.set(connection); } catch (SQLException e) { e.printStackTrace(); } }else{ return t.get(); } return t.get(); } public void removeConnection(){ try { t.get().close(); } catch (SQLException e) { e.printStackTrace(); } t.remove(); } }定义一个自己的dao
@Service public class McjStudentDao { @Autowired private JdbcUtil jdbcUtil; @McjTransaction public void save(Student student){ String format = String.format("insert into student (id,age,name,num) VALUES (null,'%s','%s','55')",student.getAge(),student.getName()); Connection connectionThreadLocal = jdbcUtil.getConnectionThreadLocal(); try { Statement statement = connectionThreadLocal.createStatement(); statement.execute(format); } catch (SQLException e) { e.printStackTrace(); } }; }运行程序发送http请求,数据库中保存了一条记录。
原本每个事物都是独立的互相不干扰的,于是通过配置事物的传播行为将不相关的操作变成了原子性的操作。事物的传播行为是指定了当有两个事物时是否需要回滚的问题
PROPAGATION_REQUIRED 如果当前存在事物则加入该事物,不存在事物则新建事物(默认的事物机制),finishWork方法产生异常,数据库中任务没有被接下,两个方法都回滚
PAOPAGATION_REQUIRE_NEW
若当前没有事务,则新建一个事务。若当前存在事务,则新建一个事务,新老事务相互独立。外部事务抛出异常回滚不会影响内部事务的正常提交,执行完成后数据库内有4个学生
PROPAGATION_SUPPORTS
如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行,运行完成后添加了5个学生
PROPAGATION_MANDATORY
如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。
PROPAGATION_NOT_SUPPORTED
总是非事务地执行,并挂起任何存在的事务。数据库中有5名学生
PROPAGATION_NEVER
总是非事务地执行,如果存在一个活动事务,则抛出异常。
PROPAGATION_NESTED
内部事物抛出异常没被捕获则会影响外部事物会滚,与required区别是对外部事物的依赖
由sql语句看起来最为直观,前提是需要mysql数据库表引擎为innodb
#设置保存点,创建一个存档 SAVEPOINT point; #执行insert语句 insert into student (id,name) VALUES (15,'吴彦祖'); select * from student; #返回到保存点,回档 ROLLBACK to point; select * from student; #提交事物 COMMIT;事物中出现异常如果被捕获了则不会回滚,默认运行时异常全部会回滚,非运行时异常不回滚,但是可以配置
spring事物中的事物回滚是利用了savepoint,在事物中配置了savepoint
@Transactional(rollbackFor = Exception.class)spring事物利用动态代理的方式,生成代理对象,在代理对象调用拦截器逻辑(transactioninterceptor)在方法前后增强,然后使用原始对象调用目标方法完成事物调用。
数据库实现了事物的功能,spring不直接操作数据库,而是让各大持久化平台实现这些与数据库事物打交道的方法,spring使用实现好的接口可以用在自己的框架中,通过事物的传播行为将一组方法组合成原子性操作。嵌套事物信息利用链表存储。
spring事物的管理,spring有一个默认主数据源,与一个默认主数据源的事物管理器(一个数据源一个事物管理器).同时transaction注解只对这个数据源管理器起作用.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UmrOxZ8C-1602061137157)(事物流程.jpg)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-brRZH9Do-1602061137159)(代理.jpg)]