Spring 和 Mybatis 使用不同的数据源会怎样?

本篇文章要讨论的一个问题点, 给Spring和Mybatis设置不同的数据库数据源会怎样?

注意. 正常情况下一定要给Spring和Mybatis设置相同的数据库数据源.

案例代码位置 https://github.com/infuq/spring-framework/tree/main/infuq-t/src/main/java/com/infuq/mybatis

案例代码结构

在这里插入图片描述

//AppConfig.java

import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

@EnableTransactionManagement
@MapperScan("com.infuq.mybatis.mapper")
@ComponentScan
public class AppConfig {


    // 数据源
    @Bean
    public DataSource druidDataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/test_0?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true");
        dataSource.setUsername("root");
        dataSource.setPassword("9527");

        return dataSource;
    }

    // 数据源
    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/test_1?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true");
        dataSource.setUsername("root");
        dataSource.setPassword("9527");

        return dataSource;
    }

    // Mybatis需要一个SqlSessionFactory, 因此向容器中注入一个SqlSessionFactory
    @Bean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {

        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);
        return factoryBean.getObject();

    }

    // 事务管理器, 用于事务管理
    @Bean
    public DataSourceTransactionManager druidTransactionManager(DataSource druidDataSource) {
        return new DataSourceTransactionManager(druidDataSource);
    }

}

通过图形的方式, 描述上面AppConfig.java代码的结构

在这里插入图片描述两个数据库数据源分别设置到SqlSessionFactory和事务管理器.

SqlSessionFactory用于Mybatis操作数据库时使用,比如insert,update等.
事务管理器用于Spring开启事务等操作.

// UserServiceImpl.java

import com.infuq.mybatis.mapper.UserMapper;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.transaction.annotation.Transactional;

public class UserServiceImpl implements UserService {

	@Autowired
	private UserMapper userMapper;
	
	@Transactional(transactionManager = "druidTransactionManager")
	@Override
	public void getList() {
	    userMapper.getList();
	}

}

代码中使用了事务管理器, 但使用了select作为案例讲解,并没有使用insert/update作为案例讲解,读者不要太在意.

程序运行之后,看一下,Spring容器中存在的UserServiceImpl实例和UserMapper实例`长啥样`.

在这里插入图片描述在容器中存放的是Service的代理对象, 代理对象中存在真正的被代理对象(即真正的UserServiceImpl实例), 在被代理对象内部, 又有mapper代理对象, mapper代理对象持有sqlSessionFactory对象, sqlSessionFactory持有数据源.

在这里插入图片描述

Service的代理对象内部还有一个事务拦截器TransactionInterceptor

在这里插入图片描述

在调用链路上,在Service代理对象和Service被代理对象之间, 还有一个事务拦截器会被调用到.


开始运行程序

在这里插入图片描述


运行程序之后,首先调用到service代理对象, 在调用到事务拦截器TransactionInterceptor, 就在这个事务拦截器中拿到了容器中的事务管理器TransactionManager, 而这个事务管理器就是我们之前配置的.

//AppConfig.java
@Bean
public DataSourceTransactionManager druidTransactionManager(DataSource druidDataSource) {
    return new DataSourceTransactionManager(druidDataSource);
}

这个事务管理器有一个很重要的事情需要做. 它需要获取一个数据库连接, 并开启事务.
那么这个数据库连接从哪里得到呢?
在配置事务管理器的时候,给它设置了一个数据源, 那么事务管理器就从这个数据源中得到一个数据库连接. 而且它是通过ThreadLocal实现的. 如果一个线程在执行的过程使用了多个数据库数据源, 那么一个数据源对应一条数据库连接的关系会被保存到ThreadLocal中, 保证线程在操作一个数据库的时候只会使用一条相同的数据库连接. 具体实现在 org.springframework.jdbc.datasource.DataSourceTransactionManager#doBegin

@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
	DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
	Connection con = null;

	try {
		if (!txObject.hasConnectionHolder() ||
				txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
			// 拿到事务管理器中设置的数据源,并根据这个数据源创建一个数据库连接
			Connection newCon = obtainDataSource().getConnection();
			txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
		}

		txObject.getConnectionHolder().setSynchronizedWithTransaction(true);

		con = txObject.getConnectionHolder().getConnection();

		Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
		txObject.setPreviousIsolationLevel(previousIsolationLevel);
		txObject.setReadOnly(definition.isReadOnly());


		if (con.getAutoCommit()) {
			txObject.setMustRestoreAutoCommit(true);
			// 开启事务
			con.setAutoCommit(false);
		}

		prepareTransactionalConnection(con, definition);
		txObject.getConnectionHolder().setTransactionActive(true);

		int timeout = determineTimeout(definition);
		if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
			txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
		}

		if (txObject.isNewConnectionHolder()) {
		    // 将 dataSource -> connection 关系存到ThreadLocal中
			TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
		}
	}

}

总结. Spring会将Service的代理对象放入容器中, 当调用代理对象的方法时, 首先会调用到事务拦截器TransactionInterceptor中,这个事务拦截器会拿到容器中的事务管理器, 事务管理器会根据设置的数据源, 创建一个数据库连接, 并开启事务. 同时也会把数据源->数据库连接保存到ThreadLocal.

接下来看Mybatis层面的代码逻辑.

在这里插入图片描述

经过层层调用, Mybatis也需要拿到数据库连接,为接下来的操作数据库. 那么它这个连接是怎么拿到的呢?

在这里插入图片描述

Mybatis本来是想从ThreadLocal中拿到一个数据库连接的, 但是Mybatis持有的这个数据源在ThreadLocal中没有对应的数据库连接, 而ThreadLocal中已存在的数据源是在事务管理器的时候放入的, 它们不是同一个数据源.
因此, Mybatis 需要根据自己拿到的数据源自己去创建一个数据库连接了. 并把它也放到ThreadLocal中.

在这里插入图片描述

如上图, 由于文章开头, 在配置事务管理器和SqlSessionFactory时,分别设置了不同的数据源, 最终就导致, 事务管理器开启事务的时候, 使用的数据源A创建的一个数据库连接. 而Mybatis在进行实际操作数据库的时候, 使用的数据源B创建的一个数据库连接. 造成了开启事务和进行实际数据库操作的连接不是同一个连接.


个人站点
语雀

公众号

在这里插入图片描述

原文地址:https://blog.csdn.net/qq_45859054/article/details/122140271

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


1.pom.xml引入依赖 <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" &qu
准备工作 ① 创建数据库&数据表 ## 创建数据库 CREATE DATABASE `dbtest1`; ## 创建数据表 CREATE TABLE `t_user` ( `id` INT NOT NULL AUTO_INCREMENT, `username` VARCHAR(20) DEF
MyBatis逆向工程是指根据数据库表结构自动生成对应的实体类、Mapper接口以及SQL映射文件的过程。这个过程可以通过MyBatis提供的逆向工程工具来完成,极大地方便了开发人员,避免了重复的代码编写,提高了开发效率。 创建逆向工程的步骤 1、添加依赖&插件 <!-- 控制Mave
MyBatis获取参数值的两种方式:${}和#{} ${}的本质就是字符串拼接,#{}的本质就是占位符赋值。 ${}使用字符串拼接的方式拼接sql,若为字符串类型或日期类型的字段进行赋值时,需要手动加单引号;但是#{}使用占位符赋值的方式拼接sql,此时为字符串类型或日期类型的字段进行赋值时,可以自
resultMap作用是处理数据表中字段与java实体类中属性的映射关系。 准备工作 ① 创建数据库&数据表 CREATE DATABASE `dbtest1`; CREATE TABLE `t_emp` ( `emp_id` int NOT NULL AUTO_INCREMENT, `em
EHCache缓存针对于MyBatis的二级缓存。 MyBatis默认二级缓存是SqlSessionFactory级别的。 添加依赖 <!-- MyBatis-EHCache整合包 --> <dependency> <groupId>org.mybatis.cac
MyBatis 提供了一级缓存和二级缓存的支持,用于提高数据库查询的性能,减少不必要的数据库访问。 一级缓存(SqlSession 级别的缓存) 一级缓存是 MyBatis 中最细粒度的缓存,也称为本地缓存。它存在于每个 SqlSession 的生命周期中,当 SqlSession 被关闭或清空时,
动态SQL是 MyBatis 中非常强大且灵活的功能,允许你根据不同的条件构建SQL查询。 这主要通过 <if>、<choose>、<when>、<otherwise>、<foreach>等标签实现。 查询场景 /** * 根据条件查询员工
本教程操作系统:windows10系统、DELL G3电脑。 MyBatis 是一个优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。在 MyBatis 中,配置数据库连接是非常重要的第一步。下面将详细介绍如何配置 MyBatis 的
今天小编给大家分享的是MyBatis批量查询、插入、更新、删除如何实现,相信很多人都不太了解,为了让大家更加了解,所以给大家总结了以下内容,一起往下看吧。
今天小编给大家分享的是Mybatis操作多数据源实现的方法,相信很多人都不太了解,为了让大家更加了解,所以给大家总结了以下内容,一起往下看吧。一定会有所收获...
本篇文章和大家了解一下mybatis集成到spring的方式有哪些。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。1 前言1.1 集成spring前使用mybat...
今天小编给大家分享的是mybatis-plus分页查询的3种方法,相信很多人都不太了解,为了让大家更加了解,所以给大家总结了以下内容,一起往下看吧。一定会有所收获...
本篇内容主要讲解“mybatis之BaseTypeHandler怎么使用”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“mybatis...
这篇文章主要介绍了mybatisforeach怎么传两个参数的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇mybatisforeach怎...
这篇“MyBatis映射文件中parameterType与resultType怎么使用”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的...
这篇文章主要介绍“MyBatis怎么获取自动生成的键值”,在日常操作中,相信很多人在MyBatis怎么获取自动生成的键值问题上存在疑惑,小编查阅了各式资料,整理出
这篇文章主要讲解了“怎么去掉IntelliJIDEA中mybatis对应的xml文件警告”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入...
这篇文章主要介绍“MybatisPlus使用@TableId主键id自增长无效如何解决”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这...