跳至主要內容

数据模块构建

wangdx大约 11 分钟

多数据源切换

server:
  port: 80
spring:
  datasource: # 数据源配置
    master: # 主库数据源
      type: com.alibaba.druid.pool.xa.DruidXADataSource
      driverClassName: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://192.168.16.8:3306/yootk
      username: yix
      password: yix
    slave: # 从库数据源
      enabled: true #是否启用,默认不启用
      type: com.alibaba.druid.pool.xa.DruidXADataSource
      driverClassName: com.mysql.cj.jdbc.Driver
      url: jdbc:mysql://192.168.16.8:3306/muyan
      username: yix
      password: yix
    druid:
      initial-size: 5                # 初始化连接池大小
      min-idle: 10                # 最小维持的连接池大小
      max-active: 20                # 最大支持的连接池大小
      max-wait: 60000                # 最大等待时间(毫秒)
      connectTimeout: 30000          #配置连接超时时间
      socketTimeout: 60000            # 配置网络超时时间
      time-between-eviction-runs-millis: 60000    # 关闭空闲连接间隔(毫秒)
      min-evictable-idle-time-millis: 300000        # 连接最小生存时间(毫秒)
      maxEvictableIdleTimeMillis: 900000           #配置一个连接在池中最大生存的时间,单位是毫秒
      validation-query: SELECT 1 FROM dual        # 数据库状态检测
      test-while-idle: true            # 申请连接的时候检测连接是否有效
      test-on-borrow: false            # 申请连接时检测连接是否有效
      test-on-return: false            # 归还连接时检测连接是否有效
      pool-prepared-statements: false        # PSCache缓存
      max-pool-prepared-statement-per-connection-size: 20    # 配置PS缓存
      filters: stat,wall,slf4j
      connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=6000;druid.stat.logSlowSql=true;druid.slf4j.enabled=true;druid.slf4j.dataSourceLogEnabled=true;druid.slf4j.statementExecutableSqlLogEnable=true;druid.slf4j.resultSetLogEnabled=true;druid.wall.enabled=true;druid.wall.config.multiStatementAllow=true;druid.wall.config.deleteAllow=false
      stat-view-servlet: # 监控界面配置
        enabled: true    # 启用druid监控界面
        allow: 127.0.0.1    # 设置访问白名单,不填则允许所有访问
        login-username: yix   # 用户名
        login-password: yix    # 密码
        reset-enable: true  # 允许重置
        url-pattern: /druid/*  # 访问路径
      web-stat-filter: # WEB监控
        enabled: true  # 启动URI监控
        url-pattern: /*   # 跟踪根路径下的全部服务
        exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"    # 跟踪排除
      aop-patterns: "com.yix.web.action.*" # Spring监控
package com.yix.framework.config.druid;

import com.alibaba.druid.filter.stat.StatFilter;
import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
import com.alibaba.druid.util.Utils;
import com.yix.common.enums.DataSourceType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;

import javax.servlet.*;
import javax.sql.DataSource;
import java.io.IOException;
import java.util.Properties;

/**
 * @author wangdx
 */
@Configuration
public class DruidDataSourceConfig {
    // 对应的application.yml配置项前缀:spring.datasource.druid.xx(xx是具体的配置子项)
    private static final String DRUID_POOL_PREFIX = "spring.datasource.druid.";
    private static final String DATABASE_MASTER_DRUID_PREFIX = "spring.datasource.master.";
    private static final String DATABASE_SLAVE_DRUID_PREFIX = "spring.datasource.slave.";

    @Bean("masterDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource masterDataSource(
            @Autowired Environment env) { // 数据源的配置
        AtomikosDataSourceBean dataSourceBean = new AtomikosDataSourceBean();
        dataSourceBean.setXaDataSourceClassName(env.getProperty(DATABASE_MASTER_DRUID_PREFIX + "type"));
        dataSourceBean.setUniqueResourceName(DataSourceType.MASTER.getSourceName());
        dataSourceBean.setMinPoolSize(env.getProperty(DRUID_POOL_PREFIX + "min-idle", Integer.class));
        dataSourceBean.setMaxPoolSize(env.getProperty(DRUID_POOL_PREFIX + "max-active", Integer.class));
        Properties properties = build(env, DATABASE_MASTER_DRUID_PREFIX, DRUID_POOL_PREFIX);
        dataSourceBean.setXaProperties(properties); // 保存所有的配置属性
        return dataSourceBean;
    }

    @Bean("slaveDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.slave")
    @ConditionalOnProperty(prefix = "spring.datasource.slave", name = "enabled", havingValue = "true")
    public DataSource slaveDataSource(
            @Autowired Environment env) { // 数据源的配置
        AtomikosDataSourceBean dataSourceBean = new AtomikosDataSourceBean();
        dataSourceBean.setXaDataSourceClassName(env.getProperty(DATABASE_SLAVE_DRUID_PREFIX + "type"));
        dataSourceBean.setUniqueResourceName(DataSourceType.SLAVE.getSourceName());
        dataSourceBean.setMinPoolSize(env.getProperty(DRUID_POOL_PREFIX + "min-idle", Integer.class));
        dataSourceBean.setMaxPoolSize(env.getProperty(DRUID_POOL_PREFIX + "max-active", Integer.class));
        Properties properties = build(env, DATABASE_SLAVE_DRUID_PREFIX, DRUID_POOL_PREFIX);
        dataSourceBean.setXaProperties(properties); // 保存所有的配置属性
        return dataSourceBean;
    }

    private Properties build(Environment env,
                             String databasePrefix, String druidPrefix) {
        Properties prop = new Properties();
        prop.put("url", env.getProperty(databasePrefix + "url"));
        prop.put("username", env.getProperty(databasePrefix + "username"));
        prop.put("password", env.getProperty(databasePrefix + "password"));
        prop.put("driverClassName", env.getProperty(
                databasePrefix + "driverClassName", ""));
        prop.put("initialSize", env.getProperty(
                druidPrefix + "initial-size", Integer.class));
        prop.put("maxActive", env.getProperty(druidPrefix + "max-active", Integer.class));
        prop.put("minIdle", env.getProperty(druidPrefix + "min-idle", Integer.class));
        prop.put("maxWait", env.getProperty(druidPrefix + "max-wait", Integer.class));
        prop.put("poolPreparedStatements", env.getProperty(
                druidPrefix + "pool-prepared-statements", Boolean.class));
        prop.put("maxPoolPreparedStatementPerConnectionSize",
                env.getProperty(druidPrefix +
                        "max-pool-prepared-statement-per-connection-size", Integer.class));
        prop.put("maxPoolPreparedStatementPerConnectionSize",
                env.getProperty(druidPrefix +
                        "max-pool-prepared-statement-per-connection-size", Integer.class));
        prop.put("validationQuery", env.getProperty(druidPrefix + "validation-query"));
        prop.put("testOnBorrow", env.getProperty(
                druidPrefix + "test-on-borrow", Boolean.class));
        prop.put("testOnReturn", env.getProperty(
                druidPrefix + "test-on-return", Boolean.class));
        prop.put("testWhileIdle", env.getProperty(
                druidPrefix + "test-while-idle", Boolean.class));
        prop.put("timeBetweenEvictionRunsMillis",
                env.getProperty(druidPrefix +
                        "time-between-eviction-runs-millis", Integer.class));
        prop.put("minEvictableIdleTimeMillis", env.getProperty(druidPrefix +
                "min-evictable-idle-time-millis", Integer.class));
        prop.put("filters", env.getProperty(druidPrefix + "filters"));
        prop.put("connectionProperties", env.getProperty(druidPrefix + "connectionProperties"));
        return prop;
    }

    /**
     * 去除监控页面底部的广告
     */
    @SuppressWarnings({"rawtypes", "unchecked"})
    @Bean
    @ConditionalOnProperty(name = "spring.datasource.druid.stat-view-servlet.enabled", havingValue = "true")
    public FilterRegistrationBean removeDruidFilterRegistrationBean(DruidStatProperties properties) {
        // 获取web监控页面的参数
        DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();
        // 提取common.js的配置路径
        String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*";
        String commonJsPattern = pattern.replaceAll("\\*", "js/common.js");
        final String filePath = "support/http/resources/js/common.js";
        // 创建filter进行过滤
        Filter filter = new Filter() {
            @Override
            public void init(javax.servlet.FilterConfig filterConfig) throws ServletException {
            }

            @Override
            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                    throws IOException, ServletException {
                chain.doFilter(request, response);
                // 重置缓冲区,响应头不会被重置
                response.resetBuffer();
                // 获取common.js
                String text = Utils.readFromResource(filePath);
                // 正则替换banner, 除去底部的广告信息
                text = text.replaceAll("<a.*?banner\"></a><br/>", "");
                text = text.replaceAll("powered.*?shrek.wang</a>", "");
                response.getWriter().write(text);
            }

            @Override
            public void destroy() {
            }
        };
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        registrationBean.setFilter(filter);
        registrationBean.addUrlPatterns(commonJsPattern);
        return registrationBean;
    }
}

package com.yix.framework.config.dynamic;

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

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

/**
 * 数据源动态切换
 *
 * @author wangdx
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
    /**
     * 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
     * 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
     */
    private static final ThreadLocal<String> DATASOURCE_CONTEXT_HOLDER = new ThreadLocal<>();
    /**
     * 构建动态数据源
     *
     * @param defaultTargetDataSource 默认的数据源对象
     * @param targetDataSources       全部的数据源对象
     */
    public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
        super.setDefaultTargetDataSource(defaultTargetDataSource); // 调用父类方法
        super.setTargetDataSources(targetDataSources); // 调用父类方法
        super.afterPropertiesSet(); // 属性的设置
    }

    @Override
    protected Object determineCurrentLookupKey() { // 获取当前的查询结果
        return getDataSource();
    }

    public static void setDataSource(String dataSourceName) {    // 扩充的数据源处理方法
        DATASOURCE_CONTEXT_HOLDER.set(dataSourceName); // 保存当前线程多少数据源名称
    }

    public static String getDataSource() { // 获取数据源的名称标记
        return DATASOURCE_CONTEXT_HOLDER.get();
    }

    public static void clearDataSource() {
        DATASOURCE_CONTEXT_HOLDER.remove(); // 清除当前线程之中保存的数据源名称
    }
}
package com.yix.framework.config.dynamic;

import com.yix.common.enums.DataSourceType;
import com.yix.common.utils.spring.SpringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Primary;

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

/**
 * 动态数据源配置
 *
 * @author wangdx
 */
@Configuration
@Slf4j
public class DynamicDataSourceConfig {
    @Bean("dataSource")
    @Primary
    public DynamicDataSource dynamicDataSource(DataSource masterDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceType.MASTER.getSourceName(), masterDataSource);
        setDataSource(targetDataSources, DataSourceType.SLAVE.getSourceName(), DataSourceType.SLAVE.getBeanName());
        return new DynamicDataSource(masterDataSource, targetDataSources);
    }

    /**
     * 设置数据源
     *
     * @param targetDataSources 备选数据源集合
     * @param sourceName        数据源名称
     * @param beanName          bean名称
     */
    public void setDataSource(Map<Object, Object> targetDataSources, String sourceName, String beanName) {
        try {
            DataSource dataSource = SpringUtils.getBean(beanName);
            targetDataSources.put(sourceName, dataSource);
        } catch (Exception e) {
            log.warn(sourceName + "数据库未开启!");
        }
    }
}

package com.yix.framework.config.dynamic;

import com.yix.common.annotation.DS;
import com.yix.common.enums.DataSourceType;
import com.yix.common.utils.StringUtils;
import com.yix.common.utils.spring.SpringUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
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.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.util.Objects;

/**
 * 数据源的切面处理类
 *
 * @author wangdx
 */
@Component
@Aspect // 是一个切面处理类
@Order(-100) // 让这个切面处理类的执行顺序可以高一些
@Slf4j // 为了便于观察,将所有的数据全部都以日志的形式输出
public class DataSourceAspect {
    @Before("execution(* com.yix.*.dao.master..*.*(..))")
    public void switchYootkDataSource() {   // 切换指定的数据源
        swtichDataSorce(DataSourceType.MASTER);
        log.debug("数据源切换到:{}", DynamicDataSource.getDataSource()); // 日志输出
    }

    @Before("execution(* com.yix.*.dao.slave..*.*(..))")
    public void switchMuyanDataSource() {   // 切换指定的数据源
        swtichDataSorce(DataSourceType.SLAVE);
        log.debug("数据源切换到:{}", DynamicDataSource.getDataSource()); // 日志输出
    }

    @Pointcut("@annotation(com.yix.common.annotation.DS)"
            + "|| @within(com.yix.common.annotation.DS)")
    public void dsPointCut() {
    }

    @Around("dsPointCut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        DS ds = getDataSource(point);
        if (StringUtils.isNotNull(ds)) {
            swtichDataSorce(ds.value());
            log.debug("数据源切换到:{}", DynamicDataSource.getDataSource()); // 日志输出
        }

        try {
            return point.proceed();
        } finally {
            // 销毁数据源 在执行方法之后
            DynamicDataSource.clearDataSource();
        }
    }

    /**
     * 数据源切换
     *
     * @param type
     */
    public void swtichDataSorce(DataSourceType type) {
        try {
            DataSource dataSource = SpringUtils.getBean(type.getBeanName());
            DynamicDataSource.setDataSource(type.getSourceName());
        } catch (Exception e) {
            log.warn(type.getSourceName() + "数据库未开启!采用兜底数据库");
            DynamicDataSource.setDataSource(DataSourceType.MASTER.getSourceName());
        }
    }

    /**
     * 获取需要切换的数据源
     */
    public DS getDataSource(ProceedingJoinPoint point) {
        MethodSignature signature = (MethodSignature) point.getSignature();
        DS dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DS.class);
        if (Objects.nonNull(dataSource)) {
            return dataSource;
        }
        return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DS.class);
    }

}

mybatis+plus 集成

package com.yix.framework.config.mybatis;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.yix.framework.config.jta.dynamic.MultiDataSourceTransactionFactory;
import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

import javax.sql.DataSource;

/**
 * Mybatis拦截器配置
 *
 * @author wangdx
 */
@Configuration
public class MybatisConfig {
    private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();

    @Bean
    public MybatisPlusInterceptor getMybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(
                new PaginationInnerInterceptor(DbType.MYSQL) // 使用MySQL分页实现
        );
        return interceptor;
    }

    @Bean("mybatisSqlSessionFactoryBean")
    public MybatisSqlSessionFactoryBean mybatisSqlSessionFactoryBean(
            @Autowired DataSource dataSource, // 要使用的数据源
            @Value("${mybatis-plus.config-location}") Resource configLocation, // 资源文件路径
            @Value("${mybatis-plus.type-aliases-package}") String typeAliasesPackage, // 扫描别名
            @Value("${mybatis-plus.mapper-locations}") String mapperLocations, // Mapping映射路径
            @Value("${mybatis-plus.global-config.banner}") Boolean banner,
            @Value("${mybatis-plus.global-config.db-config.logic-not-delete-value}") String logicNotDeleteValue,
            @Value("${mybatis-plus.global-config.db-config.logic-delete-value:}") String logicDeleteValue
    ) throws Exception {
        MybatisSqlSessionFactoryBean mybatisPlus = new MybatisSqlSessionFactoryBean();
        mybatisPlus.setDataSource(dataSource); // 配置项目中要使用的数据源
        mybatisPlus.setVfs(SpringBootVFS.class); // 配置程序的扫描类
        mybatisPlus.setTypeAliasesPackage(typeAliasesPackage); // 扫描包的别名
        mybatisPlus.setTransactionFactory(new MultiDataSourceTransactionFactory()); // 自定义事务工厂
        Resource[] mappings = this.resourcePatternResolver.getResources(mapperLocations);
        mybatisPlus.setMapperLocations(mappings);
        GlobalConfig.DbConfig dbConfig = new GlobalConfig.DbConfig(); // 数据配置
        dbConfig.setLogicNotDeleteValue(logicNotDeleteValue); // 未删除时的数据内容
        dbConfig.setLogicDeleteValue(logicDeleteValue); // 删除时的数据内容
        GlobalConfig globalConfig = new GlobalConfig(); // 定义全局配置
        globalConfig.setDbConfig(dbConfig); // 全局配置项
        globalConfig.setBanner(banner); //是否显示logo
//        globalConfig.setMetaObjectHandler(new FlagMetaObjectHandler());
        mybatisPlus.setGlobalConfig(globalConfig);
        return mybatisPlus;
    }
}

数据源事务处理

package com.yix.framework.config.jta;

import org.aspectj.lang.annotation.Aspect;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.interceptor.NameMatchTransactionAttributeSource;
import org.springframework.transaction.interceptor.RuleBasedTransactionAttribute;
import org.springframework.transaction.interceptor.TransactionAttribute;
import org.springframework.transaction.interceptor.TransactionInterceptor;

import java.util.HashMap;
import java.util.Map;

/**
 * @author wangdx
 */
@Configuration
@Aspect
public class TransactionConfig {
    private static final int TRANSACTION_METHOD_TIMEOUT = 6; // 事务处理的超时时间
    private static final String AOP_POINTCUT_EXPRESSION = "execution (* com.yix..service.*.*(..))";
    @Autowired
    private TransactionManager transactionManager; // 事务管理器

    @Bean("txAdvice")
    public TransactionInterceptor transactionInterceptorConfig() {
        // 配置数据读取事务规则
        RuleBasedTransactionAttribute readOnlyAttribute = new RuleBasedTransactionAttribute();
        readOnlyAttribute.setReadOnly(true); // 只读事务
        readOnlyAttribute.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED); // 非事务运行
        // 配置了数据更新事务规则
        RuleBasedTransactionAttribute requiredAttribute = new RuleBasedTransactionAttribute();
        requiredAttribute.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); // 事务开启
        requiredAttribute.setTimeout(TRANSACTION_METHOD_TIMEOUT); // 事务处理超时时间
        // 配置所有要进行事务处理的方法名称定义
        Map<String, TransactionAttribute> transactionAttributeMap = new HashMap<>();
        transactionAttributeMap.put("add*", requiredAttribute);
        transactionAttributeMap.put("edit*", requiredAttribute);
        transactionAttributeMap.put("delete*", requiredAttribute);
        transactionAttributeMap.put("list*", readOnlyAttribute);
        transactionAttributeMap.put("get*", readOnlyAttribute);
        NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
        source.setNameMap(transactionAttributeMap); // 配置方法名称的映射
        TransactionInterceptor interceptor = new TransactionInterceptor(transactionManager, source);
        return interceptor;
    }

    @Bean("Advisor")
    public Advisor transactionAdvisor() {
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
        return new DefaultPointcutAdvisor(pointcut, transactionInterceptorConfig());
    }

}
package com.yix.framework.config.jta.dynamic;

import com.yix.framework.config.dynamic.DynamicDataSource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.DataSourceUtils;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 多数据源事务管理器
 * 当前的项目都是基于MyBatis / MyBatisPlus开发的,所以此时一定要基于MyBatis标准开发事务管理器
 *
 * @author wangdx
 */
@Slf4j
public class MultiDataSourceTransaction implements org.apache.ibatis.transaction.Transaction {
    private DataSource dataSource; // 事务是需要有DataSource支持
    private Connection currentConnection; // 当前的数据库连接
    private boolean autoCommit; // 是否要进行自动提交启用
    private boolean isConnectionTransactional; // 是否要启用事务
    private ConcurrentHashMap<String, Connection> otherConnectionMap; // 保存其他的Connection对象
    private String currentDatabaseName; // 当前数据库名称

    public MultiDataSourceTransaction(DataSource dataSource) {
        this.dataSource = dataSource; // 保存数据元
        this.otherConnectionMap = new ConcurrentHashMap<>(); // 保存其他数据库连接
        this.currentDatabaseName = DynamicDataSource.getDataSource(); // 获取当前操作的数据库名称
    }

    private void openMainConnection() throws SQLException { // 打开一个连接
        // 通过当前得到的DataSource接口实例来获取一个Connection接口实例
        this.currentConnection = DataSourceUtils.getConnection(this.dataSource);
        this.autoCommit = this.currentConnection.getAutoCommit(); // 获取当前的是否自动提交的状态
        this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.currentConnection, this.dataSource);
        log.debug("当前数据库连接:{}、事务支持状态:{}。", this.currentConnection, this.isConnectionTransactional);
    }

    @Override
    public Connection getConnection() throws SQLException { // 获取数据库连接
        // 存在有数据源的前提下才可以实现连接的获取,那么首先要判断是否有数据源存在
        String datasourceName = DynamicDataSource.getDataSource(); // 获取当前数据源名称
        log.debug("【MultiDataSourceTransaction.getConnection()】数据源名称:{}", datasourceName);
        if (null == datasourceName || datasourceName.equals(this.currentDatabaseName)) {    // 现在的数据源为当前使用的数据库
            if (this.currentConnection != null) {   // 当前存在有数据库连接
                return this.currentConnection; // 返回当前的连接
            } else {    // 如果当前的数据源没有开启过连接
                openMainConnection(); // 开启一个数据库连接
                this.currentDatabaseName = datasourceName; // 保存当前的数据库名称
                return this.currentConnection; // 返回当前的连接
            }
        } else {    // 没有连接
            if (!this.otherConnectionMap.containsKey(datasourceName)) { // 没有当前这个数据源的名称存在
                Connection conn = dataSource.getConnection(); // 获取数据库连接
                this.otherConnectionMap.put(datasourceName, conn); // 保存连接
            }
            return this.otherConnectionMap.get(datasourceName);
        }
    }

    @Override
    public void commit() throws SQLException { // 数据库事务提交
        log.debug("commit:currentConnection = {}、isConnectionTransactional = {}、autoCommit = {}", this.currentConnection, this.isConnectionTransactional, this.autoCommit);
        // 当前存在有Connection接口实例,同时没有开启自动的事务提交,并且存在有支持事务的连接
        if (this.currentConnection != null && !this.isConnectionTransactional && !this.autoCommit) {
            log.debug("数据库事务提交,当前数据库连接:{}", this.currentConnection);
            this.currentConnection.commit(); // 提交当前的数据库事务
            for (Connection connecion : this.otherConnectionMap.values()) { // 控制其它的数据库连接
                connecion.commit(); // 保证其他的连接提交事务
            }
        }
    }

    @Override
    public void rollback() throws SQLException { // 事务回滚
        log.error("rollback:catalog = {}、isConnectionTransactional = {}、autoCommit = {}", this.currentConnection.getCatalog(), this.isConnectionTransactional, this.autoCommit);
        if (this.currentConnection != null && !this.isConnectionTransactional && !this.autoCommit) {
            log.error("数据库事务回滚,当前数据库连接:{}", this.currentConnection);
            this.currentConnection.rollback(); // 回滚当前的数据库事务
            for (Connection connecion : this.otherConnectionMap.values()) { // 控制其它的数据库连接
                connecion.rollback(); // 保证其他的连接提交回滚
            }
        }
    }

    @Override
    public void close() throws SQLException { // 事务关闭
        DataSourceUtils.releaseConnection(this.currentConnection, this.dataSource);
        for (Connection connecion : this.otherConnectionMap.values()) { // 控制其它的数据库连接
            DataSourceUtils.releaseConnection(connecion, this.dataSource);
        }
    }

    @Override
    public Integer getTimeout() throws SQLException { // 超时配置
        return 5000;
    }
}
package com.yix.framework.config.jta.dynamic;

import org.apache.ibatis.session.TransactionIsolationLevel;
import org.apache.ibatis.transaction.Transaction;
import org.mybatis.spring.transaction.SpringManagedTransactionFactory;

import javax.sql.DataSource;

/**
 * @author wangdx
 */
public class MultiDataSourceTransactionFactory extends SpringManagedTransactionFactory {
    @Override
    public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
        return new MultiDataSourceTransaction(dataSource); // 事务控制类
    }
}

package com.yix.framework.config.jta.dynamic;

import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.jta.JtaTransactionManager;

import javax.transaction.SystemException;
import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;

/**
 * XZA事务管理器
 *
 * @author wangdx
 */
@Configuration
public class XADruidTransactionManager {
    // UserTransaction可以保证在当前线程下的所有数据库操作都使用同一个Connection接口实例
    // 最终可以在进行事务提交或回滚的时候保证事务操作的原子性
    @Bean(name = "userTransaction")
    public UserTransaction getUserTransaction() throws SystemException {
        UserTransactionImp userTransactionImp = new UserTransactionImp();
        userTransactionImp.setTransactionTimeout(1000); // 超时配置
        return userTransactionImp;
    }

    @Bean(name = "atomikosTransactionManager")
    public TransactionManager getTransactionManager() {
        UserTransactionManager transactionManager = new UserTransactionManager();
        transactionManager.setForceShutdown(false); // 关闭强制退出
        return transactionManager;
    }

    @Bean("transactionManager")
    @DependsOn({"userTransaction", "atomikosTransactionManager"})
    public PlatformTransactionManager getPlatformTransactionManager(
            UserTransaction userTransaction,
            TransactionManager atomikosTransactionManager) {
        return new JtaTransactionManager(userTransaction, atomikosTransactionManager);
    }
}

联表查询

但数据库可集成,多数据库建议放弃

1.
// https://mvnrepository.com/artifact/com.github.yulichang/mybatis-plus-join-boot-starter
implementation group: 'com.github.yulichang', name: 'mybatis-plus-join-boot-starter', version: '1.4.11'

自动建表工具

actable 集成

考虑性能和开发效率,做成单独模块

1.// https://mvnrepository.com/artifact/com.gitee.sunchenbin.mybatis.actable/mybatis-enhance-actable
implementation group: 'com.gitee.sunchenbin.mybatis.actable', name: 'mybatis-enhance-actable', version: '1.5.0.RELEASE'
2.
actable: #自动建表工具
  database:
    type: mysql
  model:
    pack: ${mybatis-plus.type-aliases-package}
  table:
    auto: update

不支持多数据源,但数据源可用

mybatis-plus-ext 继承

redis

1.
//redis
        api(libraries.'commons-pool2')
        api('org.springframework.boot:spring-boot-starter-data-redis')
2.
spring:
  ## redis配置
  redis:
    host: 192.168.16.8 # Redis服务器地址
    port: 6379 # Redis服务器连接端口
    password: redis # Redis服务器连接密码
    database: 0 # Redis数据库索引
    timeout: 200ms # 连接超时时间,不能设置为0
    lettuce: # 配置Lettuce
      pool: # 配置连接池
        max-active: 100 # 连接池最大连接数
        max-idle: 29 # 连接池中的最大空闲连接
        min-idle: 10 # 连接池中的最小空闲连接
        max-wait: 1000 # 连接池最大阻塞等待时间
        time-between-eviction-runs: 2000 # 每2秒回收一次空闲连接
上次编辑于: