说明:以自定义注解切面方式(方法级),指定调用的数据源。
一、引入 druid 包
<!-- druid数据源 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
二、修改application.yaml 配置文件
# MySQL配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
druid:
db-1:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
url: jdbc:mysql://localhost:3306/db_1?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
removeAbandoned: true
removeAbandonedTimeout: 180
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT1FROMDUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 50
filters: wall
logSlowSql: true
db-2:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
url: jdbc:mysql://localhost:3306/db_2?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf8
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
removeAbandoned: true
removeAbandonedTimeout: 180
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT1FROMDUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 50
filters: wall
logSlowSql: true
jpa:
database: mysql
show-sql: true
# generate-ddl: true
#设置数据库方言 记住必须要使用 MySQL5InnoDBDialect 指定数据库类型对应InnoDB ;如果使用MySQLDialect 则对应的是MyISAM
database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
注意:
1.数据库连接地址配置信息,key为url(默认数据库连接池Hikari的配置为jdbc-url )
2.数据源名称不能首字母大写,不能使用下划线;为了方便此处数据源名称和后边初始化数据源,以及注解中指定数据源,使用相同名字。
三、动态数据源和数据源初始化
多数据源配置原理:
将多个数据源信息,以map的形式保存,在数据库操作时用key获取数据源信息
定义一个类继承 AbstractRoutingDataSource 抽象类,重写 determineCurrentLookupKey 方法,在方法内获取相应的数据源的key信息,获取数据源时,会使用此方法返回的key去map中查找对应的数据源。
package com.ylx.config.datasource;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import java.util.Map;
/**
* 自定义动态数据源
* date:2021-07-13
* author:YCH
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
// 保存数据库连接连接配置信息
private static final ThreadLocal<String> contextHolder = new ThreadLocal();
public DynamicDataSource(DruidDataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(targetDataSources);
super.afterPropertiesSet();
}
@Override
protected Object determineCurrentLookupKey() {
String dataSource = getDataSource();
System.out.println("当前数据源为:" + dataSource);
return dataSource;
}
public static void setDataSource(String dataSource) {
contextHolder.set(dataSource);
}
public static String getDataSource() {
return contextHolder.get();
}
public static void clearDataSource() {
contextHolder.remove();
System.out.println("contextHolder.remove() result :=========>>>" + contextHolder.get());
}
}
配置数据源信息
package com.ylx.config.datasource;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.util.JdbcConstants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
/**
* 多数据源配置
* date:2021-07-07
* author:YCH
*/
@Configuration
@Component
@Slf4j
public class DynamicDataSourceConfig {
@Autowired
Environment env; // 从env中获取配置文件中配置信息
/**
* db-1 数据源配置信息 前缀
*/
private final String dataApiPrefix = "spring.datasource.druid.db-1.";
/**
* db-2 数据源配置信息 前缀
*/
private final String wordDBPrefix = "spring.datasource.druid.db-2.";
/**
* db-1 库数据源
*
* @return
*/
@Bean
@ConfigurationProperties(prefix = "spring.datasource.druid.db-1")
public DruidDataSource dataApiDataSource() {
System.out.println("[================= db-1 build ===============]");
// 手动创建连接池对象,配置连接池信息
DruidDataSource dataSource = new DruidDataSource();
// 数据库类型
dataSource.setDbType(JdbcConstants.MYSQL);
// 连接地址
dataSource.setUrl(env.getProperty(dataApiPrefix + "url"));
// 数据库连接用户名
dataSource.setUsername(env.getProperty(dataApiPrefix + "username"));
// 数据库连接密码
dataSource.setPassword(env.getProperty(dataApiPrefix + "password"));
// 驱动类
dataSource.setDriverClassName(env.getProperty(dataApiPrefix + "driver-class-name"));
// 定义初始连接数
dataSource.setInitialSize(Integer.parseInt(env.getProperty(dataApiPrefix + "initialSize")));
// 最小空闲
dataSource.setMinIdle(Integer.parseInt(env.getProperty(dataApiPrefix + "minIdle")));
// 定义最大连接数
dataSource.setMaxActive(Integer.parseInt(env.getProperty(dataApiPrefix + "maxActive")));
// 获取连接等待超时的时间
dataSource.setMaxWait(Long.parseLong(env.getProperty(dataApiPrefix + "maxWait")));
// 超过时间限制是否回收
dataSource.setRemoveAbandoned(Boolean.parseBoolean(env.getProperty(dataApiPrefix + "removeAbandoned")));
// 超过时间限制多长
dataSource.setRemoveAbandonedTimeout(Integer.parseInt(env.getProperty(dataApiPrefix + "removeAbandonedTimeout")));
// 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
dataSource.setTimeBetweenEvictionRunsMillis(Long.parseLong(env.getProperty(dataApiPrefix + "timeBetweenEvictionRunsMillis")));
// 配置一个连接在池中最小生存的时间,单位是毫秒
dataSource.setMinEvictableIdleTimeMillis(Long.parseLong(env.getProperty(dataApiPrefix + "minEvictableIdleTimeMillis")));
// 用来检测连接是否有效的sql,要求是一个查询语句
dataSource.setValidationQuery(env.getProperty(dataApiPrefix + "validationQuery"));
// 申请连接的时候检测
dataSource.setTestWhileIdle(Boolean.parseBoolean(env.getProperty(dataApiPrefix + "testWhileIdle")));
// 申请连接时执行validationQuery检测连接是否有效,配置为true会降低性能
dataSource.setTestOnBorrow(Boolean.parseBoolean(env.getProperty(dataApiPrefix + "testOnBorrow")));
// 归还连接时执行validationQuery检测连接是否有效,配置为true会降低性能
dataSource.setTestOnReturn(Boolean.parseBoolean(env.getProperty(dataApiPrefix + "testOnReturn")));
// 打开PSCache,并且指定每个连接上PSCache的大小
dataSource.setPoolPreparedStatements(Boolean.parseBoolean(env.getProperty(dataApiPrefix + "poolPreparedStatements")));
dataSource.setMaxPoolPreparedStatementPerConnectionSize(Integer.parseInt(env.getProperty(dataApiPrefix + "maxPoolPreparedStatementPerConnectionSize")));
// 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:
// 监控统计用的filter:stat
// 日志用的filter:log4j
// 防御SQL注入的filter:wall
String filters = env.getProperty(dataApiPrefix + "filters");
try {
dataSource.setFilters(filters);
} catch (SQLException e) {
log.error("扩展插件失败.{}", e.getMessage());
}
return dataSource;
}
/**
* db-2 库数据源
*
* @return
*/
@Bean
@ConfigurationProperties(prefix = "spring.datasource.druid.db-2")
public DruidDataSource wordDbDataSource() {
System.out.println("[================= db-2 built ===============]");
// 手动创建连接池对象,配置连接池信息
DruidDataSource dataSource = new DruidDataSource();
// 数据库类型
dataSource.setDbType(JdbcConstants.MYSQL);
// 连接地址
dataSource.setUrl(env.getProperty(wordDBPrefix + "url"));
// 数据库连接用户名
dataSource.setUsername(env.getProperty(wordDBPrefix + "username"));
// 数据库连接密码
dataSource.setPassword(env.getProperty(wordDBPrefix + "password"));
// 驱动类
dataSource.setDriverClassName(env.getProperty(wordDBPrefix + "driver-class-name"));
// 定义初始连接数
dataSource.setInitialSize(Integer.parseInt(env.getProperty(wordDBPrefix + "initialSize")));
// 最小空闲
dataSource.setMinIdle(Integer.parseInt(env.getProperty(wordDBPrefix + "minIdle")));
// 定义最大连接数
dataSource.setMaxActive(Integer.parseInt(env.getProperty(wordDBPrefix + "maxActive")));
// 获取连接等待超时的时间
dataSource.setMaxWait(Long.parseLong(env.getProperty(wordDBPrefix + "maxWait")));
// 超过时间限制是否回收
dataSource.setRemoveAbandoned(Boolean.parseBoolean(env.getProperty(wordDBPrefix + "removeAbandoned")));
// 超过时间限制多长
dataSource.setRemoveAbandonedTimeout(Integer.parseInt(env.getProperty(wordDBPrefix + "removeAbandonedTimeout")));
// 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
dataSource.setTimeBetweenEvictionRunsMillis(Long.parseLong(env.getProperty(wordDBPrefix + "timeBetweenEvictionRunsMillis")));
// 配置一个连接在池中最小生存的时间,单位是毫秒
dataSource.setMinEvictableIdleTimeMillis(Long.parseLong(env.getProperty(wordDBPrefix + "minEvictableIdleTimeMillis")));
// 用来检测连接是否有效的sql,要求是一个查询语句
dataSource.setValidationQuery(env.getProperty(wordDBPrefix + "validationQuery"));
// 申请连接的时候检测
dataSource.setTestWhileIdle(Boolean.parseBoolean(env.getProperty(wordDBPrefix + "testWhileIdle")));
// 申请连接时执行validationQuery检测连接是否有效,配置为true会降低性能
dataSource.setTestOnBorrow(Boolean.parseBoolean(env.getProperty(wordDBPrefix + "testOnBorrow")));
// 归还连接时执行validationQuery检测连接是否有效,配置为true会降低性能
dataSource.setTestOnReturn(Boolean.parseBoolean(env.getProperty(wordDBPrefix + "testOnReturn")));
// 打开PSCache,并且指定每个连接上PSCache的大小
dataSource.setPoolPreparedStatements(Boolean.parseBoolean(env.getProperty(wordDBPrefix + "poolPreparedStatements")));
dataSource.setMaxPoolPreparedStatementPerConnectionSize(Integer.parseInt(env.getProperty(wordDBPrefix + "maxPoolPreparedStatementPerConnectionSize")));
// 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:
// 监控统计用的filter:stat
// 日志用的filter:log4j
// 防御SQL注入的filter:wall
String filters = env.getProperty(wordDBPrefix + "filters");
try {
dataSource.setFilters(filters);
} catch (SQLException e) {
log.error("扩展插件失败.{}", e.getMessage());
}
return dataSource;
}
@Bean
@Primary
public DynamicDataSource dataSource(DruidDataSource db1, DruidDataSource db2){
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put("db-1",db1);
targetDataSources.put("db-2", db2);
return new DynamicDataSource(dataApiDataSource, targetDataSources);
}
}
四、自定义注解
自定义注解,在方法上使用注解,以切面的形式指定数据源
package com.ylx.config.datasource;
import java.lang.annotation.*;
/**
* 自定义注解,方法上添加 @DataSource(name = "DatasourceName") 指定这个方法内调用的数据源
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataSource {
String name() default "";
}
定义切面
package com.ylx.config.datasource;
import com.ylx.util.ConfigUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.hibernate.engine.spi.SessionImplementor;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.lang.reflect.Method;
/**
* 数据源切面
* date:2021-07-13
* author:YCH
*/
@Aspect
@Order(-1)
@Component
public class DataSourceAspect {
@PersistenceContext
private EntityManager entityManager;
/**
* 指定 持有 @Datasource 注解的触发
*/
@Pointcut("@annotation(com.ylx.config.datasource.DataSource)")
public void dataSourcePointCut() {
}
@Around("dataSourcePointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
DataSource dataSource = method.getAnnotation(DataSource.class);
if(dataSource == null){
// 设置默认数据库
DynamicDataSource.setDataSource("db-1");
}else {
DynamicDataSource.setDataSource(dataSource.name());
}
try {
return point.proceed();
} finally {
// 清空当前数据库连接信息
DynamicDataSource.clearDataSource();
// 使用完之后断开连接,否则会一直使用同一连接(重要)
SessionImplementor sessionImplementor = entityManager.unwrap(SessionImplementor.class);
sessionImplementor.disconnect();
}
}
}
五、使用
本人是在service的实现类中方法是使用注解的。如果service方法中,不止使用一个数据源,则可以在dao层添加注解。
@Service
public class DataServiceImpl implements DataService{
@Autowired
UserRepository userRepository;
@DataSource(name = "db-1")
@Overwrite
public Object getData(){
return userRepository.findAll();
}
}