SpringBoot多數(shù)據(jù)源配置并通過注解實(shí)現(xiàn)動態(tài)切換數(shù)據(jù)源
1. 環(huán)境準(zhǔn)備
1.1 數(shù)據(jù)庫準(zhǔn)備
一個(gè)本地環(huán)境的MySQL數(shù)據(jù)庫,數(shù)據(jù)庫mydb,創(chuàng)建表t_user
CREATE TABLE `t_user` ( `c_id` varchar(20) NOT NULL, `c_username` varchar(20) DEFAULT NULL, `c_password` varchar(20) DEFAULT NULL, `c_gender` tinyint(2) DEFAULT NULL, PRIMARY KEY (`c_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; INSERT INTO `mydb`.`t_user`(`c_id`, `c_username`, `c_password`, `c_gender`) VALUES ('1', '思思', '123', 1);
一個(gè)云服務(wù)器的MySQL數(shù)據(jù)庫,創(chuàng)建數(shù)據(jù)庫book_db,創(chuàng)建表t_userinfo。
CREATE TABLE `t_user_info` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用戶id', `user_name` varchar(50) DEFAULT NULL COMMENT '用戶名', `password` varchar(255) DEFAULT NULL COMMENT '登錄密碼', `areaObj` varchar(255) DEFAULT NULL COMMENT '所在學(xué)院', `name` varchar(20) DEFAULT NULL COMMENT '姓名', `sex` tinyint(255) DEFAULT NULL COMMENT '性別', `user_photo` varchar(255) DEFAULT NULL COMMENT '學(xué)生照片', `birthday` varchar(20) DEFAULT NULL COMMENT '出生日期', `telephone` varchar(20) DEFAULT NULL COMMENT '聯(lián)系電話', `address` varchar(255) DEFAULT NULL COMMENT '家庭地址', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; INSERT INTO `book_db`.`t_user_info`(`id`, `user_name`, `password`, `areaObj`, `name`, `sex`, `user_photo`, `birthday`, `telephone`, `address`) VALUES (1, '張三', '123', '哈爾濱', '張三散', 1, '123', '02-16', '15756892458', '黑龍江省哈爾濱市');
創(chuàng)建數(shù)據(jù)庫chatroom,創(chuàng)建表admin。
CREATE TABLE `admin` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(20) NOT NULL COMMENT '登錄賬號', `nickname` varchar(20) NOT NULL COMMENT '昵稱', `password` varchar(255) NOT NULL COMMENT '密碼', `user_profile` varchar(255) DEFAULT NULL COMMENT '管理員頭像', PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC; INSERT INTO `chatroom`.`admin`(`id`, `username`, `nickname`, `password`, `user_profile`) VALUES (1, 'admin', '系統(tǒng)管理員', '$2a$10$PyloUEVGuO0fUZdfeIaROOTluRmccl.Scifa8S7Os0Wt.s4bDkb', 'https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=1784117537,3335593911&fm=26&gp=0.jpg');
1.2 項(xiàng)目創(chuàng)建
創(chuàng)建SpringBoot項(xiàng)目,整合MyBatis-Plus。pom.xml引入的依賴:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!--mybatis plus--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.0.1</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!--druid--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.9</version> </dependency> </dependencies>
配置讀取resource文件夾下的mapper文件
<build> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> </resource> <resource> <directory>src/main/resources</directory> </resource> </resources> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
2. ThreadLocal類介紹
3. AbstractRoutingDataSource類介紹
Spring Boot提供了AbstractRoutingDataSource 根據(jù)用戶定義的規(guī)則選擇要使用的數(shù)據(jù)源,這樣我們可以在每次數(shù)據(jù)庫操作前設(shè)置使用的數(shù)據(jù)源,實(shí)現(xiàn)可動態(tài)路由的數(shù)據(jù)源。它的抽象方法determineCurrentLookupKey() 決定使用哪個(gè)數(shù)據(jù)源。
getConnection()獲取數(shù)據(jù)庫連接,根據(jù)查找lookup key鍵對不同目標(biāo)數(shù)據(jù)源的調(diào)用,通常是通過(但不一定)某些線程綁定的事物上下文來實(shí)現(xiàn)。通過這我們知道可以實(shí)現(xiàn):數(shù)據(jù)源的動態(tài)切換,在程序運(yùn)行時(shí),把數(shù)據(jù)源動態(tài)織入到程序中,靈活得進(jìn)行數(shù)據(jù)源切換,從而可以不依賴中間件,實(shí)現(xiàn)讀寫分離功能。
AbstractRoutingDataSource實(shí)現(xiàn)邏輯:
- 繼承抽象類AbstractRoutingDataSource,并實(shí)現(xiàn)determineCurrentLookupKey()方法。自定義LookupKey的選擇規(guī)則。
- 把配置的多個(gè)數(shù)據(jù)源放在AbstractRoutingDataSource的 targetDataSources和defaultTargetDataSource中(使用setDefaultTargetDataSource和setTargetDataSources方法),然后通過afterPropertiesSet()方法將數(shù)據(jù)源分別進(jìn)行復(fù)制到AbstractRoutingDataSource的resolvedDataSources屬性和resolvedDefaultDataSource屬性中。
調(diào)用AbstractRoutingDataSource的getConnection()的方法的時(shí)候,先調(diào)用determineTargetDataSource()方法返回DataSource在進(jìn)行g(shù)etConnection()。
determineTargetDataSource()方法通過調(diào)用determineCurrentLookupKey() 方法返回的lookupKey決定使用哪個(gè)數(shù)據(jù)源。
4. 具體實(shí)現(xiàn)
4.1 定義數(shù)據(jù)源枚舉類
定義數(shù)據(jù)源枚舉類DataSourceTypeEnum
public enum DataSourceTypeEnum { /** * chatroom */ CHATROOM("chatroom"), /** * book_db */ BOOK_DB("book_db"), /** * mydb */ MY_DB("mydb"); private final String name; DataSourceTypeEnum(String name) { this.name = name; } public String getName() { return name; } }
4.2 創(chuàng)建動態(tài)多數(shù)據(jù)源類
定義一個(gè)動態(tài)多數(shù)據(jù)源類DynamicDataSource用于管理不同線程間多個(gè)數(shù)據(jù)源的選擇和切換,擴(kuò)展 Spring 提供的 AbstractRoutingDataSource 抽象類,重寫 determineCurrentLookupKey 方法,其中的determineCurrentLookupKey() 方法用于決定使用哪個(gè)數(shù)據(jù)源。
public class DynamicDataSource extends AbstractRoutingDataSource { /** * ThreadLocal 用于提供線程局部變量,在多線程環(huán)境可以保證各個(gè)線程里的變量獨(dú)立于其它線程里的變量。 * 也就是說 ThreadLocal 可以為每個(gè)線程創(chuàng)建一個(gè)【單獨(dú)的變量副本】,相當(dāng)于線程的 private static 類型變量。 */ private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>(); /** * 決定使用哪個(gè)數(shù)據(jù)源之前需要把多個(gè)數(shù)據(jù)源的信息以及默認(rèn)數(shù)據(jù)源信息配置好 * * @param defaultTargetDataSource 默認(rèn)數(shù)據(jù)源 * @param targetDataSources 目標(biāo)數(shù)據(jù)源 */ public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) { super.setDefaultTargetDataSource(defaultTargetDataSource); super.setTargetDataSources(targetDataSources); super.afterPropertiesSet(); } /** * determineCurrentLookupKey決定使用哪個(gè)數(shù)據(jù)庫 * @return */ @Override protected Object determineCurrentLookupKey() { return getDataSource(); } public static void setDataSource(String dataSource) { CONTEXT_HOLDER.set(dataSource); } public static String getDataSource() { return CONTEXT_HOLDER.get(); } public static void clearDataSource() { CONTEXT_HOLDER.remove(); } }
4.3 創(chuàng)建動態(tài)多數(shù)據(jù)源配置類
DynamicDataSourceConfig類作為配置類,讀取配置文件的三個(gè)數(shù)據(jù)源的配置,創(chuàng)建對應(yīng)DataSource類型的Bean。
@Configuration public class DynamicDataSourceConfig { @Bean(name="chatroom") @ConfigurationProperties("spring.datasource.druid.first") public DataSource dataSource1(){ return DruidDataSourceBuilder.create().build(); } @Bean(name ="book_db") @ConfigurationProperties("spring.datasource.druid.second") public DataSource dataSource2(){ return DruidDataSourceBuilder.create().build(); } @Bean(name="mydb") @ConfigurationProperties("spring.datasource.druid.third") public DataSource dataSource3(){ return DruidDataSourceBuilder.create().build(); } @Bean(name="dynamicDataSource") @Primary public DynamicDataSource dataSource() { Map<Object, Object> targetDataSources = new HashMap<>(5); targetDataSources.put(DataSourceTypeEnum.CHATROOM.getName(), dataSource1()); targetDataSources.put(DataSourceTypeEnum.BOOK_DB.getName(), dataSource2()); targetDataSources.put(DataSourceTypeEnum.MY_DB.getName(), dataSource3()); return new DynamicDataSource(dataSource1(), targetDataSources); } }
4.4 自定義注解用于指定數(shù)據(jù)源
自定義注解@SpecifyDataSource用于在Service層方法上標(biāo)記要使用哪個(gè)數(shù)據(jù)源。這里定義默認(rèn)使用數(shù)據(jù)源 DataSourceType.CHATROOM。
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface SpecifyDataSource { /** * @return */ DataSourceTypeEnum value() default DataSourceTypeEnum.CHATROOM; }
4.5 AOP實(shí)現(xiàn)動態(tài)切換數(shù)據(jù)源
定義數(shù)據(jù)源界面類DataSourceAspect,用于實(shí)現(xiàn)有SpecifyDataSource注解標(biāo)注的方法前切換注解指定的數(shù)據(jù)源。
@Aspect @Component @Order(value = 1) public class DataSourceAspect { private Logger logger = LoggerFactory.getLogger(this.getClass()); @Pointcut("@annotation(top.javahai.datasource.annotation.SpecifyDataSource)") public void dataSourcePointCut() { } @Around("dataSourcePointCut()") public Object around(ProceedingJoinPoint point) throws Throwable { MethodSignature signature = (MethodSignature) point.getSignature(); Method method = signature.getMethod(); SpecifyDataSource ds = method.getAnnotation(SpecifyDataSource.class); if (ds == null) { DynamicDataSource.setDataSource(DataSourceType.CHATROOM.getName()); logger.info("set datasource is " + DataSourceType.CHATROOM); } else { DynamicDataSource.setDataSource(ds.value().getName()); logger.info("set datasource is " + ds.value().getName()); } try { return point.proceed(); } finally { DynamicDataSource.clearDataSource(); logger.info("clean datasource"); } } }
5. 測試使用
5.1 配置數(shù)據(jù)源
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver # 數(shù)據(jù)源1 spring.datasource.druid.first.url=jdbc:mysql://158.156.444.68:3306/chatroom?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8 spring.datasource.druid.first.username=root spring.datasource.druid.first.password=123456 # 數(shù)據(jù)源2 spring.datasource.druid.second.url=jdbc:mysql://158.156.444.68:3306/book_db?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8 spring.datasource.druid.second.username=root spring.datasource.druid.second.password=123456 #數(shù)據(jù)源3 spring.datasource.druid.third.url=jdbc:mysql:///mydb?allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8 spring.datasource.druid.third.username=root spring.datasource.druid.third.password=123456 mybatis-plus.mapper-locations=classpath:mapper/*.xml #輸出sql執(zhí)行日志 mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
5.2 創(chuàng)建實(shí)體類
創(chuàng)建實(shí)體類Admin
public class Admin implements Serializable { private static final long serialVersionUID = 1L; @TableId(value = "id", type = IdType.AUTO) private Integer id; /** * 登錄賬號 */ private String username; /** * 昵稱 */ private String nickname; /** * 密碼 */ private String password; /** * 管理員頭像 */ private String userProfile; //省略getter/setter方法
創(chuàng)建實(shí)體類TUser
public class TUser implements Serializable { private static final long serialVersionUID = 1L; @TableId(value = "c_id", type = IdType.AUTO) private Integer cId; private String cUsername; private String cPassword; private Integer cGender; }
創(chuàng)建實(shí)體類TUserinfo
@TableName(value = "t_user_info") public class TUserinfo implements Serializable { private static final long serialVersionUID = 1L; /** * user_name */ private String userName; /** * 登錄密碼 */ private String password; /** * 所在學(xué)院 */ @TableField("areaObj") private String areaObj; /** * 姓名 */ private String name; /** * 性別 */ private Integer sex; /** * 學(xué)生照片 */ private String userPhoto; /** * 出生日期 */ private String birthday; /** * 聯(lián)系電話 */ private String telephone; /** * 家庭地址 */ private String address; }
創(chuàng)建UserVO用于測試
public class UserVO { private List<Admin> adminList; private List<TUserinfo> tUserinfos; private List<TUser> tUsers; }
5.3 服務(wù)層代碼
@Service public class AdminServiceImpl extends ServiceImpl<AdminMapper, Admin> implements IAdminService { public List<Admin> getAll(){ return this.list(null); } }
@Service public class TUserinfoServiceImpl extends ServiceImpl<TUserinfoMapper, TUserinfo> implements ITUserinfoService { @SpecifyDataSource(value = DataSourceTypeEnum.BOOK_DB) public List<TUserinfo> selectAll(){ return this.list(null); } }
@Service public class TUserServiceImpl extends ServiceImpl<TUserMapper, TUser> implements ITUserService { @SpecifyDataSource(value = DataSourceTypeEnum.MY_DB) public List<TUser> selectAll(){ return this.list(null); } }
public interface AdminMapper extends BaseMapper<Admin> { } public interface TUserinfoMapper extends BaseMapper<TUserinfo> { } public interface TUserMapper extends BaseMapper<TUser> { }
5.4 控制層代碼
創(chuàng)建接口/test/list用于測試
@RestController @RequestMapping("/test") public class TestController { @Autowired private AdminServiceImpl adminService; @Autowired private TUserinfoServiceImpl userinfoService; @Autowired private TUserServiceImpl userService; @GetMapping("/list") public UserVO list(){ List<Admin> adminList= adminService.getAll(); List<TUserinfo> tUserinfos = userinfoService.selectAll(); List<TUser> tUsers = userService.selectAll(); UserVO userVO = new UserVO(); userVO.setAdminList(adminList); userVO.settUserinfos(tUserinfos); userVO.settUsers(tUsers); return userVO; } }
瀏覽器請求/test/list
查看控制臺輸出,查看數(shù)據(jù)源的切換日志
完整Demo代碼地址:https://github.com/JustCoding-Hai/learn-everyday/tree/master/learn-multi_data_source
到此這篇關(guān)于SpringBoot多數(shù)據(jù)源配置并通過注解實(shí)現(xiàn)動態(tài)切換數(shù)據(jù)源的文章就介紹到這了,更多相關(guān)SpringBoot 動態(tài)切換數(shù)據(jù)源內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- springboot dynamic多數(shù)據(jù)源demo以及常見切換、事務(wù)的問題
- Springboot實(shí)現(xiàn)多數(shù)據(jù)源切換詳情
- SpringBoot基于AbstractRoutingDataSource實(shí)現(xiàn)多數(shù)據(jù)源動態(tài)切換
- SpringBoot多數(shù)據(jù)源切換實(shí)現(xiàn)代碼(Mybaitis)
- SpringBoot實(shí)現(xiàn)多數(shù)據(jù)源的切換實(shí)踐
- SpringBoot?+DynamicDataSource切換多數(shù)據(jù)源的全過程
- springboot中mybatis多數(shù)據(jù)源動態(tài)切換實(shí)現(xiàn)
- Springboot如何設(shè)置多數(shù)據(jù)源,隨時(shí)切換
相關(guān)文章
java 中HashMap實(shí)現(xiàn)原理深入理解
這篇文章主要介紹了java 中HashMap實(shí)現(xiàn)原理深入理解的相關(guān)資料,需要的朋友可以參考下2017-03-03idea手動執(zhí)行maven命令的三種實(shí)現(xiàn)方式
這篇文章主要介紹了idea手動執(zhí)行maven命令的三種實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-08-08HttpClient實(shí)現(xiàn)調(diào)用外部項(xiàng)目接口工具類的示例
下面小編就為大家?guī)硪黄狧ttpClient實(shí)現(xiàn)調(diào)用外部項(xiàng)目接口工具類的示例。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-10-10maven項(xiàng)目test執(zhí)行main找不到資源文件的問題及解決
這篇文章主要介紹了maven項(xiàng)目test執(zhí)行main找不到資源文件的問題及解決,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03