springboot+mybatis+PageHelper(分页)+加事物transaction的方式配置多数据源

数据源配置
mybatis.config-location=classpath:mybatis/mybatis-config.xml
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
mybatis.type-aliases-package=com.cnpc.dj.party.entity
spring.http.multipart.maxFileSize=52428800
spring.http.multipart.maxRequestSize=52428800

spring.datasource.report.driverClassName=oracle.jdbc.driver.OracleDriver
spring.datasource.report.url=连接信息
spring.datasource.report.username=账号
spring.datasource.report.password=密码

spring.datasource.report.maxActive=1
#验证连接是否有效。此参数必须设置为非空字符串,下面三项设置成true才能生效
spring.datasource.report.validationQuery=SELECT 1
#指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除.
spring.datasource.report.testWhileIdle= true
#指明是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个
spring.datasource.report.testOnBorrow= true
#指明是否在归还到池中前进行检验
spring.datasource.report.testOnReturn= false
#指定连接池等待连接返回的最大等待时间,毫秒单位
spring.datasource.report.maxWait=10000
#SQL 查询验证超时时间(秒)
spring.datasource.report.validationQueryTimeout=3

spring.datasource.detail.driverClassName=oracle.jdbc.driver.OracleDriver
spring.datasource.detail.url=连接信息
spring.datasource.detail.username=账号
spring.datasource.detail.password=密码

spring.datasource.detail.maxActive=1
#验证连接是否有效。此参数必须设置为非空字符串,下面三项设置成true才能生效
spring.datasource.detail.validationQuery: SELECT 1
#指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除.
spring.datasource.detail.testWhileIdle: true
#指明是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个
spring.datasource.detail.testOnBorrow: true
#指明是否在归还到池中前进行检验
spring.datasource.detail.testOnReturn: false
#指定连接池等待连接返回的最大等待时间,毫秒单位
spring.datasource.detail.maxWait=10000
#SQL 查询验证超时时间(秒)
spring.datasource.detail.validationQueryTimeout=3

配置文件-----Start

package com.cnpc.dj.party.config;

import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.cnpc.dj.party.enums.DataSourceEnum;
import com.cnpc.dj.party.multiple.MultipleDataSource;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
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 org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

@Configuration // 注册到springboot容器中
public class MybatisConfig {

@Bean
public PaginationInterceptor paginationInterceptor() {
    PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
    Properties properties = new Properties();
    //数据库
    properties.setProperty("dialect", "oracle");
    //是否将参数offset作为PageNum使用
    properties.setProperty("offsetAsPageNum", "true");
    //是否进行count查询
    properties.setProperty("rowBoundsWithCount", "true");
    //是否分页合理化
    properties.setProperty("reasonable", "false");
    paginationInterceptor.setProperties(properties);
    return paginationInterceptor;
}

/**
*
* @methodDesc: 功能描述:(配置test1数据库)
*
*/
@Bean(name = “reportDb”)
@ConfigurationProperties(prefix = “spring.datasource.report” )//把同类的配置信息自动封装成实体类
public DataSource reportDb() {
return DataSourceBuilder.create().build();
}

/**
*
* @methodDesc: 功能描述:(配置test2数据库)
*
*/
@Bean(name = “detailDb”)
@ConfigurationProperties(prefix = “spring.datasource.detail” )
public DataSource detailDb() {
return DataSourceBuilder.create().build();
}

/**
@Qualifier 根据名称进行注入,通常是在具有相同的多个类型的实例的一个注入(例如有多个DataSource类型的实例)
 * 动态数据源配置
 * @return
 */
@Bean
@Primary
public DataSource multipleDataSource(@Qualifier("reportDb") DataSource reportDb,
                                     @Qualifier("detailDb") DataSource detailDb
                                     ) {
    MultipleDataSource multipleDataSource = new MultipleDataSource();
    Map< Object, Object > targetDataSources = new HashMap<>();
    targetDataSources.put(DataSourceEnum.REPORT.getValue(), reportDb);
    targetDataSources.put(DataSourceEnum.DETAIL.getValue(), detailDb);
    //添加数据源
    multipleDataSource.setTargetDataSources(targetDataSources);
    //设置默认数据源
    multipleDataSource.setDefaultTargetDataSource(reportDb);
    return multipleDataSource;
}

@Bean("sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory() throws Exception {
    MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
    sqlSessionFactory.setDataSource(multipleDataSource(reportDb(),detailDb()));
   /* sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/*.xml"));*/
    // 加载配置mybatis-config
    DefaultResourceLoader configResourceLoader = new DefaultResourceLoader();
    Resource configResource = configResourceLoader.getResource("classpath:mybatis/mybatis-config.xml");
    sqlSessionFactory.setConfigLocation(configResource);
  // 添加mapper文件XML目录
    ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
    Resource[] resources = resolver.getResources("classpath:mybatis/mapper/*.xml");
    sqlSessionFactory.setMapperLocations(resources);
    sqlSessionFactory.setPlugins(new Interceptor[]{ //PerformanceInterceptor(),OptimisticLockerInterceptor()
            paginationInterceptor() //添加分页功能
    });
    return sqlSessionFactory.getObject();
}

/**

  • 配置事务管理器
    */
    @Bean
    public DataSourceTransactionManager transactionManager(MultipleDataSource dataSource) throws Exception {
    return new DataSourceTransactionManager(dataSource);
    }
    }
    配置文件-----end

**一、 首先继承AbstractRoutingDataSource,从名称上看为抽象路由数据源,就是spring为提供动态数据库而设定的。在这个类中,需要重写determineCurrentLookupKey这个方法,这个方法就是动态从
private Map<Object, Object> targetDataSources(AbstractRoutingDataSource类里面的属性)里面获取对应的数据源

**
package com.cnpc.dj.party.multiple;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**

  • 动态数据源(需要继承AbstractRoutingDataSource)
    */
    public class MultipleDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
    return DataSourceContextHolder.getDataSource();
    }
    }

/**
*

  • 作用:1、保存一个线程安全的String容器
    */
    package com.cnpc.dj.party.multiple;

public class DataSourceContextHolder {

private static final ThreadLocal<String> contextHolder = new InheritableThreadLocal<>();

/**
 *  设置数据源
 * @param db
 */
public static void setDataSource(String db){
    contextHolder.set(db);
}

/**
 * 取得当前数据源
 * @return
 */
public static String getDataSource(){
    return contextHolder.get();
}

/**
 * 清除上下文数据
 */
public static void clear(){
    contextHolder.remove();
}

}

6、

①查询test1的数据时,可以直接调用对应的mapper;(因为test1是默认数据源)

②查询test2的数据时,需要在调用之前指定数据源,如下代码:

DatabaseContextHolder.setDatabaseType(DatabaseType.test2);

③同时需要查询test1和test2的数据时,需要在调用之前指定正确的数据源。

这种是在业务中使用代码设置数据源的方式,也可以使用AOP+注解的方式实现控制,还可以前端头部设置后端通过拦截器统一设置!

使用注解的方式切块数据库
三、在controller层或者service的每个方法前切换数据源(aop切入点不同),用到AOP

重点说明:每多人会有疑问,springcloud的controller是单例,这样在多用户的情况下,会不会窜请求,线程不安全

原因解答:(1)请看上面,其实我们使用了ThreadLocal,如果不理解的,请去补ThreadLocal知识了

用多线程测试:我用多个线程,调用同一个查询方法,但每个请求的header中的dsNo都不一样,这样就真实模拟生产环境,多用户查的情况,看是否有有窜请求,线程不安全的情况,实际测试并没有这种情况出现

package com.cnpc.dj.party.aop;

import com.cnpc.dj.party.annotation.AnalyzeDataSource;
import com.cnpc.dj.party.multiple.DataSourceContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Slf4j
@Aspect
@Order(-1)
public class DataSourceAspect {

@Pointcut("@within(com.cnpc.dj.party.annotation.AnalyzeDataSource) || @annotation(com.cnpc.dj.party.annotation.AnalyzeDataSource)")
public void pointCut(){

}

@Before("pointCut() && @annotation(analyzeDataSource)")
public void doBefore(AnalyzeDataSource analyzeDataSource){
    log.info("选择数据源---"+analyzeDataSource.value().getValue());
    DataSourceContextHolder.setDataSource(analyzeDataSource.value().getValue());
}

@After("pointCut()")
public void doAfter(){
    DataSourceContextHolder.clear();
}

}

自定义注解
package com.cnpc.dj.party.annotation;

import com.cnpc.dj.party.enums.DataSourceEnum;

import java.lang.annotation.*;

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AnalyzeDataSource {

/**
 * zj2020-07-23默认是查询汇总数据
 * @return
 */
DataSourceEnum value() default DataSourceEnum.REPORT;

}

枚举类
package com.cnpc.dj.party.enums;

public enum DataSourceEnum {
/**
* 测试用的一个数据库
/
REPORT(“report”),
/
*
* 测试用的一个数据库
*/
DETAIL(“detail”);

private String value;

DataSourceEnum(String value){this.value=value;}

public String getValue() {
    return value;
}

}

mybatis-config 文件

<?xml version="1.0" encoding="UTF-8" ?>
	<!-- 全局启用或禁用延迟加载。当禁用时,所有关联对象都会即时加载。 -->
	<!-- <setting name="lazyLoadingEnabled" value="true"/> -->
	
	<!-- 当启用时,有延迟加载属性的对象在被调用时将会完全加载任意属性。否则,每种属性将会按需要加载。 -->
	<setting name="aggressiveLazyLoading" value="true"/>
	
	<!-- 是否允许单条sql 返回多个数据集  (取决于驱动的兼容性) default:true -->
	<setting name="multipleResultSetsEnabled" value="true"/>
	
	<!-- 是否可以使用列的别名 (取决于驱动的兼容性) default:true -->
	<setting name="useColumnLabel" value="true"/>
	
	<!-- 允许JDBC 生成主键。需要驱动器支持。如果设为了true,这个设置将强制使用被生成的主键,有一些驱动器不兼容不过仍然可以执行。  default:false  -->
	<setting name="useGeneratedKeys" value="false"/>
	
	<!-- 指定 MyBatis 如何自动映射 数据基表的列 NONE:不隐射 PARTIAL:部分  FULL:全部  -->  
	<setting name="autoMappingBehavior" value="PARTIAL"/>
	
	<!-- 这是默认的执行类型  (SIMPLE: 简单; REUSE: 执行器可能重复使用prepared statements语句;BATCH: 执行器可以重复执行语句和批量更新)  -->
	<setting name="defaultExecutorType" value="SIMPLE"/>
	
	<!-- 使用驼峰命名法转换字段。 -->
	<setting name="mapUnderscoreToCamelCase" value="true"/>
	
	<!-- 设置本地缓存范围 session:就会有数据的共享  statement:语句范围 (这样就不会有数据的共享 ) defalut:session -->
    <setting name="localCacheScope" value="SESSION"/>
	
    <!-- 设置但JDBC类型为空时,某些驱动程序 要指定值,default:OTHER,插入空值时不需要指定类型 -->
    <setting name="jdbcTypeForNull" value="NULL"/>
	
	<!-- 打印查询语句 -->
    <setting name="logImpl" value="STDOUT_LOGGING" />
</settings>

<typeAliases>
	<typeAlias alias="Integer" type="java.lang.Integer" />
	<typeAlias alias="Long" type="java.lang.Long" />
	<typeAlias alias="HashMap" type="java.util.HashMap" />
	<typeAlias alias="LinkedHashMap" type="java.util.LinkedHashMap" />
	<typeAlias alias="ArrayList" type="java.util.ArrayList" />
	<typeAlias alias="LinkedList" type="java.util.LinkedList" />
</typeAliases>
<mappers>
</mappers>

前端头部设置后端通过拦截器统一设置
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

//
@Aspect
@Order(1)
@Configuration
public class DataSourceAspect {
private static final String dsNo=“dsNo”;//数据库编号 从header中取

/**
 * 切入点,放在controller的每个方法上进行切入,更新数据源
 */
@Pointcut("execution(* com.eck.auto.controller..*.*(..))")
private void anyMethod(){}//定义一个切入点
@Before("anyMethod()")
public void dataSourceChange()
{
    //请求头head中获取对应数据库编号
    String no = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getHeader(dsNo);
    System.out.print("当前数据源编号:"+no);
    if(StringUtils.isEmpty(no)){
        //TODO 根据业务抛异常
    }
    DataSourceHolder.setDataSource(no);
    /*这里数据库项目编号来更改对应的数据源*/
}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值