咱們使用動態的方式進行多數據源的配置,更加靈活方便。java
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
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(); } }
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(); } }
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(); } }
/** * 數據源名稱 */ public interface DataSourceNames { String ONE = "ONE"; String TWO = "TWO"; } @Documented @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface DataSource { String value() default DataSourceNames.ONE; }
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"); } } }
具體MyBatis的mapper文件、Entity屬性文件就不一一展現了,主要展現註解應用的場景mysql
@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); } }
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); } }
// 測試讀取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); }
執行日誌:git
當前數據源: 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