赞
踩
在实际开发中,经常可能遇到在一个应用中可能需要访问多个数据库的情况。以下是两种典型场景
业务复杂(数据量大)
数据分布在不同的数据库中,数据库拆了,应用没拆。一个公司多个子项目,各用各的数据库,设计数据共享…

读写分离
为解决 数据库的读性能瓶颈(读比写性能更高,写锁会影响读阻塞,从而影响读的性能)
很多数据库拥有主从架构,也就是说,一台主数据库服务器,是对外提供增删改业务的生产服务;另一(多)台从数据库服务器,主要进行读的操作
可以通过中间件(ShardingSphere、mycat、mysql-proxy、TDDL…),但是有一些规模较小的公司,没有专门的中间件团队搭建读写分离基础设施,因此需要业务开发人员自行实现读写分离

这里的架构与上图类似,不同的是,在读写分离中,主库和从库的数据库是一致的(不考虑主从延迟)。数据更新操作(insert、update、delete)都是在主库上进行,主库将数据变更信息同步给从库。在查询时,可以在从库上进行,从而分担主库的压力
对于大多数 Java 应用,都使用了 Spring 框架,spring-jdbc 模块提供了 AbstractRoutingDataSource,其内部可以包含多个 DataSource,然后在运行时来动态的访问哪个数据库。这种方式访问数据库的架构如下图所示

测试样例一:普通的动态数据源
read和 writeCREATE DATABASE `read` /*!40100 DEFAULT CHARACTER SET utf8 */
CREATE TABLE `people` (
`name` VARCHAR(50) DEFAULT NULL
) ENGINE=INNODB DEFAULT CHARSET=utf8;
CREATE DATABASE `write` /*!40100 DEFAULT CHARACTER SET utf8 */
CREATE TABLE `people` (
`name` VARCHAR(50) DEFAULT NULL
) ENGINE=INNODB DEFAULT CHARSET=utf8;
创建一个SpringBoot 项目 dynamic_datasource
导入依赖
<dependencies> <!--jdbc--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!--web--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--mybatis--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.4</version> </dependency> <!--druid--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.8</version> </dependency> <!--mysql驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.28</version> </dependency> <!--lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.24</version> </dependency> </dependencies>
spring: application: name: dynamic_datasource # 数据源 datasource: type: com.alibaba.druid.pool.DruidDataSource # 读数据源 read: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/read?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai username: root password: 123456 # 写数据源 write: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/write?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai username: root password: 123456 # 端口号 server: port: 3355 # 别名、xml文件配置 mybatis: mapper-locations: classpath:com/vinjcent/mapper/**/*.xml type-aliases-package: com.vinjcent.pojo
package com.vinjcent.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
@Data
public class People {
private String name;
}
数据源配置类
package com.vinjcent.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 javax.sql.DataSource; @Configuration public class DataSourceConfiguration { @Bean(name = "readDatasource") @ConfigurationProperties(prefix = "spring.datasource.read") public DataSource readDatasource() { // 底层会自动拿到spring.datasource中的配置,创建一个DruidDataSource return DruidDataSourceBuilder.create().build(); } @Bean(name = "writeDatasource") @ConfigurationProperties(prefix = "spring.datasource.write") public DataSource writeDatasource() { // 底层会自动拿到spring.datasource中的配置,创建一个DruidDataSource return DruidDataSourceBuilder.create().build(); } }
动态数据源
package com.vinjcent.config; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; import javax.sql.DataSource; import java.io.PrintWriter; import java.sql.Connection; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.util.logging.Logger; @Component @Primary // 将该Bean设置为主要注入Bean public class DynamicDataSource implements DataSource, InitializingBean { // 用于存储数据源的标识 public static ThreadLocal<String> name = new ThreadLocal<>(); // 写 private final DataSource writeDataSource; // 读 private final DataSource readDataSource; @Autowired public DynamicDataSource(@Qualifier("readDatasource") DataSource readDataSource, @Qualifier("writeDatasource") DataSource writeDataSource) { this.readDataSource = readDataSource; this.writeDataSource = writeDataSource; } @Override public Connection getConnection() throws SQLException { return name.get().equals("w") ? writeDataSource.getConnection() : readDataSource.getConnection(); } @Override public Connection getConnection(String username, String password) throws SQLException { return null; } @Override public <T> T unwrap(Class<T> iface) throws SQLException { return null; } @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { return false; } @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; } /** * 初始化bean的initialization接口 * @throws Exception */ @Override public void afterPropertiesSet() throws Exception { // TODO 初始化 name.set("w"); } }
package com.vinjcent.controller; import com.vinjcent.config.DynamicDataSource; import com.vinjcent.pojo.People; import com.vinjcent.service.PeopleService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController @RequestMapping("people") public class PeopleController { private final PeopleService peopleService; @Autowired public PeopleController(PeopleService peopleService) { this.peopleService = peopleService; } @GetMapping("/list") public List<People> getAllPeople() { // 修改对应数据源 DynamicDataSource.name.set("r"); return peopleService.list(); } @GetMapping("/insert") public String addPeople() { // 修改对应数据源 DynamicDataSource.name.set("w"); peopleService.save(new People("vinjcent")); return "添加成功"; } }
package com.vinjcent; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; @MapperScan("com.vinjcent.mapper") @SpringBootApplication(exclude = DataSourceAutoConfiguration.class) // 排除SpringBoot数据源自动配置类 public class DynamicDatasourceApplication { public static void main(String[] args) { SpringApplication.run(DynamicDatasourceApplication.class, args); } }
测试实例二:实现 AbstractRoutingDataSource
应用直接操作的是 AbstractRoutingDataSource 的实现类,告诉 AbstractRoutingDataSource 访问哪个数据库,然后由 AbstractRoutingDataSource 从事先配好的数据源(readDataSource、writeDataSource)选择一个,来访问对应的数据库

流程
AbstractRoutingDataSource 分析
// targetDataSources 保存了key和数据库连接的映射关系
private Map<Object, Object> targetDataSources;
// 标识默认的连接
private Object defaultTargetDataSource;
// 这个数据结构是通过 targetDataSources 构建而来,存储结构也是数据库标识和数据源的映射关系
private Map<Object, DataSource> resolvedDataSources;
AbstractRoutingDataSource 实现了 InitializingBean 接口,并实现了 afterPropertiesSet() 方法。afterPropertiesSet() 方法是初始化bean的时候执行,通常用作数据初始化。resolvedDataSources 就是在这里赋值
public void afterPropertiesSet() { if (this.targetDataSources == null) { throw new IllegalArgumentException("Property 'targetDataSources' is required"); } else { this.resolvedDataSources = new HashMap(this.targetDataSources.size()); this.targetDataSources.forEach((key, value) -> { Object lookupKey = this.resolveSpecifiedLookupKey(key); DataSource dataSource = this.resolveSpecifiedDataSource(value); this.resolvedDataSources.put(lookupKey, dataSource); }); if (this.defaultTargetDataSource != null) { this.resolvedDefaultDataSource = this.resolveSpecifiedDataSource(this.defaultTargetDataSource); } } }
key为数据源的标识(可以是枚举、字符串都行,因为标识是Object类型)package com.vinjcent.config; import com.vinjcent.constants.DataSourceConstants; 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; @Component @Primary // 将该Bean设置为主要注入Bean public class DynamicDataSource extends AbstractRoutingDataSource { // 用于存储数据源的标识 public static ThreadLocal<String> name = new ThreadLocal<>(); // 写 private final DataSource writeDataSource; // 读 private final DataSource readDataSource; @Autowired public DynamicDataSource(@Qualifier("readDatasource") DataSource readDataSource, @Qualifier("writeDatasource") DataSource writeDataSource) { this.readDataSource = readDataSource; this.writeDataSource = writeDataSource; } @Override protected Object determineCurrentLookupKey() { return name.get(); } // 初始化完bean之后调用该方法 @Override public void afterPropertiesSet() { // 为targetDataSources 初始化所有数据源 Map<Object, Object> sources = new HashMap<>(); sources.put(DataSourceConstants.READ_DATASOURCE, readDataSource); sources.put(DataSourceConstants.WRITE_DATASOURCE, writeDataSource); super.setTargetDataSources(sources); // 为 defaultTargetDataSource 设置默认的数据源 super.setDefaultTargetDataSource(readDataSource); // resolvedDataSources 负责最终切换的数据源map super.afterPropertiesSet(); } }
package com.vinjcent.constants;
public class DataSourceConstants {
// 读数据源
public static final String READ_DATASOURCE = "read";
// 写数据源
public static final String WRITE_DATASOURCE = "write";
}
package com.vinjcent.controller; import com.vinjcent.config.DynamicDataSource; import com.vinjcent.constants.DataSourceConstants; import com.vinjcent.pojo.People; import com.vinjcent.service.PeopleService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController @RequestMapping("people") public class PeopleController { private final PeopleService peopleService; @Autowired public PeopleController(PeopleService peopleService) { this.peopleService = peopleService; } @GetMapping("/list") public List<People> getAllPeople() { // 修改对应数据源 DynamicDataSource.name.set(DataSourceConstants.READ_DATASOURCE); return peopleService.list(); } @GetMapping("/insert") public String addPeople() { // 修改对应数据源 DynamicDataSource.name.set(DataSourceConstants.WRITE_DATASOURCE); peopleService.save(new People("vinjcent")); return "添加成功"; } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。