Spring框架学习(1):spring-aop解决数据库事务问题

目录

  • spring-aop解决数据库事务问题
    • 问题描述
    • 解决方法
    • 代码示例
    • 思考
    • 参考

spring-aop解决数据库事务问题

问题描述

  1. 在操作数据库时有数据一致性的要求,对一个请求可能会同时写入不同表,这就要求数据库操作时需要用到事务,要么所有步骤都成功、要么都失败。
  2. 如果自己管理事务,一个明显的问题就是要写很多代码判断什么情况回滚、什么情况提交,这样也不能充分利用到spring框架的好处了。
  3. 在spring框架中,数据库操作默认是自动提交的,因此一次数据库写入就会提交了。当然spring有@transactional注解,加入这个注解后可以让一段代码拥有事务能力。但是该注解有一个缺陷,默认方法中抛出runtime类型异常才会触发回滚,或者需要自己指定异常类型。这意味着上层调用者需要捕获这个异常并进行处理。
  4. 问题3的延续,如果数据库操作不需要依赖异常进行回滚,而是根据函数返回值进行判定需不需要进行回滚,此时@transactional就不能满足要求了。

解决方法

  1. 为了少写代码,想方法中自行管理事务显然是不行的,写了几个方法就会叫苦了。采用@transactional的方法又要面对两个问题,如果能解决这两个问题,采用注解的方法是非常漂亮的。因此考虑自定义注解替代@transactional注解。
  2. 自定义注解@Transation, 对于用该注解注释的方法,如果抛出异常、或者返回值的status非0则进行回滚。
  3. 引入spring-aop,采用切面的设计方式,完成注解@Transation的功能。切面有多种通知方式,其中around方式可以满足要求。

代码示例

注解类

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 事务注解,通过该注解的方法会被 TransactionAspect 切面代理处理事务。注解的方法返回值应为 {@link Response},例如
 *     public Response addUser(){
 *         ...
 *     }
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Transaction {

}

切面类实现

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import javax.annotation.Priority;
**
 * 事务切面,进行事务的提交和回滚,配合使用 {@link Transaction} 确定对哪个方法进行事务管理
 */
@Aspect 
@Priority(1) //阅读思考[2]
@Slf4j
public class TransactionAspect {

    @Autowired
    private DataSourceTransactionManager transactionManager; //spring中配置的事务管理器
    private DefaultTransactionDefinition definition;

    public TransactionAspect() {
        definition = new DefaultTransactionDefinition();
        definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
    }

    @Around("@annotation(path.to.Transaction))") //标明注解的路径 和通知方式为around
    public Response watch(ProceedingJoinPoint joinPoint) {
        TransactionStatus status = transactionManager.getTransaction(definition);
        try {
            Response response = (Response)joinPoint.proceed();
            if (response.status == 0) {
                transactionManager.commit(status);
            } else { 
                transactionManager.rollback(status);
            }
            return response;
        } catch (Throwable throwable) {
            transactionManager.rollback(status);
            return new Response(1, throwable.getMessage(), null);
        }
    }
}

配置切面bean

@Configuration
@EnableAspectJAutoProxy
@ComponentScan
public class BeanManager {
   /**
     * 配置数据库事务切面
     */
    @Bean
    public TransactionAspect buildTransactionAspect() {
        return new TransactionAspect();
    }
}

response 定义

public class Response<T> {

    /* 状态码,0代表成功,非0代表失败 */
    public int status;
    /* 处理信息 */
    public String message;
    /* 处理结果 */
    public T result;

    public Response(int status, String message, T result) {
        this.status = status;
        this.message = message;
        this.result = result;
    }
}

使用示例

@Component
public class UserService {

    private UserDAO userDAO;
    
    @Transaction
    public Response<User> createUser(User user){ //这段逻辑只是示例不代码真实业务
    	//查看用户是否已存在
    	//用户表中写入用户
    	//用户状态表中写入初始的用户状态信息
    	//判断该用户是否目标用户 不是则拒绝创建用户 return new Response(1,"not target user",null)
    	return new Response(0,"success",user);
    }

思考

  1. aop中有许多概念如通知、切点、连接点、引入、织入,通知类型又有前置、后置、环绕等,该内容可在《spring实战》(第4版)中学习。spring中aop是采用代理的方式在运行时织入,不用spring-aop也有其他方式实现。
  2. @Priority 注解是未来表明该代理的优先级,spring中大量采用代理方式,(默认是最低优先级),存在多个代理的时候,就有需要要显示的注明各个代理执行顺序,因此需要标记优先级。 (@EnableTransactionManagement(order = 2) 使用在事务配置中,可设定spring中@transactional的优先级)
  3. 统一采用response作为函数返回值,目的是在watch方法中能够对返回值进行解析判断是否需要进行回滚。watch方法中会根据返回值或者捕获异常进行回滚。此处可根据业务需求进一步完善。

参考

spring aop 文档

你可能感兴趣的:(Spring框架学习)