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

网友投稿 345 2022-11-01


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

你在使用 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 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.

*

* 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支持的依赖,如下:

org.springframework.boot

spring-boot-starter-aop

(二)开始实现

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 DATASOURCE_KEY = new ThreadLocal<>();

/**

* 配置的所有数据源的 Key 值

*/

public static Set 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"kIxZiec);

}

}

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 configurationCustomizers;

public MultipleDataSourceConfig(MybatisProperties properties, ObjectProvider interceptorsProvider, ObjectProvider typeHandlersProvider, ObjectProvider languageDriversProvider, ResourceLoader resourceLoader, ObjectProvider databaseIdProvider, ObjectProvider> 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 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.apphttp://lyConfiguration(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 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 分布式服务框架


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:【复习笔记】操作系统之进程调度
下一篇:C++设计模式总结
相关文章

 发表评论

暂时没有评论,来抢沙发吧~