文章目录
多数据源管理
单数据源项目
父工程版本与依赖
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>sharding_sphere</artifactId>
<version>1.0-SNAPSHOT</version>
<modules>
<module>druid_jdbc</module>
</modules>
<packaging>pom</packaging>
<name>sharding_sphere</name>
<description>ShardingSphere分库分表总工程</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<mysql-connector.version>8.0.15</mysql-connector.version>
<druid.version>1.1.10</druid.version>
<mybatis.version>3.5.3</mybatis.version>
<mybatis-plus.version>3.3.2</mybatis-plus.version>
<swagger2.version>2.7.0</swagger2.version>
<fastjson.version>2.0.51</fastjson.version>
<!-- 微服务技术栈版本 -->
<spring-boot.version>2.3.12.RELEASE</spring-boot.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--集成druid连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<!-- MyBatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!--Mysql数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-connector.version}</version>
</dependency>
<!--Swagger-UI API文档生产工具-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${swagger2.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${swagger2.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>${fastjson.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 数据库相关 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!--Swagger-UI API文档生产工具 User对象需要用到Swagger相关的注释 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
</dependency>
<!-- 请求参数校验 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>${fastjson.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
yml配置文件
server:
port: 8081
spring:
application:
name: druid_jdbc_service
datasource:
druid:
db-type: mysql
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/sharding_sphere1?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8
username: root
password: 1234
initial-size: 5 #连接池初始化大小
min-idle: 10 #最小空闲连接数
max-active: 20 #最大连接数
mybatis-plus:
configuration:
# 日志配置 选择StdOutImpl表示在控制台输出
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true
mapper-locations: classpath:com/hs/single/mapper/*.xml
实体类
package com.hs.single.entity;
import java.util.HashMap;
import java.util.Map;
/**
* 返回数据
*
* @author hushang
*/
public class R extends HashMap<String, Object> {
private static final long serialVersionUID = 1L;
public R() {
put("code", 0);
put("msg", "success");
}
public static R error() {
return error(500, "未知异常,请联系管理员");
}
public static R error(String msg) {
return error(500, msg);
}
public static R error(int code, String msg) {
R r = new R();
r.put("code", code);
r.put("msg", msg);
return r;
}
public static R ok(String msg) {
R r = new R();
r.put("msg", msg);
return r;
}
public static R ok(Map<String, Object> map) {
R r = new R();
r.putAll(map);
return r;
}
public static R ok(Object obj) {
R r = new R();
r.put("data", obj);
return r;
}
public static R ok() {
return new R();
}
@Override
public R put(String key, Object value) {
super.put(key, value);
return this;
}
}
package com.hs.single.entity;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.Date;
/**
* <p>
* 用户表
* </p>
*
* @author 胡尚
* @since 2024-08-02
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("sys_user")
@ApiModel(value="User对象", description="用户表")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "id")
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
@ApiModelProperty(value = "用户名")
private String username;
@ApiModelProperty(value = "密码")
private String password;
@ApiModelProperty(value = "姓名")
private String name;
@ApiModelProperty(value = "描述")
private String description;
@ApiModelProperty(value = "状态(1:正常 0:停用)")
private Integer status;
@ApiModelProperty(value = "创建时间")
@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
@TableField(fill = FieldFill.INSERT)
private Date createTime;
@ApiModelProperty(value = "最后修改时间")
@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
}
package com.hs.single.dto.req;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
/**
* @Description: 请求参数
* @Author 胡尚
* @Date: 2024/8/2 10:21
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserReq {
@NotBlank(message = "用户名不能为null")
@ApiModelProperty(value = "用户名")
private String username;
@NotBlank(message = "密码不能为null")
@ApiModelProperty(value = "密码")
private String password;
@ApiModelProperty(value = "姓名")
private String name;
@ApiModelProperty(value = "描述")
private String description;
}
新增与修改时间
package com.hs.single.handle;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* @Description: 新增与修改时自动填充值
* @Author 胡尚
* @Date: 2024/8/2 10:56
*/
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
// 该方法就是设置某字段的值
// 该方法的三个参数是 要自动填充的字段 填充什么内容 最后一个是要给那个字段处理
// 注意 该方法的第一个参数对应的是实体类的变量名 而不是数据表中的字段
this.setFieldValByName("createTime" , new Date() , metaObject);
this.setFieldValByName("updateTime" , new Date() , metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
//更新操作就仅仅只需要修改updateTime 这一个字段即可。
this.setFieldValByName("updateTime" , new Date() , metaObject);
}
}
Mapper
package com.hs.single.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.hs.single.entity.User;
/**
* @Description: TODO
* @Author 胡尚
* @Date: 2024/8/2 10:08
*/
public interface UserMapper extends BaseMapper<User> {
}
<?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="com.hs.single.mapper.UserMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="com.hs.single.entity.User">
<id column="id" property="id" />
<result column="username" property="username" />
<result column="password" property="password" />
<result column="name" property="name" />
<result column="description" property="description" />
<result column="status" property="status" />
<result column="create_time" property="createTime" />
<result column="update_time" property="updateTime" />
</resultMap>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
id, username, password, name, description, status, create_time, update_time
</sql>
</mapper>
Service
package com.hs.single.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.hs.single.dto.req.UserReq;
import com.hs.single.entity.User;
/**
* @Description: TODO
* @Author 胡尚
* @Date: 2024/8/2 10:10
*/
public interface UserService extends IService<User> {
/**
* 新增用户
* @param req user请求参数
* @return 操作数
*/
int addUser(UserReq req);
}
package com.hs.single.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hs.single.dto.req.UserReq;
import com.hs.single.entity.User;
import com.hs.single.mapper.UserMapper;
import com.hs.single.service.UserService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @Description: TODO
* @Author 胡尚
* @Date: 2024/8/2 10:11
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public int addUser(UserReq req) {
User user = new User();
// 对象转换
BeanUtils.copyProperties(req,user);
int insertCount = userMapper.insert(user);
return insertCount;
}
}
Controller
package com.hs.single.controller;
import com.hs.single.dto.req.UserReq;
import com.hs.single.entity.R;
import com.hs.single.entity.User;
import com.hs.single.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.validation.constraints.NotNull;
/**
* @Description: TODO
* @Author 胡尚
* @Date: 2024/8/2 10:12
*/
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@PostMapping
public R addUser(@RequestBody UserReq req){
int i = userService.addUser(req);
return R.ok();
}
@GetMapping
public R queryUser(@RequestParam("userId")
@NotNull(message = "userId不能为null") Long userId){
User user = userService.getById(userId);
return R.ok(user);
}
}
主启动类
package com.hs.single;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 使用了 @MapperScan 注解那么我们的mapper接口中就不需要再加@Mapper注解了
* @Description: TODO
* @Author 胡尚
* @Date: 2024/8/2 9:49
*/
@SpringBootApplication
@MapperScan("com.hs.single.mapper")
public class DruidApplication {
public static void main(String[] args) {
SpringApplication.run(DruidApplication.class, args);
}
}
测试类
package com.hs.single;
import com.hs.single.dto.req.UserReq;
import com.hs.single.service.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @Description: TODO
* @Author 胡尚
* @Date: 2024/8/2 10:42
*/
@SpringBootTest
@RunWith(SpringRunner.class)
public class UserTest {
@Autowired
private UserService userService;
@Test
public void testAddUser(){
UserReq userReq = new UserReq("deimkf", "123", "hushang","描述");
userService.addUser(userReq);
}
}
多数据源初始版
yml配置文件
在yml配置文件中配置多个数据源
server:
port: 8082
spring:
application:
name: route_datasource_service
datasource:
type: com.alibaba.druid.pool.DruidDataSource
# 自定义第一个数据源
datasource1:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/sharding_sphere1?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8
username: root
password: 1234
initial-size: 2
min-idle: 2
max-active: 20
test-on-borrow: true
# 自定义第二个数据源
datasource2:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/sharding_sphere2?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8
username: root
password: 1234
initial-size: 2
min-idle: 2
max-active: 20
test-on-borrow: true
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
配置类
配置类中读取yml配置文件中的内容,并生成多个DataSource对象存入Spring容器中,在后面还可以添加对应的事务管理器中添加对应的数据源
package com.hs.dynamic.config;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
/**
* @Description: 数据源的配置类
* 通过我们yml配置文件中的配置,创建两个数据源,并存入spring容器中
* @Author 胡尚
* @Date: 2024/8/2 12:37
*/
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.datasource1")
public DataSource dataSource1(){
// 底层会拿到我们spring.datasource.datasource1中的配置,创建一个DruidDataSource
return DruidDataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.datasource2")
public DataSource dataSource2(){
// 底层会拿到我们spring.datasource.datasource2中的配置,创建一个DruidDataSource
return DruidDataSourceBuilder.create().build();
}
/**
* 事务管理器
*/
@Bean
public DataSourceTransactionManager transactionManager1(DynamicDataSource dataSource){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
@Bean
public DataSourceTransactionManager transactionManager2(DynamicDataSource dataSource){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
}
创建一个AbstractRoutingDataSource
接下来自定义一个类,继承AbstractRoutingDataSource类
package com.hs.dynamic.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
* @Description: 往Spring容器中添加一个AbstractRoutingDataSource 类型的bean
* 使用AbstractRoutingDataSource创建两个库,R表示读库,W表示写库。
* @Author 胡尚
* @Date: 2024/8/2 12:32
*/
@Component("dynamicDataSource")
@Primary
public class DynamicDataSource extends AbstractRoutingDataSource {
/**
* 声明一个ThreadLocal对象,在controller方法中会往这里面存值
*/
public static ThreadLocal<String> dataSourceKey =new ThreadLocal<>();
@Autowired
@Qualifier("dataSource1")
private DataSource dataSource1;
@Autowired
@Qualifier("dataSource2")
private DataSource dataSource2;
@Override
protected Object determineCurrentLookupKey() {
// 从ThreadLocal中取值
return dataSourceKey.get();
}
@Override
public void afterPropertiesSet() {
// 在初始化方法中,将我们创建的两个数据源进行set
// 我们定义其中一个为写库 一个为读库
// 这里的key要和Controller层存入ThreadLocal中的key对应上
Map<Object, Object> targetDataSources = new HashMap<>(16);
targetDataSources.put("hsW", dataSource1);
targetDataSources.put("hsR", dataSource2);
// 将我们创建的两个数据源进行set
super.setTargetDataSources(targetDataSources);
// 为defaultTargetDataSource 设置默认的数据源
super.setDefaultTargetDataSource(dataSource1);
super.afterPropertiesSet();
}
}
Controller层
package com.hs.dynamic.controller;
import com.hs.dynamic.config.DynamicDataSource;
import com.hs.dynamic.dto.req.UserReq;
import com.hs.dynamic.entity.R;
import com.hs.dynamic.entity.User;
import com.hs.dynamic.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;
/**
* @Description: TODO
* @Author 胡尚
* @Date: 2024/8/2 10:12
*/
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
/**
* 添加一个dsKey的请求参数,表示使用哪一个数据库
* @param dsKey 使用哪一个数据库,默认值是hsW
* @param req 请求参数
* @return 。
*/
@PostMapping
public R addUser(@RequestParam(value = "dsKey",defaultValue = "hsW") String dsKey,
@RequestBody UserReq req){
// 使用哪一个数据库的key 保存至ThreadLocal中
DynamicDataSource.dataSourceKey.set(dsKey);
try{
userService.addUser(req);
} finally {
// 请求完成后需要移除
DynamicDataSource.dataSourceKey.remove();
}
return R.ok();
}
/**
* 添加一个dsKey的请求参数,表示使用哪一个数据库
* @param dsKey 使用哪一个数据库,默认值是hsR
* @return 。
*/
@GetMapping
public R queryUser(@RequestParam(value = "dsKey",defaultValue = "hsR") String dsKey){
// 使用哪一个数据库的key 保存至ThreadLocal中
DynamicDataSource.dataSourceKey.set(dsKey);
List<User> userList = new ArrayList<>();
try {
userList.addAll(userService.list());
} finally {
// 请求完成后需要移除
DynamicDataSource.dataSourceKey.remove();
}
return R.ok(userList);
}
}
测试
添加用户
查询用户
DynamicDataSource版本
引入依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.5.0</version>
</dependency>
yml配置文件
server:
port: 8083
spring:
application:
name: dynamic_datasource_service
datasource:
# 使用dynamic DataSource框架
dynamic:
# 设置默认的数据源
primary: hsWrite
# 是否启用严格模式,默认不启动. 严格模式下未匹配到数据源直接报错, 非严格模式下则使用默认数据源
strict: false
datasource:
# 配置第一个数据源
hsWrite:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/sharding_sphere1?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8
username: root
password: 1234
initial-size: 1
min-idle: 1
max-active: 20
test-on-borrow: true
# 配置第二个数据源
hsRead:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/sharding_sphere2?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=UTF-8
username: root
password: 1234
initial-size: 1
min-idle: 1
max-active: 20
test-on-borrow: true
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
Controller层
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@PostMapping
public R addUser(@RequestBody UserReq req) {
userService.addUser(req);
return R.ok();
}
@GetMapping
public R queryUser() {
List<User> userList = new ArrayList<>();
userList.addAll(userService.findAll());
return R.ok(userList);
}
}
Service层
在类上使用@DS注解,也可以在方法上使用@DS注解来指定使用某个数据源
package com.hs.dynamic.service.impl;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hs.dynamic.req.UserReq;
import com.hs.dynamic.entity.User;
import com.hs.dynamic.mapper.UserMapper;
import com.hs.dynamic.service.UserService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Collection;
/**
* @Description: 在类上使用@DS注解,也可以在方法上使用@DS注解来指定使用某个数据源
* @Author 胡尚
* @Date: 2024/8/2 10:11
*/
@Service
@DS("hsWrite")
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public int addUser(UserReq req) {
User user = new User();
// 对象转换
BeanUtils.copyProperties(req,user);
int insertCount = userMapper.insert(user);
return insertCount;
}
@Override
@DS("hsRead")
public Collection<? extends User> findAll() {
return userMapper.selectList(new QueryWrapper<>());
}
}
动态新增/切换数据源
在上方的案例中我们是在yml配置文件中指定了两个数据源。并且在Service方法上使用@DS
注解指定了使用某一个数据源。
我们也可以动态的新增数据源,或者是根据请求参数动态的更改使用某个数据源
动态切换数据源
Controller层方法
从请求参数中取出指定访问数据源的key,存入session中,这里存入session的key要和Service方法中@DS注解中的值对应上。
@GetMapping("/sesstionTest")
public R sesstionTest(@RequestParam(value = "dsKey",defaultValue = "hsWrite") String dsKey, HttpServletRequest request) {
// 注意,这里存入session的key要和Service方法中@DS注解中的值对应上。而value要和配置文件中定义的数据源对应
request.getSession().setAttribute("hs_session_db_key", dsKey);
List<User> userList = new ArrayList<>();
userList.addAll(userService.sessionFindAll());
return R.ok(userList);
}
Service方法
@DS注解中需要以#session.
开头,之后的值要和上面Controller成存入session的key对应上
@DS("#session.hs_session_db_key")
public Collection<? extends User> sessionFindAll() {
return userMapper.selectList(new QueryWrapper<>());
}
动态新增数据源
我们为单独的用户保存单独的数据连接信息
Controller层方法
// 根据userId查询到该用户对应的数据源信息
@Autowired
private UserDriverService userDriverService;
@Resource
DynamicRoutingDataSource dataSource;
@GetMapping("/dynamicAddTest")
public R dynamicAddTest(@RequestParam(value = "userId") Long userId, HttpServletRequest request) {
// 根据userId查询到该用户对应的数据源信息
UserDriver userDriver = userDriverService.getByUserId(userId);
// 创建一个数据源
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName(userDriver.getDriverClassName());
druidDataSource.setUrl(userDriver.getUrl());
druidDataSource.setUsername(userDriver.getUsername());
druidDataSource.setPassword(userDriver.getPassword());
// 把新创建的数据源添加进DynamicRoutingDataSource中,这里的key就是该数据源的名字
dataSource.addDataSource("hsNewDataSource", druidDataSource);
// 注意,这里存入session的key要和Service方法中@DS注解中的值对应上。而value就是数据源的名字
request.getSession().setAttribute("hs_session_db_key", "hsNewDataSource");
List<User> userList = new ArrayList<>();
userList.addAll(userService.sessionFindAll());
return R.ok(userList);
}
Service的方法还是没有变动
@Override
@DS("#session.hs_session_db_key")
public Collection<? extends User> sessionFindAll() {
return userMapper.selectList(new QueryWrapper<>());
}
很简单就实现了每个用户使用单独的数据源
测试
新增数据源 源码分析
源码入口,还是通过spring.factiries文件找自动配置类,进入到DynamicDataSourceAutoConfiguration
中
在DynamicDataSourceAutoConfiguration
自动配置类中会添加一个DynamicRoutingDataSource
数据源
@Bean
@ConditionalOnMissingBean
public DataSource dataSource() {
// 查看DynamicRoutingDataSource类的创建流程
DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();
dataSource.setPrimary(properties.getPrimary());
dataSource.setStrict(properties.getStrict());
dataSource.setStrategy(properties.getStrategy());
dataSource.setP6spy(properties.getP6spy());
dataSource.setSeata(properties.getSeata());
return dataSource;
}
进入到DynamicRoutingDataSource
类中就会发现它实现了InitializingBean
接口。在通过查看afterPropertiesSet()
方法就会发现在这里会遍历我们在yml配置文件中配置的数据源,并调用addDataSource()
方法添加进DynamicRoutingDataSource
类中
@Override
public void afterPropertiesSet() throws Exception {
// 检查开启了配置但没有相关依赖
checkEnv();
// 添加并分组数据源
Map<String, DataSource> dataSources = new HashMap<>(16);
for (DynamicDataSourceProvider provider : providers) {
dataSources.putAll(provider.loadDataSources());
}
// 遍历yml配置文件中配置的数据源
for (Map.Entry<String, DataSource> dsItem : dataSources.entrySet()) {
// 在下面的方法中会把我们定义的数据源添加进DataSourceMap集合中: dataSourceMap.put(ds, dataSource);
addDataSource(dsItem.getKey(), dsItem.getValue());
}
// 打印日志 ......
}
至此,通过源码就找到了如何动态的添加数据源
切换数据源 源码分析
在DynamicDataSourceAutoConfiguration
自动配置类中会添加一个Advisor
,相信大家对Spring的源码应该是已经非常熟悉了吧,这里就不过多介绍Spring了
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@Bean
@ConditionalOnProperty(...)
public Advisor dynamicDatasourceAnnotationAdvisor(DsProcessor dsProcessor) {
DynamicDatasourceAopProperties aopProperties = properties.getAop();
// 创建一个 interceptor
DynamicDataSourceAnnotationInterceptor interceptor = new DynamicDataSourceAnnotationInterceptor(aopProperties.getAllowedPublicOnly(), dsProcessor);
// 利用的AOP 解析处理@DS注解,而要执行的增强方法就在上方Interceptor的invoke()方法中
// 这里的Interceptor就是Advice,DS.class会封装为CheckPoint Advisor = advice + CheckPoint
DynamicDataSourceAnnotationAdvisor advisor = new DynamicDataSourceAnnotationAdvisor(interceptor, DS.class);
advisor.setOrder(aopProperties.getOrder());
return advisor;
}
DynamicDataSourceAnnotationInterceptor
的invoke()
方法。该方法的处理流程就和上文中《多数据源初始版》非常类似
- 调用determineDatasourceKey()方法获取到代表数据源的key
- 获取@DS注解中设置的值,
- 如果不是#开头就直接返回
- 如果是#开头就去调用DsProcessor#determineDatasource方法,调用所有DsProcessor的子类去进行匹配,最终找到对应的数据源的key返回
- 存入ThreadLocal中
- 执行下一个Interceptor --> 目标方法
- ThreadLocal中移除
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// 调用determineDatasourceKey()方法获取到代表数据源的key
String dsKey = determineDatasourceKey(invocation);
// 存入ThreadLocal中
DynamicDataSourceContextHolder.push(dsKey);
try {
// 执行下一个Interceptor --> 目标方法
return invocation.proceed();
} finally {
// ThreadLocal中移除
DynamicDataSourceContextHolder.poll();
}
}
private String determineDatasourceKey(MethodInvocation invocation) {
// key 为我们@DS()注解中设置的值
String key = dataSourceClassResolver.findKey(invocation.getMethod(), invocation.getThis());
// 如果key为#开头,那么就调用DsProcessor.determineDatasource()方法,否则就直接返回我们@DS()注解中设置的值
return key.startsWith(DYNAMIC_PREFIX) ? dsProcessor.determineDatasource(invocation, key) : key;
}
public abstract class DsProcessor {
/**
* 抽象匹配条件 匹配才会走当前执行器否则走下一级执行器
*
* @param key DS注解里的内容
* @return 是否匹配
*/
public abstract boolean matches(String key);
/**
* 决定数据源
* <pre>
* 调用底层doDetermineDatasource,
* 如果返回的是null则继续执行下一个,否则直接返回
* </pre>
*
* @param invocation 方法执行信息
* @param key DS注解里的内容
* @return 数据源名称
*/
public String determineDatasource(MethodInvocation invocation, String key) {
// 调用子类重写该抽象方法具体的实现
if (matches(key)) {
// 调用抽象方法具体的实现
String datasource = doDetermineDatasource(invocation, key);
if (datasource == null && nextProcessor != null) {
//如果返回的是null则继续执行下一个
return nextProcessor.determineDatasource(invocation, key);
}
return datasource;
}
if (nextProcessor != null) {
return nextProcessor.determineDatasource(invocation, key);
}
return null;
}
/**
* 抽象最终决定数据源
*
* @param invocation 方法执行信息
* @param key DS注解里的内容
* @return 数据源名称
*/
public abstract String doDetermineDatasource(MethodInvocation invocation, String key);
}
这里就以Session举例
public class DsSessionProcessor extends DsProcessor {
/**
* session开头
*/
private static final String SESSION_PREFIX = "#session";
@Override
public boolean matches(String key) {
// 进行匹配 是否为#session开头
return key.startsWith(SESSION_PREFIX);
}
@Override
public String doDetermineDatasource(MethodInvocation invocation, String key) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
// 从session中取值,最终返回代表数据源的key
return request.getSession().getAttribute(key.substring(9)).toString();
}
}
DynamicDataSource事务
在Service方法上使用@DSTransactional
注解,它的功能就和Spring提供的@Transaction
注解的功能一样
所以它们都只能支持本地事务,不能满足分布式事务
DynamicDataSource分布式事务扩展思路
获得DynamicRoutingDataSource对象,取出其中所有的数据源。再获得所有的连接对象,在通过XA去实现