druid mysql escape_springboot 动态数据源(Mybatis+Druid)

Spring多数据源实现的方式大概有2中,一种是新建多个MapperScan扫描不同包,另外一种则是通过继承AbstractRoutingDataSource实现动态路由。今天作者主要基于后者做的实现,且方式1的实现比较简单这里不做过多探讨。

实现方式

方式1的实现(核心代码):

@Configuration

@MapperScan(basePackages = "com.goofly.test1", sqlSessionTemplateRef = "test1SqlSessionTemplate")

public class DataSource1Config1 {

@Bean(name = "dataSource1")

@ConfigurationProperties(prefix = "spring.datasource.test1")

@Primary

public DataSource testDataSource() {

return DataSourceBuilder.create().build();

}

// .....略

}

@Configuration

@MapperScan(basePackages = "com.goofly.test2", sqlSessionTemplateRef = "test1SqlSessionTemplate")

public class DataSourceConfig2 {

@Bean(name = "dataSource2")

@ConfigurationProperties(prefix = "spring.datasource.test2")

@Primary

public DataSource testDataSource() {

return DataSourceBuilder.create().build();

}

// .....略

}

方式2的实现(核心代码):

public class DynamicRoutingDataSource extends AbstractRoutingDataSource {

private static final Logger log = Logger.getLogger(DynamicRoutingDataSource.class);

@Override

protected Object determineCurrentLookupKey() {

//从ThreadLocal中取值

return DynamicDataSourceContextHolder.get();

}

}

​ 第1种方式虽然实现比较加单,劣势就是不同数据源的mapper文件不能在同一包名,就显得不太灵活了。所以为了更加灵活的作为一个组件的存在,作者采用的第二种方式实现。

设计思路

c6ca0fe192b3ac4812222d277863f00a.png

当请求经过被注解修饰的类后,此时会进入到切面逻辑中。

切面逻辑会获取注解中设置的key值,然后将该值存入到ThreadLocal中

执行完切面逻辑后,会执行AbstractRoutingDataSource.determineCurrentLookupKey()方法,然后从ThreadLocal中获取之前设置的key值,然后将该值返回。

由于AbstractRoutingDataSource的targetDataSources是一个map,保存了数据源key和数据源的对应关系,所以能够顺利的找到该对应的数据源。

源码解读

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

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {

private Map targetDataSources;

private Object defaultTargetDataSource;

private boolean lenientFallback = true;

private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();

private Map resolvedDataSources;

private DataSource resolvedDefaultDataSource;

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.

*/

protected abstract Object determineCurrentLookupKey();

//........略

targetDataSources是一个map结构,保存了key与数据源的对应关系;

dataSourceLookup是一个DataSourceLookup类型,默认实现是JndiDataSourceLookup。点开该类源码会发现,它实现了通过key获取DataSource的逻辑。当然,这里可以通过setDataSourceLookup()来改变其属性,因为关于此处有一个坑,后面会讲到。

public class JndiDataSourceLookup extends JndiLocatorSupport implements DataSourceLookup {

public JndiDataSourceLookup() {

setResourceRef(true);

}

@Override

public DataSource getDataSource(String dataSourceName) throws DataSourceLookupFailureException {

try {

return lookup(dataSourceName, DataSource.class);

}

catch (NamingException ex) {

throw new DataSourceLookupFailureException(

"Failed to look up JNDI DataSource with name '" + dataSourceName + "'", ex);

}

}

}

组件使用

多数据源

# db1

spring.datasource.master.url = jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false

spring.datasource.master.username = root

spring.datasource.master.password = 123456

spring.datasource.master.driverClassName = com.mysql.jdbc.Driver

spring.datasource.master.validationQuery = true

spring.datasource.master.testOnBorrow = true

## db2

spring.datasource.slave.url = jdbc:mysql://127.0.0.1:3306/test1?useUnicode=true&characterEncoding=utf8&useSSL=false

spring.datasource.slave.username = root

spring.datasource.slave.password = 123456

spring.datasource.slave.driverClassName = com.mysql.jdbc.Driver

spring.datasource.slave.validationQuery = true

spring.datasource.slave.testOnBorrow = true

#主数据源名称

spring.maindb=master

#mapperper包路径

mapper.basePackages =com.btps.xli.multidb.demo.mapper

单数据源

为了让使用者能够用最小的改动实现最好的效果,作者对单数据源的多种配置做了兼容。

示例配置1(配置数据源名称):

spring.datasource.master.url = jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false

spring.datasource.master.username = root

spring.datasource.master.password = 123456

spring.datasource.master.driverClassName = com.mysql.jdbc.Driver

spring.datasource.master.validationQuery = true

spring.datasource.master.testOnBorrow = true

# mapper包路径

mapper.basePackages = com.goofly.xli.multidb.demo.mapper

# 主数据源名称

spring.maindb=master

示例配置2(不配置数据源名称):

spring.datasource.url = jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false

spring.datasource.username = root

spring.datasource.password = 123456

spring.datasource.driverClassName = com.mysql.jdbc.Driver

spring.datasource.validationQuery = true

spring.datasource.testOnBorrow = true

# mapper包路径

mapper.basePackages = com.goofly.xli.multidb.demo.mapper

踩坑之路

多数据源的循环依赖

Description:

The dependencies of some of the beans in the application context form a cycle:

happinessController (field private com.db.service.HappinessService com.db.controller.HappinessController.happinessService)

happinessServiceImpl (field private com.db.mapper.MasterDao com.db.service.HappinessServiceImpl.masterDao)

masterDao defined in file [E:\GitRepository\framework-gray\test-db\target\classes\com\db\mapper\MasterDao.class]

sqlSessionFactory defined in class path resource [com/goofly/xli/datasource/core/DynamicDataSourceConfiguration.class]

┌─────┐

| dynamicDataSource defined in class path resource [com/goofly/xli/datasource/core/DynamicDataSourceConfiguration.class]

↑ ↓

| firstDataSource defined in class path resource [com/goofly/xli/datasource/core/DynamicDataSourceConfiguration.class]

↑ ↓

| dataSourceInitializer

解决方案:

在Spring boot启动的时候排除DataSourceAutoConfiguration即可。如下:

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})

public class DBMain {

public static void main(String[] args) {

SpringApplication.run(DBMain.class, args);

}

}

​ 但是作者在创建多数据源的时候由于并未创建多个DataSource的Bean,而是只创建了一个即需要做动态数据源的那个Bean。 其他的DataSource则直接创建实例然后存放在Map里面,然后再设置到DynamicRoutingDataSource#setTargetDataSources即可。

因此这种方式也不会出现循环依赖的问题!

动态刷新数据源

​ 笔者在设计之初是想构建一个动态刷新数据源的方案,所以利用了SpringCloud的@RefreshScope去标注数据源,然后利用RefreshScope#refresh实现刷新。但是在实验的时候发现由Druid创建的数据源会因此而关闭,由Spring的DataSourceBuilder创建的数据源则不会发生任何变化。

​ 最后关于此也没能找到解决方案。同时思考,如果只能的可以实现动态刷新的话,那么数据源的原有连接会因为刷新而中断吗还是会有其他处理?

多数据源事务

​ 有这么一种特殊情况,一个事务中调用了两个不同数据源,这个时候动态切换数据源会因此而失效。

翻阅了很多文章,大概找了2中解决方案,一种是Atomikos进行事务管理,但是貌似性能并不是很理想。

另外一种则是通过优先级控制,切面的的优先级必须要大于数据源的优先级,用注解@Order控制。

此处留一个坑!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Spring Boot项目中使用MyBatis Plus和Druid数据的步骤如下: 1. 添加依赖 在`pom.xml`文件中添加以下依赖: ```xml <!-- MyBatis Plus --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.3.1</version> </dependency> <!-- Druid --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.2.6</version> </dependency> ``` 2. 配置Druid数据 在`application.yml`中添加Druid数据的配置: ```yaml spring: datasource: # 主数据 druid: url: jdbc:mysql://localhost:3306/main_db?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8 username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver # Druid配置 initialSize: 5 minIdle: 5 maxActive: 20 testOnBorrow: false testOnReturn: false testWhileIdle: true timeBetweenEvictionRunsMillis: 60000 validationQuery: SELECT 1 FROM DUAL # 从数据 druid2: url: jdbc:mysql://localhost:3306/sub_db?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8 username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver # Druid配置 initialSize: 5 minIdle: 5 maxActive: 20 testOnBorrow: false testOnReturn: false testWhileIdle: true timeBetweenEvictionRunsMillis: 60000 validationQuery: SELECT 1 FROM DUAL ``` 3. 配置MyBatis Plus 在`application.yml`中添加MyBatis Plus的配置: ```yaml mybatis-plus: # 主数据配置 mapper-locations: classpath:mapper/main/*.xml type-aliases-package: com.example.main.entity global-config: db-config: id-type: auto field-strategy: not_empty logic-delete-value: 1 logic-not-delete-value: 0 configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 从数据配置 multi-datasource: main: mapper-locations: classpath:mapper/main/*.xml type-aliases-package: com.example.main.entity sub: mapper-locations: classpath:mapper/sub/*.xml type-aliases-package: com.example.sub.entity ``` 4. 配置数据路由 在`com.example.config`包下创建`DynamicDataSourceConfig`类,用于配置数据路由: ```java @Configuration public class DynamicDataSourceConfig { @Bean @ConfigurationProperties("spring.datasource.druid") public DataSource mainDataSource() { return DruidDataSourceBuilder.create().build(); } @Bean @ConfigurationProperties("spring.datasource.druid2") public DataSource subDataSource() { return DruidDataSourceBuilder.create().build(); } @Bean public DataSource dynamicDataSource() { DynamicDataSource dynamicDataSource = new DynamicDataSource(); Map<Object, Object> dataSourceMap = new HashMap<>(2); dataSourceMap.put("main", mainDataSource()); dataSourceMap.put("sub", subDataSource()); // 将主数据作为默认数据 dynamicDataSource.setDefaultTargetDataSource(mainDataSource()); dynamicDataSource.setTargetDataSources(dataSourceMap); return dynamicDataSource; } @Bean public SqlSessionFactory sqlSessionFactory() throws Exception { MybatisSqlSessionFactoryBean sqlSessionFactoryBean = new MybatisSqlSessionFactoryBean(); sqlSessionFactoryBean.setDataSource(dynamicDataSource()); sqlSessionFactoryBean.setTypeAliasesPackage("com.example.main.entity"); sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/main/*.xml")); return sqlSessionFactoryBean.getObject(); } @Bean public SqlSessionTemplate sqlSessionTemplate() throws Exception { return new SqlSessionTemplate(sqlSessionFactory()); } } ``` 5. 配置数据切换 在`com.example.config`包下创建`DynamicDataSource`类,用于实现数据切换: ```java public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DataSourceContextHolder.getDataSource(); } } ``` 在`com.example.config`包下创建`DataSourceContextHolder`类,用于存储当前数据: ```java public class DataSourceContextHolder { private static final ThreadLocal<String> DATASOURCE_CONTEXT_HOLDER = new ThreadLocal<>(); public static void setDataSource(String dataSource) { DATASOURCE_CONTEXT_HOLDER.set(dataSource); } public static String getDataSource() { return DATASOURCE_CONTEXT_HOLDER.get(); } public static void clearDataSource() { DATASOURCE_CONTEXT_HOLDER.remove(); } } ``` 在`com.example.aop`包下创建`DataSourceAspect`类,用于切换数据: ```java @Aspect @Component public class DataSourceAspect { @Pointcut("@annotation(com.example.annotation.DataSource)") public void dataSourcePointCut() { } @Before("dataSourcePointCut()") public void before(JoinPoint joinPoint) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); DataSource dataSource = signature.getMethod().getAnnotation(DataSource.class); if (dataSource != null) { String value = dataSource.value(); DataSourceContextHolder.setDataSource(value); } } @After("dataSourcePointCut()") public void after(JoinPoint joinPoint) { DataSourceContextHolder.clearDataSource(); } } ``` 6. 使用多数据 在需要使用从数据的方法上加上`@DataSource("sub")`注解,如: ```java @Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override public List<User> listUsers() { DataSourceContextHolder.setDataSource("sub"); List<User> users = userMapper.selectList(null); DataSourceContextHolder.clearDataSource(); return users; } } ``` 这样就完成了Spring Boot项目中使用MyBatis Plus和Druid数据的配置。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值