我們使用動態的方式進行多數據源的配置,更加靈活方便。
1. 配置文件
spring:
datasource:
druid:
one:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/multiple_one?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=CONVERT_TO_NULL&useSSL=false&serverTimezone=GMT%2B8
username: root
password: 123456
two:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/multiple_two?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=CONVERT_TO_NULL&useSSL=false&serverTimezone=GMT%2B8
username: root
password: 123456
mapper:
identity: MYSQL
mappers: tk.mybatis.mapper.common.Mapper
not-empty: false
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.why.zing.product.entity
logging:
level:
com.why.multipledatasource.dao: debug
2. 多數據源配置
2.1 數據源屬性注入DataSourceConfig
package com.why.multipledatasource.datasource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Slf4j
@Configuration
public class DataSourceConfig {
@Bean(name = "masterDataSource")
@ConfigurationProperties(prefix = "spring.datasource.druid.one")
public DataSource oneDataSource() {
return DruidDataSourceBuilder.create().build();
}
@Bean(name = "slaveDataSource")
@ConfigurationProperties(prefix = "spring.datasource.druid.two")
public DataSource twoDataSource() {
return DruidDataSourceBuilder.create().build();
}
}
2.2 動態路由控制 DynamicDataSource
package com.why.multipledatasource.datasource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.sql.DataSource;
import java.util.Map;
public class DynamicDataSource extends AbstractRoutingDataSource {
private static final ThreadLocal<String> CONTEXT_HOLDER= new ThreadLocal<>();
/**
* 配置DataSource, defaultTargetDataSource爲主數據庫
*/
public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(targetDataSources);
super.afterPropertiesSet();
}
@Override
protected Object determineCurrentLookupKey() {
return getDataSource();
}
public static void setDataSource(String dataSource) {
CONTEXT_HOLDER.set(dataSource);
}
public static String getDataSource() {
return CONTEXT_HOLDER.get();
}
public static void clearDataSource() {
CONTEXT_HOLDER.remove();
}
}
2.3 SqlSessionConfig
package com.why.multipledatasource.datasource;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Configuration
public class SqlSessionConfig {
/**
* 如果還有數據源,在這繼續添加 DataSource Bean
*/
@Bean
@Primary
public DataSource dataSource(@Qualifier("masterDataSource") DataSource oneDataSource, @Qualifier("slaveDataSource") DataSource twoDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>(2);
targetDataSources.put(DataSourceNames.ONE, oneDataSource);
targetDataSources.put(DataSourceNames.TWO, twoDataSource);
// 還有數據源,在targetDataSources中繼續添加
log.info("DataSources:{}", targetDataSources);
return new DynamicDataSource(oneDataSource, targetDataSources);
}
@Bean
@Primary
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml"));
return bean.getObject();
}
}
2.4 註解實現路由選擇
/**
* 數據源名稱
*/
public interface DataSourceNames {
String ONE = "ONE";
String TWO = "TWO";
}
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {
String value() default DataSourceNames.ONE;
}
2.5 通過AOP針對不同數據源執行
package com.why.multipledatasource.datasource;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Slf4j
@Aspect
@Component
public class DataSourceAspect {
/**
* 切點: 所有配置 DataSource 註解的方法
*/
@Pointcut("@annotation(com.why.multipledatasource.datasource.DataSource)")
public void dataSourcePointCut() {}
@Around("dataSourcePointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
MethodSignature signature = (MethodSignature) point.getSignature();
Method method = signature.getMethod();
DataSource ds = method.getAnnotation(DataSource.class);
// 通過判斷 DataSource 中的值來判斷當前方法應用哪個數據源
DynamicDataSource.setDataSource(ds.value());
System.out.println("當前數據源: " + ds.value());
log.info("set datasource is " + ds.value());
try {
return point.proceed();
} finally {
DynamicDataSource.clearDataSource();
log.info("clean datasource");
}
}
}
3. 應用
具體MyBatis的mapper文件、Entity屬性文件就不一一展示了,主要展示註解應用的場景
3.1 使用ONE數據源的SQL
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private RoleService roleService;
@Override
@DataSource
public List<User> selectList(User user){
return userMapper.select(user);
}
@Override
@DataSource
public int update(User user) {
return userMapper.updateByPrimaryKeySelective(user);
}
@Override
@DataSource
@Transactional(rollbackFor = Exception.class)
public void saveUserRole(User user, Role role) {
userMapper.insertSelective(user);
roleService.save(role);
}
}
3.2 使用TWO數據源的SQL
import com.why.multipledatasource.dao.ChatAppMapper;
import com.why.multipledatasource.datasource.DataSource;
import com.why.multipledatasource.datasource.DataSourceNames;
import com.why.multipledatasource.entity.ChatApp;
import com.why.multipledatasource.service.ChatAppService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class ChatAppServiceImpl implements ChatAppService {
@Autowired
private ChatAppMapper appMapper;
@Override
@DataSource(value = DataSourceNames.TWO)
public List<ChatApp> selectList(ChatApp chatApp){
return appMapper.select(chatApp);
}
@Override
@DataSource(value = DataSourceNames.TWO)
public int update(ChatApp chatApp) {
return appMapper.updateByPrimaryKeySelective(chatApp);
}
}
3.3 編寫單元測試類
// 測試讀取ONE的數據源
@Test
public void selectList() {
ChatApp app = new ChatApp();
app.setId(1);
List<ChatApp> chatApps = chatAppService.selectList(app);
System.out.println(chatApps);
}
// 測試讀取TWO的數據源
@Test
public void selectList() {
User user = new User();
user.setUsername("Tom");
List<User> users = userService.selectList(user);
System.out.println(users);
}
// 測試讀取ONE數據源並添加事務
/**
* 驗證同數據庫事務情況,對於跨數據庫的分佈式事務,需要引入分佈式事務的解決方案
*/
@Test
public void saveUserRole(){
User user = new User();
user.setUsername("睜眼看世界");
user.setAge(19);
user.setSex("男");
Role role = new Role();
role.setRole("管理員");
userService.saveUserRole(user,role);
}
執行日誌:
當前數據源: ONE
2020-03-04 14:51:49.023 INFO 18512 --- [ main] c.w.m.datasource.DataSourceAspect : set datasource is ONE
2020-03-04 14:52:09.124 INFO 18512 --- [ main] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} inited
2020-03-04 14:52:10.944 INFO 18512 --- [ main] c.w.m.datasource.DataSourceAspect : clean datasource
[User(id=1, username=Tom, age=18, sex=男)]
當前數據源: TWO
2020-03-04 16:13:26.431 INFO 21336 --- [ main] c.w.m.datasource.DataSourceAspect : set datasource is TWO
2020-03-04 16:13:26.690 INFO 21336 --- [ main] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} inited
2020-03-04 16:13:26.850 DEBUG 21336 --- [ main] c.w.m.dao.ChatAppMapper.select : ==> Preparing: SELECT id,username,qq,wx FROM chat_app WHERE id = ?
2020-03-04 16:13:26.879 DEBUG 21336 --- [ main] c.w.m.dao.ChatAppMapper.select : ==> Parameters: 1(Integer)
2020-03-04 16:13:26.915 DEBUG 21336 --- [ main] c.w.m.dao.ChatAppMapper.select : <== Total: 1
2020-03-04 16:13:26.920 INFO 21336 --- [ main] c.w.m.datasource.DataSourceAspect : clean datasource
[ChatApp(id=1, username=TONY, qq=12312312, wx=098765)]
2020-03-04 16:13:49.673 INFO 34140 --- [ main] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} inited
當前數據源: ONE
2020-03-04 16:13:49.888 INFO 34140 --- [ main] c.w.m.datasource.DataSourceAspect : set datasource is ONE
2020-03-04 16:13:49.972 DEBUG 34140 --- [ main] c.w.m.dao.UserMapper.insertSelective : ==> Preparing: INSERT INTO user ( id,username,age,sex ) VALUES( ?,?,?,? )
2020-03-04 16:13:50.005 DEBUG 34140 --- [ main] c.w.m.dao.UserMapper.insertSelective : ==> Parameters: null, 睜眼看世界(String), 19(Integer), 男(String)
2020-03-04 16:13:50.010 DEBUG 34140 --- [ main] c.w.m.dao.UserMapper.insertSelective : <== Updates: 1
2020-03-04 16:13:50.015 DEBUG 34140 --- [ main] c.w.m.d.U.insertSelective!selectKey : ==> Executing: SELECT LAST_INSERT_ID()
2020-03-04 16:13:50.044 DEBUG 34140 --- [ main] c.w.m.d.U.insertSelective!selectKey : <== Total: 1
2020-03-04 16:13:50.051 DEBUG 34140 --- [ main] c.w.m.dao.RoleMapper.insertSelective : ==> Preparing: INSERT INTO role ( id,role ) VALUES( ?,? )
2020-03-04 16:13:50.052 DEBUG 34140 --- [ main] c.w.m.dao.RoleMapper.insertSelective : ==> Parameters: null, 管理員(String)
2020-03-04 16:13:50.053 DEBUG 34140 --- [ main] c.w.m.dao.RoleMapper.insertSelective : <== Updates: 1
2020-03-04 16:13:50.054 DEBUG 34140 --- [ main] c.w.m.d.R.insertSelective!selectKey : ==> Executing: SELECT LAST_INSERT_ID()
2020-03-04 16:13:50.055 DEBUG 34140 --- [ main] c.w.m.d.R.insertSelective!selectKey : <== Total: 1
2020-03-04 16:13:50.056 INFO 34140 --- [ main] c.w.m.datasource.DataSourceAspect : clean datasource
java.lang.ArithmeticException: / by zero
4. 具體源碼
https://gitee.com/whyCodeData/java_repository/tree/master/springboot/multiple-datasource