编辑推荐: |
本文介绍了SpringBoot多数据源以及事务处理相关知识。希望对您的学习有所帮助。
本文来自于博客园,由火龙果软件Linda编辑、推荐。 |
|
背景
在高并发的项目中,单数据库已无法承载大数据量的访问,因此需要使用多个数据库进行对数据的读写分离,此外就是在微服化的今天,我们在项目中可能采用各种不同存储,因此也需要连接不同的数据库,居于这样的背景,这里简单分享实现的思路以及实现方案。
如何实现
多数据源实现思路有两种,一种是通过配置多个SqlSessionFactory实现多数据源; 另外一种是通过Spring提供的AbstractRoutingDataSource抽象了一个DynamicDataSource实现动态切换数据源;
实现方案
准备
采用Spring Boot2.7.8框架,数据库Mysql,ORM框架采用Mybatis,整个Maven依赖如下:
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<spring-boot.version>2.7.8</spring-boot.version>
<mysql-connector-java.version>5.1.46</mysql-connector- java.version>
<mybatis-spring-boot-starter.version>2.0.0</mybatis- spring-boot-starter.version>
<mybatis.version>3.5.1</mybatis.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-connector-java.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis-spring-boot-starter.version}</version>
</dependency>
</dependencies>
</dependencyManagement> |
指定数据源操作指定目录XML文件
该种方式需要操作的数据库的Mapper层和Dao层分别建立一个文件夹,分包放置,整体项目结构如下图:
Maven依赖如下:
<dependencies>
<dependency> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency> <dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>4.0.3</version> </dependency>
<dependency> <groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency> <dependency>
<groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId>
</dependency> <dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</dependency> <dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency> <dependency>
<groupId>junit</groupId> <artifactId>junit</artifactId>
<scope>test</scope> </dependency>
</dependencies> |
Yaml文件
spring:
datasource:
user:
jdbc-url: jdbc:mysql://127.0.0.1:3306/study_user?useSSL=false& useUnicode=true&characterEncoding=UTF-8
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
type: com.zaxxer.hikari.HikariDataSource
#hikari连接池配置
hikari:
#pool name
pool-name: user
#最小空闲连接数
minimum-idle: 5
#最大连接池
maximum-pool-size: 20
#链接超时时间 3秒
connection-timeout: 3000
# 连接测试query
connection-test-query: SELECT 1
soul:
jdbc-url: jdbc:mysql://127.0.0.1:3306/soul?useSSL=false& useUnicode=true&characterEncoding=UTF-8
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
type: com.zaxxer.hikari.HikariDataSource
#hikari连接池配置
hikari:
#pool name
pool-name: soul
#最小空闲连接数
minimum-idle: 5
#最大连接池
maximum-pool-size: 20
#链接超时时间 3秒
connection-timeout: 3000
# 连接测试query
connection-test-query: SELECT 1 |
不同库的Mapper指定不同的SqlSessionFactory
针对不同的库分别放置对用不同的SqlSessionFactory
@Configuration
@MapperScan(basePackages = "org.datasource. demo1.usermapper",
sqlSessionFactoryRef = "userSqlSessionFactory")
public class UserDataSourceConfiguration {
public static final String MAPPER_LOCATION
= "classpath:usermapper/*.xml";
@Primary
@Bean("userDataSource")
@ConfigurationProperties(prefix = "spring.datasource.user")
public DataSource userDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "userTransactionManager")
@Primary
public PlatformTransactionManager userTransactionManager (@Qualifier("userDataSource")
DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Primary
@Bean(name = "userSqlSessionFactory")
public SqlSessionFactory userSqlSessionFactory(@Qualifier ("userDataSource")
DataSource dataSource) throws Exception {
final SqlSessionFactoryBean sessionFactoryBean
= new SqlSessionFactoryBean();
sessionFactoryBean.setDataSource(dataSource);
sessionFactoryBean.setMapperLocations(new PathMatching ResourcePatternResolver().getResources (UserDataSourceConfiguration.MAPPER_LOCATION));
return sessionFactoryBean.getObject();
}
}
@Configuration
@MapperScan(basePackages = "org.datasource.demo1.soulmapper",
sqlSessionFactoryRef = "soulSqlSessionFactory")
public class SoulDataSourceConfiguration {
public static final String MAPPER_LOCATION
= "classpath:soulmapper/*.xml";
@Bean("soulDataSource")
@ConfigurationProperties(prefix = "spring.datasource.soul")
public DataSource soulDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "soulTransactionManager")
public PlatformTransactionManager soulTransaction Manager(@Qualifier("soulDataSource")
DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "soulSqlSessionFactory")
public SqlSessionFactory soulSqlSessionFactory (@Qualifier("soulDataSource")
DataSource dataSource) throws Exception {
final SqlSessionFactoryBean sessionFactoryBean
= new SqlSessionFactoryBean();
sessionFactoryBean.setDataSource(dataSource);
sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources (SoulDataSourceConfiguration.MAPPER_LOCATION));
return sessionFactoryBean.getObject();
}
} |
使用
@Service
public class AppAuthService {
@Autowired
private AppAuthMapper appAuthMapper;
@Transactional(rollbackFor = Exception.class)
public int getCount() {
int a = appAuthMapper.listCount();
int b = 1 / 0;
return a;
}
}
@SpringBootTest
@RunWith(SpringRunner.class)
public class TestDataSource {
@Autowired
private AppAuthService appAuthService;
@Autowired
private SysUserService sysUserService;
@Test
public void test_dataSource1(){
int b=sysUserService.getCount();
int a=appAuthService.getCount();
}
} |
总结
此种方式使用起来分层明确,不存在任何冗余代码,不足地方就是每个库都需要对应一个配置类,该配置类中实现方式都基本类似,该种解决方案每个配置类中都存在事务管理器,因此不需要单独再去额外的关注。
AOP+自定义注解
关于采用Spring AOP方式实现原理就是把多个数据源存储在一个 Map中,当需要使用某个数据源时,从
Map中获取此数据源进行处理。
AbstractRoutingDataSource
在Spring中提供了AbstractRoutingDataSource来实现此功能,继承AbstractRoutingDataSource类并覆写其determineCurrentLookupKey()方法就可以完成数据源切换,该方法只需要返回数据源key即可,也就是存放数据源的Map的key,接下来我们来看一下AbstractRoutingDataSource整体的继承结构,看他是如何做到的。
在整体的继承结构上我们会发现AbstractRoutingDataSource最终是继承于DataSource,因此当我们继承AbstractRoutingDataSource是我们自身也是一个数据源,对于数据源必然有连接数据库的动作,如下代码:
public Connection
getConnection() throws SQLException {
return this.determineTargetDataSource().getConnection();
}
public Connection getConnection(String username,
String password) throws SQLException {
return this.determineTargetDataSource ().getConnection(username,
password);
} |
只是AbstractRoutingDataSource的getConnection()方法里实际是调用determineTargetDataSource()返回的数据源的getConnection()方法。
protected DataSource
determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource
router not initialized");
Object lookupKey = this.determineCurrentLookupKey();
DataSource dataSource = (DataSource)this.reso lvedDataSources.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 + "]");
} else {
return dataSource;
}
} |
该方法通过determineCurrentLookupKey()方法获取一个key,通过key从resolvedDataSources中获取数据源DataSource对象。determineCurrentLookupKey()是个抽象方法,需要继承AbstractRoutingDataSource的类实现;而resolvedDataSources是一个Map<Object,
DataSource>,里面应该保存当前所有可切换的数据源,接下来我们来聊聊实现,我们首先来看下目录,与分包的不同的是将所有的Mapper文件都放到一起,其他Maven依赖以及配置文件都保持一致。
DataSourceType
该枚举用来存放数据源的名称,
public enum DataSourceType
{
USERDATASOURCE("userDataSource"),
SOULDATASOURCE("soulDataSource");
private String name;
DataSourceType(String name) {
this.name=name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
} |
DynamicDataSourceConfiguration
通过读取配置文件中的数据源配置信息,创建数据连接,将多个数据源放入Map中,注入到容器中:
@Configuration
@MapperScan(basePackages = "org.datasource.demo2.mapper")
public class DynamicDataSourceConfiguration {
@Primary
@Bean(name = "userDataSource")
@ConfigurationProperties(prefix = "spring.datasource.user")
public DataSource userDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "soulDataSource")
@ConfigurationProperties(prefix = "spring.datasource.soul")
public DataSource soulDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "dynamicDataSource")
public DynamicDataSource DataSource( @Qualifier("userDataSource")
DataSource userDataSource,
@Qualifier("soulDataSource") DataSource
soulDataSource) {
//targetDataSource 集合是我们数据库和名字之间的映射
Map<Object, Object> targetDataSource =
new HashMap<>();
targetDataSource.put(DataSourceType.USERDATASOURCE. getName(),
userDataSource);
targetDataSource.put(DataSourceType.SOULDATASOURCE. getName(),
soulDataSource);
DynamicDataSource dataSource = new DynamicDataSource();
dataSource.setTargetDataSources(targetDataSource);
//设置默认对象
dataSource.setDefaultTargetDataSource(userDataSource);
return dataSource;
}
@Bean(name = "sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(@Qualifier ("dynamicDataSource")
DataSource dynamicDataSource)
throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setTransactionFactory(new MultiDataSourceTransactionFactory());
bean.setDataSource(dynamicDataSource);
//设置我们的xml文件路径
bean.setMapperLocations(new PathMatching ResourcePatternResolver().getResources(
"classpath*:mapper/*.xml"));
return bean.getObject();
}
}
|
DataSourceContext
DataSourceContext使用ThreadLocal存放当前线程使用的数据源类型信息;
public class
DataSourceContext {
private final static ThreadLocal<String>
LOCAL_DATASOURCE =
new ThreadLocal<>();
public static void set(String name) {
LOCAL_DATASOURCE.set(name);
}
public static String get() {
return LOCAL_DATASOURCE.get();
}
public static void remove() {
LOCAL_DATASOURCE.remove();
}
} |
DynamicDataSource
DynamicDataSource继承AbstractRoutingDataSource,重写determineCurrentLookupKey()方法,可以选择对应Key;
public class
DynamicDataSource extends Abstract RoutingDataSource
{
@Override
protected Object determineCurrentLookupKey()
{
return DataSourceContext.get();
}
} |
CurrentDataSource
定义数据源的注解;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CurrentDataSource {
DataSourceType value() default DataSourceType .USERDATASOURCE;
} |
DataSourceAspect
定义切面切点,用来切换数据源,
@Aspect
@Aspect
@Order(-1)
@Component
public class DataSourceAspect {
@Pointcut("@annotation(org.datasource.demo2.constant. CurrentDataSource)")
public void dsPointCut() {
}
@Around("dsPointCut()")
public Object around(ProceedingJoinPoint point)
throws Throwable {
MethodSignature signature = (MethodSignature)
point.getSignature();
Method method = signature.getMethod();
CurrentDataSource dataSource = method.get Annotation(CurrentDataSource.class);
if (Objects.nonNull(dataSource)) {
System.out.println("切换数据源为" + dataSource.value().getName());
DataSourceContext.set(dataSource.value().getName());
}
try {
return point.proceed();
} finally {
// 销毁数据源 在执行方法之后
System.out.println("销毁数据源" + dataSource.value().getName());
DataSourceContext.remove();
}
}
} |
多数据源切换以后事务问题
Spring使用事务的方式有两种,一种是声明式事务,一种是编程式事务,我们讨论的都是关于声明式事务,这种方式很方便,也是大家常用的,这里为什么讨论这个问题,当我们想将不同库的表放在同一个事务使用的时候,这个是时候我们会报错,如下图: 这部分也就是其他技术贴没讲解的部分,因此这里我们来补充一下这个话题,背过八股们的小伙伴都知道Spring事务是居于AOP实现,从这个角度很容易会理解到这个问题,当我们将两个Service方法放在同一个Transactional下的时候,这个代理对象就是当前类,因此导致数据源对象也是当前类下的DataSource,导致就出现表不存在问题,当Transactional分别放在不同Service的时候没有这种情况。
@Transactional(rollbackFor = Exception.class)
@Transactional(rollbackFor
= Exception.class)
public void update(){
sysUserMapper.updateSysUser("111");
appAuthService.update("111");
} |
有没有办法解决这个问题呢,当然是有的,这里我就不一步一步去探讨源码问题,我就直接直捣黄龙,把问题本质说一下,在Spring事务管理中有一个核心类DataSourceTransactionManager,该类是Spring事务核心的默认实现,AbstractPlatformTransactionManager是整体的Spring事务实现模板类,整体的继承结构如下图, 在方案一中,我们针对每个DataSourece都创建对应的DataSourceTransactionManager实现,也可以看出DataSourceTransactionManager就是管理我们整体的事务的,当我们配置了事物管理器以及拦截Service中的方法后,每次执行Service中方法前会开启一个事务,并且同时会缓存DataSource、SqlSessionFactory、Connection,因为DataSource、Conneciton都是从缓存中拿的,因此我们怎么切换数据源也没用,因此就出现表不存在的报错,具体源码可参考下面截图部分:
看到这里我们大致明白了为什么会报错,那么我们该如何做才能实现这种情况呢?其实我们要做的事就是动态的根据DataSourceType获取不同的Connection,不从缓存中获取Connection。
解决方案
我们来自定义一个MultiDataSourceTransaction实现Mybatis的事务接口,使用Map存储Connection相关连接,所有事务都采用手动提交,之后将MultiDataSourceTransaction交给SpringManagedTransactionFactory处理。
public class
MultiDataSourceTransaction implements Transaction
{
private final DataSource dataSource;
private ConcurrentMap<String, Connection>
concurrentMap;
private boolean autoCommit;
public MultiDataSourceTransaction(DataSource
dataSource) {
this.dataSource = dataSource;
concurrentMap = new ConcurrentHashMap<>();
}
@Override
public Connection getConnection() throws SQLException
{
String databaseIdentification = DataSourceContext.get();
if (StringUtils.isEmpty(databaseIdentification))
{
databaseIdentification = DataSourceType. USERDATASOURCE.getName();
}
//获取数据源
if (!this.concurrentMap.containsKey(databaseIdentification))
{
try {
Connection conn = this.dataSource.getConnection();
autoCommit=false;
conn.setAutoCommit(false);
this.concurrentMap.put(databaseIdentification,
conn);
} catch (SQLException ex) {
throw new CannotGetJdbcConnectionException ("Could
bot get JDBC otherConnection", ex);
}
}
return this.concurrentMap.get(databaseIdentification);
}
@Override
public void commit() throws SQLException {
for (Connection connection : concurrentMap.values())
{
if (!autoCommit) {
connection.commit();
}
}
}
@Override
public void rollback() throws SQLException {
for (Connection connection : concurrentMap.values())
{
connection.rollback();
}
}
@Override
public void close() throws SQLException {
for (Connection connection : concurrentMap.values())
{
DataSourceUtils.releaseConnection(connection,
this.dataSource);
}
}
@Override
public Integer getTimeout() throws SQLException
{
return null;
}
}
public class MultiDataSourceTransactionFactory
extends SpringManagedTransactionFactory {
@Override
public Transaction newTransaction(DataSource
dataSource, TransactionIsolationLevel level,
boolean autoCommit) {
return new MultiDataSourceTransaction(dataSource);
}
} |
为什么可以这么做
在Mybatis自动装配式会将配置文件装配为Configuration对象,也就是在方案一种SqlSessionFactory配置的过程,其中SqlSessionFactoryBean类实现了InitializingBean接口,初始化后执行afterPropertiesSet()方法,在afterPropertiesSet()方法中会执行
BuildSqlSessionFactory() 方法生成一个SqlSessionFactory对象。在BuildSqlSessionFactory中,会创建SpringManagedTransactionFactory对象,该对象就是MyBatis跟
Spring的桥梁。
在MapperScan自动扫描Mapper过程中,会通过ClassPathMapperScanner扫描器找到Mapper接口,封装成各自的BeanDefinition,然后循环遍历对Mapper的BeanDefinition修改beanClass为MapperFactoryBean。 由于MapperFactoryBean实现了FactoryBean,在Bean生命周期管理时会调用getObject方法,通过JDK动态代理生成代理对象MapperProxy,Mapper接口请求的时候,执行MapperProxy代理类的invoke方法,执行的过程中通过SqlSessionFactory创建的SqlSession去调用Executor执行器,进行数据库操作。下图是SqlSession创建的整个过程: openSession方法是将Spring事务管理关联起来的核心代码,首先这里将通过
getTransactionFactoryFromEnvironment()方法获取TransactionFactory。这个操作会得到初始化时候注入的
SpringManagedTransactionFactory对象。然后将执行TransactionFactory#newTransaction()
方法,初始化 MyBatis的Transaction。 这里通过Configuration.newExecutor()创建一个Executor,Configuration指定在Executor默认为Simple,因此这里会创建一个SimpleExecutor,并初始化Transaction属性。接下来我们来看下SimpleExecutor执行执行update方法时候执行prepareStatement方法,在prepareStatement方法中执行了getConnection方法, image.png这里我们可以看到Connection获取过程,是通过Transaction获取的getConnection(),也就是通过之前注入的Transaction来获取Connection,这个Transaction就是SpringManagedTransaction,整体的时序图如下: 在整个调用链过程中,我们看到在DataSourceUtils有我们熟悉的TransactionSynchronizationManager,在上面Spring事务的时候我们也提到这个类,在开始Spring事务以后就会把Connetion绑定到当前线程,在DataSourceUtils获取到的Connection对象就是Srping开启事务时候创建的对象,这样就保证了Spring
Transaction中的Connection跟MyBatis中执行SQL语句用的Connection为同一个
Connection,也就可以通过Spring事务管理机制进行事务管理了。明白了整个流程,我们要做的事也就很简单,也就是每次切换DataSoure的同时获取最新的Connection,然后用一个Map对象来记录整个过程中的Connection,出现回滚这个Map对象里面Connection对象都回滚就可以了,然后将我们自定义的Transaction,委托给Spring在进行管理。
总结
采用AOP的方式是切换数据源已经非常好了,唯一不太好的地方就在于依然要手动去创建DataSource,每次增加都需要增加一个Bean,那有没有办法解决呢?当然是有的,让我们来更上一层楼,解放双手。
更上一层楼
ImportBeanDefinitionRegistrar
ImportBeanDefinitionRegistrar接口是Spring提供一个扩展点,主要用来注册BeanDefinition,常见的第三方框架在集成Spring的时候,都会通过该接口,实现扫描指定的类,然后注册到Spring容器中。比如
Mybatis中的Mapper接口,SpringCloud中的Feignlient接口,都是通过该接口实现的自定义注册逻辑。
我们要做的事情就是通过ImportBeanDefinitionRegistrar帮助我们动态的将DataSource扫描的到容器中去,不在采用增加Bean的方式,整体代码如下:
public class
DynamicDataSourceBeanDefinitionRegistrar implements
ImportBeanDefinitionRegistrar, EnvironmentAware
{
/**
* 默认dataSource
*/
private DataSource defaultDataSource;
/**
* 数据源map
*/
private Map<String, DataSource> dataSourcesMap
= new HashMap<>();
@Override
public void setEnvironment(Environment environment)
{
initConfig(environment);
}
private void initConfig(Environment env) {
//读取配置文件获取更多数据源
String dsNames = env.getProperty("spring.datasource.names");
for (String dsName : dsNames.split(","))
{
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setPoolName(dsName);
hikariConfig.setDriverClassName(env.getProperty ("spring.datasource."
+ dsName.trim() + ".driver-class-name"));
hikariConfig.setJdbcUrl(env.getProperty("spring. datasource."
+ dsName.trim() + ".jdbc-url"));
hikariConfig.setUsername(env.getProperty("spring. datasource."
+ dsName.trim() + ".username"));
hikariConfig.setPassword(env.getProperty("spring. datasource."
+ dsName.trim() + ".password"));
hikariConfig.setConnectionTimeout(Long.parseLong (Objects.requireNonNull(env.getProperty("spring. datasource."
+ dsName.trim() + ".hikari.connection-timeout"))));
hikariConfig.setMinimumIdle(Integer.parseInt (Objects.requireNonNull(env.getProperty("spring. datasource."
+ dsName.trim() + ".hikari.minimum-idle"))));
hikariConfig.setMaximumPoolSize(Integer.parseInt (Objects.requireNonNull(env.getProperty("spring. datasource."
+ dsName.trim() + ".hikari.maximum-pool-size"))));
hikariConfig.setConnectionInitSql("SELECT
1");
HikariDataSource dataSource = new HikariDataSource(hikariConfig);
if (dataSourcesMap.size() == 0) {
defaultDataSource = dataSource;
}
dataSourcesMap.put(dsName, dataSource);
}
}
@Override
public void registerBeanDefinitions(AnnotationMetadata
importingClassMetadata, BeanDefinitionRegistry
registry) {
Map<Object, Object> targetDataSources
= new HashMap<Object, Object>();
//添加其他数据源
targetDataSources.putAll(dataSourcesMap);
//创建DynamicDataSource
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(DynamicDataSource.class);
beanDefinition.setSynthetic(true);
MutablePropertyValues mpv = beanDefinition.getPropertyValues();
//defaultTargetDataSource 和 targetDataSources 属性是
AbstractRoutingDataSource的两个属性Map
mpv.addPropertyValue("defaultTargetDataSource",
defaultDataSource);
mpv.addPropertyValue("targetDataSources",
targetDataSources);
//注册
registry.registerBeanDefinition("dataSource",
beanDefinition);
}
} |
@Import
@Import模式是向容器导入Bean是一种非常重要的方式,在注解驱动的Spring项目中,@Enablexxx的设计模式中有大量的使用,我们通过ImportBeanDefinitionRegistrar完成Bean的扫描,通过@Import导入到容器中,然后将EnableDynamicDataSource放入SpringBoot的启动项之上,到这里有没有感觉到茅塞顿开的感觉。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import({DynamicDataSourceBeanDefinitionRegistrar.class})
public @interface EnableDynamicDataSource {
}
@SpringBootApplication
@EnableAspectJAutoProxy
@EnableDynamicDataSource
public class DataSourceApplication {
public static void main(String[] args) {
SpringApplication.run(DataSourceApplication.class,
args);
}
} |
DynamicDataSourceConfig
该类负责将Mapper扫描以及SpringFactory定义;
@Configuration
@MapperScan(basePackages = "org.datasource.demo3.mapper")
public class DynamicDataSourceConfig {
@Autowired
private DataSource dynamicDataSource;
@Bean(name = "sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory()
throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setTransactionFactory(new MultiDataSource TransactionFactory());
bean.setDataSource(dynamicDataSource);
//设置我们的xml文件路径
bean.setMapperLocations(new PathMatching ResourcePatternResolver().getResources(
"classpath*:mapper/*.xml"));
return bean.getObject();
}
} |
yaml
关于yaml部分我们增加了names定义,方便识别出来配置了几个DataSource,剩下的部分与AOP保持一致。
spring:
datasource:
names: user,soul
user:
jdbc-url: jdbc:mysql://127.0.0.1:3306/study_user?useSSL =false&useUnicode=true&characterEncoding=UTF-8
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
type: com.zaxxer.hikari.HikariDataSource
#hikari连接池配置
hikari:
#最小空闲连接数
minimum-idle: 5
#最大连接池
maximum-pool-size: 20
#链接超时时间 3秒
connection-timeout: 3000
soul:
jdbc-url: jdbc:mysql://127.0.0.1:3306/soul?useSSL=false& useUnicode=true&characterEncoding=UTF-8
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
type: com.zaxxer.hikari.HikariDataSource
#hikari连接池配置
hikari:
#最小空闲连接数
minimum-idle: 5
#最大连接池
maximum-pool-size: 20
#链接超时时间 3秒
connection-timeout: 3000 |
|