前言:项目启动过程中有错,未解决,这里仅是实现思路
一,业务场景:新增,修改,删除操作主库,查询操作从库
1,主,从库配置,通过配置类,分别读取主从库配置信息,创建不同数据源,并设置不同数据源
2,通过aop区分,不同标识的方法,使用ThreadLocal记录当前线程的数据源key
二,代码
1,AOP
@Component
@Aspect
public class DataSourceAop {
//主库切点,标注了DataSourceMaster注解或者方法名为insert,update等开头的方法,走主库
@Pointcut("@annotation(com.anji.allways.business.payment.ReadWriteDataSourceAop.DataSourceMasterAnno)" +
"|| execution(* com.anji.allways.business.payment.controller..*.insert*(..))" +
"|| execution(* com.anji.allways.business.payment.controller..*.add*(..))" +
"|| execution(* com.anji.allways.business.payment.controller..*.update*(..))" +
"|| execution(* com.anji.allways.business.payment.controller..*.edit*(..))" +
"|| execution(* com.anji.allways.business.payment.controller..*.delete*(..))" +
"|| execution(* com.anji.allways.business.payment.controller..*.remove*(..))")
public void masterPointcut() {
}
//从库切点,没有标注了DataSourceMaster注解且方法名为select,get等开头的方法,走从库
@Pointcut("!@annotation(com.anji.allways.business.payment.ReadWriteDataSourceAop.DataSourceMasterAnno)" +
"&& (execution(* com.anji.allways.business.payment.controller..*.select*(..))" +
"|| execution(* com.anji.allways.business.payment.controller..*.get*(..))" +
"|| execution(* com.anji.allways.business.payment.controller..*.find*(..))" +
"|| execution(* com.anji.allways.business.payment.controller..*.query*(..)))")
public void slavePointcut() {
}
@Before("masterPointcut()")
public void master() {
DynamicDataSourceHolderAop.markMaster();
}
@Before("slavePointcut()")
public void slave() {
DynamicDataSourceHolderAop.markSlave();
}
}
2,数据源配置
@Configuration
@EnableTransactionManagement
@MapperScan(basePackages = DataSourceConfig.PACKAGE)
public class DataSourceConfig {
//public class DataSourceConfig extends MybatisAutoConfiguration {
static final String PACKAGE = "com.anji.allways.business.sales.mapper";
/* public DataSourceConfig(MybatisProperties properties, ObjectProvider<Interceptor[]> interceptorsProvider, ResourceLoader resourceLoader, ObjectProvider<DatabaseIdProvider> databaseIdProvider, ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) {
super(properties, interceptorsProvider, resourceLoader, databaseIdProvider, configurationCustomizersProvider);
}*/
/**
* 主库数据源
* @return
*/
@Bean
@ConfigurationProperties(prefix = "primary.datasource")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}
/**
* 从库数据源
* @return
*/
@Bean
@ConfigurationProperties(prefix = "secondary.datasource")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().build();
}
/* @Bean
@Override
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
return super.sqlSessionFactory(dataSource());
}*/
/**
* 自定义数据源,内部持有了主库和从库数据源
* 通过某种机制应用程序读取不同数据源
* @param masterDataSource
* @param slaveDataSource
* @return
*/
@Bean
public DataSource myRoutingDataSource(
@Qualifier("masterDataSource") DataSource masterDataSource,
@Qualifier("slaveDataSource") DataSource slaveDataSource) {
MyRoutingDataSource proxy = new MyRoutingDataSource();
Map<Object, Object> targetDataResources = new HashMap<>();
targetDataResources.put(DataSourceType.MASTER, masterDataSource);
targetDataResources.put(DataSourceType.SLAVE, slaveDataSource);
//当方法没有别aop拦截,默认主库
proxy.setDefaultTargetDataSource(masterDataSource());
proxy.setTargetDataSources(targetDataResources);
proxy.afterPropertiesSet();
return proxy;
}
}
3,
数据源的key
/**
* 使用ThreadLocal技术来记录当前线程中的数据源的key
*
* @author zhijun
*/
public class DynamicDataSourceHolderAop {
//使用ThreadLocal记录当前线程的数据源key
private static final ThreadLocal<String> holder = new ThreadLocal<String>();
/**
* 设置数据源key
*
* @param key
*/
public static void putDataSourceKey(String key) {
holder.set(key);
}
/**
* 获取数据源key
*
* @return
*/
public static String getDataSourceKey() {
return holder.get();
}
/**
* 标记写库
*/
public static void markMaster() {
putDataSourceKey(DataSourceType.MASTER.toString());
}
/**
* 标记读库
*/
public static void markSlave() {
putDataSourceKey(DataSourceType.SLAVE.toString());
}
}
4,数据源路由
public class MyRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceHolderAop.getDataSourceKey();
}
}
5,自定义主数据源注解
/**
* <pre>
* 主数据源自定义注解
* </pre>
*
* @author shenke
* @version $Id: LoggerManage.java, v 0.1 2018年3月16日 上午9:40:56 wanglong Exp $
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSourceMasterAnno {
String description();
}
6,数据源类型枚举
/**
* 主库从库标记key
*/
public enum DataSourceType {
MASTER, SLAVE;
}
7,测试
测试1
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class DatasourceApplicationTest {
private static Logger logger = LoggerFactory.getLogger(DatasourceApplicationTest.class);
@Autowired
private TradeMapper tradeMapper;
/**
* 从从库中读取-开发库
*/
@Test
public void readData() {
TradeVo tradeVo = new TradeVo();
List<TradeVo> listSecond = tradeMapper.selectTrade(tradeVo);
log.info("从库secondary dataSource size:{}", listSecond.size());
}
/**
* 从主库中读取-测试库
*/
@Test
public void insertTest() {
TradeVo tradeVo = new TradeVo();
List<TradeVo> listP = tradeMapper.selectTrade(tradeVo);
logger.info("主库primary dataSource size:{}", listP.size());
}
}
测试二
@Slf4j
@RestController
@RequestMapping("/dataSource")
public class ReadWriteController {
private static Logger logger = LoggerFactory.getLogger(ReadWriteController.class);
@Autowired
private TradeMapper tradeMapper;
/**
* 从从库中读取-开发库
*/
@RequestMapping("readMaster")
public void readData() {
TradeVo tradeVo = new TradeVo();
List<TradeVo> listSecond = tradeMapper.selectTrade(tradeVo);
log.info("从库secondary dataSource size:{}", listSecond.size());
}
/**
* 从主库中读取-测试库
*/
@RequestMapping("readSlave")
public void insertTest() {
TradeVo tradeVo = new TradeVo();
List<TradeVo> listP = tradeMapper.selectTrade(tradeVo);
logger.info("主库primary dataSource size:{}", listP.size());
}
}