SpringBoot配置数据库密码加密的实现

作者:Zack_tzh 时间:2024-01-26 07:33:01 

你在使用 MyBatis 的过程中,是否有想过多个数据源应该如何配置,如何去实现?出于这个好奇心,我在 Druid Wiki 的数据库多数据源中知晓 Spring 提供了对多数据源的支持,基于 Spring 提供的 AbstractRoutingDataSource,可以自己实现数据源的切换。

一、配置动态数据源

下面就如何配置动态数据源提供一个简单的实现:

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");
 // 确定当前要使用的数据源
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();

// 省略相关代码...
}

 重写 AbstractRoutingDataSource 的 determineCurrentLookupKey() 方法,可以实现对多数据源的支持

思路:

  • 重写其 determineCurrentLookupKey() 方法,支持选择不同的数据源

  • 初始化多个 DataSource 数据源到 AbstractRoutingDataSource 的 resolvedDataSources 属性中

  • 然后通过 Spring AOP, 以自定义注解作为切点,根据不同的数据源的 Key 值,设置当前线程使用的数据源

接下来的实现方式是 Spring Boot 结合 Druid 配置动态数据源

(一)引入依赖

基于 3.继承SpringBoot 中已添加的依赖再添加对AOP支持的依赖,如下:


<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

(二)开始实现

1. DataSourceContextHolder

DataSourceContextHolder 使用 ThreadLocal 存储当前线程指定的数据源的 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<>();

/**
 * 配置的所有数据源的 Key 值
 */
public static Set<Object> ALL_DATASOURCE_KEY = new HashSet<>();

/**
 * 设置当前线程的数据源的 Key
 *
 * @param dataSourceKey 数据源的 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);
 }
}

/**
 * 获取当前线程的数据源的 Key 值
 *
 * @return 数据源的 Key 值
 */
public static String getDataSourceKey() {
 return DATASOURCE_KEY.get();
}

/**
 * 移除当前线程持有的数据源的 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 {

/**
 * 返回当前线程是有的数据源的 Key
 *
 * @return dataSourceKey
 */
@Override
protected Object determineCurrentLookupKey() {
 return DataSourceContextHolder.getDataSourceKey();
}
}

3. DataSourceAspect切面

使用 Spring AOP 功能,定义一个切面,用于设置当前需要使用的数据源,代码如下:


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);
 // 设置要监控的bean的id
 beanNameAutoProxyCreator.setInterceptorNames("druid-stat-interceptor");
 return beanNameAutoProxyCreator;
}
}

5. MultipleDataSourceConfig

MyBatis 的配置类,配置了 2 个数据源,代码如下:


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() {
 // 配置事务管理器
 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 # 初始化时建立物理连接的个数
 min-idle: 20 # 最小连接池数量
 max-active: 20 # 最大连接池数量
 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 # 初始化时建立物理连接的个数
 min-idle: 20 # 最小连接池数量
 max-active: 20 # 最大连接池数量
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 # 分页合理化参数
offset-as-page-num: true # 将 RowBounds 中的 offset 参数当成 pageNum 使用
supportMethodsArguments: true # 支持通过 Mapper 接口参数来传递分页参数

其中分别定义了 master 和 slave 数据源的相关配置

这样一来,在 DataSourceAspect 切面中根据自定义注解,设置 DataSourceContextHolder 当前线程所使用的数据源的 Key 值,MultipleDataSource 动态数据源则会根据该值设置需要使用的数据源,完成了动态数据源的切换

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);
}

 总结

上面就如何配置动态数据源的实现方式仅提供一种思路,其中关于多事务方面并没有实现,采用 Spring 提供的事务管理器,如果同一个方法中使用了多个数据源,并不支持多事务的,需要自己去实现(笔者能力有限),可以整合JAT组件,参考:SpringBoot2 整合JTA组件多数据源事务管理

分布式事务解决方案推荐使用 Seata 分布式服务框架

来源:https://blog.csdn.net/Zack_tzh/article/details/112175270

标签:SpringBoot,数据库,密码,加密
0
投稿

猜你喜欢

  • Mysql my.ini 配置文件详解

    2024-01-28 06:16:57
  • 详解基于webpack&gettext的前端多语言方案

    2024-04-16 09:52:34
  • pandas pd.read_csv()函数中parse_dates()参数的用法说明

    2023-07-22 04:51:26
  • 详解Python如何生成词云的方法

    2022-01-12 17:16:37
  • 淘宝2011新版首页开发实践

    2011-01-20 20:07:00
  • PHP cookie,session的使用与用户自动登录功能实现方法分析

    2023-11-21 15:00:43
  • Python查看Tensor尺寸及查看数据类型的实现

    2023-06-06 15:24:54
  • 举例详解Python中的split()函数的使用方法

    2021-06-23 07:26:49
  • Python线程编程之Thread详解

    2022-04-10 17:37:01
  • C#编程实现连接ACCESS数据库实例详解

    2024-01-19 10:25:15
  • VSCode必装Go语言以下插件的思路详解

    2024-04-30 09:53:42
  • python 集合常用操作汇总

    2023-11-15 08:41:22
  • Python实现密钥密码(加解密)实例详解

    2022-09-10 12:03:37
  • 网页标准化-CSS命名规划整理

    2007-12-10 18:13:00
  • 如何在ADSI中查询用户属性?

    2010-06-17 12:53:00
  • MySQL表的增删改查(CRUD)

    2024-01-12 18:55:34
  • Python干货实战之八音符酱小游戏全过程详解

    2021-08-20 11:21:27
  • Vue3响应式对象Reactive和Ref的用法解读

    2024-05-02 16:34:48
  • AJAX 自学练习 无刷新提交并修改数据库数据并显示

    2024-01-18 20:21:23
  • Yii框架实现乐观锁与悲观锁流程详解

    2023-11-16 13:38:38
  • asp之家 网络编程 m.aspxhome.com