好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

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

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

一、配置动态数据源

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

org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource,代码如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

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

?

1

2

3

4

< dependency >

  < groupId >org.springframework.boot</ groupId >

  < artifactId >spring-boot-starter-aop</ artifactId >

</ dependency >

(二)开始实现

1. DataSourceContextHolder

DataSourceContextHolder 使用 ThreadLocal 存储当前线程指定的数据源的 Key 值,代码如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

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() 方法,代码如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

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 功能,定义一个切面,用于设置当前需要使用的数据源,代码如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

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 配置类,代码如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

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 个数据源,代码如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

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. 添加配置

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

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,如下:

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

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 分布式服务框架

到此这篇关于SpringBoot配置数据库 密码 加密 的实现的文章就介绍到这了,更多相关SpringBoot 数据库密码加密内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!

原文链接:https://blog.csdn.net/Zack_tzh/article/details/112175270

查看更多关于SpringBoot配置数据库密码加密的实现的详细内容...

  阅读:24次