在高並行的專案中,單資料庫已無法承載巨量資料量的存取,因此需要使用多個資料庫進行對資料的讀寫分離,此外就是在微服化的今天,我們在專案中可能採用各種不同儲存,因此也需要連線不同的資料庫,居於這樣的背景,這裡簡單分享實現的思路以及實現方案。
多資料來源實現思路有兩種,一種是通過設定多個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>
該種方式需要操作的資料庫的Mapper層和Dao層分別建立一個資料夾,分包放置,整體專案結構如下圖:
<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>
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
針對不同的庫分別放置對用不同的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 PathMatchingResourcePatternResolver().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 soulTransactionManager(@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();
}
}
此種方式使用起來分層明確,不存在任何冗餘程式碼,不足地方就是每個庫都需要對應一個設定類,該設定類中實現方式都基本類似,該種解決方案每個設定類中都存在事務管理器,因此不需要單獨再去額外的關注。
關於採用Spring AOP方式實現原理就是把多個資料來源儲存在一個 Map中,當需要使用某個資料來源時,從 Map中獲取此資料來源進行處理。
在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.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 + "]");
} else {
return dataSource;
}
}
該方法通過determineCurrentLookupKey()方法獲取一個key,通過key從resolvedDataSources中獲取資料來源DataSource物件。determineCurrentLookupKey()是個抽象方法,需要繼承AbstractRoutingDataSource的類實現;而resolvedDataSources是一個Map<Object, DataSource>,裡面應該儲存當前所有可切換的資料來源,接下來我們來聊聊實現,我們首先來看下目錄,與分包的不同的是將所有的Mapper檔案都放到一起,其他Maven依賴以及組態檔都保持一致。
該列舉用來存放資料來源的名稱,
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;
}
}
通過讀取組態檔中的資料來源設定資訊,建立資料連線,將多個資料來源放入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 PathMatchingResourcePatternResolver().getResources(
"classpath*:mapper/*.xml"));
return bean.getObject();
}
}
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繼承AbstractRoutingDataSource,重寫determineCurrentLookupKey()方法,可以選擇對應Key;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContext.get();
}
}
定義資料來源的註解;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface CurrentDataSource {
DataSourceType value() default DataSourceType.USERDATASOURCE;
}
定義切面切點,用來切換資料來源,
@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.getAnnotation(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)
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方法, 這裡我們可以看到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介面是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模式是向容器匯入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);
}
}
該類負責將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 MultiDataSourceTransactionFactory());
bean.setDataSource(dynamicDataSource);
//設定我們的xml檔案路徑
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(
"classpath*:mapper/*.xml"));
return bean.getObject();
}
}
關於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
歡迎大家點點關注,點點贊! 今年前半年文章會偏Spring、SpringCloud相關的實戰,後半年文章會多一些理論。