<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
公司專案有連線多個不同資料庫的需求,特研究了一下,根據網上的資料,造了一個基於AOP方式的資料來源切換輪子,但繼續探索,突然發現有開源的多資料來源管理啟動器。不過,本篇兩種方式都會介紹。
dynamic-datasource-spring-boot-starter 是一個基於springboot的快速整合多資料來源的啟動器。
其支援 Jdk 1.7+, SpringBoot 1.4.x 1.5.x 2.x.x
我們目前只探討使用dynamic-datasource進行資料來源切換,其他請自行搜尋
dynamic-datasource的相關約定
引入dynamic-datasource依賴
<dependency> <groupId>com.baomidou</groupId> <artifactId>dynamic-datasource-spring-boot-starter</artifactId> <version>${version}</version> </dependency>
設定資料來源
spring: datasource: dynamic: primary: mysql #設定預設的資料來源或者資料來源組,預設值即為master strict: false #嚴格匹配資料來源,預設false. true未匹配到指定資料來源時拋異常,false使用預設資料來源 datasource: mysql: url: jdbc:mysql://xx.xx.xx.xx:3306/dynamic username: root password: 123456 driver-class-name: com.mysql.jdbc.Driver # 3.2.0開始支援SPI可省略此設定 pgsql: url: ENC(xxxxx) # 內建加密 username: ENC(xxxxx) password: ENC(xxxxx) driver-class-name: org.postgresql.Driver
@DS 可以註解在方法上或類上,同時存在就近原則 方法上註解 優先於 類上註解。
註解 | 結果 |
---|---|
不使用@DS註解 | 預設資料來源,即primary: mysql |
@DS(“dsName”) | dsName可以為組名也可以為具體某個庫的名稱 |
@Service @DS("mysql") public class UserServiceImpl implements UserService { @Autowired private JdbcTemplate jdbcTemplate; // 不使用@DS註解則代表使用預設資料來源 // 如果類上存在,則使用類上標註的資料來源 public List selectAll() { return jdbcTemplate.queryForList("select * from user"); } @Override @DS("pgsql") // 方法上註解 優先於 類上註解,即使類上標註也優先採用方法上的標註 public List selectByCondition() { return jdbcTemplate.queryForList("select * from user where age >10"); } }
本次程式碼參考 https://github.com/mianshenglee/my-example/tree/master/multi-datasource/dynamic-datasource ,因原始碼不滿足的需求,因此我在此基礎做了修改。
專案工程結構
專案依賴
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.2.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>me.mason.demo</groupId> <artifactId>dynamic-datasource</artifactId> <version>0.0.1-SNAPSHOT</version> <name>dynamic-datasource</name> <description>Demo project for dynamic datasource</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <!--spring boot--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.9</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <!--mysql 驅動--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!--mybatis-plus--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.3.0</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
組態檔
server.port=8080 server.servlet.context-path=/dd logging.level.root=INFO logging.level.me.mason.demo.dynamicdatasource.mapper=DEBUG # mybatis-plus mybatis-plus.type-aliases-package=me.mason.demo.dynamicdatasource.entity # 預設位置,可不設定 #mybatis-plus.mapper-locations=classpath*:/mapper/*.xml mybatis.mapper-locations=classpath*:/mapper/*.xml # 使用資料庫自增ID mybatis-plus.global-config.db-config.id-type=auto # master spring.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.master.url=jdbc:mysql://10.0.1.243:3306/scheduling?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=UTF-8 spring.datasource.master.username=root spring.datasource.master.password=123456 # slave spring.datasource.slave.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.slave.url=jdbc:mysql://10.0.1.243:3306/scheduling1?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=UTF-8 spring.datasource.slave.username=root spring.datasource.slave.password=123456
自定義註解
// 標記註解可使用在方法與類上 @Target({ElementType.METHOD,ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface DS { // 預設值為MASTER String value() default DataSourceConstants.DS_KEY_MASTER; }
編寫DataSourceConstants
/** * 資料來源常數 **/ public class DataSourceConstants { /** * master資料來源 */ public static final String DS_KEY_MASTER = "master"; /** * slave資料來源 */ public static final String DS_KEY_SLAVE = "slave"; }
動態資料來源名稱上下文處理
/** * 動態資料來源名稱上下文處理 **/ public class DynamicDataSourceContextHolder { /** * 動態資料來源名稱上下文 */ private static final ThreadLocal<String> DATASOURCE_CONTEXT_KEY_HOLDER = new ThreadLocal<>(); /** * 設定資料來源 * @param key */ public static void setContextKey(String key){ DATASOURCE_CONTEXT_KEY_HOLDER.set(key); } /** * 獲取資料來源名稱 * @return */ public static String getContextKey(){ String key = DATASOURCE_CONTEXT_KEY_HOLDER.get(); return key == null?DataSourceConstants.DS_KEY_MASTER:key; } /** * 刪除當前資料來源名稱 */ public static void removeContextKey(){ DATASOURCE_CONTEXT_KEY_HOLDER.remove(); } }
獲取當前動態資料來源方法
/** * 動態資料來源 **/ public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DynamicDataSourceContextHolder.getContextKey(); } }
動態資料來源設定
/** * 動態資料來源設定 **/ @EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class }) @Configuration // 此處我們 //@PropertySource("classpath:config/jdbc.properties") @MapperScan(basePackages = "me.mason.demo.dynamicdatasource.mapper") public class DynamicDataSourceConfig { @Bean(DataSourceConstants.DS_KEY_MASTER) // 需要與組態檔中對應 @ConfigurationProperties(prefix = "spring.datasource.master") public DruidDataSource masterDataSource() { return DruidDataSourceBuilder.create().build(); } @Bean(DataSourceConstants.DS_KEY_SLAVE) @ConfigurationProperties(prefix = "spring.datasource.slave") public DruidDataSource slaveDataSource() { return DruidDataSourceBuilder.create().build(); } @Bean @Primary public DynamicDataSource dynamicDataSource() { Map<Object, Object> dataSourceMap = new HashMap<>(2); dataSourceMap.put(DataSourceConstants.DS_KEY_MASTER, masterDataSource()); dataSourceMap.put(DataSourceConstants.DS_KEY_SLAVE, slaveDataSource()); //設定動態資料來源 DynamicDataSource dynamicDataSource = new DynamicDataSource(); dynamicDataSource.setTargetDataSources(dataSourceMap); dynamicDataSource.setDefaultTargetDataSource(masterDataSource()); return dynamicDataSource; } }
AOP切面
/** * 切面 */ @Aspect @Component //@Order(-10) public class DynamicDataSourceAspect { // 以在類上使用了@Service作為切入點 @Pointcut("@within(org.springframework.stereotype.Service)") public void dataSourcePointCut() { } @Around("dataSourcePointCut()") public Object around(ProceedingJoinPoint joinPoint) throws Throwable { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Class<?> aClass = Class.forName(signature.getDeclaringType().getName()); // 方法優先,如果方法上存在註解,則優先使用方法上的註解 if (signature.getMethod().isAnnotationPresent(DS.class)) { DynamicDataSourceContextHolder.setContextKey(signature.getMethod().getAnnotation(DS.class).value()); // 其次類優先,如果類上存在註解,則使用類上的註解 }else if (aClass.isAnnotationPresent(DS.class)) { DynamicDataSourceContextHolder.setContextKey(aClass.getAnnotation(DS.class).value()); // 如果都不存在,則使用預設 } else { DynamicDataSourceContextHolder.setContextKey(DataSourceConstants.DS_KEY_MASTER); } try { return joinPoint.proceed(); } finally { DynamicDataSourceContextHolder.removeContextKey(); } } }
編寫TestUser實體
@Data @TableName("test_user") public class TestUser implements Serializable { private static final long serialVersionUID = 1L; /** id */ private Long id; /** 姓名 */ private String name; /** 手機號 */ private String phone; /** 職稱職別 */ private String title; /** 郵箱 */ private String email; /** 性別 */ private String gender; /** 出生時間 */ private Date dateOfBirth; /** 1:已刪除,0:未刪除 */ private Integer deleted; /** 建立時間 */ private Date sysCreateTime; /** 建立人 */ private String sysCreateUser; /** 更新時間 */ private Date sysUpdateTime; /** 更新人 */ private String sysUpdateUser; /** 版本號 */ private Long recordVersion; public TestUser() { } }
TestUserMapper
@Service //@DS(DataSourceConstants.DS_KEY_SLAVE) public class TestUserService { @Autowired private TestUserMapper testUserMapper; /** * 查詢master庫User * @return */ // @DS(DataSourceConstants.DS_KEY_MASTER) public List<TestUser> getMasterUser(){ QueryWrapper<TestUser> queryWrapper = new QueryWrapper<>(); return testUserMapper.selectAll(queryWrapper.isNotNull("name")); } /** * 查詢slave庫User * @return */ // @DS(DataSourceConstants.DS_KEY_MASTER) public List<TestUser> getSlaveUser(){ return testUserMapper.selectList(null); } }
TestUserService
@Service //@DS(DataSourceConstants.DS_KEY_SLAVE) public class TestUserService { @Autowired private TestUserMapper testUserMapper; /** * 查詢master庫User * @return */ // @DS(DataSourceConstants.DS_KEY_MASTER) public List<TestUser> getMasterUser(){ QueryWrapper<TestUser> queryWrapper = new QueryWrapper<>(); return testUserMapper.selectAll(queryWrapper.isNotNull("name")); } /** * 查詢slave庫User * @return */ // @DS(DataSourceConstants.DS_KEY_MASTER) public List<TestUser> getSlaveUser(){ return testUserMapper.selectList(null); } }
TestUserController
@RestController @RequestMapping("/user") public class TestUserController { @Autowired private TestUserService testUserService; /** * 查詢全部 */ @GetMapping("/listall") public Object listAll() { int initSize = 2; Map<String, Object> result = new HashMap<>(initSize); List<TestUser> masterUser = testUserService.getMasterUser(); result.put("masterUser", masterUser); List<TestUser> slaveUser = testUserService.getSlaveUser(); result.put("getSlaveUser", slaveUser); return ResponseResult.success(result); } }
MapperXml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="me.mason.demo.dynamicdatasource.mapper.TestUserMapper"> <select id="selectAll" resultType="me.mason.demo.dynamicdatasource.entity.TestUser"> select * from test_user <if test="ew!=null"> ${ew.customSqlSegment} </if> </select> </mapper>
啟動測試
不使用註解
@Service //@DS(DataSourceConstants.DS_KEY_SLAVE) public class TestUserService { @Autowired private TestUserMapper testUserMapper; /** * 查詢master庫User * @return */ // @DS(DataSourceConstants.DS_KEY_MASTER) public List<TestUser> getMasterUser(){ QueryWrapper<TestUser> queryWrapper = new QueryWrapper<>(); return testUserMapper.selectAll(queryWrapper.isNotNull("name")); } /** * 查詢slave庫User * @return */ // @DS(DataSourceConstants.DS_KEY_MASTER) public List<TestUser> getSlaveUser(){ return testUserMapper.selectList(null); } }
效果
該程式碼優先順序與使用框架效果一致,即不使用註解將預設使用MASTER資料庫,方法上存在註解優先使用方法上標註的註解。
已知MASTER 6條資料, SLAVE4條資料
存取 http://127.0.0.1:8080/dd/user/listall 檢視效果
類上使用註解
@Service @DS(DataSourceConstants.DS_KEY_SLAVE) public class TestUserService { @Autowired private TestUserMapper testUserMapper; /** * 查詢master庫User * @return */ // @DS(DataSourceConstants.DS_KEY_MASTER) public List<TestUser> getMasterUser(){ QueryWrapper<TestUser> queryWrapper = new QueryWrapper<>(); return testUserMapper.selectAll(queryWrapper.isNotNull("name")); } /** * 查詢slave庫User * @return */ // @DS(DataSourceConstants.DS_KEY_MASTER) public List<TestUser> getSlaveUser(){ return testUserMapper.selectList(null); } }
效果
方法上使用註解
@Service @DS(DataSourceConstants.DS_KEY_SLAVE) public class TestUserService { @Autowired private TestUserMapper testUserMapper; /** * 查詢master庫User * @return */ @DS(DataSourceConstants.DS_KEY_SLAVE) public List<TestUser> getMasterUser(){ QueryWrapper<TestUser> queryWrapper = new QueryWrapper<>(); return testUserMapper.selectAll(queryWrapper.isNotNull("name")); } /** * 查詢slave庫User * @return */ @DS(DataSourceConstants.DS_KEY_MASTER) public List<TestUser> getSlaveUser(){ return testUserMapper.selectList(null); } }
效果
到此這篇關於SpringBoot多資料來源的兩種實現方式的文章就介紹到這了,更多相關SpringBoot多資料來源實現內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45