因为系统里需要同时用到多个数据源,于是对mybatis以及jpa都做了多数据源的一些配置。原理呢就是利用切面的前置通知加注解的方式实现动态切换数据源。
创建一个注解
创建该注解的目的是利用注解的value告诉程序我们需要切换的目标数据源名称是哪个。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface DataSource {
String value() default "";
}
创建DataSourceHolder
该目的是用于持有当前线程中使用的数据源标识。
public class DataSourceHolder {
private static final ThreadLocal contextHolder=new ThreadLocal();
public static void setDataSourceType(String dataSourceType) {
contextHolder.set(dataSourceType);
}
public static String getDataSourceType() {
return contextHolder.get();
}
public static void clearDataSourceType() {
contextHolder.remove();
}
}
重写查找数据源标识的方法
AbstractRoutingDataSource获取数据源之前会先调用determineCurrentLookupKey方法查找当前的lookupKey,这个lookupKey就是数据源标识。
因此通过重写查找数据源标识的方法就可以让spring切换到指定的数据源了。
public class RoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceHolder.getDataSourceType();
}
}
配置基本的数据源bean
@Configuration
public class BaseDataSourceConfig {
@Bean(name = "oracleDataSource")
@Qualifier("oracleDataSource")
@Primary
@ConfigurationProperties(prefix = "spring.datasource.oracle")
public DataSource oracleDataSource(){
return DataSourceBuilder.create().build();
}
@Bean(name = "mysqlDataSource")
@Qualifier("mysqlDataSource")
@ConfigurationProperties(prefix = "spring.datasource.mysql")
public DataSource mysqlDataSource(){
return DataSourceBuilder.create().build();
}
@Bean(name="routingDataSource")
public RoutingDataSource dataSource(){
RoutingDataSource dataSource=new RoutingDataSource();
Map targetDataSource=new HashMap();
targetDataSource.put("mysql",mysqlDataSource());
targetDataSource.put("oracle",oracleDataSource());
dataSource.setTargetDataSources(targetDataSource);
dataSource.setDefaultTargetDataSource(oracleDataSource());
return dataSource;
}
配置Jpa数据源
@Configuration
@EnableJpaRepositories(basePackages = "com.magicdu.e300pro.dataquery.dao.repository")
@EntityScan(basePackages = "com.magicdu.e300pro.dataquery.entity")
public class JpaDatasourceConfig {
@Bean
public JdbcTemplate jdbcTemplate(RoutingDataSource dataSource){
return new JdbcTemplate(dataSource);
}
@Bean("entityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(RoutingDataSource dataSource, JpaVendorAdapter jpaVendorAdapter){
LocalContainerEntityManagerFactoryBean entityManagerFactory=new LocalContainerEntityManagerFactoryBean();
entityManagerFactory.setDataSource(dataSource);
entityManagerFactory.setJpaVendorAdapter(jpaVendorAdapter);
entityManagerFactory.setPackagesToScan("com.magicdu.e300pro.dataquery.*");
return entityManagerFactory;
}
@Bean
public JpaVendorAdapter jpaVendorAdapter(){
HibernateJpaVendorAdapter jpaVendorAdapter=new HibernateJpaVendorAdapter();
jpaVendorAdapter.setDatabase(Database.ORACLE);
jpaVendorAdapter.setShowSql(true);
jpaVendorAdapter.setGenerateDdl(false);
jpaVendorAdapter.setDatabasePlatform("org.hibernate.dialect.Oracle10gDialect");
return jpaVendorAdapter;
}
@Bean("transactionManager")
public PlatformTransactionManager transactionManager(LocalContainerEntityManagerFactoryBean entityManagerFactoryBean) {
JpaTransactionManager transactionManager
= new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactoryBean.getObject());
return transactionManager;
}
配置mybatis数据源
@Configuration
@MapperScan(basePackages = "com.magicdu.e300pro.dataquery.dao.mapper")
@EnableTransactionManagement
public class MybatisDataSourceConfig {
@Bean
public SqlSessionFactory sqlSessionFactory(RoutingDataSource dataSource) throws Exception{
SqlSessionFactoryBean sqlSessionFactoryBean=new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
return sqlSessionFactoryBean.getObject();
}
@Bean
public DataSourceTransactionManager dataSourceTransactionManager( RoutingDataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory){
return new SqlSessionTemplate(sqlSessionFactory);
}
}
最重要的一步,配置切面以及前置通知
这一步的目的是根据注解通过切面自动识别数据源,并切换当前线程中的数据源
@Aspect
@Order(-1) //ensure execute the aop before @Transactional
@Component
public class DataSourceAspect {
public DataSourceAspect(){
System.err.println("execution(* com.bickcess.haji.dao..*.*(..))");
}
@Pointcut("execution(* com.magicdu.e300pro.dataquery.dao.mapper.*.*(..)) || execution(* com.magicdu.e300pro.dataquery.dao.repository.*.*(..))")
private void anyMethod(){
}
@AfterReturning(value ="anyMethod()",returning = "result")
public void afterReturning(JoinPoint joinPoint, Object result){
DataSourceHolder.clearDataSourceType();
}
@Before(value="anyMethod()")
public void before(JoinPoint joinPoint) throws Throwable {
System.err.println("performing announce ");
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
//if method use DataSource annotation
if (method.isAnnotationPresent(DataSource.class)) {
//get annotation name of the method
DataSource datasource = method.getAnnotation(DataSource.class);
System.err.println(datasource.value());
//assign the value of annotation to the datasource holder class
DataSourceHolder.setDataSourceType(datasource.value());
}
}
}