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方法里面一个数据源和一个事务,不能进行多个库交互使用,既然多数据源肯定是需要多库数据交互,这样数据一致性也无法控制。分布式事务?下一篇讲解