环境准备
- 创建数据库
create database test01;
create database test02;
- 创建表
use test01;
CREATE TABLE `person` (
`id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
`name` varchar(255) NOT NULL COMMENT '名称',
`age` int(3) NOT NULL COMMENT '年龄',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
use test02;
CREATE TABLE `course` (
`id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT 'id',
`name` varchar(255) NOT NULL COMMENT '课程名称',
`teacher_name` varchar(255) NOT NULL COMMENT '老师名称',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
程序配置
- 引入依赖
<!-- 引入mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!-- druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<!-- 引入mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.18</version>
</dependency>
<!-- 引入aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
- 设置application.properties中的数据源配置
spring.datasource.mysql01.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.mysql01.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.mysql01.url=jdbc:mysql://localhost:3306/test01?useUnicode=true&characterEncoding=utf8¤tSchema=public
spring.datasource.mysql01.username=root
spring.datasource.mysql01.password=123456
#config druid
#连接池的设置
#初始化时建立物理连接的个数
spring.datasource.mysql01.druid.initial-size=5
#最小连接池数量
spring.datasource.mysql01.druid.min-idle=5
#最大连接池数量 maxIdle已经不再使用
spring.datasource.mysql01.druid.max-active=20
#获取连接时最大等待时间,单位毫秒
spring.datasource.mysql01.druid.max-wait=60000
#申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
spring.datasource.mysql01.druid.test-while-idle=true
#既作为检测的间隔时间又作为testWhileIdel执行的依据
spring.datasource.mysql01.druid.time-between-eviction-runs-millis=60000
#销毁线程时检测当前连接的最后活动时间和当前时间差大于该值时,关闭当前连接
spring.datasource.mysql01.druid.min-evictable-idle-time-millis=30000
#用来检测连接是否有效的sql 必须是一个查询语句
#mysql中为 select 'x'
#oracle中为 select 1 from dual
spring.datasource.mysql01.druid.validation-query=select 'x'
#申请连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
spring.datasource.mysql01.druid.test-on-borrow=false
#归还连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
spring.datasource.mysql01.druid.test-on-return=false
#是否缓存preparedStatement,mysql5.5+建议开启
spring.datasource.mysql01.druid.pool-prepared-statements=true
#当值大于0时poolPreparedStatements会自动修改为true
spring.datasource.mysql01.druid.max-pool-prepared-statement-per-connection-size=20
#配置扩展插件
spring.datasource.mysql01.druid.filters=stat,wall
#通过connectProperties属性来打开mergeSql功能;慢SQL记录
spring.datasource.mysql01.druid.connection-properties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
#合并多个DruidDataSource的监控数据
spring.datasource.mysql01.druid.use-global-data-source-stat=true
#设置访问druid监控页的账号和密码,默认没有
spring.datasource.mysql01.druid.stat-view-servlet.login-username=admin
spring.datasource.mysql01.druid.stat-view-servlet.login-password=admin
spring.datasource.mysql02.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.mysql02.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.mysql02.url=jdbc:mysql://localhost:3306/test02?useUnicode=true&characterEncoding=utf8¤tSchema=public
spring.datasource.mysql02.username=root
spring.datasource.mysql02.password=123456
#config druid
#连接池的设置
#初始化时建立物理连接的个数
spring.datasource.mysql02.druid.initial-size=5
#最小连接池数量
spring.datasource.mysql02.druid.min-idle=5
#最大连接池数量 maxIdle已经不再使用
spring.datasource.mysql02.druid.max-active=20
#获取连接时最大等待时间,单位毫秒
spring.datasource.mysql02.druid.max-wait=60000
#申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
spring.datasource.mysql02.druid.test-while-idle=true
#既作为检测的间隔时间又作为testWhileIdel执行的依据
spring.datasource.mysql02.druid.time-between-eviction-runs-millis=60000
#销毁线程时检测当前连接的最后活动时间和当前时间差大于该值时,关闭当前连接
spring.datasource.mysql02.druid.min-evictable-idle-time-millis=30000
#用来检测连接是否有效的sql 必须是一个查询语句
#mysql中为 select 'x'
#oracle中为 select 1 from dual
spring.datasource.mysql02.druid.validation-query=select 'x'
#申请连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
spring.datasource.mysql02.druid.test-on-borrow=false
#归还连接时会执行validationQuery检测连接是否有效,开启会降低性能,默认为true
spring.datasource.mysql02.druid.test-on-return=false
#是否缓存preparedStatement,mysql5.5+建议开启
spring.datasource.mysql02.druid.pool-prepared-statements=true
#当值大于0时poolPreparedStatements会自动修改为true
spring.datasource.mysql02.druid.max-pool-prepared-statement-per-connection-size=20
#配置扩展插件
spring.datasource.mysql02.druid.filters=stat,wall
#通过connectProperties属性来打开mergeSql功能;慢SQL记录
spring.datasource.mysql02.druid.connection-properties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
#合并多个DruidDataSource的监控数据
spring.datasource.mysql02.druid.use-global-data-source-stat=true
#设置访问druid监控页的账号和密码,默认没有
spring.datasource.mysql02.druid.stat-view-servlet.login-username=admin
spring.datasource.mysql02.druid.stat-view-servlet.login-password=admin
- 配置mybatis
### mybatis ###
mybatis.mapper-locations=classpath:mapper/**/*.xml
mybatis.configuration.map-underscore-to-camel-case=true
- 创建多数据源config
DataSource1Config
package top.cwcenter.dynamic.config;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.springframework.beans.factory.annotation.Qualifier;
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 org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
/**
* @Author C.W
* @Date 2021/1/14 上午10:17
* @Description um MySQL配置
*/
@Configuration
public class DataSource1Config {
@Bean(name = "dataSource1")
@ConfigurationProperties(prefix = "spring.datasource.mysql01")
public DataSource tidbDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean(name = "transactionManager1")
public PlatformTransactionManager dataSourceTransactionManager(@Qualifier("dataSource1") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
DataSource2Config
package top.cwcenter.dynamic.config;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.springframework.beans.factory.annotation.Qualifier;
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 org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
/**
* @Author C.W
* @Date 2021/1/14 上午10:17
* @Description um MySQL配置
*/
@Configuration
public class DataSource2Config {
@Bean(name = "dataSource2")
@ConfigurationProperties(prefix = "spring.datasource.mysql02")
public DataSource tidbDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean(name = "transactionManager2")
public PlatformTransactionManager dataSourceTransactionManager(@Qualifier("dataSource2") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
DataSourceContextHolder — 记录数据源,配和aop实现切换
package top.cwcenter.dynamic.config;
/**
* ThreadLocal变量,用于记录当前线程使用的数据源,配合AOP实现数据源切换.
*
* @Author: C.W
* @Date: 2019/9/29 3:15 下午
*/
public class DataSourceContextHolder {
private static final String MYSQL_DATASOURCE = "dataSource1";
private static final ThreadLocal<String> DATASOURCE_CONTEXT = new ThreadLocal<>();
public static void switchDataSource(String datasource) {
DATASOURCE_CONTEXT.set(datasource);
}
public static String getDataSource() {
String dataSource = DATASOURCE_CONTEXT.get();
if (dataSource == null || dataSource.equals("")) {
return MYSQL_DATASOURCE;
}
return dataSource;
}
/**
* 清除ThreadLocal的值,每次切换数据源查询后清理,避免与线程池同时使用时ThreadLocal中有残留.
*/
public static void clear() {
DATASOURCE_CONTEXT.remove();
}
}
RoutingDataSource 多数据源
package top.cwcenter.dynamic.config;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* 动态数据源.
*
* @Author: C.W
* @Date: 2019/9/29 3:14 下午
*/
public class RoutingDataSource extends AbstractRoutingDataSource {
/**
* 获取当前线程的数据源.
*
* @return 当前使用的数据源
*/
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSource();
}
}
DataSourceHolderConfig 数据源配置
package top.cwcenter.dynamic.config;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
* @author: C.W
* @date: 2020/4/13 5:44 下午
*/
@Configuration
public class DataSourceHolderConfig {
@Autowired
@Qualifier("dataSource1")
private DataSource dataSource1;
@Autowired
@Qualifier("dataSource2")
private DataSource dataSource2;
@Bean(name = "routingDataSource")
public RoutingDataSource routingDataSource() {
Map<Object, Object> dataSourceMap = new HashMap<>(4);
dataSourceMap.put("dataSource1", dataSource1);
dataSourceMap.put("dataSource2", dataSource2);
RoutingDataSource routingDataSource = new RoutingDataSource();
routingDataSource.setTargetDataSources(dataSourceMap);
// 默认使用um数据源.
routingDataSource.setDefaultTargetDataSource(dataSource1);
return routingDataSource;
}
@Bean(name = "mybatisConfig")
@ConfigurationProperties(prefix = "mybatis.configuration")
public org.apache.ibatis.session.Configuration mybatisConfig() {
return new org.apache.ibatis.session.Configuration();
}
@Bean(name = "sqlSessionFactory")
public SqlSessionFactory SqlSessionFactory(@Qualifier("routingDataSource") DataSource routingDataSource, @Qualifier("mybatisConfig") org.apache.ibatis.session.Configuration configuration)
throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(routingDataSource);
bean.setConfiguration(configuration);
bean.setMapperLocations(
new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/**/*.xml"));
return bean.getObject();
}
}
DataSource 注解,用于标识使用的数据源
package top.cwcenter.dynamic.aop;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义注解,注解在dao接口或方法上,指定使用的数据源.
*
* @Author: Jie.He, hejie@baijiahulian.com
* @Date: 2019/9/29 4:24 下午
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {
String value() default "";
}
HandleDataSourceAspect aop切面,用于实际进行数据源切换操作
package top.cwcenter.dynamic.aop;
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.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import top.cwcenter.dynamic.config.DataSourceContextHolder;
import java.lang.reflect.Method;
/**
* 数据源切面,作用于注解了@DataSource的方法上,取注解的name属性作为数据源.
*
* @Author: C.W
* @Date: 2019/9/29 4:26 下午
*/
@Aspect
@Component
public class HandleDataSourceAspect {
@Pointcut("@annotation(top.cwcenter.dynamic.aop.DataSource)")
public void pointcut() {
}
@Before("pointcut()")
public void beforeExecute(JoinPoint joinPoint) {
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
DataSource annotation = method.getAnnotation(DataSource.class);
if (null == annotation) {
annotation = joinPoint.getTarget().getClass().getAnnotation(DataSource.class);
}
if (null != annotation) {
// 切换数据源
DataSourceContextHolder.switchDataSource(annotation.value());
}
}
@After("pointcut()")
public void afterExecute() {
DataSourceContextHolder.clear();
}
}
PersonPO 实体对象
package top.cwcenter.dynamic.po;
/**
* @Author C.W
* @Date 2021/1/14 下午5:04
* @Description person对象
*/
public class PersonPO {
/**
* id
*/
private Long id;
/**
* 姓名
*/
private String name;
/**
* 年龄
*/
private Integer age;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "PersonPO{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
CoursePO实体对象
package top.cwcenter.dynamic.po;
/**
* @Author C.W
* @Date 2021/1/14 下午5:05
* @Description course对象
*/
public class CoursePO {
/**
* id
*/
private Long id;
/**
* 课程名
*/
private String name;
/**
* 教师名
*/
private String teacherName;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getTeacherName() {
return teacherName;
}
public void setTeacherName(String teacherName) {
this.teacherName = teacherName;
}
@Override
public String toString() {
return "CoursePO{" +
"id=" + id +
", name='" + name + '\'' +
", teacherName='" + teacherName + '\'' +
'}';
}
}
PersonDao 数据库操作层—使用默认数据源
package top.cwcenter.dynamic.dao;
import org.apache.ibatis.annotations.Mapper;
import top.cwcenter.dynamic.po.PersonPO;
import java.util.List;
/**
* @Author C.W
* @Date 2021/1/14 下午5:06
* @Description person操作
*/
@Mapper
public interface PersonDao {
/**
* 插入
*
* @param personPO
* @return
*/
Integer insert(PersonPO personPO);
/**
* 查询所有
*
* @return
*/
List<PersonPO> selectAll();
}
CourseDao 数据库操作层—使用02数据源
package top.cwcenter.dynamic.dao;
import org.apache.ibatis.annotations.Mapper;
import top.cwcenter.dynamic.aop.DataSource;
import top.cwcenter.dynamic.po.CoursePO;
import java.util.List;
/**
* @Author C.W
* @Date 2021/1/14 下午5:06
* @Description course操作
*/
@Mapper
public interface CourseDao {
/**
* 插入
*
* @param coursePO
* @return
*/
@DataSource("dataSource2")
Integer insert(CoursePO coursePO);
/**
* 查询所有
*
* @return
*/
@DataSource("dataSource2")
List<CoursePO> selectAll();
}
- 配置mapper
CourseDao.xml
<?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="top.cwcenter.dynamic.dao.CourseDao">
<insert id="insert" parameterType="top.cwcenter.dynamic.po.CoursePO">
insert into course (name, teacher_name)
values (#{name}, #{teacherName})
</insert>
<select id="selectAll" resultType="top.cwcenter.dynamic.po.CoursePO">
select *
from course
</select>
</mapper>
PersonDao.xml
<?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="top.cwcenter.dynamic.dao.PersonDao">
<insert id="insert" parameterType="top.cwcenter.dynamic.po.PersonPO">
insert into person (name, age)
values (#{name}, #{age})
</insert>
<select id="selectAll" resultType="top.cwcenter.dynamic.po.PersonPO">
select *
from person
</select>
</mapper>
- 测试使用
MybatisTests
package top.cwcenter.dynamic;
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;
import top.cwcenter.dynamic.dao.CourseDao;
import top.cwcenter.dynamic.dao.PersonDao;
import top.cwcenter.dynamic.po.CoursePO;
import top.cwcenter.dynamic.po.PersonPO;
import java.util.List;
/**
* @Author C.W
* @Date 2021/1/14 下午5:11
* @Description mybatis测试
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class MybatisTests {
@Autowired
private PersonDao personDao;
@Autowired
private CourseDao courseDao;
@Test
public void test() {
// 查询两个数据源数据
List<PersonPO> personPOList = personDao.selectAll();
List<CoursePO> coursePOList = courseDao.selectAll();
System.out.println(String.format("数据源1 数据条数:%d, 数据:%s", personPOList.size(), personPOList));
System.out.println(String.format("数据源2 数据条数:%d, 数据:%s", coursePOList.size(), coursePOList));
// 新增数据
PersonPO personPO = new PersonPO();
personPO.setName("小明");
personPO.setAge(25);
personDao.insert(personPO);
CoursePO coursePO = new CoursePO();
coursePO.setName("java程序设计");
coursePO.setTeacherName("王老师");
courseDao.insert(coursePO);
personPOList = personDao.selectAll();
coursePOList = courseDao.selectAll();
System.out.println(String.format("数据源1 数据条数:%d, 数据:%s", personPOList.size(), personPOList));
System.out.println(String.format("数据源2 数据条数:%d, 数据:%s", coursePOList.size(), coursePOList));
}
}
输出结果:
2021-01-14 17:15:22.754 INFO 12674 --- [ main] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} inited
2021-01-14 17:15:24.528 INFO 12674 --- [ main] com.alibaba.druid.pool.DruidDataSource : {dataSource-2} inited
数据源1 数据条数:0, 数据:[]
数据源2 数据条数:0, 数据:[]
数据源1 数据条数:1, 数据:[PersonPO{id=1, name='小明', age=25}]
数据源2 数据条数:1, 数据:[CoursePO{id=1, name='java程序设计', teacherName='王老师'}]
2021-01-14 17:15:25.233 INFO 12674 --- [ Thread-2] com.alibaba.druid.pool.DruidDataSource : {dataSource-2} closed
2021-01-14 17:15:25.234 INFO 12674 --- [ Thread-2] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} closed
demo地址
https://github.com/ChengChengWei/blog-demo/tree/main/dynamic-datasource
小短腿的个人公众号上线啦,后续技术分享将优先在公众号进行哈,大家可以关注公众号,小短腿有什么新的分享会及时同步哈

1544

被折叠的 条评论
为什么被折叠?



