SpringBoot配置數(shù)據(jù)庫密碼加密的實現(xiàn)
你在使用 MyBatis 的過程中,是否有想過多個數(shù)據(jù)源應(yīng)該如何配置,如何去實現(xiàn)?出于這個好奇心,我在 Druid Wiki 的數(shù)據(jù)庫多數(shù)據(jù)源中知曉 Spring 提供了對多數(shù)據(jù)源的支持,基于 Spring 提供的 AbstractRoutingDataSource,可以自己實現(xiàn)數(shù)據(jù)源的切換。
一、配置動態(tài)數(shù)據(jù)源
下面就如何配置動態(tài)數(shù)據(jù)源提供一個簡單的實現(xiàn):
org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource,代碼如下:
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
@Nullable
private Object defaultTargetDataSource;
@Nullable
private Map<Object, DataSource> resolvedDataSources;
@Nullable
private DataSource resolvedDefaultDataSource;
@Override
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return determineTargetDataSource().getConnection(username, password);
}
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
// 確定當(dāng)前要使用的數(shù)據(jù)源
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
}
return dataSource;
}
/**
* Determine the current lookup key. This will typically be implemented to check a thread-bound transaction context.
* <p>
* Allows for arbitrary keys. The returned key needs to match the stored lookup key type, as resolved by the
* {@link #resolveSpecifiedLookupKey} method.
*/
@Nullable
protected abstract Object determineCurrentLookupKey();
// 省略相關(guān)代碼...
}
重寫 AbstractRoutingDataSource 的 determineCurrentLookupKey() 方法,可以實現(xiàn)對多數(shù)據(jù)源的支持
思路:
- 重寫其 determineCurrentLookupKey() 方法,支持選擇不同的數(shù)據(jù)源
- 初始化多個 DataSource 數(shù)據(jù)源到 AbstractRoutingDataSource 的 resolvedDataSources 屬性中
- 然后通過 Spring AOP, 以自定義注解作為切點,根據(jù)不同的數(shù)據(jù)源的 Key 值,設(shè)置當(dāng)前線程使用的數(shù)據(jù)源
接下來的實現(xiàn)方式是 Spring Boot 結(jié)合 Druid 配置動態(tài)數(shù)據(jù)源
(一)引入依賴
基于 3.繼承SpringBoot 中已添加的依賴再添加對AOP支持的依賴,如下:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
(二)開始實現(xiàn)
1. DataSourceContextHolder
DataSourceContextHolder 使用 ThreadLocal 存儲當(dāng)前線程指定的數(shù)據(jù)源的 Key 值,代碼如下:
package cn.tzh.mybatis.config;
import lombok.extern.slf4j.Slf4j;
import java.util.HashSet;
import java.util.Set;
/**
* @author tzh
* @date 2021/1/4 11:42
*/
@Slf4j
public class DataSourceContextHolder {
/**
* 線程本地變量
*/
private static final ThreadLocal<String> DATASOURCE_KEY = new ThreadLocal<>();
/**
* 配置的所有數(shù)據(jù)源的 Key 值
*/
public static Set<Object> ALL_DATASOURCE_KEY = new HashSet<>();
/**
* 設(shè)置當(dāng)前線程的數(shù)據(jù)源的 Key
*
* @param dataSourceKey 數(shù)據(jù)源的 Key 值
*/
public static void setDataSourceKey(String dataSourceKey) {
if (ALL_DATASOURCE_KEY.contains(dataSourceKey)) {
DATASOURCE_KEY.set(dataSourceKey);
} else {
log.warn("the datasource [{}] does not exist", dataSourceKey);
}
}
/**
* 獲取當(dāng)前線程的數(shù)據(jù)源的 Key 值
*
* @return 數(shù)據(jù)源的 Key 值
*/
public static String getDataSourceKey() {
return DATASOURCE_KEY.get();
}
/**
* 移除當(dāng)前線程持有的數(shù)據(jù)源的 Key 值
*/
public static void clear() {
DATASOURCE_KEY.remove();
}
}
2. MultipleDataSource
重寫其 AbstractRoutingDataSource 的 determineCurrentLookupKey() 方法,代碼如下:
package cn.tzh.mybatis.config;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* @author tzh
* @date 2021/1/4 11:44
*/
public class MultipleDataSource extends AbstractRoutingDataSource {
/**
* 返回當(dāng)前線程是有的數(shù)據(jù)源的 Key
*
* @return dataSourceKey
*/
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSourceKey();
}
}
3. DataSourceAspect切面
使用 Spring AOP 功能,定義一個切面,用于設(shè)置當(dāng)前需要使用的數(shù)據(jù)源,代碼如下:
package cn.tzh.mybatis.config;
import lombok.extern.log4j.Log4j2;
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.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* @author tzh
* @date 2021/1/4 11:46
*/
@Aspect
@Component
@Log4j2
public class DataSourceAspect {
@Before("@annotation(cn.tzh.mybatis.config.TargetDataSource)")
public void before(JoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
if (method.isAnnotationPresent(TargetDataSource.class)) {
TargetDataSource targetDataSource = method.getAnnotation(TargetDataSource.class);
DataSourceContextHolder.setDataSourceKey(targetDataSource.value());
log.info("set the datasource of the current thread to [{}]", targetDataSource.value());
} else if (joinPoint.getTarget().getClass().isAnnotationPresent(TargetDataSource.class)) {
TargetDataSource targetDataSource = joinPoint.getTarget().getClass().getAnnotation(TargetDataSource.class);
DataSourceContextHolder.setDataSourceKey(targetDataSource.value());
log.info("set the datasource of the current thread to [{}]", targetDataSource.value());
}
}
@After("@annotation(cn.tzh.mybatis.config.TargetDataSource)")
public void after() {
DataSourceContextHolder.clear();
log.info("clear the datasource of the current thread");
}
}
4. DruidConfig
Druid 配置類,代碼如下:
package cn.tzh.mybatis.config;
import com.alibaba.druid.support.spring.stat.DruidStatInterceptor;
import org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author tzh
* @date 2021/1/4 11:49
*/
@Configuration
public class DruidConfig {
@Bean(value = "druid-stat-interceptor")
public DruidStatInterceptor druidStatInterceptor() {
return new DruidStatInterceptor();
}
@Bean
public BeanNameAutoProxyCreator beanNameAutoProxyCreator() {
BeanNameAutoProxyCreator beanNameAutoProxyCreator = new BeanNameAutoProxyCreator();
beanNameAutoProxyCreator.setProxyTargetClass(true);
// 設(shè)置要監(jiān)控的bean的id
beanNameAutoProxyCreator.setInterceptorNames("druid-stat-interceptor");
return beanNameAutoProxyCreator;
}
}
5. MultipleDataSourceConfig
MyBatis 的配置類,配置了 2 個數(shù)據(jù)源,代碼如下:
package cn.tzh.mybatis.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.apache.ibatis.mapping.DatabaseIdProvider;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.TypeHandler;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer;
import org.mybatis.spring.boot.autoconfigure.MybatisProperties;
import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ResourceLoader;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import javax.sql.DataSource;
import java.beans.FeatureDescriptor;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* @author tzh
* @projectName code-demo
* @title MultipleDataSourceConfig
* @description
* @date 2021/1/4 13:43
*/
@Configuration
@EnableConfigurationProperties({MybatisProperties.class})
public class MultipleDataSourceConfig {
private final MybatisProperties properties;
private final Interceptor[] interceptors;
private final TypeHandler[] typeHandlers;
private final LanguageDriver[] languageDrivers;
private final ResourceLoader resourceLoader;
private final DatabaseIdProvider databaseIdProvider;
private final List<ConfigurationCustomizer> configurationCustomizers;
public MultipleDataSourceConfig(MybatisProperties properties, ObjectProvider<Interceptor[]> interceptorsProvider, ObjectProvider<TypeHandler[]> typeHandlersProvider, ObjectProvider<LanguageDriver[]> languageDriversProvider, ResourceLoader resourceLoader, ObjectProvider<DatabaseIdProvider> databaseIdProvider, ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) {
this.properties = properties;
this.interceptors = (Interceptor[]) interceptorsProvider.getIfAvailable();
this.typeHandlers = (TypeHandler[]) typeHandlersProvider.getIfAvailable();
this.languageDrivers = (LanguageDriver[]) languageDriversProvider.getIfAvailable();
this.resourceLoader = resourceLoader;
this.databaseIdProvider = (DatabaseIdProvider) databaseIdProvider.getIfAvailable();
this.configurationCustomizers = (List) configurationCustomizersProvider.getIfAvailable();
}
@Bean(name = "master", initMethod = "init", destroyMethod = "close")
@ConfigurationProperties(prefix = "spring.datasource.druid.master")
public DruidDataSource master() {
return DruidDataSourceBuilder.create().build();
}
@Bean(name = "slave", initMethod = "init", destroyMethod = "close")
@ConfigurationProperties(prefix = "spring.datasource.druid.slave")
public DruidDataSource slave() {
return DruidDataSourceBuilder.create().build();
}
@Bean(name = "dynamicDataSource")
public DataSource dynamicDataSource() {
MultipleDataSource dynamicRoutingDataSource = new MultipleDataSource();
Map<Object, Object> dataSources = new HashMap<>();
dataSources.put("master", master());
dataSources.put("slave", slave());
dynamicRoutingDataSource.setDefaultTargetDataSource(master());
dynamicRoutingDataSource.setTargetDataSources(dataSources);
DataSourceContextHolder.ALL_DATASOURCE_KEY.addAll(dataSources.keySet());
return dynamicRoutingDataSource;
}
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dynamicDataSource());
factory.setVfs(SpringBootVFS.class);
if (StringUtils.hasText(this.properties.getConfigLocation())) {
factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
}
this.applyConfiguration(factory);
if (this.properties.getConfigurationProperties() != null) {
factory.setConfigurationProperties(this.properties.getConfigurationProperties());
}
if (!ObjectUtils.isEmpty(this.interceptors)) {
factory.setPlugins(this.interceptors);
}
if (this.databaseIdProvider != null) {
factory.setDatabaseIdProvider(this.databaseIdProvider);
}
if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
}
if (this.properties.getTypeAliasesSuperType() != null) {
factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());
}
if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
}
if (!ObjectUtils.isEmpty(this.typeHandlers)) {
factory.setTypeHandlers(this.typeHandlers);
}
if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
factory.setMapperLocations(this.properties.resolveMapperLocations());
}
Set<String> factoryPropertyNames = (Set) Stream.of((new BeanWrapperImpl(SqlSessionFactoryBean.class)).getPropertyDescriptors()).map(FeatureDescriptor::getName).collect(Collectors.toSet());
Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
if (factoryPropertyNames.contains("scriptingLanguageDrivers") && !ObjectUtils.isEmpty(this.languageDrivers)) {
factory.setScriptingLanguageDrivers(this.languageDrivers);
if (defaultLanguageDriver == null && this.languageDrivers.length == 1) {
defaultLanguageDriver = this.languageDrivers[0].getClass();
}
}
if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) {
factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver);
}
return factory.getObject();
}
private void applyConfiguration(SqlSessionFactoryBean factory) {
org.apache.ibatis.session.Configuration configuration = this.properties.getConfiguration();
if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
configuration = new org.apache.ibatis.session.Configuration();
}
if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
Iterator var3 = this.configurationCustomizers.iterator();
while (var3.hasNext()) {
ConfigurationCustomizer customizer = (ConfigurationCustomizer) var3.next();
customizer.customize(configuration);
}
}
factory.setConfiguration(configuration);
}
@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
ExecutorType executorType = this.properties.getExecutorType();
return executorType != null ? new SqlSessionTemplate(sqlSessionFactory, executorType) : new SqlSessionTemplate(sqlSessionFactory);
}
@Bean
public PlatformTransactionManager masterTransactionManager() {
// 配置事務(wù)管理器
return new DataSourceTransactionManager(dynamicDataSource());
}
}
6. 添加配置
server: port: 9092 servlet: context-path: /mybatis-springboot-demo tomcat: accept-count: 200 min-spare-threads: 200 spring: application: name: mybatis-springboot-demo profiles: active: test servlet: multipart: max-file-size: 100MB max-request-size: 100MB datasource: type: com.alibaba.druid.pool.DruidDataSource druid: master: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/mybatis-demo username: root password: root initial-size: 5 # 初始化時建立物理連接的個數(shù) min-idle: 20 # 最小連接池數(shù)量 max-active: 20 # 最大連接池數(shù)量 slave: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/mybatis-demo1 username: root password: root initial-size: 5 # 初始化時建立物理連接的個數(shù) min-idle: 20 # 最小連接池數(shù)量 max-active: 20 # 最大連接池數(shù)量 mybatis: type-aliases-package: cn.tzh.mybatis.entity mapper-locations: classpath:cn/tzh/mybatis/mapper/*.xml config-location: classpath:mybatis-config.xml pagehelper: helper-dialect: mysql reasonable: true # 分頁合理化參數(shù) offset-as-page-num: true # 將 RowBounds 中的 offset 參數(shù)當(dāng)成 pageNum 使用 supportMethodsArguments: true # 支持通過 Mapper 接口參數(shù)來傳遞分頁參數(shù)
其中分別定義了 master 和 slave 數(shù)據(jù)源的相關(guān)配置
這樣一來,在 DataSourceAspect 切面中根據(jù)自定義注解,設(shè)置 DataSourceContextHolder 當(dāng)前線程所使用的數(shù)據(jù)源的 Key 值,MultipleDataSource 動態(tài)數(shù)據(jù)源則會根據(jù)該值設(shè)置需要使用的數(shù)據(jù)源,完成了動態(tài)數(shù)據(jù)源的切換
7. 使用示例
在 Mapper 接口上面添加自定義注解 @TargetDataSource,如下:
package cn.tzh.mybatis.mapper;
import cn.tzh.mybatis.config.TargetDataSource;
import cn.tzh.mybatis.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
* @author tzh
* @date 2020/12/28 14:29
*/
@Mapper
public interface UserMapper {
User selectUserOne(@Param("id") Long id);
@TargetDataSource("slave")
User selectUserTwo(@Param("id") Long id);
}
總結(jié)
上面就如何配置動態(tài)數(shù)據(jù)源的實現(xiàn)方式僅提供一種思路,其中關(guān)于多事務(wù)方面并沒有實現(xiàn),采用 Spring 提供的事務(wù)管理器,如果同一個方法中使用了多個數(shù)據(jù)源,并不支持多事務(wù)的,需要自己去實現(xiàn)(筆者能力有限),可以整合JAT組件,參考:SpringBoot2 整合JTA組件多數(shù)據(jù)源事務(wù)管理
分布式事務(wù)解決方案推薦使用 Seata 分布式服務(wù)框架
到此這篇關(guān)于SpringBoot配置數(shù)據(jù)庫密碼加密的實現(xiàn)的文章就介紹到這了,更多相關(guān)SpringBoot 數(shù)據(jù)庫密碼加密內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java基于遞歸和循環(huán)兩種方式實現(xiàn)未知維度集合的笛卡爾積算法示例
這篇文章主要介紹了Java基于遞歸和循環(huán)兩種方式實現(xiàn)未知維度集合的笛卡爾積算法,結(jié)合實例形式分析了Java使用遞歸與循環(huán)兩種方式實現(xiàn)未知維度集合的笛卡爾積相關(guān)概念、原理與操作技巧,需要的朋友可以參考下2017-12-12
Spring Boot 中嵌入式 Servlet 容器自動配置原理解析
這篇文章主要介紹了Spring Boot 中嵌入式 Servlet 容器自動配置原理解析,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11
Spring Boot中使用Spring-data-jpa的配置方法詳解
今天小編就為大家分享一篇關(guān)于Spring Boot中使用Spring-data-jpa的配置方法詳解,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-03-03
java?SpringBoot?分布式事務(wù)的解決方案(JTA+Atomic+多數(shù)據(jù)源)
這篇文章主要介紹了java?SpringBoot?分布式事務(wù)的解決方案(JTA+Atomic+多數(shù)據(jù)源),文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價值,感興趣的小伙伴可以參考一下2022-08-08

