SpringBoot Jpa 多數(shù)據(jù)源動態(tài)切換
在大型應用程序中,配置主從數(shù)據(jù)庫并使用讀寫分離是常見的設(shè)計模式。常用的實現(xiàn)方式是使用數(shù)據(jù)庫中間件,此文介紹如何通過編寫代碼的方式實現(xiàn)多數(shù)據(jù)源的配置和動態(tài)切換。核心是使用Spring 內(nèi)置的?AbstractRoutingDataSource?這個抽象類,它可以把多個數(shù)據(jù)源配置成一個Map,然后,根據(jù)不同的key返回不同的數(shù)據(jù)源。
環(huán)境介紹
SpringBoot 1.5.10.RELEASE
MySQL 5.7
數(shù)據(jù)源配置
首先在?application.yml?里配置兩個數(shù)據(jù)源:
spring:
??datasource:???#多數(shù)據(jù)源配置
????master:
??????url:?jdbc:mysql://localhost:3307/testdb?useUnicode=true&characterEncoding=utf8&useSSL=false
??????username:?root
??????password:?123456
??????driver-class-name:?com.mysql.jdbc.Driver
????slave:
??????url:?jdbc:mysql://localhost:3308/testdb?useUnicode=true&characterEncoding=utf8&useSSL=false
??????username:?root
??????password:?123456
??????driver-class-name:?com.mysql.jdbc.Driver
????type:?com.alibaba.druid.pool.DruidDataSource
??jpa:
????show-sql:?true
#????hibernate:
#??????naming:?這個屬性不知道為什么無法自動獲取到,需要在代碼賦值
#????????physical-strategy:?org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
server:
??port:?8080
??context-path:?/imooc
初始化數(shù)據(jù)源
編寫數(shù)據(jù)源配置類,初始化數(shù)據(jù)源,并把兩個物理數(shù)據(jù)源封裝成一個AbstractRoutingDataSource:
@Configuration
public?class?DataSourceConfiguration?{
????private?final?static?String?MASTER_DATASOURCE_KEY?=?"masterDataSource";
????private?final?static?String?SLAVE_DATASOURCE_KEY?=?"slaveDataSource";
????@Value("${spring.datasource.type}")
????private?Class?extends?DataSource>?dataSourceType;
????@Primary
????@Bean(value?=?MASTER_DATASOURCE_KEY)
????@Qualifier(MASTER_DATASOURCE_KEY)
????@ConfigurationProperties(prefix?=?"spring.datasource.master")
????public?DataSource?masterDataSource()?{
????????log.info("create?master?datasource...");
????????return?DataSourceBuilder.create().type(dataSourceType).build();
????}
????@Bean(value?=?SLAVE_DATASOURCE_KEY)
????@Qualifier(SLAVE_DATASOURCE_KEY)
????@ConfigurationProperties(prefix?=?"spring.datasource.slave")
????public?DataSource?slaveDataSource()?{
????????log.info("create?slave?datasource...");
????????return?DataSourceBuilder.create().type(dataSourceType).build();
????}
????@Bean(name?=?"routingDataSource")
????public?AbstractRoutingDataSource?routingDataSource(@Qualifier("masterDataSource")?DataSource?masterDataSource,
????????????@Qualifier("slaveDataSource")?DataSource?slaveDataSource)?{
????????DynamicDataSourceRouter?proxy?=?new?DynamicDataSourceRouter();
????????Map注意需要把其中一個數(shù)據(jù)源使用@Primary?注解標明為主數(shù)據(jù)源,并且這個主數(shù)據(jù)源不能是AbstractRoutingDataSource類型的,必須是DataSource?類型的。
編寫?JpaEntityManager?配置類
使用多數(shù)據(jù)源后,需要手動對?Jpa?的?EntityManager?進行初始化和配置,不能使用默認的自動配置,不然的話并不能實際創(chuàng)建兩個不同的數(shù)據(jù)源。
@Configuration
@EnableConfigurationProperties(JpaProperties.class)
@EnableJpaRepositories(value?=?"com.imooc.dao.repository")
public?class?JpaEntityManager?{
????@Autowired
????private?JpaProperties?jpaProperties;
????@Resource(name?=?"routingDataSource")
????private?DataSource?routingDataSource;
????//@Primary
????@Bean(name?=?"entityManagerFactoryBean")
????public?LocalContainerEntityManagerFactoryBean?entityManagerFactoryBean(EntityManagerFactoryBuilder?builder)?{
????????//?不明白為什么這里獲取不到?application.yml?里的配置
????????Map?properties?=?jpaProperties.getProperties();
????????//要設(shè)置這個屬性,實現(xiàn)?CamelCase?->?UnderScore?的轉(zhuǎn)換
????????properties.put("hibernate.physical_naming_strategy",
????????????????"org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy");
????????return?builder
????????????????.dataSource(routingDataSource)//關(guān)鍵:注入routingDataSource
????????????????.properties(properties)
????????????????.packages("com.imooc.entity")
????????????????.persistenceUnit("myPersistenceUnit")
????????????????.build();
????}
????@Primary
????@Bean(name?=?"entityManagerFactory")
????public?EntityManagerFactory?entityManagerFactory(EntityManagerFactoryBuilder?builder)?{
????????return?this.entityManagerFactoryBean(builder).getObject();
????}
????@Primary
????@Bean(name?=?"transactionManager")
????public?PlatformTransactionManager?transactionManager(EntityManagerFactoryBuilder?builder)?{
????????return?new?JpaTransactionManager(entityManagerFactory(builder));
????}
}
編寫動態(tài)保存數(shù)據(jù)源類型key的實現(xiàn)類
使用?ThreadLocal?來動態(tài)設(shè)置和保存數(shù)據(jù)源類型的key
public?class?DataSourceContextHolder?{
????private?static?final?ThreadLocal?holder?=?new?ThreadLocal<>();
????public?static?void?setDataSource(String?type)?{
????????holder.set(type);
????}
????public?static?String?getDataSource()?{
????????String?lookUpKey?=?holder.get();
????????return?lookUpKey?==?null???"masterDataSource"?:?lookUpKey;
????}
????public?static?void?clear()?{
????????holder.remove();
????}
}
實現(xiàn)AbstractRoutingDataSource
編寫一個類繼承AbstractRoutingDataSource,并重寫?determineCurrentLookupKey?這個路由方法:
public?class?DynamicDataSourceRouter?extends?AbstractRoutingDataSource?{
????@Override
????protected?Object?determineCurrentLookupKey()?{
????????return?DataSourceContextHolder.getDataSource();
????}
}
編寫切面實現(xiàn)動態(tài)切換
日常工作中,通常是根據(jù)Service?層的方法簽名,區(qū)分讀寫操作,最便捷的方式是使用 AOP 進行攔截:
@Slf4j
@Aspect
@Component
public?class?DynamicDataSourceAspect?{
????@Pointcut("execution(*?com.imooc.service..*.*(..))")
????private?void?aspect()?{}
????@Around("aspect()")
????public?Object?around(ProceedingJoinPoint?joinPoint)?throws?Throwable?{
????????String?method?=?joinPoint.getSignature().getName();
????????if?(method.startsWith("find")?||?method.startsWith("select")?||?method.startsWith("query")?||?method
????????????????.startsWith("search"))?{
????????????DataSourceContextHolder.setDataSource("slaveDataSource");
????????????log.info("switch?to?slave?datasource...");
????????}?else?{
????????????DataSourceContextHolder.setDataSource("masterDataSource");
????????????log.info("switch?to?master?datasource...");
????????}
????????try?{
????????????return?joinPoint.proceed();
????????}finally?{
????????????log.info("清除?datasource?router...");
????????????DataSourceContextHolder.clear();
????????}
????}
}
總結(jié)
至此,核心的代碼和細節(jié)已經(jīng)講解結(jié)束,其余的實體類、Repository接口、Service 方法、測試用例等,可以參考。
https://github.com/linyongfu2013/springboot-multi-datasource.git//linyongfu2013.github.io/2018/03/08/springboot-jpa-dynamic-datasource/分享&在看
