- 使用注解切换数据源
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Datasource {
/**- 数据源的值
- @return
*/
String value() default “”;
}
- yml配置数据源
datasource:
#主数据源
rch:
jdbcUrl: jdbc:mysql://localhost:3306/master?relaxAutoCommit=true&zeroDateTimeBehavior=convertToNull&characterEncoding=utf-8&allowMultiQueries=true&tinyInt1isBit=false
username: root
password: 123456
#从数据源
talent:
username: root
password: 123456
jdbcUrl: jdbc:mysql://localhost:3306/slave?relaxAutoCommit=true&zeroDateTimeBehavior=convertToNull&characterEncoding=utf-8&allowMultiQueries=true&tinyInt1isBit=false - 数据源注入
@Data
public class MysqlConfig {
private String jdbcUrl;
private String username;
private String password;
}
/**
* 获取主数据源配置
* @return
*/
@Bean(“masterConfig”)
@ConfigurationProperties(“datasource.rch”)
public MysqlConfig rchConfig(){
return new MysqlConfig();
}
/**
* 获取slave数据源配置
* @return
*/
@Bean("slaveConfig")
@ConfigurationProperties("datasource.talent")
public MysqlConfig talentConfig(){
return new MysqlConfig();
}
/**
* master数据源
* @return
*/
@Bean("master")
public DataSource rchDataSource(@Qualifier("masterConfig") MysqlConfig rchConfig) {
log.info("rchDataSource init!");
return createAtomikosDataSource(rchConfig,"rchHikariCP");
}
/**
* slave数据源
* @return
*/
@Bean("slave")
public DataSource talentDataSource(@Qualifier("slaveConfig") MysqlConfig talentConfig) {
log.info("talentDataSource init!");
return createAtomikosDataSource(talentConfig,"talentHikariCP");
}
/**
* 生成Atomikos数据源
* @param mysqlConfig
* @param poolName
* @return
*/
private DataSource createAtomikosDataSource(MysqlConfig mysqlConfig,String poolName){
MysqlXADataSource mysqlXADataSource = new MysqlXADataSource();
mysqlXADataSource.setUrl(mysqlConfig.getJdbcUrl());
mysqlXADataSource.setUser(mysqlConfig.getUsername());
mysqlXADataSource.setPassword(mysqlConfig.getPassword());
mysqlXADataSource.setPinGlobalTxToPhysicalConnection(true);
// 事务管理器
AtomikosDataSourceBean atomikosDataSourceBean = new AtomikosDataSourceBean();
atomikosDataSourceBean.setXaDataSource(mysqlXADataSource);
atomikosDataSourceBean.setUniqueResourceName(poolName);
//连接池中最大的连接数
atomikosDataSourceBean.setMaxPoolSize(60);
atomikosDataSourceBean.setBorrowConnectionTimeout(120);
atomikosDataSourceBean.setMaxIdleTime(3600);
atomikosDataSourceBean.setReapTimeout(300);
atomikosDataSourceBean.setMaxLifetime(60000);
atomikosDataSourceBean.setMaintenanceInterval(60);
atomikosDataSourceBean.setTestQuery(“select 1”);
return atomikosDataSourceBean;
}
-
重写AbstractRoutingDataSource 类,修改数据源路由方式
@Slf4j
public class HandleDataSource extends AbstractRoutingDataSource {public static final ThreadLocal holder = new ThreadLocal();
/*默认数据源/
private static DataSource defaultDataSource;/*设置默认数据源/
@Override
public void setDefaultTargetDataSource(Object defaultTargetDataSource) {
super.setDefaultTargetDataSource(defaultTargetDataSource);
if (defaultTargetDataSource instanceof DataSource){
defaultDataSource=(DataSource)defaultTargetDataSource;
}
}/**
- 返回默认数据源
- @return
*/
public static DataSource getDefaultTargetDataSource(){
return defaultDataSource;
}
public static void setDataSource(String dataSource){
log.info(“设置了数据源{}”,dataSource);
holder.set(dataSource);
}public static String getDataSource(){
String dataSource = holder.get();
log.info(“获取了数据源{}”,dataSource);
return dataSource;
}
public static void clearDataSource(){
log.debug(“清除数据源了,恢复默认数据源!”);
holder.remove();
}@Override
protected Object determineCurrentLookupKey() {
return getDataSource();
}
} -
设置注解切换切面处理
@Aspect
@Slf4j
@Component
@Order(-1)
public class HandlerDataSourceAop {
//@within在类上设置
//@annotation在方法上进行设置
@Pointcut("@within(com.yjh.jta.annonation.Datasource)||@annotation(com.yjh.jta.annonation.Datasource)")
public void pointcut() {
}@Before(“pointcut()”)
public void doBefore(JoinPoint joinPoint) {
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
Datasource annotationClass = method.getAnnotation(Datasource.class);//获取方法上的注解
if (annotationClass == null) {
annotationClass = joinPoint.getTarget().getClass().getAnnotation(Datasource.class);//获取类上面的注解
if (annotationClass == null) return;
}
//获取注解上的数据源的值的信息
String dataSourceKey = annotationClass.value();
if (!StringUtils.isEmpty(dataSourceKey)) {
//给当前的执行SQL的操作设置特殊的数据源的信息
HandleDataSource.setDataSource(dataSourceKey);
}
log.info(“AOP动态切换数据源,className” + joinPoint.getTarget().getClass().getName()
+ “methodName” + method.getName() + “;dataSourceKey:” + dataSourceKey);
}@After(“pointcut()”)
public void after(JoinPoint point) {
//清理掉当前设置的数据源,让默认的数据源不受影响
HandleDataSource.clearDataSource();}
-
配置多数据源
@Primary
@Bean(“dynamicDataSource”)
public HandleDataSource multipleDataSource(@Qualifier(“master”) DataSource master,
@Qualifier(“slave”) DataSource slave) {
HandleDataSource handleDataSource = new HandleDataSource();
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(“master”, master);
//targetDataSources.put(DataSourceEnum.firm.getValue(), firmDataSource);
targetDataSources.put(“talent”, slave);
handleDataSource.setDefaultTargetDataSource(master);
handleDataSource.setTargetDataSources(slave);
//添加数据源
return handleDataSource;
}
设置sqlsessionfactory
@Bean(“sqlSessionFactory”)
@Primary
public MybatisSqlSessionFactoryBean sqlSessionFactoryBean(@Autowired HandleDataSource dynamicDataSource) throws Exception {
MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean();
sessionFactory.setDataSource(dynamicDataSource);
//设置我们自己重写的事务管理器,这里如果不设置,在是默认数据源没有添加注解时会获取数据源氏空的,会导致事务内切换数据源失败
//sessionFactory.setTransactionFactory(new MyTransactionsFactory());
sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(“classpath:/config/common-mybatis-config.xml”));
sessionFactory.getObject().getConfiguration().setMapUnderscoreToCamelCase(true);
return sessionFactory;
}
/**
* 注入事物管理器
* @return
*/
@Bean(name = “jtaTransaction”)
@Primary
public JtaTransactionManager regTransactionManager () {
UserTransactionManager userTransactionManager = new UserTransactionManager();
UserTransaction userTransaction = new UserTransactionImp();
return new JtaTransactionManager(userTransaction, userTransactionManager);
}
7. 测试:
@Override
//@Transactional(transactionManager = “jtaTransaction”)
//@Transactional(transactionManager = “masterTransaction”)
//@Transactional
public void update() {
//默认数据源
Department department = departmentMapper.selectCompanyById(“1”);
log.info(“department{}”,department);
//从数据源
int n = talentService.updateTalentStatus(“1330461138895355905”, 5);
//int i=7/0;
//默认数据源,主数据源,没有加注解
departmentMapper.updateDept(“产品研发中心”,“1”);
}
在我们没加注解@Transitonal()时能正常切换数据源

加入注解:发现数据源切换成从数据源后,被清除后事务中的数据源其实还是主数据源,就会报主库中没有改数据表

-
针对以上问题可添加一个自定义的事务管理
public class MyManagedTransaction extends SpringManagedTransaction {DataSource dataSource;
ConcurrentHashMap<String, Connection> map = new ConcurrentHashMap<>();public MyManagedTransaction(DataSource dataSource) {
super(dataSource);
this.dataSource = dataSource;
}@Override
public Connection getConnection() throws SQLException {
String key = HandleDataSource.getDataSource();
//String key = DynamicDataSourceContextHolder.getDateSourceType();
if(key==null){
Connection con = dataSource.getConnection();
map.put(“default”, con);
return con;
}
if (map.containsKey(key)) {
return map.get(key);
}
Connection con = dataSource.getConnection();
map.put(key, con);
return con;
}
}
然后添加到事务管理工厂
public class MyTransactionsFactory extends SpringManagedTransactionFactory {
@Override
public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
return new MyManagedTransaction(dataSource);
}
}
- 最后需要加入到我们的session工厂内就是在第六点内的一行代码
sessionFactory.setTransactionFactory(new MyTransactionsFactory());
这里就能在事务中来回切换数据源而且也能保证事务的一致性,你们可以通过自己手动写异常测试。
这里也是参考别人的写法:当然在这也有自己调整的地方
转载:https://blog.youkuaiyun.com/weixin_42324471/article/details/106264883
本文介绍了一种在Java应用中实现动态数据源切换的方法,包括使用注解切换数据源、YAML配置数据源、创建数据源及配置多数据源等步骤,并通过自定义事务管理器确保事务一致性。
1万+

被折叠的 条评论
为什么被折叠?



