场景
公司有一个业务需要控制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();
}
}
}