spring boot+mybatis+druid 多資料來源配置
阿新 • • 發佈:2018-12-11
application.yml(application.properties)配置:
spring: datasource: druid: type: com.alibaba.druid.pool.xa.DruidXADataSource driver-class-name: com.mysql.jdbc.Driver platform: mysql default: driverClassName: com.mysql.jdbc.Driver url: jdbc:mysql://xxxxxxxxxxx:3306/mypinyu?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false username: root password: initialSize: 5 minIdle: 5 maxActive: 20 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 validationQuery: SELECT1FROMDUAL testWhileIdle: true testOnBorrow: false testOnReturn: false second: driverClassName: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/mypinyu?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&useSSL=false username: root password: admin initialSize: 5 minIdle: 5 maxActive: 20 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 #驗證連線是否可用,使用的SQL語句 validationQuery: SELECT 1 #指明連線是否被空閒連接回收器(如果有)進行檢驗.如果檢測失敗,則連線將被從池中去除. testWhileIdle: true #借出連線時不要測試,否則很影響效能 testOnBorrow: false testOnReturn: false #jta相關引數配置 jta: log-dir: classpath:tx-logs transaction-manager-id: txManager logging.config: classpath: log4j2.xml #返回檢視的字首 目錄對應src/main/webapp下 spring.mvc.view.prefix: /WEB-INF/jsp/ #返回的字尾 spring.mvc.view.suffix: .jsp
核心配置
package com.pinyu.system.global.config.datasource; import java.util.HashMap; import java.util.Map; import javax.sql.DataSource; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.mybatis.spring.SqlSessionTemplate; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder; /** * @author ypp 建立時間:2018年11月6日 上午11:18:16 * @Description: TODO(用一句話描述該檔案做什麼) */ @Configuration public class DynamicDataSourceConfig { @Bean(name = DataSourceNames.DEFAULT) @ConfigurationProperties("spring.datasource.druid."+DataSourceNames.DEFAULT) public DataSource defaultDataSource() { return DruidDataSourceBuilder.create().build(); } @Bean(name = DataSourceNames.SECOND) @ConfigurationProperties("spring.datasource.druid."+DataSourceNames.SECOND) public DataSource secondDataSource() { return DruidDataSourceBuilder.create().build(); } @Bean @Primary public DynamicDataSource dataSource(@Qualifier(DataSourceNames.SECOND) DataSource secondDataSource, @Qualifier(DataSourceNames.DEFAULT) DataSource firstDataSource) { Map<String, DataSource> targetDataSources = new HashMap<>(); targetDataSources.put(DataSourceNames.SECOND, secondDataSource); targetDataSources.put(DataSourceNames.DEFAULT, firstDataSource); return new DynamicDataSource(firstDataSource, targetDataSources); } }
package com.pinyu.system.global.config.datasource; import java.util.HashMap; import java.util.Map; import javax.sql.DataSource; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; /** * @author ypp * 建立時間:2018年11月6日 上午11:31:42 * @Description: TODO(重寫資料來源) */ public class DynamicDataSource extends AbstractRoutingDataSource{ private static final ThreadLocal<String> contextHolder = new ThreadLocal<>(); public DynamicDataSource(DataSource defaultTargetDataSource, Map<String, DataSource> targetDataSources) { super.setDefaultTargetDataSource(defaultTargetDataSource); super.setTargetDataSources(new HashMap<>(targetDataSources)); super.afterPropertiesSet(); } @Override protected Object determineCurrentLookupKey() { return getDataSource(); } public static void setDataSource(String dataSource) { contextHolder.set(dataSource); } public static String getDataSource() { return contextHolder.get(); } public static void clearDataSource() { contextHolder.remove(); } }
上面我沒有像其他人那樣配置在資料來源上面設定主資料來源,只是下面設定重寫資料來源的時候也相當於設定了主資料來源
package com.pinyu.system.global.config.datasource;
/**
* @author ypp 建立時間:2018年11月6日 上午11:39:03
* @Description: TODO()
*/
public interface DataSourceNames {
public static final String DEFAULT = "default";
public static final String SECOND = "second";
}
這裡配置資料來源,對我來說其實就是一個常量使用而已。多一個庫加一個常量和相應的配置就是
自定義資料來源註解,方便後面AOP切面切換資料來源使用:
package com.pinyu.system.global.config.datasource.ann;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author ypp
* 建立時間:2018年11月6日 上午11:40:45
* @Description: TODO(資料來源註解)
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDataSource {
String value() default "";
}
切面,用於切換資料來源
package com.pinyu.system.global.config.datasource;
import java.lang.reflect.Method;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.annotation.Order;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Component;
import com.pinyu.system.global.config.datasource.ann.TargetDataSource;
/**
* @author ypp
* 建立時間:2018年11月6日 下午1:33:53
* @Description: TODO(多資料來源切面處理類)
*/
@Aspect
@Component
@Order(-1)
public class DataSourceAspect {
protected Logger logger = LogManager.getLogger(getClass());
@Autowired
@Qualifier(TransactionConfig.DEFAULT)
private DataSourceTransactionManager lyTransactionManager;
@Autowired
@Qualifier(TransactionConfig.SECOND)
private DataSourceTransactionManager zhTransactionManager;
//切入到註解 service+註解防止執行2次
// @Pointcut("execution(* com.pinyu.system.service.*.*(..))&&@annotation(com.pinyu.system.global.config.mybatis.ann.TargetDataSource)")
// public void dataSourcePointCut() {
//
// }
// 只切入到service
@Pointcut("execution(* com.pinyu.system.service.*.*(..))")
public void dataSourcePointCut() {
}
@Before("dataSourcePointCut()")
public void around(JoinPoint pjp) throws Throwable {
String methodName=pjp.getSignature().getName();
Class<?> classTarget=pjp.getTarget().getClass();
Class<?>[] par=((MethodSignature) pjp.getSignature()).getParameterTypes();
Method method=classTarget.getMethod(methodName, par);
TargetDataSource ds = method.getAnnotation(TargetDataSource.class);
if(ds == null){
DynamicDataSource.setDataSource(DataSourceNames.DEFAULT);
}else {
String name = ds.value();
if(StringUtils.isBlank(name)){
DynamicDataSource.setDataSource(DataSourceNames.DEFAULT);
}else {
DynamicDataSource.setDataSource(name);
}
}
}
@AfterReturning("dataSourcePointCut()")
public void after(){
DynamicDataSource.clearDataSource();
logger.debug("clean datasource");
}
}
到這裡資料來源基本配置完成了,啟動類需要禁掉springboot原有的DataSourceAutoConfiguration,因為它會預設去載入application.yml裡面資料庫的配置
package com.pinyu.system;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.web.support.SpringBootServletInitializer;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@ComponentScan(basePackages = "com.pinyu.system")
@MapperScan("com.pinyu.system.mapper")
@EnableTransactionManagement
@SpringBootApplication(exclude={
DataSourceAutoConfiguration.class,
// HibernateJpaAutoConfiguration.class, //(如果使用Hibernate時,需要加)
DataSourceTransactionManagerAutoConfiguration.class,
})
public class Application extends SpringBootServletInitializer {
}
現在可以切換資料來源了,但是會存在問題。
如果不配置事務事務管理器會出現以下問題:
以上已經完成了動態資料來源的切換,只需在Service方法上加上@TargetDataScoure註解並且指定需要切換的資料來源名稱,first資料來源為預設資料來源。
如果使用@Transactional,預設資料來源的事務正常執行,如果使@TargetDataScoure切換為第二資料來源並執行事務時,則資料來源切換失敗
事務控制:
package com.pinyu.system.global.config.datasource;
import javax.sql.DataSource;
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.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.interceptor.DefaultTransactionAttribute;
import org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource;
import org.springframework.transaction.interceptor.TransactionInterceptor;
/**
* @author ypp
* 建立時間:2018年11月6日 下午2:08:14
* @Description: TODO(多資料來源事務控制)
*/
@Configuration
public class TransactionConfig {
public final static String DEFAULT = "defaultTx";
public final static String SECOND = "secondTx";
@Bean(DEFAULT)
public DataSourceTransactionManager defaultTransaction(@Qualifier(DataSourceNames.DEFAULT) DataSource dataScoure){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(dataScoure);
return dataSourceTransactionManager;
}
@Bean(SECOND)
public DataSourceTransactionManager secondTransaction(@Qualifier(DataSourceNames.SECOND)DataSource dataSource){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(dataSource);
return dataSourceTransactionManager;
}
}
現在在service方法上面加上2個註解就可以了
比如:
@Override
@Transactional(TransactionConfig.DEFAULT)
@TargetDataSource(name=DataSourceNames.DEFAULT)
public void add(UserEntity user,UserEntity loginUser,String ip) {
userMapper.add(user);
RoleEntity roleEntity = roleService.findByCode(GlobalConstants.UserCodeType.DEFAULT.getCode());
UserRoleEntity userRoleEntity = new UserRoleEntity();
userRoleEntity.setRoleId(roleEntity.getId());
userRoleEntity.setUserId(user.getId());
List<UserRoleEntity> arrayList = new ArrayList<UserRoleEntity>();
arrayList.add(userRoleEntity);
userRoleService.add(arrayList);
}
注意,一定要在事務之前切換到資料來源,在切面我用的@order(-1),其實order(0)即可
資料來源配置完成,這樣配置只能一個service方法裡面一個數據源和一個事務,不能進行多個庫互動使用,既然多資料來源肯定是需要多庫資料互動,這樣資料一致性也無法控制。分散式事務?下一篇講解