首页 > 其他分享 >【Database】基于Mybatis的多数据源连接实现方式

【Database】基于Mybatis的多数据源连接实现方式

时间:2024-03-19 14:44:54浏览次数:22  
标签:return String Database 数据源 private statement Mybatis public inheritDoc

场景

出于种种原因有些时候我们的项目中会用到同时连接多个数据源的情况 ,比如数据太大了分了多个库,又比如统计任务只能读原始数据的数据库,写只能在统计结果的库中。

设计思路

基于mybatis我们的方案是这样的,需要自己实现一个SqlSessionTemplate,里面维护了一个dbId到SqlSessionFactory的映射,SqlSessionFactory在初始化时需要指定数据源信息,在执行sql前会调用SqlSessionTemplate的getSqlSessionFactory方法,我们通过重写这个方法,使其通过dbId映射到对应的SqlSessionFactory即可,dbId的储存就需要一个ThreadLocal来存储,保证线程之间的隔离,这样每个线程就能按需去连对应的DB了。

DynamicSqlSessionTemplate

public class DynamicSqlSessionTemplate extends SqlSessionTemplate {
    private final SqlSessionFactory sqlSessionFactory;
    private final ExecutorType executorType;
    private final SqlSession sqlSessionProxy;
    private final PersistenceExceptionTranslator exceptionTranslator;

    // 可以动态增加减少sqlSession
    private Map<String, SqlSessionFactory> targetSqlSessionFactorys;
    private SqlSessionFactory defaultTargetSqlSessionFactory;

    public DynamicSqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
        this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
    }

    public DynamicSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
        this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration()
                .getEnvironment().getDataSource(), true));
    }

    public DynamicSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
                                     PersistenceExceptionTranslator exceptionTranslator) {

        super(sqlSessionFactory, executorType, exceptionTranslator);

        this.sqlSessionFactory = sqlSessionFactory;
        this.executorType = executorType;
        this.exceptionTranslator = exceptionTranslator;

        this.sqlSessionProxy = (SqlSession) newProxyInstance(
                SqlSessionFactory.class.getClassLoader(),
                new Class[]{SqlSession.class},
                new SqlSessionInterceptor());

        this.defaultTargetSqlSessionFactory = sqlSessionFactory;
    }

    public Map<String, SqlSessionFactory> getTargetSqlSessionFactorys() {
        return targetSqlSessionFactorys;
    }

    public void setTargetSqlSessionFactorys(Map<String, SqlSessionFactory> targetSqlSessionFactorys) {
        this.targetSqlSessionFactorys = targetSqlSessionFactorys;
    }

    public void setDefaultTargetSqlSessionFactory(SqlSessionFactory defaultTargetSqlSessionFactory) {
        this.defaultTargetSqlSessionFactory = defaultTargetSqlSessionFactory;
    }

    @Override
    public SqlSessionFactory getSqlSessionFactory() {
        SqlSessionFactory targetSqlSessionFactory = targetSqlSessionFactorys.get(SqlSessionContext.getDatasourceName());
        if (targetSqlSessionFactory != null) {
            return targetSqlSessionFactory;
        } else if (defaultTargetSqlSessionFactory != null) {
            return defaultTargetSqlSessionFactory;
        } else {
            Assert.notNull(targetSqlSessionFactorys, "Property 'targetSqlSessionFactorys' or 'defaultTargetSqlSessionFactory' are required");
            Assert.notNull(defaultTargetSqlSessionFactory, "Property 'defaultTargetSqlSessionFactory' or 'targetSqlSessionFactorys' are required");
        }
        return this.sqlSessionFactory;
    }

    @Override
    public Configuration getConfiguration() {
        return this.getSqlSessionFactory().getConfiguration();
    }

    public ExecutorType getExecutorType() {
        return this.executorType;
    }

    public PersistenceExceptionTranslator getPersistenceExceptionTranslator() {
        return this.exceptionTranslator;
    }

    /**
     * {@inheritDoc}
     */
    public <T> T selectOne(String statement) {
        return this.sqlSessionProxy.<T>selectOne(statement);
    }

    /**
     * {@inheritDoc}
     */
    public <T> T selectOne(String statement, Object parameter) {
        return this.sqlSessionProxy.<T>selectOne(statement, parameter);
    }

    /**
     * {@inheritDoc}
     */
    public <K, V> Map<K, V> selectMap(String statement, String mapKey) {
        return this.sqlSessionProxy.<K, V>selectMap(statement, mapKey);
    }

    /**
     * {@inheritDoc}
     */
    public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) {
        return this.sqlSessionProxy.<K, V>selectMap(statement, parameter, mapKey);
    }

    /**
     * {@inheritDoc}
     */
    public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
        return this.sqlSessionProxy.<K, V>selectMap(statement, parameter, mapKey, rowBounds);
    }

    /**
     * {@inheritDoc}
     */
    public <E> List<E> selectList(String statement) {
        return this.sqlSessionProxy.<E>selectList(statement);
    }

    /**
     * {@inheritDoc}
     */
    public <E> List<E> selectList(String statement, Object parameter) {
        return this.sqlSessionProxy.<E>selectList(statement, parameter);
    }

    /**
     * {@inheritDoc}
     */
    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        return this.sqlSessionProxy.<E>selectList(statement, parameter, rowBounds);
    }

    /**
     * {@inheritDoc}
     */
    public void select(String statement, ResultHandler handler) {
        this.sqlSessionProxy.select(statement, handler);
    }

    /**
     * {@inheritDoc}
     */
    public void select(String statement, Object parameter, ResultHandler handler) {
        this.sqlSessionProxy.select(statement, parameter, handler);
    }

    /**
     * {@inheritDoc}
     */
    public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
        this.sqlSessionProxy.select(statement, parameter, rowBounds, handler);
    }

    /**
     * {@inheritDoc}
     */
    public int insert(String statement) {
        return this.sqlSessionProxy.insert(statement);
    }

    /**
     * {@inheritDoc}
     */
    public int insert(String statement, Object parameter) {
        return this.sqlSessionProxy.insert(statement, parameter);
    }

    /**
     * {@inheritDoc}
     */
    public int update(String statement) {
        return this.sqlSessionProxy.update(statement);
    }

    /**
     * {@inheritDoc}
     */
    public int update(String statement, Object parameter) {
        return this.sqlSessionProxy.update(statement, parameter);
    }

    /**
     * {@inheritDoc}
     */
    public int delete(String statement) {
        return this.sqlSessionProxy.delete(statement);
    }

    /**
     * {@inheritDoc}
     */
    public int delete(String statement, Object parameter) {
        return this.sqlSessionProxy.delete(statement, parameter);
    }

    /**
     * {@inheritDoc}
     */
    public <T> T getMapper(Class<T> type) {
        return getConfiguration().getMapper(type, this);
    }

    /**
     * {@inheritDoc}
     */
    public void commit() {
        throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession");
    }

    /**
     * {@inheritDoc}
     */
    public void commit(boolean force) {
        throw new UnsupportedOperationException("Manual commit is not allowed over a Spring managed SqlSession");
    }

    /**
     * {@inheritDoc}
     */
    public void rollback() {
        throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession");
    }

    /**
     * {@inheritDoc}
     */
    public void rollback(boolean force) {
        throw new UnsupportedOperationException("Manual rollback is not allowed over a Spring managed SqlSession");
    }

    /**
     * {@inheritDoc}
     */
    public void close() {
        throw new UnsupportedOperationException("Manual close is not allowed over a Spring managed SqlSession");
    }

    /**
     * {@inheritDoc}
     */
    public void clearCache() {
        this.sqlSessionProxy.clearCache();
    }

    /**
     * {@inheritDoc}
     */
    public Connection getConnection() {
        return this.sqlSessionProxy.getConnection();
    }

    /**
     * {@inheritDoc}
     *
     * @since 1.0.2
     */
    public List<BatchResult> flushStatements() {
        return this.sqlSessionProxy.flushStatements();
    }

    /**
     * Proxy needed to route MyBatis method calls to the proper SqlSession got from Spring's Transaction Manager It also
     * unwraps exceptions thrown by {@code Method#invoke(Object, Object...)} to pass a {@code PersistenceException} to
     * the {@code PersistenceExceptionTranslator}.
     */
    private class SqlSessionInterceptor implements InvocationHandler {
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            final SqlSession sqlSession = getSqlSession(
                    DynamicSqlSessionTemplate.this.getSqlSessionFactory(),
                    DynamicSqlSessionTemplate.this.executorType,
                    DynamicSqlSessionTemplate.this.exceptionTranslator);
            try {
                Object result = method.invoke(sqlSession, args);
                if (!isSqlSessionTransactional(sqlSession, DynamicSqlSessionTemplate.this.getSqlSessionFactory())) {
                    // force commit even on non-dirty sessions because some databases require
                    // a commit/rollback before calling close()
                    sqlSession.commit(true);
                }
                return result;
            } catch (Throwable t) {
                Throwable unwrapped = unwrapThrowable(t);
                if (DynamicSqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
                    Throwable translated = DynamicSqlSessionTemplate.this.exceptionTranslator
                            .translateExceptionIfPossible((PersistenceException) unwrapped);
                    if (translated != null) {
                        unwrapped = translated;
                    }
                }
                throw unwrapped;
            } finally {
                closeSqlSession(sqlSession, DynamicSqlSessionTemplate.this.getSqlSessionFactory());
            }
        }
    }

}

SqlSessionContext

public abstract class SqlSessionContext {

    // 解决子线程不共享数据问题
    private static final ThreadLocal<String> contextHolder = new InheritableThreadLocal<String>();

    public static String getDatasourceName() {
        return contextHolder.get();
    }

    public static void switchDatasource(String datasourceId) {
        contextHolder.set(datasourceId);
    }

    public static void clearContextType() {
        contextHolder.remove();
    }

    private static void print(String datasourceName) {
        DynamicSqlSessionTemplate dynamicSqlSessionTemplate = SpringUtil.getBean(DynamicSqlSessionTemplate.class);
        Map<String, SqlSessionFactory> targetSqlSessionFactorys = dynamicSqlSessionTemplate.getTargetSqlSessionFactorys();
        SqlSessionFactory sqlSessionFactory = targetSqlSessionFactorys.getOrDefault(datasourceName, null);
        if (sqlSessionFactory == null) {
            return;
        }
        HikariDataSource dataSource = (HikariDataSource) sqlSessionFactory.getConfiguration()
                .getEnvironment()
                .getDataSource();
        if (dataSource == null) {
            return;
        }
        log.info("datasource switch to {}", datasourceName);
    }
}

DynamicDatasourceConfig

@Configuration
@MapperScan(basePackages = {"cn.witsky.cpc.dao"}, sqlSessionTemplateRef = "dynamicSqlSessionTemplate")
public class DynamicDatasourceConfig {
    @Resource(name = "cpcConfig")
    private CpcConfig config;

    @Bean(name = "dynamicSqlSessionTemplate")
    public DynamicSqlSessionTemplate getDynamicSqlSessionTemplate() {
        if (ArrayUtil.isEmpty(config.getDatasource())) {
            log.error("config at least one datasource!");
        }
        HashSet<String> names = new HashSet<>();
        HashMap<String, SqlSessionFactory> map = new HashMap<>();
        for (Datasource datasource : config.getDatasource()) {
            log.info("loading datasource|id ={}", datasource.getDatabaseId());
            if (!names.add(datasource.getDatabaseId())) {
                log.info("databaseId duplicate,skip!");
                continue;
            }
            SqlSessionFactory sqlSessionFactory = buildSqlSessionFactory(datasource);
            if (ObjUtil.isNull(sqlSessionFactory)) {
                continue;
            }
            map.put(datasource.getDatabaseId(), sqlSessionFactory);
        }
        // 取第一个数据源为默认数据源
        String first = map.keySet().stream().findFirst().orElse("");
        DynamicSqlSessionTemplate dynamicSqlSessionTemplate = new DynamicSqlSessionTemplate(map.get(first));
        dynamicSqlSessionTemplate.setTargetSqlSessionFactorys(map);
        return dynamicSqlSessionTemplate;
    }

    private SqlSessionFactory buildSqlSessionFactory(Datasource datasource) {
        try {
            HikariDataSource hikariDataSource = new HikariDataSource();
            // 数据库配置
            hikariDataSource.setJdbcUrl(datasource.getUrl());
            hikariDataSource.setUsername(datasource.getUsername());
            hikariDataSource.setPassword(datasource.getPassword());
            hikariDataSource.setPoolName(datasource.getDatabaseId());
            // 数据源连接池hikari公共配置
            hikariDataSource.setDriverClassName(config.getDatasourceHikari().getDriverClassName());
            hikariDataSource.setMinimumIdle(config.getDatasourceHikari().getMinimumIdle());
            hikariDataSource.setMaximumPoolSize(config.getDatasourceHikari().getMaximumPoolSize());
            hikariDataSource.setConnectionTimeout(config.getDatasourceHikari().getConnectionTimeout());
            hikariDataSource.setIdleTimeout(config.getDatasourceHikari().getIdleTimeout());
            hikariDataSource.setMaxLifetime(config.getDatasourceHikari().getMaxLifetime());
            // 连接mapper文件组装SqlSessionFactory
            SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
            bean.setDataSource(hikariDataSource);
            bean.setMapperLocations(new PathMatchingResourcePatternResolver()
                    .getResources(config.getMybatisMapperLocations()));
            return bean.getObject();
        } catch (Exception e) {
            log.info(e.getMessage(), e);
            return null;
        }
    }
}

Datasource

public class Datasource {
    private String databaseId; // 数据源id
    private String url; // 数据库连接地址
    private String username; // 数据库用户名
    private String password; // 数据库密码
    private String driverClassName; // jdbc驱动
    private String mapperLocations; // mapper文件地址
}

DatasourceHikari

public class DatasourceHikari {
    // 数据库驱动类
    private String driverClassName;
    // 连接池核心线程数。默认值10。小于0或大于maximum-pool-size,都会重置为maximum-pool-size。
    private int minimumIdle = 10;
    // 连接池总最大允许的连接数,超过此值的并发请求会被阻塞。默认值10。小于等于0会被重置为默认值10。
    private int maximumPoolSize = 20;
    // 连接超时时间。默认值30秒。小于250ms会被重置为最小值250ms。
    private long connectionTimeout = 300L;
    // 线程空闲超时时间,当大于核心线程小于最大线程时才生效。默认值10分钟。
    private Long idleTimeout = 6000000L;
    // 线程的最大存活时间,从线程创建开始计时,到此时间会自动关闭,应小于数据库配置的单个连接的最大时长。默认值30分钟。
    private Long maxLifetime = 60000L;
    // 数据库验证SQL
    private String connectionTestQuery = "select 1 from dual";
}

注意,需要关闭自动配置否则启动报错

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})

标签:return,String,Database,数据源,private,statement,Mybatis,public,inheritDoc
From: https://www.cnblogs.com/VergilYang/p/18082782

相关文章

  • 【Database】可选的数据源配置项
    【Database】可选的数据源配置项场景之前的版本中没有数据源依赖,新版本中有数据源依赖,只有部分模块需要配置数据源,其余模块仍然读老的配置,如果使用自动装配的mybatis会启动报错。方案通过一个默认为false的开关控制数据源的加载@Configuration@MapperScan(basePackages={"......
  • SpringBoot集成MybatisPlus
    创建一个基于SpringBoot集成MybatisPlus的示例项目是一个相对直接且实用的过程,它结合了SpringBoot的自动配置特性与MybatisPlus的增强功能,使得数据库操作变得更为简便和高效。下面是一个简单的步骤说明和代码示例,帮助你快速搭建一个SpringBoot集成MybatisPlus的Demo项......
  • Mybatis之like、likeRight、likeLeft的使用
    前言Mybatis中的like用于模糊查询,可以匹配和指定字符串相似的字段。而likeRight和likeLeft则是like的变种,分别表示从开头和结尾匹配。1.likelike:like方法允许您使用’%'通配符执行区分大小写的模式匹配操作。例如:SELECT*FROMusersWHEREusernameLIKE'%张%';......
  • Mybatis-Plus 快速入门
    快速入门地址:快速测试|MyBatis-Plus(baomidou.com)使用第三方组件:1、导入对应依赖2、研究依赖如何配置3、代码如何编写4、提高扩展技术能力步骤1、创建数据库mybatis_plus2、创建user表DROPTABLEIFEXISTS`user`;​CREATETABLE`user`( idBIGINTN......
  • MyBatisPlus怎么多表关联查询?
    在MyBatisPlus中进行多表关联查询通常需要自定义SQL语句,因为MyBatisPlus的默认方法主要是针对单表操作。你可以在Mapper接口中定义自定义查询方法,并使用@Select注解编写SQL语句。以下是一个简单的例子,假设我们有两个表user和order,我们想要查询用户及其订单信息:定义User和Or......
  • 鸿鹄电子招投标系统源码实现与立项流程:基于Spring Boot、Mybatis、Redis和Layui的企业
    随着企业的快速发展,招采管理逐渐成为企业运营中的重要环节。为了满足公司对内部招采管理提升的要求,建立一个公平、公开、公正的采购环境至关重要。在这个背景下,我们开发了一款电子招标采购软件,以最大限度地控制采购成本,提高招投标工作的公开性和透明性,并确保符合国家电子招投标......
  • MyBatis框架
    一、什么是mybatis?MyBatis是一款优秀的半自动的ORM持久层框架,它支持自定义SQL、存储过程以及高级映射。MyBatis免除了几乎所有的JDBC代码以及设置参数和获取结果集的工作。MyBatis可以通过简单的XML或注解来配置和映射原始类型、接口和JavaPOJO(PlainOldJavaObject......
  • mybatis-plus-extension 百万数据多行插入,几秒入库
    百万数据多行插入,几秒入库最近遇到过一个导入大批量数据耗时过长的问题,查了一下资料,找到一个mybatis-puls的一个插入,大大提高了入库效率,这里给大家分析分析。先介绍一下,本次测试用例是MVC三层结构的例子,大概结构如下:pom.xmlcom.tring.ysyn.entity.Pull.java//数据库实体类c......
  • MyBatisPlus 之四:MP 的乐观锁和逻辑删除、分组、排序、链式的实现步骤
    乐观锁乐观锁是相对悲观锁而言的,乐观锁假设数据一般情况不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果冲突,则返回给用户异常信息,让用户决定如何去做。乐观锁适用于读多写少的场景,这样可以提高程序的吞吐量。乐观锁采取了更加宽松的加......
  • 疫苗接种管理系统(JSP+java+springmvc+mysql+MyBatis)
    本项目包含程序+源码+数据库+LW+调试部署环境,文末可获取一份本项目的java源码和数据库参考。项目文件图 项目介绍疫苗接种管理系统是一种旨在提高疫苗注射效率、确保接种记录准确性的信息系统。在全球疫情常态化防控的背景下,此类系统对于实现疫苗的快速分发、接种情况追......