SpringBoot配置多數(shù)據(jù)源的四種方式分享
1、所需的資源
- Spring boot
- Mybatis-plus
- Alibab Druid數(shù)據(jù)庫(kù)連接池
- MySql 數(shù)據(jù)庫(kù)
2、Spring Boot配置多數(shù)據(jù)源
數(shù)據(jù)庫(kù)
在YAML文件中定義數(shù)據(jù)源所需的數(shù)據(jù)
spring: datasource: type: com.alibaba.druid.pool.DruidDataSource ## 聲明數(shù)據(jù)源的類型 mysql-datasource1: ## 聲明第一個(gè)數(shù)據(jù)源所需的數(shù)據(jù) url: jdbc:mysql://localhost:3306/mybatis?useSSL=true&serverTimezone=Asia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mysql-datasource2: ## 聲明第二個(gè)數(shù)據(jù)源所需的數(shù)據(jù) url: jdbc:mysql://localhost:3306/bookstore?useSSL=true&serverTimezone=Asia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver druid: ## druid數(shù)據(jù)庫(kù)連接池的基本初始化屬性 initial-size: 5 ## 連接池初始化的大小 min-idle: 1 ## 最小空閑的線程數(shù) max-active: 20 ## 最大活動(dòng)的線程數(shù) mybatis-plus: mapper-locations: classpath:/mapper/*.xml ## 配置MyBatis-Plus掃描Mapper文件的位置 type-aliases-package: com.example.sqlite.entity ## 創(chuàng)建別名的類所在的包
mysql-datasource1、mysql-datasource2是自定義的數(shù)據(jù)。
定義多個(gè)數(shù)據(jù)源
@Configuration public class DataSourceConfig { @Bean(name = "mysqlDataSource1") @ConfigurationProperties(prefix = "spring.datasource.mysql-datasource1") public DataSource dataSource1(){ DruidDataSource build = DruidDataSourceBuilder.create().build(); return build; } @Bean(name = "mysqlDataSource2") @ConfigurationProperties(prefix = "spring.datasource.mysql-datasource2") public DataSource dataSource2(){ DruidDataSource build = DruidDataSourceBuilder.create().build(); return build; } }
@ConfigurationProperties注解用于將YAML中指定的數(shù)據(jù)創(chuàng)建成指定的對(duì)象,但是,YAML中的數(shù)據(jù)必須要與對(duì)象對(duì)象中的屬性同名,不然無(wú)法由Spring Boot完成賦值。
由于我們要定義多個(gè)數(shù)據(jù)源,所以在Spring Boot數(shù)據(jù)源自動(dòng)配置類中就無(wú)法確定導(dǎo)入哪個(gè)數(shù)據(jù)源來(lái)完成初始化,所以我們就需要禁用掉Spring Boot的數(shù)據(jù)源自動(dòng)配置類,然后使用我們自定義的數(shù)據(jù)源配置類來(lái)完成數(shù)據(jù)源的初始化與管理。
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) public class DatasourceDomeApplication { public static void main(String[] args) { SpringApplication.run(DatasourceDomeApplication.class, args); } }
在啟動(dòng)類上聲明需要禁用的自動(dòng)配置類:exclude = {DataSourceAutoConfiguration.class}
2.1、實(shí)現(xiàn)DataSource接口
缺點(diǎn):產(chǎn)生大量的代碼冗余,在代碼中存在硬編碼。
2.1.1、代碼
@Component @Primary public class DynamicDataSource implements DataSource { //使用ThreadLocal而不是String,可以在多線程的時(shí)候保證數(shù)據(jù)的可靠性 public static ThreadLocal<String> flag = new ThreadLocal<>(); @Resource private DataSource mysqlDataSource1; // 注入第一個(gè)數(shù)據(jù)源 @Resource private DataSource mysqlDataSource2; // 注入第二個(gè)數(shù)據(jù)源 public DynamicDataSource(){ // 使用構(gòu)造方法初始化ThreadLocal的值 flag.set("r"); } @Override public Connection getConnection() throws SQLException { // 通過(guò)修改ThreadLocal來(lái)修改數(shù)據(jù)源, // 為什么通過(guò)修改狀態(tài)就能改變已經(jīng)注入的數(shù)據(jù)源? 這就得看源碼了。 if(flag.get().equals("r")){ return mysqlDataSource1.getConnection(); } return mysqlDataSource2.getConnection(); } @Override public Connection getConnection(String username, String password) throws SQLException { return null; } @Override public PrintWriter getLogWriter() throws SQLException { return null; } @Override public void setLogWriter(PrintWriter out) throws SQLException { } @Override public void setLoginTimeout(int seconds) throws SQLException { } @Override public int getLoginTimeout() throws SQLException { return 0; } @Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { return null; } @Override public <T> T unwrap(Class<T> iface) throws SQLException { return null; } @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { return false; } }
實(shí)現(xiàn)DataSource接口我們本質(zhì)上只使用了一個(gè)方法,就是getConnection()這個(gè)無(wú)參的方法,但是DataSource接口中所有的方法我們也都需要實(shí)現(xiàn),只是不用寫(xiě)方法體而已,也就是存在了很多的 “廢方法” 。
@Primary注解 == @Order(1),用于設(shè)置此類的注入順序。
2.1.2、使用
// 訪問(wèn)第一個(gè)數(shù)據(jù)庫(kù)的t_user表 @RestController public class UserController { @Resource private UserService userService; @GetMapping(value = "/user_list") public List<User> showUserList(){ DynamicDataSource.flag.set("read"); // 修改數(shù)據(jù)源的狀態(tài) List<User> list = userService.list(); return list; } }
// 訪問(wèn)第二個(gè)數(shù)據(jù)庫(kù)的Book表 @RestController public class BookController { @Resource private BookService BookService; @GetMapping(value = "/Book_list") public List<Book> getBookList(){ DynamicDataSource.flag.set("write"); // 修改數(shù)據(jù)源的狀態(tài) List<Book> list = BookService.list(); return list; } }
2.2、繼承AbstrictRoutingDataSource類
減少了代碼的冗余,但是還是會(huì)存在硬編碼。
2.2.1、代碼
@Primary @Component public class DynamicDataSource extends AbstractRoutingDataSource { public static ThreadLocal<String> flag = new ThreadLocal<>(); @Resource private DataSource mysqlDataSource1; @Resource private DataSource mysqlDataSource2; public DynamicDataSource(){ flag.set("read"); } @Override protected Object determineCurrentLookupKey() { // 通過(guò)Key來(lái)得到數(shù)據(jù)源 return flag.get(); } @Override public void afterPropertiesSet() { Map<Object,Object> targetDataSource = new ConcurrentHashMap<>(); targetDataSource.put("read",mysqlDataSource1); // 將第一個(gè)數(shù)據(jù)源設(shè)置為默認(rèn)的數(shù)據(jù)源。 super.setDefaultTargetDataSource(mysqlDataSource1); targetDataSource.put("write",mysqlDataSource2); // 將Map對(duì)象賦值給AbstrictRoutingDataSource內(nèi)部的Map對(duì)象中。 super.setTargetDataSources(targetDataSource); super.afterPropertiesSet(); } }
AbstrictRoutingDataSource的本質(zhì)就是利用一個(gè)Map將數(shù)據(jù)源存儲(chǔ)起來(lái),然后通過(guò)Key來(lái)得到Value來(lái)修改數(shù)據(jù)源。
2.2.2、使用
// 訪問(wèn)第一個(gè)數(shù)據(jù)庫(kù)的t_user表 @RestController public class UserController { @Resource private UserService userService; @GetMapping(value = "/user_list") public List<User> showUserList(){ DynamicDataSource.flag.set("read"); // 修改數(shù)據(jù)源的狀態(tài) List<User> list = userService.list(); return list; } }
// 訪問(wèn)第二個(gè)數(shù)據(jù)庫(kù)的Book表 @RestController public class BookController { @Resource private BookService BookService; @GetMapping(value = "/Book_list") public List<Book> getBookList(){ DynamicDataSource.flag.set("write"); // 修改數(shù)據(jù)源的狀態(tài) List<Book> list = BookService.list(); return list; } }
2.3、使用Spring AOP + 自定義注解的形式
Spring AOP + 自定義注解的形式是一種推薦的寫(xiě)法,減少代碼的冗余且不存在硬編碼。
此方法適合對(duì)指定功能操作指定數(shù)據(jù)庫(kù)的模式。
2.3.1、導(dǎo)入依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
2.3.2、開(kāi)啟AOP支持
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) @EnableAspectJAutoProxy //開(kāi)啟Spring Boot對(duì)AOP的支持 public class AopDatasourceApplication { public static void main(String[] args) { SpringApplication.run(AopDatasourceApplication.class, args); } }
2.3.3、定義枚舉來(lái)表示數(shù)據(jù)源的標(biāo)識(shí)
public enum DataSourceType { MYSQL_DATASOURCE1, MYSQL_DATASOURCE2, }
2.3.4、繼承AbstractRoutingDataSource類
@Primary @Component public class DataSourceManagement extends AbstractRoutingDataSource { public static ThreadLocal<String> flag = new ThreadLocal<>(); @Resource private DataSource mysqlDataSource1; @Resource private DataSource mysqlDataSource2; public DataSourceManagement(){ flag.set(DataSourceType.MYSQL_DATASOURCE1.name()); } @Override protected Object determineCurrentLookupKey() { return flag.get(); } @Override public void afterPropertiesSet() { Map<Object,Object> targetDataSource = new ConcurrentHashMap<>(); targetDataSource.put(DataSourceType.MYSQL_DATASOURCE1.name(),mysqlDataSource1); targetDataSource.put(DataSourceType.MYSQL_DATASOURCE2.name(),mysqlDataSource2); super.setTargetDataSources(targetDataSource); super.setDefaultTargetDataSource(mysqlDataSource1); super.afterPropertiesSet(); } }
2.3.5、自定義注解
@Target({ElementType.TYPE,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface TargetDataSource { DataSourceType value() default DataSourceType.MYSQL_DATASOURCE1; }
2.3.6、定義注解的實(shí)現(xiàn)類
@Component @Aspect @Slf4j public class TargetDataSourceAspect { @Before("@within(TargetDataSource) || @annotation(TargetDataSource)") public void beforeNoticeUpdateDataSource(JoinPoint joinPoint){ TargetDataSource annotation = null; Class<? extends Object> target = joinPoint.getTarget().getClass(); if(target.isAnnotationPresent(TargetDataSource.class)){ // 判斷類上是否標(biāo)注著注解 annotation = target.getAnnotation(TargetDataSource.class); log.info("類上標(biāo)注了注解"); }else{ Method method = ((MethodSignature) joinPoint.getSignature()).getMethod(); if(method.isAnnotationPresent(TargetDataSource.class)){ // 判斷方法上是否標(biāo)注著注解,如果類和方法上都沒(méi)有標(biāo)注,則報(bào)錯(cuò) annotation = method.getAnnotation(TargetDataSource.class); log.info("方法上標(biāo)注了注解"); }else{ throw new RuntimeException("@TargetDataSource注解只能用于類或者方法上, 錯(cuò)誤出現(xiàn)在:[" + target.toString() +" " + method.toString() + "];"); } } // 切換數(shù)據(jù)源 DataSourceManagement.flag.set(annotation.value().name()); } }
在有的博客中也會(huì)使用@Around環(huán)繞通知的方式,但是環(huán)繞通知需要執(zhí)行joinPoint.process()方法來(lái)調(diào)用目標(biāo)對(duì)象的方法,最后返回執(zhí)行的值,不然得不到所需要的數(shù)據(jù)。
我這里使用了@Before前置通知,效果是一樣的,因?yàn)锧Around就會(huì)包含@Before。
@Around("@within(TargetDataSource) || @annotation(TargetDataSource)") public Object beforeNoticeUpdateDataSource(ProceedingJoinPoint joinPoint){ // 省略邏輯代碼 Object result = null; try { result = joinPoint.proceed(); } catch (Throwable e) { e.printStackTrace(); } return result; }
ProceedingJoinPoint 對(duì)象只能在@Around環(huán)繞通知中使用,在其他通知中使用就會(huì)報(bào)錯(cuò)。
2.3.7、使用
// 訪問(wèn)第一個(gè)數(shù)據(jù)源。 @RestController // 將注解標(biāo)注在類上,表示本類中所有的方法都是使用數(shù)據(jù)源1 @TargetDataSource(value = DataSourceType.MYSQL_DATASOURCE1) public class UserController { @Resource private UserService userService; @GetMapping(value = "/user_list") public List<User> showUserList(){ System.out.println(DataSourceType.MYSQL_DATASOURCE1.name()); List<User> list = userService.list(); return list; } }
// 訪問(wèn)第二個(gè)數(shù)據(jù)源 @RestController public class BookController { @Resource private BookService BookService; @GetMapping(value = "/Book_list") // 將注解標(biāo)注在方法上,表示此方法使用數(shù)據(jù)源2 @TargetDataSource(value = DataSourceType.MYSQL_DATASOURCE2) public List<Book> getBookList(){ List<Book> list = BookService.list(); return list; } }
2.4、通過(guò)SqlSessionFactory指定的數(shù)據(jù)源來(lái)操作指定目錄的XML文件
使用此方法則不會(huì)與上面所述的類有任何關(guān)系,本方法會(huì)重新定義類。本方法也是一種推薦的方法,適用于對(duì)指定數(shù)據(jù)庫(kù)的操作,也就是適合讀寫(xiě)分離。不會(huì)存在代碼冗余和存在硬編碼。
2.4.1、項(xiàng)目的目錄結(jié)構(gòu)
對(duì)所需要操作的數(shù)據(jù)庫(kù)的Mapper層和dao層分別建立一個(gè)文件夾。
2.4.2、配置YAML文件
spring: datasource: type: com.alibaba.druid.pool.DruidDataSource mysql-datasource: jdbc-url: jdbc:mysql://localhost:3306/mybatis?useSSL=true&serverTimezone=Asia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver sqlite-datasource: jdbc-url: jdbc:mysql://localhost:3306/bookstore?useSSL=true&serverTimezone=Asia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver druid: initial-size: 5 min-idle: 1 max-active: 20 mybatis-plus: mapper-locations: classpath:/mapper/*.xml type-aliases-package: com.example.sqlite.entity
2.4.3、針對(duì)Mapper層通過(guò)SqlSessionFactory指定數(shù)據(jù)源來(lái)操作
2.4.3.1、創(chuàng)建MySql數(shù)據(jù)源
@Configuration @MapperScan(basePackages = "com.example.sqlite.dao.mysql", sqlSessionFactoryRef = "MySQLSqlSessionFactory") public class MySQLDataSourceConfig { @Bean(name = "MySQLDataSource") @Primary @ConfigurationProperties(prefix = "spring.datasource.mysql-datasource") public DataSource getDateSource1() { return DataSourceBuilder.create().build(); } @Bean(name = "MySQLSqlSessionFactory") @Primary public SqlSessionFactory test1SqlSessionFactory( @Qualifier("MySQLDataSource") DataSource datasource) throws Exception { MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean (); bean.setDataSource(datasource); bean.setMapperLocations(// 設(shè)置mybatis的xml所在位置 new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/mysql/*.xml")); return bean.getObject(); } @Bean("MySQLSqlSessionTemplate") @Primary public SqlSessionTemplate test1SqlSessionTemplate( @Qualifier("MySQLSqlSessionFactory") SqlSessionFactory sessionFactory) { return new SqlSessionTemplate(sessionFactory); } @Bean public PlatformTransactionManager transactionManager(@Qualifier("MySQLDataSource")DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } }
2.4.3.2、創(chuàng)建Sqlite數(shù)據(jù)源
@Configuration @MapperScan(basePackages = "com.example.sqlite.dao.sqlite", sqlSessionFactoryRef = "SqliteSqlSessionFactory") public class SqliteDataSourceConfig { @Bean(name = "SqliteDateSource") @ConfigurationProperties(prefix = "spring.datasource.sqlite-datasource") public DataSource getDateSource1() { return DataSourceBuilder.create().build(); } @Bean(name = "SqliteSqlSessionFactory") public SqlSessionFactory test1SqlSessionFactory( @Qualifier("SqliteDateSource") DataSource datasource) throws Exception { MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean(); bean.setDataSource(datasource); bean.setMapperLocations( new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/sqlite/*.xml")); return bean.getObject(); } @Bean("SqliteSqlSessionTemplate") public SqlSessionTemplate test1SqlSessionTemplate( @Qualifier("SqliteSqlSessionFactory") SqlSessionFactory sessionFactory) { return new SqlSessionTemplate(sessionFactory); } @Bean public PlatformTransactionManager transactionManager(@Qualifier("SqliteDateSource")DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } }
- @MapperScan注解中的basePackages指向的是指定的Dao層。
- @MapperScan注解中sqlSessionFactoryRef 用來(lái)指定使用某個(gè)SqlSessionFactory來(lái)操作數(shù)據(jù)源。
- bean.setMapperLocations(
new PathMatchingResourcePatternResolver()
.getResources(“classpath*:mapper/sqlite/*.xml”)); 指向的是操作執(zhí)行數(shù)據(jù)庫(kù)的Mapper層。
如果使用SQLite數(shù)據(jù)庫(kù),那么就必須在項(xiàng)目中內(nèi)嵌SQLite數(shù)據(jù)庫(kù),這個(gè)一個(gè)輕量級(jí)的數(shù)據(jù)庫(kù),不同于Mysql,SQLite不需要服務(wù)器,SQLite適合使用于移動(dòng)APP開(kāi)發(fā)。
像微信,用戶的聊天記錄就是使用這個(gè)數(shù)據(jù)庫(kù)進(jìn)行存儲(chǔ)。SQLite也可以使用在Web端,只是不太方便。
2.4.4、使用
// 訪問(wèn)第一個(gè)數(shù)據(jù)庫(kù) @RestController public class UserController { @Resource private UserService userService; @GetMapping(value = "/user_list") public List<User> showUserList(){ List<User> list = userService.list(); return list; } }
// 訪問(wèn)第二個(gè)數(shù)據(jù)庫(kù) @RestController public class AddressController { @Resource private AddressService addressService; @GetMapping(value = "/address_list") public List<Address> getAddressList(){ List<Address> list = addressService.list(); return list; } }
使用此種方法不會(huì)存在任何代碼的冗余以及硬編碼的存在,但是需要分層明確。
唯一的不足就是添加一個(gè)數(shù)據(jù)源就需要重新寫(xiě)一個(gè)類,而這個(gè)類中的代碼大部分又是相同的。
3、總結(jié)
- 實(shí)現(xiàn)DataSource接口這種寫(xiě)法是不推薦的。
- 推薦使用Spring Boot + 自定義注解的方式與SqlSessionFactory方式。
另外,Spring AOP中各種通知的執(zhí)行順序如下圖所示:
以上就是SpringBoot配置多數(shù)據(jù)源的四種方式分享的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot配置多數(shù)據(jù)源的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- springboot dynamic多數(shù)據(jù)源demo以及常見(jiàn)切換、事務(wù)的問(wèn)題
- SpringBoot多數(shù)據(jù)源配置方式以及報(bào)錯(cuò)問(wèn)題的解決
- springboot項(xiàng)目實(shí)現(xiàn)多數(shù)據(jù)源配置使用dynamic-datasource-spring-boot-starter的操作步驟
- SpringBoot項(xiàng)目配置postgresql數(shù)據(jù)庫(kù)完整步驟(配置多數(shù)據(jù)源)
- SpringBoot項(xiàng)目多數(shù)據(jù)源及mybatis 駝峰失效的問(wèn)題解決方法
- 淺析SpringBoot多數(shù)據(jù)源實(shí)現(xiàn)方案
相關(guān)文章
springboot集成shiro遭遇自定義filter異常的解決
這篇文章主要介紹了springboot集成shiro遭遇自定義filter異常的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11java String類功能、原理與應(yīng)用案例【統(tǒng)計(jì)、判斷、轉(zhuǎn)換等】
這篇文章主要介紹了java String類功能、原理與應(yīng)用案例,結(jié)合實(shí)例形式詳細(xì)分析了java String類的基本功能、構(gòu)造方法,以及使用String類實(shí)現(xiàn)統(tǒng)計(jì)、判斷、轉(zhuǎn)換等功能相關(guān)操作技巧,需要的朋友可以參考下2019-03-03基于SpringBoot實(shí)現(xiàn)定時(shí)發(fā)送郵件過(guò)程解析
這篇文章主要介紹了基于SpringBoot實(shí)現(xiàn)定時(shí)發(fā)送郵件過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06java中帶參數(shù)的try(){}語(yǔ)法含義詳解
這篇文章主要介紹了java中帶參數(shù)的try(){}語(yǔ)法含義詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02java語(yǔ)言基礎(chǔ)之標(biāo)識(shí)符和命名規(guī)則詳解
這篇文章主要給大家介紹了關(guān)于java語(yǔ)言基礎(chǔ)之標(biāo)識(shí)符和命名規(guī)則的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03Spring如何基于注解顯式實(shí)現(xiàn)自動(dòng)裝配
這篇文章主要介紹了Spring如何基于注解顯式實(shí)現(xiàn)自動(dòng)裝配,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08Spring2.5.6開(kāi)發(fā)環(huán)境搭建圖文教程
這篇文章主要為大家詳細(xì)介紹了Spring2.5.6開(kāi)發(fā)環(huán)境搭建圖文教程,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05