目錄
1:MybatisPlus(讀寫(xiě)分離)
1.1:首先創(chuàng)建三個(gè)數(shù)據(jù)庫(kù)1主2從
1.2:代碼實(shí)例
1.3:優(yōu)缺點(diǎn)分析
2:SpringBoot路由數(shù)據(jù)源(讀寫(xiě)分離)
2.1:實(shí)現(xiàn)原理
2.2:代碼實(shí)現(xiàn)
2.3:測(cè)試代碼?
2.4:優(yōu)缺點(diǎn)分析
這里列舉了兩種讀寫(xiě)分離實(shí)現(xiàn)方案,如下
1:MybatisPlus(讀寫(xiě)分離)
1.1:首先創(chuàng)建三個(gè)數(shù)據(jù)庫(kù)1主2從
表名是user表
1.2:代碼實(shí)例
1:導(dǎo)入pom
<!--MybatisPlus的jar 3.0基于jdk8-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<!-- mybatisPlus多數(shù)據(jù)源依賴 實(shí)現(xiàn)讀寫(xiě)分離-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.6.1</version>
</dependency>
<!-- mysql的依賴-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<!--直接使用druid的starter 連接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.16</version>
</dependency>
2:配置spring的主從
server:
port: 8082
spring:
datasource:
dynamic:
primary: master #設(shè)置默認(rèn)的數(shù)據(jù)源或者數(shù)據(jù)源組,默認(rèn)值即為master
strict: false #嚴(yán)格匹配數(shù)據(jù)源,默認(rèn)false. true未匹配到指定數(shù)據(jù)源時(shí)拋異常,false使用默認(rèn)數(shù)據(jù)源
datasource:
master:
url: jdbc:mysql://localhost:3306/W1?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource #配置德魯伊數(shù)據(jù)源
druid:
initial-size: 10 #連接池初始化大小
min-idle: 10 #最小空閑連接數(shù)
max-active: 20 #最大連接數(shù)
slave_1:
url: jdbc:mysql://localhost:3306/W1R1?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource #配置德魯伊數(shù)據(jù)源
druid:
initial-size: 10 #連接池初始化大小
min-idle: 10 #最小空閑連接數(shù)
max-active: 20 #最大連接數(shù)
slave_2:
url: jdbc:mysql://localhost:3306/W1R2?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource #配置德魯伊數(shù)據(jù)源
druid:
initial-size: 10 #連接池初始化大小
min-idle: 10 #最小空閑連接數(shù)
max-active: 20 #最大連接數(shù)
#......省略
#以上會(huì)配置一個(gè)默認(rèn)庫(kù)master,一個(gè)組slave下有兩個(gè)子庫(kù)slave_1,slave_2
3:代碼實(shí)例 @DS("slave") 注解用來(lái)切換數(shù)據(jù)源
/**
* @description 針對(duì)表【User】的數(shù)據(jù)庫(kù)操作Service實(shí)現(xiàn)
* @createDate 2023-11-01 17:17:57
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService{
@Resource
UserMapper userMapper;
//多個(gè)從庫(kù) 負(fù)載均衡輪訓(xùn)查詢 也可以注解到類上
//@DS("slave")
@DS("slave")
public User selectRW_S(Integer id) {
return userMapper.selectById(id);
}
//主庫(kù)查詢數(shù)據(jù)
//@DS("master")
@DS("master")
public User selectRW_M(Integer id) {
return userMapper.selectById(id);
}
//無(wú)注解,不指定數(shù)據(jù), 默認(rèn)查詢主庫(kù)
@Override
public User selectRW(Integer id) {
return userMapper.selectById(id);
}
}
controller代碼根據(jù)請(qǐng)求不同訪問(wèn)不同的數(shù)據(jù)源
@RestController
public class UserControllerRW {
@Resource
UserServiceImpl userService;
//查詢主庫(kù) @DS("master")
@GetMapping(value = "selectRW1")
public User selectRW_M(@RequestParam(name = "id",required = true) Integer id){
User user = userService.selectRW_M(id);
System.out.println(user);
return user;
}
//查詢從庫(kù) @DS("slave") 輪訓(xùn)查詢
@GetMapping(value = "selectRW2")
public User selectRW_S(@RequestParam(name = "id",required = true) Integer id){
User user = userService.selectRW_S(id);
System.out.println(user);
return user;
}
//無(wú)注解,不指定數(shù)據(jù), 默認(rèn)查詢主庫(kù)
@GetMapping(value = "selectRW3")
public User selectRW(@RequestParam(name = "id",required = true) Integer id){
User user = userService.selectRW(id);
System.out.println(user);
return user;
}
}
查詢結(jié)果截圖
1.3:優(yōu)缺點(diǎn)分析
dynamic-datasource的jar包的官方文檔
基礎(chǔ)必讀(免費(fèi)) · dynamic-datasource · 看云
本框架只做切換數(shù)據(jù)的事情,不限制你的具體操作,從庫(kù)也可以增刪改查,讀寫(xiě)分離只是他的一個(gè)小功能,分庫(kù)分表要自己實(shí)現(xiàn)
- 支持?數(shù)據(jù)源分組?,適用于多種場(chǎng)景 純粹多庫(kù) 讀寫(xiě)分離 一主多從 混合模式。
- 支持?jǐn)?shù)據(jù)庫(kù)敏感配置信息?加密(可自定義)?ENC()。
- 支持每個(gè)數(shù)據(jù)庫(kù)獨(dú)立初始化表結(jié)構(gòu)schema和數(shù)據(jù)庫(kù)database。
- 支持無(wú)數(shù)據(jù)源啟動(dòng),支持懶加載數(shù)據(jù)源(需要的時(shí)候再創(chuàng)建連接)。
- 支持?自定義注解?,需繼承DS(3.2.0+)。
- 提供并簡(jiǎn)化對(duì)Druid,HikariCp,BeeCp,Dbcp2的快速集成。
- 提供對(duì)Mybatis-Plus,Quartz,ShardingJdbc,P6sy,Jndi等組件的集成方案。
- 提供?自定義數(shù)據(jù)源來(lái)源?方案(如全從數(shù)據(jù)庫(kù)加載)。
- 提供項(xiàng)目啟動(dòng)后?動(dòng)態(tài)增加移除數(shù)據(jù)源?方案。
- 提供Mybatis環(huán)境下的?純讀寫(xiě)分離?方案。
- 提供使用?spel動(dòng)態(tài)參數(shù)?解析數(shù)據(jù)源方案。內(nèi)置spel,session,header,支持自定義。
- 支持?多層數(shù)據(jù)源嵌套切換?。(ServiceA >>> ServiceB >>> ServiceC)。
- 提供?基于seata的分布式事務(wù)方案?。
- 提供?本地多數(shù)據(jù)源事務(wù)方案。
2:SpringBoot路由數(shù)據(jù)源(讀寫(xiě)分離)
2.1:實(shí)現(xiàn)原理
實(shí)現(xiàn)方案是通過(guò)在spring容器中注入多個(gè)數(shù)據(jù)源,通過(guò)不同的key來(lái)找到指定的數(shù)據(jù)源,AbstractRoutingDataSource的源碼知道,AbstractRoutingDataSource里邊通過(guò)Map可以過(guò)個(gè)數(shù)據(jù)源,通過(guò)key查找。
2.2:代碼實(shí)現(xiàn)
1:maven依賴?
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</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>
</dependency>
<!--mybatis依賴-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.2</version>
</dependency>
<!--aop依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- mysql的依賴-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<!--直接使用druid的starter 連接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.16</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
</dependencies>
2:配置數(shù)據(jù)源的1主2從的application.properties
#讀寫(xiě)分離數(shù)據(jù)源配置
#讀寫(xiě)分離數(shù)據(jù)源配置
#配置主數(shù)據(jù)庫(kù)
spring.datasource.master.jdbc-url=jdbc:mysql://localhost:3306/W1?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.master.username=root
spring.datasource.master.password=123456
spring.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver
#配置讀數(shù)據(jù)庫(kù)1
spring.datasource.read1.jdbc-url=jdbc:mysql://localhost:3306/W1R1?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.read1.username=root
spring.datasource.read1.password=123456
spring.datasource.read1.driver-class-name=com.mysql.cj.jdbc.Driver
#配置讀數(shù)據(jù)庫(kù)2
spring.datasource.read2.jdbc-url=jdbc:mysql://localhost:3306/W1R2?useUnicode=true&characterEncoding=utf-8&useSSL=false
spring.datasource.read2.username=root
spring.datasource.read2.password=123456
spring.datasource.read2.driver-class-name=com.mysql.cj.jdbc.Driver
3:代碼注入多個(gè)數(shù)據(jù)源
@Configuration
public class MyDataSourceConfig {
/**
* 主庫(kù)
* @return
*/
@Bean(value = "master")
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource(){
return DataSourceBuilder.create().build();
}
/**
* 從庫(kù)1
* @return
*/
@Bean(value = "read1")
@ConfigurationProperties(prefix = "spring.datasource.read1")
public DataSource read1DataSource(){
return DataSourceBuilder.create().build();
}
/**
* 從庫(kù)2
* @return
*/
@Bean(value = "read2")
@ConfigurationProperties(prefix = "spring.datasource.read2")
public DataSource read2DataSource(){
return DataSourceBuilder.create().build();
}
/**
* 配置4個(gè)數(shù)據(jù)源 1個(gè)路由數(shù)據(jù)源 3個(gè)自定義數(shù)據(jù)源
* 自定義的路由數(shù)據(jù)源
* @return
*/
@Bean(value = "myRoutingDataSource")
public DataSource myRoutingDataSource(@Qualifier(value="master")DataSource master,
@Qualifier(value="read1")DataSource read1,
@Qualifier(value="read2") DataSource read2){
//自定義路由數(shù)據(jù)源
MyRoutingDataSource routingDataSource=new MyRoutingDataSource();
//目標(biāo)數(shù)據(jù)源 master read1 read2
Map<Object, Object> targetDataSources=new HashMap<>();
targetDataSources.put("master",master);
targetDataSources.put("read1",read1);
targetDataSources.put("read2",read2);
//放入默認(rèn)數(shù)據(jù)源是 master
routingDataSource.setDefaultTargetDataSource(master);
routingDataSource.setTargetDataSources(targetDataSources);
return routingDataSource;
}
}
?4:數(shù)據(jù)源切換代碼實(shí)現(xiàn)
public class DBContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal();
private static final AtomicInteger count = new AtomicInteger(1);
//ThreadLocal的get、set方法設(shè)置數(shù)據(jù)源
public static String get() {
return contextHolder.get();
}
public static void set(String dataSource) {
contextHolder.set(dataSource);
}
public static void remove() {
contextHolder.remove();
}
/**
* 獲取 主數(shù)據(jù)庫(kù)
*/
public static void master() {
set("master");
System.out.println("切換到master數(shù)據(jù)源");
}
/**
* 獲取從數(shù)據(jù)庫(kù) 隨機(jī)獲取 或者隨機(jī)獲取
*/
public static void read() {
// //輪詢數(shù)據(jù)源進(jìn)行讀操作
// Random random=new Random();
// int index = random.nextInt(10);
// System.out.println("隨機(jī)數(shù):"+index);
// if(index>5){
// set("read1");
// System.out.println("切換到read1數(shù)據(jù)源");
// }else {
// set("read2");
// System.out.println("切換到read2數(shù)據(jù)源");
// }
int index = count.getAndIncrement()%2;
System.out.println("模數(shù):" + index);
System.out.println("隨機(jī)數(shù):" + count);
//輪詢數(shù)據(jù)源進(jìn)行讀操作
if (count.get() > 10) {
count.set(1);
}
if (index == 0) {
set("read1");
System.out.println("切換到slave1數(shù)據(jù)源");
} else {
set("read2");
System.out.println("切換到slave2數(shù)據(jù)源");
}
}
public static void main(String[] args) {
//輪詢數(shù)據(jù)源進(jìn)行讀操作
Random random = new Random();
int index = random.nextInt(10);
System.out.println(index);
}
}
5:獲取路由數(shù)據(jù)源
/**
* 自定義路由數(shù)據(jù)源 繼承AbstractRoutingDataSource
*/
public class MyRoutingDataSource extends AbstractRoutingDataSource {
/**
* 通過(guò)指定的key來(lái)確認(rèn)當(dāng)前的數(shù)據(jù)源方法
*
* 目標(biāo)數(shù)據(jù)源 master read1 read2
* Map<Object, Object> targetDataSources=new HashMap<>();
* targetDataSources.put("master",master);
* targetDataSources.put("read1",read1);
* targetDataSources.put("read2",read2);
*
*
* 根據(jù)Aop 在執(zhí)行不同方法之前 設(shè)置不同的數(shù)據(jù)源的key
*/
@Override
protected Object determineCurrentLookupKey() {
return DBContextHolder.get();
}
}
?6:mybatis的數(shù)據(jù)源配置
/**
* mybatis的數(shù)據(jù)源配置
* 由于Spring容器中現(xiàn)在有4個(gè)數(shù)據(jù)源,所以我們需要為事務(wù)管理器和MyBatis手動(dòng)指定一個(gè)明確的數(shù)據(jù)源。
*/
@Configuration
public class MyBatisConfig {
@Resource(name = "myRoutingDataSource")
private DataSource myRoutingDataSource;
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(myRoutingDataSource);
sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
return sqlSessionFactoryBean.getObject();
}
@Bean
public PlatformTransactionManager platformTransactionManager() {
return new DataSourceTransactionManager(myRoutingDataSource);
}
}
7:AOP切入處理,在service方法中通過(guò)切面提前設(shè)置數(shù)據(jù)源的key
@Aspect
@Component
public class DataSourceAop {
/**
* 不存在自定義的Master注解
* 所有service包下的的所有select和get方法
* 設(shè)置為讀從庫(kù)數(shù)據(jù)
*/
@Pointcut("!@annotation(com.example.springboot08_rw1.annotation.Master) " +
"&& (execution(* com.example.springboot08_rw1.service..*.select*(..)) " +
"|| execution(* com.example.springboot08_rw1.service..*.get*(..)))")
public void readPointcut() {
}
/**
* 存在Master注解或者add insert等方法 去主庫(kù)操作
*/
@Pointcut("@annotation(com.example.springboot08_rw1.annotation.Master) " +
"|| execution(* com.example.springboot08_rw1.service..*.insert*(..)) " +
"|| execution(* com.example.springboot08_rw1.service..*.add*(..)) " +
"|| execution(* com.example.springboot08_rw1.service..*.update*(..)) " +
"|| execution(* com.example.springboot08_rw1.service..*.edit*(..)) " +
"|| execution(* com.example.springboot08_rw1.service..*.delete*(..)) " +
"|| execution(* com.example.springboot08_rw1.service..*.remove*(..))")
public void writePointcut() {
}
@Before("readPointcut()")
public void read() {
DBContextHolder.read();
}
@Before("writePointcut()")
public void write() {
DBContextHolder.master();
}
// /**
// * 另一種寫(xiě)法:if...else... 判斷哪些需要讀從數(shù)據(jù)庫(kù),其余的走主數(shù)據(jù)庫(kù)
// */
// @Before("execution(* com.example.springboot08_rw1.service.UserServiceImpl.select*(..))")
// public void before(JoinPoint jp) {
// String methodName = jp.getSignature().getName();
// System.out.println("方法名字:"+methodName);
if (StringUtils.startsWithAny(methodName, "get", "select", "find")) {
}else {
DBContextHolder.master();
}
//
// DBContextHolder.read();
//
// }
}
//自定義注解
public @interface Master {
}
2.3:測(cè)試代碼?
service代碼
@Service
public class UserServiceImpl implements UserService {
@Resource
UserMapper userMapper;
/**
* 添加自定義注解 @Master 主庫(kù)查詢
*/
@Master
public User selectById_Master(Integer id) {
return userMapper.selectById(1);
}
/**
* 不使用注解 走從庫(kù)查詢
*/
public User selectById_MoRen(Integer id) {
return userMapper.selectById(1);
}
/**
* 不使用注解 add方法 走主庫(kù)
*/
public User add(Integer id) {
return userMapper.selectById(1);
}
}
controller層的代碼測(cè)試
@RestController
public class UserController {
@Autowired
UserServiceImpl userService;
//兩個(gè)從庫(kù)切換查詢
@GetMapping(value = "selectUser")
public User select(){
return userService.selectById_MoRen(1);
}
//使用注解 查詢主庫(kù)
@GetMapping(value = "selectUser1")
public User select1(){
return userService.selectById_Master(1);
}
//方法是add 查詢主庫(kù)
@GetMapping(value = "selectUser2")
public User select2(){
return userService.add(1);
}
}
頁(yè)面效果
http://localhost:8082/selectUser兩個(gè)從切換查詢
http://localhost:8082/selectUser1主庫(kù)查詢
http://localhost:8082/selectUser2主庫(kù)查詢
2.4:優(yōu)缺點(diǎn)分析
SpringBoot讀寫(xiě)分離_springboot 讀寫(xiě)分離-CSDN博客文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-738437.html
代碼比較復(fù)雜,需要制定好spring的數(shù)據(jù)源切換的基礎(chǔ)原理,分庫(kù)分表代碼進(jìn)一步負(fù)載話文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-738437.html
到了這里,關(guān)于SpringBoot_第七章(讀寫(xiě)分離)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!