1. 程式人生 > >spring boot+mybatis+druid 多資料來源配置

spring boot+mybatis+druid 多資料來源配置

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方法裡面一個數據源和一個事務,不能進行多個庫互動使用,既然多資料來源肯定是需要多庫資料互動,這樣資料一致性也無法控制。分散式事務?下一篇講解