我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Mybatis操作多数据源实现的方法

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Mybatis操作多数据源实现的方法

今天小编给大家分享的是Mybatis操作多数据源实现的方法,相信很多人都不太了解,为了让大家更加了解,所以给大家总结了以下内容,一起往下看吧。一定会有所收获的哦。

现在有一个Mysql数据源和一个Postgresql数据源,使用Mybatis对两个数据源进行操作:

1. 注入多数据源

可以对两个数据源分别实现其Service层和Mapper层,以及Mybatis的配置类:

@Configuration// 这里需要配置扫描包路径,以及sqlSessionTemplateRef@MapperScan(basePackages = "com.example.mybatisdemo.mapper.mysql", sqlSessionTemplateRef = "mysqlSqlSessionTemplate")public class MysqlMybatisConfigurer {        @Bean    @ConfigurationProperties(prefix = "spring.datasource.mysql")    public DataSource mysqlDatasource() {        return new DruidDataSource();    }        @Bean    public SqlSessionFactory mysqlSqlSessionFactory(DataSource mysqlDatasource) throws Exception {        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();        factoryBean.setDataSource(mysqlDatasource);        // 设置对应的mapper文件        factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:" +                "/mappers/MysqlMapper.xml"));        return factoryBean.getObject();    }        @Bean    public SqlSessionTemplate mysqlSqlSessionTemplate(SqlSessionFactory mysqlSqlSessionFactory) {        return new SqlSessionTemplate(mysqlSqlSessionFactory);    }        @Bean    public DataSourceTransactionManager mysqlTransactionalManager(DataSource mysqlDatasource) {        return new DataSourceTransactionManager(mysqlDatasource);    }}
@Configuration// 这里需要配置扫描包路径,以及sqlSessionTemplateRef@MapperScan(basePackages = "com.example.mybatisdemo.mapper.postgresql", sqlSessionTemplateRef = "postgresqlSqlSessionTemplate")public class PostgresqlMybatisConfigurer {        @Bean    @ConfigurationProperties(prefix = "spring.datasource.postgresql")    public DataSource postgresqlDatasource() {        return new DruidDataSource();    }        @Bean    public SqlSessionFactory postgresqlSqlSessionFactory(DataSource postgresqlDatasource) throws Exception {        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();        factoryBean.setDataSource(postgresqlDatasource);        // 设置对应的mapper文件        factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:" +                "/mappers/PostgresqlMapper.xml"));        return factoryBean.getObject();    }        @Bean    public SqlSessionTemplate postgresqlSqlSessionTemplate(SqlSessionFactory postgresqlSqlSessionFactory) {        return new SqlSessionTemplate(postgresqlSqlSessionFactory);    }        @Bean    public DataSourceTransactionManager postgresqlTransactionalManager(DataSource postgresqlDatasource) {        return new DataSourceTransactionManager(postgresqlDatasource);    }}

在配置类中,分别注入了一个事务管理器TransactionManager,这个和事务管理是相关的。在使用@Transactional注解时,需要配置其value属性指定对应的事务管理器。

2. 动态数据源

Spring中提供了AbstractRoutingDataSource抽象类,可以用于动态地选择数据源。

public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {    @Nullable    private Map<Object, Object> targetDataSources;    @Nullable    private Object defaultTargetDataSource;    private boolean lenientFallback = true;    private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();    @Nullable    private Map<Object, DataSource> resolvedDataSources;    @Nullable    private DataSource resolvedDefaultDataSource;    // 略}

通过源码可以看到,该抽象类实现了InitializingBean接口,并在其afterPropertiesSet方法中将数据源以<lookupkey, dataSource>的形式放入一个Map中。

public void afterPropertiesSet() {    if (this.targetDataSources == null) {        throw new IllegalArgumentException("Property 'targetDataSources' is required");    } else {        this.resolvedDataSources = CollectionUtils.newHashMap(this.targetDataSources.size());        this.targetDataSources.forEach((key, value) -> {            Object lookupKey = this.resolveSpecifiedLookupKey(key);            DataSource dataSource = this.resolveSpecifiedDataSource(value);            // 将数据源以<lookupkey, dataSource>的形式放入Map中            this.resolvedDataSources.put(lookupKey, dataSource);        });        if (this.defaultTargetDataSource != null) {            this.resolvedDefaultDataSource = this.resolveSpecifiedDataSource(this.defaultTargetDataSource);        }    }}

该类中还有一个determineTargetDataSource方法,是根据lookupkey从Map中获取对应的数据源,如果没有获取到,则使用默认的数据源。

protected DataSource determineTargetDataSource() {    Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");    Object lookupKey = this.determineCurrentLookupKey();    // 根据lookupkey从Map中获取对应的数据源    DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);    if (dataSource == null && (this.lenientFallback || lookupKey == null)) {        dataSource = this.resolvedDefaultDataSource;    }    if (dataSource == null) {        throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");    } else {        return dataSource;    }}

lookupkey是通过determineTargetDataSource方法获取到的,而它是一个抽象方法,我们要做的就是通过实现这个方法,来控制获取到的数据源。

@Nullableprotected abstract Object determineCurrentLookupKey();

(1) 创建并注入动态数据源

创建AbstractRoutingDataSource的子类,实现determineCurrentLookupKey方法

public class RoutingDataSource extends AbstractRoutingDataSource {    @Override    protected Object determineCurrentLookupKey() {        return DataSourceContextHolder.get();    }}

这里的DataSourceContextHolder是一个操作ThreadLocal对象的工具类

public class DataSourceContextHolder {        private static final ThreadLocal<DataSourceType> contextHolder = new ThreadLocal<>();        public static void set(DataSourceType type) {        contextHolder.set(type);    }        public static DataSourceType get() {        return contextHolder.get();    }        public static void mysql() {        set(DataSourceType.MYSQL);    }        public static void postgresql() {        set(DataSourceType.POSTGRESQL);    }    public static void remove() {        contextHolder.remove();    }}

通过调用DataSourceContextHolder.mysql()或者DataSourceContextHolder.postgresql()就能修改contextHolder的值,从而在动态数据源的determineTargetDataSource方法中就能获取到对应的数据源。

在数据源配置类中,将mysql和postgresql的数据源设置到动态数据源的Map中,并注入容器。

@Configurationpublic class DataSourceConfigurer {    @Bean    @ConfigurationProperties(prefix = "spring.datasource.mysql")    public DataSource mysqlDatasource() {        return new DruidDataSource();    }    @Bean    @ConfigurationProperties(prefix = "spring.datasource.postgresql")    public DataSource postgresqlDatasource() {        return new DruidDataSource();    }    @Bean    public RoutingDataSource routingDataSource(DataSource mysqlDatasource, DataSource postgresqlDatasource) {        Map<Object, Object> dataSources = new HashMap<>();        dataSources.put(DataSourceType.MYSQL, mysqlDatasource);        dataSources.put(DataSourceType.POSTGRESQL, postgresqlDatasource);        RoutingDataSource routingDataSource = new RoutingDataSource();        routingDataSource.setDefaultTargetDataSource(mysqlDatasource);        // 设置数据源        routingDataSource.setTargetDataSources(dataSources);        return routingDataSource;    }}

(2) Mybatis配置类

由于使用了动态数据源,所以只需要编写一个配置类即可。

@Configuration@MapperScan(basePackages = "com.example.mybatisdemo.mapper", sqlSessionTemplateRef = "sqlSessionTemplate")public class MybatisConfigurer {    // 注入动态数据源    @Resource    private RoutingDataSource routingDataSource;    @Bean    public SqlSessionFactory sqlSessionFactory() throws Exception {        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();        sqlSessionFactoryBean.setDataSource(routingDataSource);        // 这里可以直接设置所有的mapper.xml文件        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath" +                ":mappers/*.xml"));        return sqlSessionFactoryBean.getObject();    }    @Bean    public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {        return new SqlSessionTemplate(sqlSessionFactory);    }    @Bean    public DataSourceTransactionManager transactionalManager(DataSource mysqlDatasource) {        return new DataSourceTransactionManager(mysqlDatasource);    }}

(3) 使用注解简化数据源切换

我们虽然可以使用DataSourceContextHolder类中的方法进行动态数据源切换,但是这种方式有些繁琐,不够优雅。可以考虑使用注解的形式简化数据源切换。
我们先定义两个注解,表示使用Mysql数据源或Postgresql数据源:

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

再定义一个切面,当使用了注解时,会先调用切换数据源的方法,再执行后续逻辑。

@Component@Aspectpublic class DataSourceAspect {    @Pointcut("@within(com.example.mybatisdemo.aop.Mysql) || @annotation(com.example.mybatisdemo.aop.Mysql)")    public void mysqlPointcut() {    }    @Pointcut("@within(com.example.mybatisdemo.aop.Postgresql) || @annotation(com.example.mybatisdemo.aop.Postgresql)")    public void postgresqlPointcut() {    }    @Before("mysqlPointcut()")    public void mysql() {        DataSourceContextHolder.mysql();    }    @Before("postgresqlPointcut()")    public void postgresql() {        DataSourceContextHolder.postgresql();    }}

在使用动态数据源的事务操作时有两个需要注意的问题:

问题一    同一个事务操作两个数据源

Mybatis使用Executor执行SQL时需要获取连接,BaseExecutor类中的getConnection方法调用了SpringManagedTransaction中的getConnection方法,这里优先从connection字段获取连接,如果connection为空,才会调用openConnection方法,并把连接赋给connection字段。

也就是说,如果你使用的是同一个事务来操作两个数据源,那拿到的都是同一个连接,会导致数据源切换失败。

protected Connection getConnection(Log statementLog) throws SQLException {    Connection connection = this.transaction.getConnection();    return statementLog.isDebugEnabled() ? ConnectionLogger.newInstance(connection, statementLog, this.queryStack) : connection;}
public Connection getConnection() throws SQLException {    if (this.connection == null) {        this.openConnection();    }    return this.connection;}
private void openConnection() throws SQLException {    this.connection = DataSourceUtils.getConnection(this.dataSource);    this.autoCommit = this.connection.getAutoCommit();    this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);    LOGGER.debug(() -> {        return "JDBC Connection [" + this.connection + "] will" + (this.isConnectionTransactional ? " " : " not ") + "be managed by Spring";    });}

问题二      两个独立事务分别操作两个数据源

(1) 在开启事务的时候,DataSourceTransactionManager中的doBegin方法会先获取Connection,并保存到ConnectionHolder中,将数据源和ConnectionHolder的对应关系绑定到TransactionSynchronizationManager中。

protected void doBegin(Object transaction, TransactionDefinition definition) {    DataSourceTransactionManager.DataSourceTransactionObject txObject = (DataSourceTransactionManager.DataSourceTransactionObject)transaction;    Connection con = null;    try {        if (!txObject.hasConnectionHolder() || txObject.getConnectionHolder().isSynchronizedWithTransaction()) {            // 获取连接            Connection newCon = this.obtainDataSource().getConnection();            if (this.logger.isDebugEnabled()) {                this.logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");            }            // 保存到ConnectionHolder中            txObject.setConnectionHolder(new ConnectionHolder(newCon), true);        }        txObject.getConnectionHolder().setSynchronizedWithTransaction(true);        // 从ConnectionHolder获取连接        con = txObject.getConnectionHolder().getConnection();        // 略        // 将数据源和ConnectionHolder的关系绑定到TransactionSynchronizationManager中        if (txObject.isNewConnectionHolder()) {            TransactionSynchronizationManager.bindResource(this.obtainDataSource(), txObject.getConnectionHolder());        }         // 略}

(2) TransactionSynchronizationManager的bindResource方法将数据源和ConnectionHolder的对应关系存入线程变量resources中。

public abstract class TransactionSynchronizationManager {    // 线程变量    private static final ThreadLocal<Map<Object, Object>> resources =         new NamedThreadLocal<>("Transactional resources");    // 略    // 绑定数据源和ConnectionHolder的对应关系    public static void bindResource(Object key, Object value) throws IllegalStateException {       Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);       Assert.notNull(value, "Value must not be null");       Map<Object, Object> map = resources.get();       // set ThreadLocal Map if none found       if (map == null) {          map = new HashMap<>();          resources.set(map);       }       Object oldValue = map.put(actualKey, value);       // Transparently suppress a ResourceHolder that was marked as void...       if (oldValue instanceof ResourceHolder && ((ResourceHolder) oldValue).isVoid()) {          oldValue = null;       }       if (oldValue != null) {          throw new IllegalStateException(                "Already value [" + oldValue + "] for key [" + actualKey + "] bound to thread");       }    }    // 略}

(3) 上边提到的openConnection方法,其实最终也是从TransactionSynchronizationManager的resources中获取连接的

public static Connection doGetConnection(DataSource dataSource) throws SQLException {    Assert.notNull(dataSource, "No DataSource specified");    // 获取ConnectionHolder    ConnectionHolder conHolder = (ConnectionHolder)TransactionSynchronizationManager.getResource(dataSource);    if (conHolder == null || !conHolder.hasConnection() && !conHolder.isSynchronizedWithTransaction()) {        logger.debug("Fetching JDBC Connection from DataSource");        Connection con = fetchConnection(dataSource);        if (TransactionSynchronizationManager.isSynchronizationActive()) {            try {                ConnectionHolder holderToUse = conHolder;                if (conHolder == null) {                    holderToUse = new ConnectionHolder(con);                } else {                    conHolder.setConnection(con);                }                holderToUse.requested();                TransactionSynchronizationManager.registerSynchronization(new DataSourceUtils.ConnectionSynchronization(holderToUse, dataSource));                holderToUse.setSynchronizedWithTransaction(true);                if (holderToUse != conHolder) {                    TransactionSynchronizationManager.bindResource(dataSource, holderToUse);                }            } catch (RuntimeException var4) {                releaseConnection(con, dataSource);                throw var4;            }        }        return con;    } else {        conHolder.requested();        if (!conHolder.hasConnection()) {            logger.debug("Fetching resumed JDBC Connection from DataSource");            conHolder.setConnection(fetchConnection(dataSource));        }        // 从ConnectionHolder中获取连接        return conHolder.getConnection();    }}

也就是说,如果修改了数据源,那么resources中就找不到对应的连接,就可以重新获取连接,从而达到切换数据源的目的。然而我们数据源的只有一个,就是动态数据源,因此即使使用两个独立事务,也不能成功切换数据源。

3. 结语

如果想要使用动态数据源的事务处理,可能需要考虑使用多线程分布式的事务处理机制;
如果使用直接注入多个数据源的方式实现事务处理,实现简单,但是各数据源事务是独立的;
应该根据具体情况进行选择。

关于Mybatis操作多数据源实现的方法就分享到这里了,希望以上内容可以对大家有一定的参考价值,可以学以致用。如果喜欢本篇文章,不妨把它分享出去让更多的人看到。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Mybatis操作多数据源实现的方法

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Mybatis操作多数据源实现的方法

今天小编给大家分享的是Mybatis操作多数据源实现的方法,相信很多人都不太了解,为了让大家更加了解,所以给大家总结了以下内容,一起往下看吧。一定会有所收获的哦。现在有一个Mysql数据源和一个Postgresql数据源,使用Mybatis
2023-07-06

Mybatis操作多数据源的实现

本文主要介绍了Mybatis操作多数据源,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
2023-05-20

SpringBoot整合Mybatis Plus多数据源的实现方法是什么

这篇文章主要讲解了“SpringBoot整合Mybatis Plus多数据源的实现方法是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“SpringBoot整合Mybatis Plus多数
2023-06-25

Spring+Mybatis 实现aop数据库读写分离与多数据库源配置操作

在数据库层面大都采用读写分离技术,就是一个Master数据库,多个Slave数据库。Master库负责数据更新和实时数据查询,Slave库当然负责非实时数据查询。因为在实际的应用中,数据库都是读多写少(读取数据的频率高,更新数据的频率相对较
2023-05-31

一文搞懂MyBatis多数据源Starter实现

本文将实现一个MyBatis的Springboot的Starter包,引用这个Starter包后,仅需要提供少量配置信息,就能够完成MyBatis多数据源的初始化和使用,需要的小伙伴可以参考一下
2023-05-16

【Java多数据源实现教程】实现动态数据源、多数据源切换方式

前言 本文为 【Java多数据源实现教程】 相关知识,由于自己最近在做导师的项目的时候需要使用这种技术,于是自学了相关技术原理与实现,并将其整理如下,具体包含:多数据源的典型使用场景(包含业务复杂场景、读写分离场景),多数据源实现原理及实
2023-08-16

Java实现多数据源的方式

Java实现多数据源的方式 文章目录 Java实现多数据源的方式一、利用Spring提供的类实现1)在yml文件当中配置多数据源2) 定义一个DataSourceConfig 配置类来配置两个数据源3)自定义一个类 来 继承 org
2023-08-23

SpringBoot整合mybatis/mybatis-plus实现数据持久化的操作

这篇文章主要介绍了SpringBoot整合mybatis/mybatis-plus实现数据持久化,本节内容我们介绍了数据持久化的相关操作,并且是基础传统的关系型数据库——mysql,需要的朋友可以参考下
2022-11-13

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录