SpringBoot配置多套数据源
前言
在使用springBoot开发系统的过程中,随着开发的进行,可能会遇到需要配置多个不同数据源的情况。这时我们可以通过Spring的重要功能AOP来解决这个问题。
数据库
首先我们需要新建两个数据库,结构如下
数据库 | testdatasource1 | testdatasource2 |
---|---|---|
数据表 | sys_user | sys_user2 |
字段 | id,name,age,sex | id,name,age,sex |
代码实现
- 新建一个springboot项目
略
- maven依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.2</version>
</dependency>
- yml配置类
首先我们在配置类中分别配置两套数据源,其中主数据源为primary,次数据源为secondary。
注意:Springboot2.0 在配置数据库连接的时候需要使用jdbc-url,如果只使用url的话会报jdbcUrl is required with driverClassName.错误。
spring:
datasource:
primary:
jdbc-url: jdbc:mysql://localhost:3306/testdatasource1?userSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&allowMultiQueries=true
username: root
password: 1234
driver-class-name: com.mysql.cj.jdbc.Driver
secondary:
jdbc-url: jdbc:mysql://localhost:3306/testdatasource2?userSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&allowMultiQueries=true
username: root
password: 1234
driver-class-name: com.mysql.cj.jdbc.Driver
server:
port: 7000
- 配置类
新建一个配置文件,DynamicDataSourceConfig 用来配置我们相关的bean,代码如下
package com.jzxx.demo.config;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Configuration
@MapperScan(basePackages = "com.jzxx.demo.mapper", sqlSessionFactoryRef = "SqlSessionFactory") //basePackages 我们接口文件的地址
public class DynamicDataSourceConfig {
// 将这个对象放入Spring容器中
@Bean(name = "PrimaryDataSource")
// 表示这个数据源是默认数据源
@Primary
// 读取application.properties中的配置参数映射成为一个对象
// prefix表示参数的前缀
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource getDateSource1() {
return DataSourceBuilder.create().build();
}
@Bean(name = "SecondaryDataSource")
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource getDateSource2() {
return DataSourceBuilder.create().build();
}
@Bean(name = "dynamicDataSource")
public DynamicDataSource DataSource(@Qualifier("PrimaryDataSource") DataSource primaryDataSource,
@Qualifier("SecondaryDataSource") DataSource secondaryDataSource) {
//这个地方是比较核心的targetDataSource 集合是我们数据库和名字之间的映射
Map<Object, Object> targetDataSource = new HashMap<>();
targetDataSource.put(DataSourceType.DataBaseType.Primary, primaryDataSource);
targetDataSource.put(DataSourceType.DataBaseType.Secondary, secondaryDataSource);
DynamicDataSource dataSource = new DynamicDataSource();
dataSource.setTargetDataSources(targetDataSource);
dataSource.setDefaultTargetDataSource(primaryDataSource);//设置默认对象
return dataSource;
}
@Bean(name = "SqlSessionFactory")
public SqlSessionFactory SqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dynamicDataSource)
throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dynamicDataSource);
bean.setMapperLocations(
new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/*.xml"));//设置我们的xml文件路径
return bean.getObject();
}
}
而在这所有的配置中,最核心的地方就是DynamicDataSource这个类了,DynamicDataSource是我们自定义的动态切换数据源的类。
DynamicDataSource代码如下
package com.jzxx.demo.config;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
DataSourceType.DataBaseType dataBaseType = DataSourceType.getDataBaseType();
return dataBaseType;
}
}
DataSourceType类的代码如下:
package com.jzxx.demo.config;
public class DataSourceType {
//内部枚举类,用于选择特定的数据类型
public enum DataBaseType {
Primary, Secondary
}
// 使用ThreadLocal保证线程安全
private static final ThreadLocal<DataBaseType> TYPE = new ThreadLocal<DataBaseType>();
// 往当前线程里设置数据源类型
public static void setDataBaseType(DataBaseType dataBaseType) {
if (dataBaseType == null) {
throw new NullPointerException();
}
TYPE.set(dataBaseType);
}
// 获取数据源类型
public static DataBaseType getDataBaseType() {
DataBaseType dataBaseType = TYPE.get() == null ? DataBaseType.Primary : TYPE.get();
return dataBaseType;
}
// 清空数据类型
public static void clearDataBaseType() {
TYPE.remove();
}
}
- dao层
package com.jzxx.demo.mapper.one;
import com.jzxx.demo.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import java.util.List;
@Component
@Mapper
public interface PrimaryUserMapper {
List<User> findAll();
}
package com.jzxx.demo.mapper.two;
import com.jzxx.demo.pojo.;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import java.util.List;
@Component
@Mapper
public interface SecondaryUserMapper {
List<User> findAll();
}
- mapper文件
<?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.jzxx.demo.mapper.one.PrimaryUserMapper">
<select id="findAll" resultType="com.jzxx.demo.pojo.User">
select id,name,age,sex from sys_user
</select>
</mapper>
<?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.jzxx.demo.mapper.two.SecondaryUserMapper">
<select id="findAll" resultType="com.jzxx.demo.pojo.User">
select id,name,age,sex from sys_user
</select>
</mapper>
- 相关接口文件编写好之后,就可以编写我们的aop代码了:
package com.jzxx.demo.aop;
import com.jzxx.demo.config.DataSourceType;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class DataSourceAop {
//在primary方法前执行
@Before("execution(* com.jzxx.demo.controller.TestController.primary(..))")
public void setDataSource2test01() {
System.err.println("Primary业务");
DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Primary);
}
//在secondary方法前执行
@Before("execution(* com.jzxx.demo.controller.TestController.secondary(..))")
public void setDataSource2test02() {
System.err.println("Secondary业务");
DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Secondary);
}
}
编写我们的测试 TestController: 代码如下:
package com.jzxx.demo.controller;
import com.jzxx.demo.mapper.one.PrimaryUserMapper;
import com.jzxx.demo.mapper.two.SecondaryUserMapper;
import com.jzxx.demo.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class TestController {
@Autowired
private PrimaryUserMapper primaryUserMapper;
@Autowired
private SecondaryUserMapper secondaryUserMapper;
@RequestMapping("primary")
public Object primary(){
List<Healthitem> list = primaryUserMapper.findAll();
return list;
}
@RequestMapping("secondary")
public Object secondary(){
List<HistoryData> list = secondaryUserMapper.findAll();
return list;
}
}
测试
启动项目,在浏览器中分别输入http://127.0.0.1:7000/primary 和http://127.0.0.1:7000/secondary
优化
上面的代码还不够灵活,每写一个接口就需要我们去写aop代码。下面我们将通过拦截注解的方式,来替代拦截接口的方式。
首先自定义我们的注解 @DataSource
package com.jzxx.demo.config;
import java.lang.annotation.*;
/**
* 切换数据注解 可以用于类或者方法级别 方法级别优先级 > 类级别
*/
@Target({ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
String value() default "primary"; //该值即key值,默认使用默认数据库
}
通过使用aop拦截,获取注解的属性value的值。如果value的值并没有在我们DataBaseType里面,则使用我们默认的数据源,如果有的话,则切换为相应的数据源。
package com.jzxx.demo.aop;
import com.jzxx.demo.config.DataSource;
import com.jzxx.demo.config.DataSourceType;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class DynamicDataSourceAspect {
private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
@Before("@annotation(dataSource)")//拦截我们的注解
public void changeDataSource(JoinPoint point, DataSource dataSource) throws Throwable {
String value = dataSource.value();
if (value.equals("primary")){
DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Primary);
}else if (value.equals("secondary")){
DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Secondary);
}else {
DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Primary);//默认使用主数据库
}
}
@After("@annotation(dataSource)") //清除数据源的配置
public void restoreDataSource(JoinPoint point, DataSource dataSource) {
DataSourceType.clearDataBaseType();
}
}
修改我们的dao,添加我们的自定义的@DataSouse注解,并注解掉我们DataSourceAop类里面的内容。
自定义的这个注解@DataSouse可以用于类或者方法级别 方法级别优先级 > 类级别
package com.jzxx.demo.mapper.one;
import com.jzxx.demo.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import java.util.List;
@Component
@Mapper
public interface PrimaryUserMapper {
@DataSource
List<User> findAll();
}
package com.jzxx.demo.mapper.two;
import com.jzxx.demo.pojo.;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import java.util.List;
@Component
@Mapper
public interface SecondaryUserMapper {
@DataSource("secondary")//指定数据源为:secondary
List<User> findAll();
}