SpringBoot +自定义dao框架 自定义注解管理多数据源与事务

场景

公司有一个业务需要控制db0和db1的两个数据源的表的事务,每次都要自己多个开启,比较麻烦,因为公司是自定义框架所以模拟springjdbc的DataSourceTransactionManager多数据源控制 和@Transactional注解模拟写了一个多数据源管理事务的注解@LdTransactional

解决方案

查看DataSourceTransactionManager源码得知,实际是多个数据源合并成一个
同时我们公司的事务的开启,提交,回滚,已经保障了线程安全,所以我才用的思路
1.建立一个能够合并多个数据源的对象类 MultiTransactionDao
2.建立一个控制事务的注解@LdTransactional
3.aop切入@LdTransactional注解,在到达事务控制方法之前开启事务,方法执行完毕提交事务LdTransactionalAop
4.然后测试多个效果

测试效果图

代码

package com.ld.action.index;

import java.util.Date;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.ld.aop.annotation.LdTransactional;
import com.ld.aop.model.ErrorLog;
import com.ld.aop.model.LoginLog;
import com.ld.dao.CreditBaseDB1Dao;
import com.ld.dao.CreditBaseNewDao;
import com.ld.model.creditsystem.jiben.IcpGudongModel;
import com.ld.service.creditsystem.jiben.IcpGuDongService;
import com.ld.service.log.ErrorLogService;
import com.ld.service.log.LoginLogService;
import com.ld.util.CmUtil;
import com.ld.util.SessionUtil;

@Controller
@RequestMapping("ldTransactional")
public class LdTransactionalController extends BaseController {

    @Autowired
    private ErrorLogService errorLogService;
    @Autowired
    private LoginLogService loginLogService;
    @Autowired
    IcpGuDongService icpGuDongService;

    // 出现特定异常不会滚
    @RequestMapping("")
    @LdTransactional(noRollbackFor = ArithmeticException.class)
    @ResponseBody
    public void test() {
        // 测试对应的事务
        System.err.println(Thread.currentThread().getName());
        // 记录错误日志
        ErrorLog errorLog = getErrorLog();
        errorLogService.save(errorLog);
        int i = 0 / 0;
        LoginLog loginLog = getLoginLog();
        loginLogService.save(loginLog);
        System.err.println("执行完毕");
    }

    // 多事务
    @RequestMapping("test2")
    @LdTransactional(dataSourceDao = { CreditBaseNewDao.class, CreditBaseDB1Dao.class })
    @ResponseBody
    public void test2() {
        // 测试对应的事务
        System.err.println(Thread.currentThread().getName());
        // 记录错误日志
        ErrorLog errorLog = getErrorLog();
        errorLogService.save(errorLog);
        int i = 0 / 0;
        IcpGudongModel IcpGudongModel = new IcpGudongModel();
        IcpGudongModel.setId(CmUtil.getPkId());
        IcpGudongModel.setGudongren("测试两个数据源的事务");
        System.err.println("执行完毕");
        icpGuDongService.save(IcpGudongModel);
    }

    private LoginLog getLoginLog() {
        LoginLog loginLog = new LoginLog();
        loginLog.setId(CmUtil.getPkId());
        loginLog.setLoginName("测试事务");
        loginLog.setLoginTime(new Date());
        loginLog.setIp(SessionUtil.getUserIP());
        loginLog.setStatus(1);
        loginLog.setMsg("测试事务");
        return loginLog;
    }

    private ErrorLog getErrorLog() {
        ErrorLog errorLog = new ErrorLog();
        errorLog.setId(CmUtil.getPkId());
        errorLog.setCreateTime(new Date());
        errorLog.setErrorInfo("测试事务");
        errorLog.setUserId(SessionUtil.getloginUserId());
        errorLog.setUserName(SessionUtil.getLoginName());
        errorLog.setIp(SessionUtil.getUserIP());
        errorLog.setUrl(request.getRequestURI());
        return errorLog;
    }
}

代码执行效果
test1(单个数据源事务) 按照预期效果执行 打印事务错误,并回滚事务
在这里插入图片描述
test2(多个数据源事务 ) 按照预期效果执行
在这里插入图片描述
test3(单个数据源事务 ,开启指定异常不需要回滚) 按照预期效果执行,提交了事务
在这里插入图片描述

下面是代码

MultiTransactionDao.java

package com.ld.aop.dao;

import java.sql.SQLException;
import java.util.HashSet;
import java.util.Set;

import com.ld.dao.TransactionDao;

/**
 * 多个不同数据源事务的dao进行合并
 * 
 * @ClassName MultiTransactionDao
 * @author <a href="892042158@qq.com" target="_blank">于国帅</a>
 * @date 2019年2月13日 下午3:05:02
 *
 */
public class MultiTransactionDao {

    private Set<TransactionDao> multiDaoSet = new HashSet<>();

    // 通过构造函数合并多个数据源
    public MultiTransactionDao(Class<? extends TransactionDao>[] dataSourceDaos) { // 直接注入已经实例化好的
        for (Class<? extends TransactionDao> class1 : dataSourceDaos) {
            multiDaoSet.add(MultiDataSourceFactory.getDao(class1));
        }
    }

    public MultiTransactionDao(Set<TransactionDao> multiDaoSet) {
        this.multiDaoSet = multiDaoSet;
    }

    // 开启事务
    public void starTransaction() throws SQLException {
        for (TransactionDao transactionDao : multiDaoSet) {
            transactionDao.starTransaction();
        }
    }

    // 提交事务
    public void commitTransaction() throws Exception {
        for (TransactionDao transactionDao : multiDaoSet) {
            transactionDao.commitTransaction();
        }
    }

    // 回滚事务
    public void rollbackTransaction() {
        for (TransactionDao transactionDao : multiDaoSet) {
            transactionDao.rollbackTransaction();
        }
    }
}

为了防止反复处理dao对象,导致内存变大,建立缓存MultiDataSourceFactory.java

package com.ld.aop.dao;

import java.util.HashSet;
import java.util.Set;

import com.ld.dao.CreditBaseDB1Dao;
import com.ld.dao.CreditBaseNewDao;
import com.ld.dao.TransactionDao;

public class MultiDataSourceFactory {
    public static final CreditBaseNewDao DB0_DAO = new CreditBaseNewDao();
    public static final CreditBaseDB1Dao DB1_DAO = new CreditBaseDB1Dao();
    private static Set<TransactionDao> multiDaoSet = new HashSet<>();
    static {
        multiDaoSet.add(DB0_DAO);
        multiDaoSet.add(DB1_DAO);
    }

    /***
     * 获取需要开启事务的dao
     * 
     * @Title getDao
     * @author 于国帅
     * @date 2019年3月6日 下午2:18:01
     * @param dao
     * @return TransactionDao
     */
    public static TransactionDao getDao(Class<? extends TransactionDao> dao) {
        for (TransactionDao transactionDao : multiDaoSet) {
            transactionDao.getClass().isAssignableFrom(dao);
            return transactionDao;
        }
        return DB0_DAO;
    }

    /**
     * 返回一个multiDaoSet的完整副本
     * 
     * @Title getAllDao
     * @author 于国帅
     * @date 2019年3月6日 下午1:41:26
     * @return Set<TransactionDao>
     */
    public static Set<TransactionDao> getAllDao() {
        return new HashSet<>(multiDaoSet);
    }
}

@LdTransactional

package com.ld.aop.annotation;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import org.springframework.core.annotation.AliasFor;

import com.ld.dao.CreditBaseNewDao;
import com.ld.dao.TransactionDao;

/**
 * Transactional 一步步模拟这个注解
 * 
 * @ClassName LdTransactional
 * @author <a href="892042158@qq.com" target="_blank">于国帅</a>
 * @date 2019年2月13日 上午10:58:19
 *
 */
@Documented
@Retention(RUNTIME)
@Target(METHOD)
public @interface LdTransactional {
    /**
     * 指定数据源的Dao,存在多个的时候,多个数据源执行提交
     * 
     * @默认为db0的mysqlDao
     * @Title dataSourceDao
     * @author 于国帅
     * @date 2019年2月13日 下午2:53:43
     * @return Class<? extends TransactionDao>[]
     */
    @AliasFor("dataSourceDao")
    Class<? extends TransactionDao>[] value() default { CreditBaseNewDao.class };

    /**
     * 指定数据源的Dao,存在多个的时候,多个数据源执行提交
     * 
     * @默认为db0的mysqlDao
     * @Title dataSourceDao
     * @author 于国帅
     * @date 2019年2月13日 下午2:53:43
     * @return Class<? extends TransactionDao>[]
     */
    @AliasFor("value")
    Class<? extends TransactionDao>[] dataSourceDao() default { CreditBaseNewDao.class };

    /**
     * 是否开启所有的数据源的事务,默认不开启,采用dataSourceDao 管理需要开启的事务
     * 
     * @Title daoAll
     * @author 于国帅
     * @date 2019年2月13日 下午4:16:35
     * @return boolean
     */
    boolean daoAll() default false;

    /**
     * Class对象数组,必须继承自Throwable 不会导致事务回滚的异常类数组
     * 
     * @默认所有异常都要回滚
     * @Title noRollbackFor
     * @author 于国帅
     * @date 2019年2月13日 上午11:41:16
     * @return Class<? extends Throwable>[]
     */
    Class<? extends Throwable>[] noRollbackFor() default {};

    /**
     * 
     * Class对象数组,必须继承自Throwable 会导致事务回滚的异常类数组
     * 
     * @当有不需要回滚的异常中包含一个异常需要回滚的时候用这个
     * @Title rollbackFor
     * @author 于国帅
     * @date 2019年2月13日 下午12:55:33
     * @return Class<? extends Throwable>[]
     */
    Class<? extends Throwable>[] rollbackFor() default {};
}

LdTransactionalAop

package com.ld.aop;

import java.lang.reflect.Method;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;

import com.ld.aop.annotation.LdTransactional;
import com.ld.aop.dao.MultiDataSourceFactory;
import com.ld.aop.dao.MultiTransactionDao;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Aspect
@Component
public class LdTransactionalAop {

    @Pointcut("@annotation(com.ld.aop.annotation.LdTransactional)")
    public void cutLdTransactional() {
    }

    /**
     * 因为aop 切入时 与当前的方法为一个线程,所以随便找一个dao 开启事务就可以
     */
    @Around("cutLdTransactional()")
    public Object doLdTransactional(ProceedingJoinPoint point) {
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        LdTransactional ldTransactional = AnnotationUtils.findAnnotation(method, LdTransactional.class);
        MultiTransactionDao multiTransactionDao = null;
        if (ldTransactional.daoAll()) { // 如果开启了全部 那么加载全部
            multiTransactionDao = new MultiTransactionDao(MultiDataSourceFactory.getAllDao());
        } else {
            multiTransactionDao = new MultiTransactionDao(ldTransactional.dataSourceDao());
        }
        Object result = null;
        System.err.println(Thread.currentThread().getName());
        try {
            multiTransactionDao.starTransaction();
            result = point.proceed();
        } catch (Throwable e) {
            handleThrowable(e, ldTransactional, multiTransactionDao);
        } finally {
            try {
                multiTransactionDao.commitTransaction();
            } catch (Exception e) {
                handleThrowable(e, ldTransactional, multiTransactionDao);
            }
        }
        return result;
    }

    // 处理异常是否回滚
    private void handleThrowable(Throwable e, LdTransactional ldTransactional, MultiTransactionDao mysqlBaseDao) {
        log.error("打印事务异常信息", e);
        boolean rollbackFalg = true; // 默认回滚
        Class<? extends Throwable>[] noRollbackFor = ldTransactional.noRollbackFor();
        for (Class<? extends Throwable> class1 : noRollbackFor) {
            if (class1.isAssignableFrom(e.getClass())) { // 如果有符合不会滚的条件那么 不会滚
                rollbackFalg = false;
                break;
            }
        }
        Class<? extends Throwable>[] rollbackFor = ldTransactional.rollbackFor();
        for (Class<? extends Throwable> class1 : rollbackFor) {
            if (class1.isAssignableFrom(e.getClass())) { // 如果有符合不会滚的条件那么 不会滚
                rollbackFalg = true;
                break;
            }
        }
        if (rollbackFalg) {
            mysqlBaseDao.rollbackTransaction();
        }
    }
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是一个基本的Spring Boot + MyBatis项目的框架搭建步骤: 1. 创建Maven项目 使用Maven创建一个新的Java项目。 2. 添加依赖 在pom.xml文件中添加Spring Boot和MyBatis的依赖: ```xml <dependencies> <!-- Spring Boot --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- MyBatis --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.4</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.23</version> </dependency> </dependencies> ``` 其中,mysql-connector-java为MySQL数据库驱动的依赖。根据需要可以替换为其他数据库的驱动。 3. 配置数据库连接 在application.properties文件中添加数据库连接配置: ``` spring.datasource.url=jdbc:mysql://localhost:3306/mydatabase spring.datasource.username=root spring.datasource.password=password ``` 其中,mydatabase为需要连接的数据库名称,root为数据库用户名,password为数据库用户密码。 4. 创建实体类和DAO层接口 创建实体类和DAO层接口,并在接口中定义需要执行的SQL语句。 5. 创建MyBatis映射文件 在resources目录下创建MyBatis映射文件,并定义SQL语句和参数的映射关系。 6. 配置MyBatis 在application.properties文件中添加MyBatis的配置: ``` mybatis.mapper-locations=classpath:mapper/*.xml mybatis.type-aliases-package=com.example.demo.entity ``` 其中,mapper-locations为MyBatis映射文件的位置,type-aliases-package为实体类所在的包名。 7. 创建Service层和Controller层 创建Service层和Controller层,并调用DAO层接口中的方法。 8. 运行项目 运行Spring Boot项目,并测试接口是否能正常调用。 以上就是一个基本的Spring Boot + MyBatis项目的框架搭建步骤。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值