Boot版本 2.0
此处
@EnableTransactionManagement(order = 2) 需要打个问号,文章结尾会解释
application.yml
/**
* 多数据源连接池配置
*/
@Bean
public DynamicDataSource mutiDataSource(DruidProperties druidProperties, MutiDataSourceProperties mutiDataSourceProperties) {
DruidDataSource dataSourceGuns = dataSource(druidProperties);
DruidDataSource bizDataSource = bizDataSource(druidProperties, mutiDataSourceProperties);
try {
dataSourceGuns.init();
bizDataSource.init();
} catch (SQLException sql) {
sql.printStackTrace();
}
DynamicDataSource dynamicDataSource = new DynamicDataSource();
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put(mutiDataSourceProperties.getDataSourceNames()[0], dataSourceGuns);
hashMap.put(mutiDataSourceProperties.getDataSourceNames()[1], bizDataSource);
dynamicDataSource.setTargetDataSources(hashMap);
dynamicDataSource.setDefaultTargetDataSource(dataSourceGuns);
return dynamicDataSource;
}
/**
* guns的数据源
*/
private DruidDataSource dataSource(DruidProperties druidProperties) {
DruidDataSource dataSource = new DruidDataSource();
druidProperties.config(dataSource);
return dataSource;
}
/**
* 多数据源,第二个数据源
*/
private DruidDataSource bizDataSource(DruidProperties druidProperties, MutiDataSourceProperties mutiDataSourceProperties) {
DruidDataSource dataSource = new DruidDataSource();
druidProperties.config(dataSource);
mutiDataSourceProperties.config(dataSource);
return dataSource;
}
把初始参数赋值给 dataSource
@Bean
@ConfigurationProperties(prefix = "guns.muti-datasource")
public MutiDataSourceProperties mutiDataSourceProperties() {
return new MutiDataSourceProperties();
}
import com.alibaba.druid.pool.DruidDataSource;
/**
* 默认多数据源配置
*
*/
public class MutiDataSourceProperties {
private String url = "jdbc:mysql://127.0.0.1:3306/biz?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull";
private String username = "root";
private String password = "root";
private String driverClassName = "com.mysql.cj.jdbc.Driver";
private String validationQuery = "SELECT 'x'";
private String[] dataSourceNames = {"dataSourceGuns", "dataSourceBiz"};
public void config(DruidDataSource dataSource) {
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
dataSource.setDriverClassName(driverClassName);
dataSource.setValidationQuery(validationQuery);
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getDriverClassName() {
return driverClassName;
}
public void setDriverClassName(String driverClassName) {
this.driverClassName = driverClassName;
}
public String getValidationQuery() {
return validationQuery;
}
public void setValidationQuery(String validationQuery) {
this.validationQuery = validationQuery;
}
public String[] getDataSourceNames() {
return dataSourceNames;
}
public void setDataSourceNames(String[] dataSourceNames) {
this.dataSourceNames = dataSourceNames;
}
}
需要改写一下
AbstractRoutingDataSource.java
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSourceType();
}
}
public class DataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
/**
* 设置数据源类型
*
* @param dataSourceType 数据库类型
*/
public static void setDataSourceType(String dataSourceType) {
contextHolder.set(dataSourceType);
}
/**
* 获取数据源类型
*/
public static String getDataSourceType() {
return contextHolder.get();
}
/**
* 清除数据源类型
*/
public static void clearDataSourceType() {
contextHolder.remove();
}
}
其中
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
是每一个线程的私有副本,不会造成脏读,但是这些副本变量必须要清除,防止内存泄漏。具体内存泄漏原理请参考
https://www.jianshu.com/p/dde92ec37bd1。添加一句 此处的内存泄漏一般只存在线程池中。
AOP
@Aspect
public class MultiSourceExAop implements Ordered {
private Logger log = LoggerFactory.getLogger(this.getClass());
@Autowired
MutiDataSourceProperties mutiDataSourceProperties;
@Pointcut(value = "@annotation(com.stylefeng.guns.core.mutidatasource.annotion.DataSource)")
private void cut() {
}
@Around("cut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
Signature signature = point.getSignature();
MethodSignature methodSignature = null;
if (!(signature instanceof MethodSignature)) {
throw new IllegalArgumentException("该注解只能用于方法");
}
methodSignature = (MethodSignature) signature;
//通过反射获得方法
Object target = point.getTarget();
Method currentMethod = target.getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
//通过方法获得注解
DataSource datasource = currentMethod.getAnnotation(DataSource.class);
//通过注解 获得数据源Key
if (datasource != null) {
DataSourceContextHolder.setDataSourceType(datasource.name());
log.debug("设置数据源为:" + datasource.name());
} else {
DataSourceContextHolder.setDataSourceType(mutiDataSourceProperties.getDataSourceNames()[0]);
log.debug("设置数据源为:dataSourceCurrent");
}
try {
return point.proceed();
} finally {
log.debug("清空数据源信息!");
DataSourceContextHolder.clearDataSourceType();
}
}
/**
* aop的顺序要早于spring的事务
*/
@Override
public int getOrder() {
return 1;
}
}
/**
*
* 多数据源标识
*
*/
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })
public @interface DataSource {
String name() default "";
}
public interface DatasourceEnum {
String DATA_SOURCE_GUNS = "dataSourceGuns"; //guns数据源
String DATA_SOURCE_BIZ = "dataSourceBiz"; //其他业务的数据源
}
//客户端代码 -----完工哈哈哈
@DataSource(name = DatasourceEnum.DATA_SOURCE_GUNS)
@Transactional
public void testGuns() {
Test test = new Test();
test.setBbb("gunsTest");
testMapper.insert(test);
}
为什么@EnableTransactionManagement(order = 2)
因为切换数据源和开启事务管理是分先后的,spring事务管理是跟数据库事务绑定一起的,开启一个事务就已经和数据源绑定在一起,再切换数据源时,会造成切换数据源失效。所以切换数据源aop要在开启事务aop之前,所以切换数据源aop的order是1,事务的是2。
肥肠欢迎大家给我回复和提意见。我准备下一篇文章讨论下事务,这个事务感觉内容很复杂啊