时间:2021-05-20
你在使用 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() 方法,可以实现对多数据源的支持
思路:
接下来的实现方式是 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 */@Slf4jpublic 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@Log4j2public 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 */@Configurationpublic 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. 添加配置
其中分别定义了 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 */@Mapperpublic interface UserMapper { User selectUserOne(@Param("id") Long id); @TargetDataSource("slave") User selectUserTwo(@Param("id") Long id);}上面就如何配置动态数据源的实现方式仅提供一种思路,其中关于多事务方面并没有实现,采用 Spring 提供的事务管理器,如果同一个方法中使用了多个数据源,并不支持多事务的,需要自己去实现(笔者能力有限),可以整合JAT组件,参考:SpringBoot2 整合JTA组件多数据源事务管理
分布式事务解决方案推荐使用 Seata 分布式服务框架
到此这篇关于SpringBoot配置数据库密码加密的实现的文章就介绍到这了,更多相关SpringBoot 数据库密码加密内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!
声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。
一、概述在用户模块,对于用户密码的保护,通常都会进行加密。我们通常对密码进行加密,然后存放在数据库中,在用户进行登录的时候,将其输入的密码进行加密然后与数据库中
用户密码或其它机密数据必须用成熟加密技术加密后再存放到数据库使用明文在数据库中存储用户密码,信用卡号等数据是非常危险的,即使您使用的是非常安全的数据库技术,仍然
起因前端日子写完的Python入库脚本,通过直接读取配置文件的内容(包含了数据库的ip,数据库的用户名,数据库的密码),因为配置文件中的数据库密码是明文显示的,
在之前给大家介绍过SpringbootDruid自定义加密数据库密码的几种方案,感兴趣的朋友可以点击查看下,今天通过本文给大家介绍springboot整合dru
WEB数据库加密。对WEB数据库加密是指对数据库中的数据进行加密处理。目前对WEB数据库加密主要是对数据库中的表、数据、属性和记录日志进行加密处理。通过数据库加