1. SpringBoot Jpa 多數(shù)據源動態(tài)切換

        共 1349字,需瀏覽 3分鐘

         ·

        2022-01-26 13:29

        在大型應用程序中,配置主從數(shù)據庫并使用讀寫分離是常見的設計模式。常用的實現(xiàn)方式是使用數(shù)據庫中間件,此文介紹如何通過編寫代碼的方式實現(xiàn)多數(shù)據源的配置和動態(tài)切換。核心是使用Spring 內置的?AbstractRoutingDataSource?這個抽象類,它可以把多個數(shù)據源配置成一個Map,然后,根據不同的key返回不同的數(shù)據源。

        環(huán)境介紹

        • SpringBoot 1.5.10.RELEASE

        • MySQL 5.7

        數(shù)據源配置

        首先在?application.yml?里配置兩個數(shù)據源:

        spring:
        ??datasource:???#多數(shù)據源配置
        ????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ù)據源

        編寫數(shù)據源配置類,初始化數(shù)據源,并把兩個物理數(shù)據源封裝成一個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?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?targetDataSources?=?new?HashMap<>(2);
        ????????targetDataSources.put("masterDataSource",?masterDataSource);
        ????????targetDataSources.put("slaveDataSource",?slaveDataSource);

        ????????proxy.setDefaultTargetDataSource(masterDataSource);
        ????????proxy.setTargetDataSources(targetDataSources);
        ????????return?proxy;
        ????}
        }

        注意需要把其中一個數(shù)據源使用@Primary?注解標明為主數(shù)據源,并且這個主數(shù)據源不能是AbstractRoutingDataSource類型的,必須是DataSource?類型的。

        編寫?JpaEntityManager?配置類

        使用多數(shù)據源后,需要手動對?Jpa?的?EntityManager?進行初始化和配置,不能使用默認的自動配置,不然的話并不能實際創(chuàng)建兩個不同的數(shù)據源。

        @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();
        ????????//要設置這個屬性,實現(xiàn)?CamelCase?->?UnderScore?的轉換
        ????????properties.put("hibernate.physical_naming_strategy",
        ????????????????"org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy");

        ????????return?builder
        ????????????????.dataSource(routingDataSource)//關鍵:注入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ù)據源類型key的實現(xiàn)類

        使用?ThreadLocal?來動態(tài)設置和保存數(shù)據源類型的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)切換

        日常工作中,通常是根據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é)已經講解結束,其余的實體類、Repository接口、Service 方法、測試用例等,可以參考。

        https://github.com/linyongfu2013/springboot-multi-datasource.git
        //linyongfu2013.github.io/2018/03/08/springboot-jpa-dynamic-datasource/

        分享&在看


        瀏覽 57
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
          
          

            1. 丁香花高清在线完整版 | 夜夜春夜夜爽 | 91在线偷拍系列 | 国产一级毛片爽 | 欧美人人草 |