在公司的项目上,有个多数据源的功能,它通过注解完成实现,在执行一个Dao方法的时候,去走指定的数据库。
这个大家应该应该都不陌生,后面可以看看分享一波。
我项目中,多数据源主要是用于读写分离,尤其是一些读取的SQL全部都走到了读库。
但今天这个问题,有点诡异为什么明明加了@Ds注解,但还是走到了主库,注解没有生效?
这实际上很简单,因为这个注解是在非事务的条件下使用的,一但所处的环境是事务包裹着的话,就会失效
所以,只需要在Ds注解上,再加个@Transactional(propagation = Propagation.NOT_SUPPORTED)即可,这样就可以解决这个问题
这涉及到Spring事务的传播机制,我们来重温一下
事务的传播机制,简单的来说,就是一个方法,调用另一个方法。原本就有的事务,在遇到一个新的事务后会发生什么机制。这就是我们要讲得事务传播机制。
通过@Transactional的propagation属性我们可以进行配置事务的传播机制,我们先看看这个枚举里面都有些什么
package org.springframework.transaction.annotation;
public enum Propagation {
REQUIRED(0),
SUPPORTS(1),
MANDATORY(2),
REQUIRES_NEW(3),
NOT_SUPPORTED(4),
NEVER(5),
NESTED(6);
private final int value;
private Propagation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}简简单单,没有说明,一个一个来看吧。先写出两个Dao,一会测试用
package com.banmoon.test.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Transactional
public void insert(){
jdbcTemplate.execute("INSERT INTO `user`(`name`) VALUES ('半月')");
}
} package com.banmoon.test.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class PlatformDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Transactional
public void insert(){
jdbcTemplate.execute("INSERT INTO `platform`(`name`) VALUES ('博客平台')");
}
}这是spring默认的事务传播机制。
我们写一个Service去调用上面两个Dao
package com.banmoon.test.service;
import com.banmoon.test.dao.PlatformDao;
import com.banmoon.test.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private UserDao userDao;
@Autowired
private PlatformDao platformDao;
// 如果外部方法已经有事务了,那么本方法将加入这个事务
public void insert(){
// 此处将创建事务
userDao.insert();
// 此处将创建事务,如果发生异常,只会回滚自己的事务
platformDao.insert();
}
} package com.banmoon.test.service;
import com.banmoon.test.dao.PlatformDao;
import com.banmoon.test.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Autowired
private UserDao userDao;
@Autowired
private PlatformDao platformDao;
/**
* 外部方法有事务,里面的两个方法的事务,将直接加入到外部方法的事务中
* 如果发生异常,导致外部方法中整个事务的回滚
*/
@Transactional
public void insert(){
// 不创建事务了,加入到外部事务
userDao.insert();
// 不创建事务了,加入到外部事务
platformDao.insert();
}
} package com.banmoon.test.service;
import com.banmoon.test.dao.PlatformDao;
import com.banmoon.test.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Autowired
private UserDao userDao;
@Autowired
private PlatformDao platformDao;
/**
* 外部方法有事务,里面的两个方法的事务,将直接加入到外部方法的事务中
* 如果发生异常,导致外部方法中整个事务的回滚
*/
@Transactional
public void insert(){
// 这里的修饰是@Transactional(propagation = Propagation.SUPPORTS)
userDao.insert();
// 这里的修饰是@Transactional(propagation = Propagation.SUPPORTS)
platformDao.insert();
}
/**
* 外部方法没有事务,里面的两个方法不再会创建事务,直接以非事务的方式运行
*/
public void insert(){
// 这里的修饰是@Transactional(propagation = Propagation.SUPPORTS)
userDao.insert();
// 这里的修饰是@Transactional(propagation = Propagation.SUPPORTS)
platformDao.insert();
}
} package com.banmoon.test.service;
import com.banmoon.test.dao.PlatformDao;
import com.banmoon.test.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Autowired
private UserDao userDao;
@Autowired
private PlatformDao platformDao;
/**
* 外部方法有事务,里面的两个方法的事务,将直接加入到外部方法的事务中
* 如果发生异常,导致外部方法中整个事务的回滚
*/
@Transactional
public void insert(){
// 这里的修饰是@Transactional(propagation = Propagation.MANDATORY)
userDao.insert();
// 这里的修饰是@Transactional(propagation = Propagation.MANDATORY)
platformDao.insert();
}
/**
* 外部方法没有事务,里面直接会抛出异常
*/
public void insert(){
// 这里的修饰是@Transactional(propagation = Propagation.MANDATORY)
userDao.insert();
// 这里的修饰是@Transactional(propagation = Propagation.MANDATORY)
platformDao.insert();
}
}来看看异常吧

这也就是说,无论外部有无事务,内部方法都将会创建新的事务
package com.banmoon.test.service;
import com.banmoon.test.dao.PlatformDao;
import com.banmoon.test.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Autowired
private UserDao userDao;
@Autowired
private PlatformDao platformDao;
/**
* 外部方法已经有事务了,内部方法还是会重新创建一个新的事务
* 如果发生异常,只会导致自己所在的方法事务回滚
* 这里创建了三个事务
*/
@Transactional
public void insert(){
// 这里的修饰是@Transactional(propagation = Propagation.REQUIRES_NEW)
userDao.insert();
// 这里的修饰是@Transactional(propagation = Propagation.REQUIRES_NEW)
platformDao.insert();
}
/**
* 外部方法没有事务,内部方法会创建一个新的事务
* 如果发生异常,只会导致自己所在的方法事务回滚
* 这里创建了两个事务
*/
public void insert(){
// 这里的修饰是@Transactional(propagation = Propagation.REQUIRES_NEW)
userDao.insert();
// 这里的修饰是@Transactional(propagation = Propagation.REQUIRES_NEW)
platformDao.insert();
}
}也就是说,无论外部方法有无事务,里面的方法执行都是没有事务的。
但我有点疑惑,如果里面方法抛出异常了,外部方法会怎么样。简单测试一下吧
package com.banmoon.test.service;
import com.banmoon.test.dao.PlatformDao;
import com.banmoon.test.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Autowired
private UserDao userDao;
@Autowired
private PlatformDao platformDao;
/**
* 无论外部方法有无事务,里面的方法执行都是没有事务的
*/
@Transactional
public void insert(){
// 这里的修饰是@Transactional(propagation = Propagation.NOT_SUPPORTED)
userDao.insert();
// 外部方法的执行
jdbcTemplate.execute("INSERT INTO `user`(`name`) VALUES ('半月1')");
// 这里的修饰是@Transactional(propagation = Propagation.NOT_SUPPORTED)
platformDao.insert();
}
}我执行了,结果是只有26行的数据插入被回退。
24行和28行是非事务运行的,就算异常了也不会回退
和SUPPORTS相反的一个传播机制
package com.banmoon.test.service;
import com.banmoon.test.dao.PlatformDao;
import com.banmoon.test.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Autowired
private UserDao userDao;
@Autowired
private PlatformDao platformDao;
/**
* 外部方法已经有事务了,将抛出异常
*/
@Transactional
public void insert(){
// 这里的修饰是@Transactional(propagation = Propagation.NEVER)
userDao.insert();
// 这里的修饰是@Transactional(propagation = Propagation.NEVER)
platformDao.insert();
}
/**
* 外部方法没有事务,内部方法也不再创建,以非事务的方式运行
*/
public void insert(){
// 这里的修饰是@Transactional(propagation = Propagation.NEVER)
userDao.insert();
// 这里的修饰是@Transactional(propagation = Propagation.NEVER)
platformDao.insert();
}
}组成嵌套事务是什么意思呢? 我们将外部方法的事务称为父事务,内部方法创建的事务为子事务
这里同样,我们来进行测试一下
package com.banmoon.test.service;
import com.banmoon.test.dao.PlatformDao;
import com.banmoon.test.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Autowired
private UserDao userDao;
@Autowired
private PlatformDao platformDao;
@Autowired
private JdbcTemplate jdbcTemplate;
@Transactional
public void insert(){
// 这里的修饰是@Transactional(propagation = Propagation.NESTED)
userDao.insert();
// 外部方法的执行
jdbcTemplate.execute("INSERT INTO `user`(`name`) VALUES ('半月1')");
try {
// 这里的修饰是@Transactional(propagation = Propagation.NESTED)
platformDao.insert();// 此处将抛出空指针
} catch (Exception e) {
e.printStackTrace();
}
}
@Transactional
public void insert1(){
// 这里的修饰是@Transactional(propagation = Propagation.NESTED)
userDao.insert();
// 外部方法的执行
jdbcTemplate.execute("INSERT INTO `user`(`name`) VALUES ('半月1')");
// 这里的修饰是@Transactional(propagation = Propagation.NESTED)
platformDao.insert();
throw new NullPointException();// 此处将抛出空指针
}
}执行insert(),这是子事务中抛出的异常。结果是只有30行的插入数据回滚。注意的就是,内部方法的异常要自己捕获,别被父事务发现了。如果发现了,大家就一起回滚吧。
执行insert1(),这是父事务中抛出的异常。结果发现所有插入的数据都回滚了。
那么,为什么多数据源遇到了事务就会失效了呢,这还是因为dynamic-datasource切换数据源的原理就是实现了DataSource接口,实现了getConnection方法,只要处于事务中,@Ds标记的方法对其他数据源操作只会使用开启事务的数据源,因为开启事务数据源会被缓存下来,可以在DataSourceTransactionManager的doBegin()方法中看见那个txObject,如果在一个事务内,就会复用Connection,所以切换不了数据源

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。