因为系统里需要同时用到多个数据源,于是对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()); } } }