spring boot+mybatis+druid 多数据源配置

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/ypp91zr/article/details/83988238

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

猜你喜欢

转载自blog.csdn.net/ypp91zr/article/details/83988238