我们配置主从数据源的时候,就需要多数据源配置。
#数据库连接 Master
spring.datasource.master.username=root
spring.datasource.master.password=12345678
spring.datasource.master.jdbc-url=jdbc:mysql://localhost:3308/h5_db?characterEncoding=utf-8
spring.datasource.master.driver-class-name=com.mysql.cj.jdbc.Driver
#数据库连接 Slave
spring.datasource.slave.username=user
spring.datasource.slave.password=12345678
spring.datasource.slave.jdbc-url=jdbc:mysql://localhost:3307/h5_db?characterEncoding=utf-8
spring.datasource.slave.driver-class-name=com.mysql.cj.jdbc.Driver
代码
@Configuration
public class DataSourceConfig {
@Bean
@ConfigurationProperties("spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
@Primary
@Bean
public DynamicDataSource dataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
@Qualifier("slaveDataSource") DataSource slaveDataSource) {
Map<Object, Object> dataSourceMap = new HashMap<>();
dataSourceMap.put(DataSourceType.Master, masterDataSource);
dataSourceMap.put(DataSourceType.Slave, slaveDataSource);
DynamicDataSource dataSource = new DynamicDataSource();
dataSource.setTargetDataSources(dataSourceMap);
dataSource.setDefaultTargetDataSource(masterDataSource());
dataSource.afterPropertiesSet();
return dataSource;
}
}
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="masterDataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="jdbcUrl"
value="jdbc:mysql://localhost:3308/h5_db?characterEncoding=utf-8"/>
<property name="username" value="root"/>
<property name="password" value="12345678"/>
</bean>
<bean id="slaveDataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="jdbcUrl"
value="jdbc:mysql://localhost:3307/h5_db?characterEncoding=utf-8"/>
<property name="username" value="user"/>
<property name="password" value="12345678"/>
</bean>
<bean id="dataSource" class="com.ysp.template.route.DynamicDataSource" primary="true">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="master" value-ref="masterDataSource"/>
<entry key="slave" value-ref="slaveDataSource"/>
</map>
</property>
<property name="defaultTargetDataSource" ref="masterDataSource"/>
</bean>
</beans>
此外使用primary="true"设置默认数据源为动态数据源。SpringBoot自动装配SqlSessionFactory和DataSourceTransactionManager时注入的都是动态数据源。就不需要代码配置。
重点:配置多数据源强烈建议过滤数据源自动配置
过滤代码
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
SqlSessionFactory配置
xml方式
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis/mybatis-config.xml"/>
<property name="typeAliasesPackage" value="com.ysp.template.pojo.dao"/>
<property name="mapperLocations" value="classpath:mybatis/mapper/*.xml"/>
</bean>
java方式
@Bean
public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) throws Exception {
System.out.println("=>>>=======>: " + dataSource.getClass().getName());
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setConfigLocation(new ClassPathResource("mybatis/mybatis-config.xml"));
bean.setTypeAliasesPackage("com.ysp.template.pojo.dao");
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/*.xml"));
return bean.getObject();
}
重点:ThreadLocal 和 AbstractRoutingDataSource 使用
AbstractRoutingDataSource 源码
org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource
// 获取数据库连接
public Connection getConnection() throws SQLException {
return this.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;
}
}
// 重新这个方法,返回数据源标示Key就可以动态切换
@Nullable
protected abstract Object determineCurrentLookupKey();
代码
public class DataSourceHandler {
private static ThreadLocal<String> datasourceContext = new ThreadLocal<>();
public static void switchDataSource(String datasource) {
System.out.println("switchDataSource: " + datasource);
datasourceContext.set(datasource);
}
public static String getDataSource() {
return datasourceContext.get();
}
public static void clear() {
datasourceContext.remove();
}
}
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
//System.out.println(">>>>>>>>>>>>>: determineCurrentLookupKey");
return DataSourceHandler.getDataSource();
}
}
@Target({METHOD, TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface DataSource {
String value() default "";
}
@Aspect
@Component
@Order(1) // 重点:当使用事务注解(@Transactional)的时候,切换数据源aop要优先事务执行。
public class DataSourceAspect {
@Pointcut("@annotation(com.ysp.template.annotation.DataSource)")
public void pointcut() {
}
@Before(value = "pointcut()")
public void switchDataSource(JoinPoint joinPoint) {
//从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//获取切入点所在的方法
Method method = signature.getMethod();
DataSource annotation = method.getAnnotation(DataSource.class);
DataSourceHandler.switchDataSource(annotation.value());
}
@After(value = "pointcut()") // 必须加@After
public void clear(){
DataSourceHandler.clear();
}
}
@Transactional
@DataSource(DataSourceType.Master)
public String addProject(ProjectDo projectDo, List<ProjectMemberDo> projectMembers) {
return addProjectPrivate(projectDo, projectMembers);
}
1、首先是AbstractRoutingDataSource类
2、DataSourceTransactionManager
org.springframework.jdbc.datasource.DataSourceTransactionManager
protected void doBegin(Object transaction, TransactionDefinition definition) {
DataSourceTransactionManager.DataSourceTransactionObject txObject = (DataSourceTransactionManager.DataSourceTransactionObject)transaction;
Connection con = null;
// 中间省略了,主要逻辑,事务获取数据源连接。此处需要断点。
}
3、DataSourceTransactionManagerAutoConfiguration
1、事务自动配置类:我们在这里打断点查看数据源是否是动态数据源(DynamicDataSource)
2、如果我们代码自己配置事务建议过滤调事务自动配置
@SpringBootApplication(exclude = {DataSourceTransactionManagerAutoConfiguration.class})
源码
@ConditionalOnSingleCandidate(DataSource.class)
static class DataSourceTransactionManagerConfiguration {
DataSourceTransactionManagerConfiguration() {
}
@Bean
@ConditionalOnMissingBean({PlatformTransactionManager.class})
DataSourceTransactionManager transactionManager(DataSource dataSource, ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
// DataSource dataSource 是否为DynamicDataSource
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource);
transactionManagerCustomizers.ifAvailable((customizers) -> {
customizers.customize(transactionManager);
});
return transactionManager;
}
}
点赞