优化阅读格式

This commit is contained in:
heibaiying
2019-07-31 17:39:13 +08:00
parent 3020ff8efa
commit 246e5cfca1
55 changed files with 10521 additions and 10482 deletions

View File

@ -15,7 +15,7 @@
### 1.1 项目说明
本用例基于spring boot + druid + mybatis 配置多数据源,并采用 **JTA 实现分布式事务**
本用例基于 spring boot + druid + mybatis 配置多数据源,并采用 **JTA 实现分布式事务**
### 1.2 项目结构
@ -31,9 +31,9 @@
### 2.1 导入基本依赖
除了mybatis 、durid 等依赖外我们依靠切面来实现动态数据源的切换所以还需要导入aop依赖。
除了 mybatis 、durid 等依赖外,我们依靠切面来实现动态数据源的切换,所以还需要导入 aop 依赖。
最主要的是还要导入spring-boot-starter-jta-atomikosSpring Boot通过[Atomkos](http://www.atomikos.com/)或[Bitronix](http://docs.codehaus.org/display/BTM/Home)的内嵌事务管理器支持跨多个XA资源的分布式JTA事务当发现JTA环境时Spring Boot将使用SpringJtaTransactionManager来管理事务。自动配置的JMSDataSourceJPA beans将被升级以支持XA事务。
最主要的是还要导入 spring-boot-starter-jta-atomikosSpring Boot 通过[Atomkos](http://www.atomikos.com/) 或[Bitronix](http://docs.codehaus.org/display/BTM/Home) 的内嵌事务管理器支持跨多个 XA 资源的分布式 JTA 事务,当发现 JTA 环境时Spring Boot 将使用 SpringJtaTransactionManager 来管理事务。自动配置的 JMSDataSourceJPA beans 将被升级以支持 XA 事务。
```xml
<!--mybatis starter-->
@ -80,7 +80,7 @@
**注意**Spring Boot 2.X 版本不再支持配置继承,多数据源的话每个数据源的所有配置都需要单独配置,否则配置不会生效。
这里我用的本地数据库mysqlmysql02,配置如下:
这里我用的本地数据库 mysqlmysql02,配置如下:
```yml
spring:
@ -92,29 +92,29 @@ spring:
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
# 初始化时建立物理连接的个数。初始化发生在显示调用init方法或者第一次getConnection时
# 初始化时建立物理连接的个数。初始化发生在显示调用 init 方法,或者第一次 getConnection
initialSize: 5
# 最小连接池数量
minIdle: 5
# 最大连接池数量
maxActive: 10
# 获取连接时最大等待时间单位毫秒。配置了maxWait之后缺省启用公平锁并发效率会有所下降如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
# 获取连接时最大等待时间,单位毫秒。配置了 maxWait 之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置 useUnfairLock 属性为 true 使用非公平锁。
maxWait: 60000
# Destroy线程会检测连接的间隔时间如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接。
# Destroy 线程会检测连接的间隔时间,如果连接空闲时间大于等于 minEvictableIdleTimeMillis 则关闭物理连接。
timeBetweenEvictionRunsMillis: 60000
# 连接保持空闲而不被驱逐的最小时间
minEvictableIdleTimeMillis: 300000
# 用来检测连接是否有效的sql 因数据库方言而异, 例如 oracle 应该写成 SELECT 1 FROM DUAL
# 用来检测连接是否有效的 sql 因数据库方言而异, 例如 oracle 应该写成 SELECT 1 FROM DUAL
validationQuery: SELECT 1
# 建议配置为true不影响性能并且保证安全性。申请连接的时候检测如果空闲时间大于timeBetweenEvictionRunsMillis执行validationQuery检测连接是否有效。
# 建议配置为 true不影响性能并且保证安全性。申请连接的时候检测如果空闲时间大于 timeBetweenEvictionRunsMillis执行 validationQuery 检测连接是否有效。
testWhileIdle: true
# 申请连接时执行validationQuery检测连接是否有效做了这个配置会降低性能。
# 申请连接时执行 validationQuery 检测连接是否有效,做了这个配置会降低性能。
testOnBorrow: false
# 归还连接时执行validationQuery检测连接是否有效做了这个配置会降低性能。
# 归还连接时执行 validationQuery 检测连接是否有效,做了这个配置会降低性能。
testOnReturn: false
# 是否自动回收超时连接
removeAbandoned: true
# 超时时间(以秒数为单位)
# 超时时间 (以秒数为单位)
remove-abandoned-timeout: 1800
db2:
@ -123,45 +123,45 @@ spring:
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
# 初始化时建立物理连接的个数。初始化发生在显示调用init方法或者第一次getConnection时
# 初始化时建立物理连接的个数。初始化发生在显示调用 init 方法,或者第一次 getConnection
initialSize: 6
# 最小连接池数量
minIdle: 6
# 最大连接池数量
maxActive: 10
# 获取连接时最大等待时间单位毫秒。配置了maxWait之后缺省启用公平锁并发效率会有所下降如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
# 获取连接时最大等待时间,单位毫秒。配置了 maxWait 之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置 useUnfairLock 属性为 true 使用非公平锁。
maxWait: 60000
# Destroy线程会检测连接的间隔时间如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接。
# Destroy 线程会检测连接的间隔时间,如果连接空闲时间大于等于 minEvictableIdleTimeMillis 则关闭物理连接。
timeBetweenEvictionRunsMillis: 60000
# 连接保持空闲而不被驱逐的最小时间
minEvictableIdleTimeMillis: 300000
# 用来检测连接是否有效的sql 因数据库方言而异, 例如 oracle 应该写成 SELECT 1 FROM DUAL
# 用来检测连接是否有效的 sql 因数据库方言而异, 例如 oracle 应该写成 SELECT 1 FROM DUAL
validationQuery: SELECT 1
# 建议配置为true不影响性能并且保证安全性。申请连接的时候检测如果空闲时间大于timeBetweenEvictionRunsMillis执行validationQuery检测连接是否有效。
# 建议配置为 true不影响性能并且保证安全性。申请连接的时候检测如果空闲时间大于 timeBetweenEvictionRunsMillis执行 validationQuery 检测连接是否有效。
testWhileIdle: true
# 申请连接时执行validationQuery检测连接是否有效做了这个配置会降低性能。
# 申请连接时执行 validationQuery 检测连接是否有效,做了这个配置会降低性能。
testOnBorrow: false
# 归还连接时执行validationQuery检测连接是否有效做了这个配置会降低性能。
# 归还连接时执行 validationQuery 检测连接是否有效,做了这个配置会降低性能。
testOnReturn: false
# 是否自动回收超时连接
removeAbandoned: true
# 超时时间(以秒数为单位)
# 超时时间 (以秒数为单位)
remove-abandoned-timeout: 1800
# WebStatFilter用于采集web-jdbc关联监控的数据。
# WebStatFilter 用于采集 web-jdbc 关联监控的数据。
web-stat-filter:
# 是否开启 WebStatFilter 默认是true
# 是否开启 WebStatFilter 默认是 true
enabled: true
# 需要拦截的url
# 需要拦截的 url
url-pattern: /*
# 排除静态资源的请求
exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"
# Druid内置提供了一个StatViewServlet用于展示Druid的统计信息。
# Druid 内置提供了一个 StatViewServlet 用于展示 Druid 的统计信息。
stat-view-servlet:
#是否启用StatViewServlet 默认值true
#是否启用 StatViewServlet 默认值 true
enabled: true
# 需要拦截的url
# 需要拦截的 url
url-pattern: /druid/*
# 允许清空统计数据
reset-enable: true
@ -188,9 +188,9 @@ public class DruidMybatisMultiApplication {
#### 2. 创建多数据源配置类`DataSourceFactory.java`, 手动配置多数据源
+ 这里我们创建druid数据源的时候创建的是`DruidXADataSource`,它继承自`DruidDataSource `并支持XA分布式事务
+ 使用 `AtomikosDataSourceBean` 包装我们创建的`DruidXADataSource`,使得数据源能够被 JTA 事务管理器管理;
+ 这里我们使用的sqlSessionTemplate是我们重写的`CustomSqlSessionTemplate`,原生的sqlSessionTemplate是不能实现在一个事务中实现数据源切换的。为了不占用篇幅我会在后文再给出详细的原因分析
+ 这里我们创建 druid 数据源的时候,创建的是 `DruidXADataSource`,它继承自 `DruidDataSource ` 并支持 XA 分布式事务;
+ 使用 `AtomikosDataSourceBean` 包装我们创建的 `DruidXADataSource`,使得数据源能够被 JTA 事务管理器管理;
+ 这里我们使用的 sqlSessionTemplate 是我们重写的 `CustomSqlSessionTemplate`,原生的 sqlSessionTemplate 是不能实现在一个事务中实现数据源切换的。(为了不占用篇幅,我会在后文再给出详细的原因分析)
```java
/**
@ -207,7 +207,7 @@ public class DataSourceFactory {
/***
* 创建 DruidXADataSource 1 用@ConfigurationProperties自动配置属性
* 创建 DruidXADataSource 1 用@ConfigurationProperties 自动配置属性
*/
@Bean
@ConfigurationProperties("spring.datasource.druid.db1")
@ -225,7 +225,7 @@ public class DataSourceFactory {
}
/**
* 创建支持XA事务的Atomikos数据源1
* 创建支持 XA 事务的 Atomikos 数据源 1
*/
@Bean
public DataSource dataSourceOne(DataSource druidDataSourceOne) {
@ -237,7 +237,7 @@ public class DataSourceFactory {
}
/**
* 创建支持XA事务的Atomikos数据源2
* 创建支持 XA 事务的 Atomikos 数据源 2
*/
@Bean
public DataSource dataSourceTwo(DataSource druidDataSourceTwo) {
@ -249,8 +249,8 @@ public class DataSourceFactory {
/**
* @param dataSourceOne 数据源1
* @return 数据源1的会话工厂
* @param dataSourceOne 数据源 1
* @return 数据源 1 的会话工厂
*/
@Bean
public SqlSessionFactory sqlSessionFactoryOne(DataSource dataSourceOne)
@ -260,8 +260,8 @@ public class DataSourceFactory {
/**
* @param dataSourceTwo 数据源2
* @return 数据源2的会话工厂
* @param dataSourceTwo 数据源 2
* @return 数据源 2 的会话工厂
*/
@Bean
public SqlSessionFactory sqlSessionFactoryTwo(DataSource dataSourceTwo)
@ -271,10 +271,10 @@ public class DataSourceFactory {
/***
* sqlSessionTemplateSpring事务管理一起使用以确保使用的实际SqlSession是与当前Spring事务关联的,
* 此外它还管理会话生命周期包括根据Spring事务配置根据需要关闭提交或回滚会话
* @param sqlSessionFactoryOne 数据源1
* @param sqlSessionFactoryTwo 数据源2
* sqlSessionTemplateSpring 事务管理一起使用,以确保使用的实际 SqlSession 是与当前 Spring 事务关联的,
* 此外它还管理会话生命周期,包括根据 Spring 事务配置根据需要关闭,提交或回滚会话
* @param sqlSessionFactoryOne 数据源 1
* @param sqlSessionFactoryTwo 数据源 2
*/
@Bean
public CustomSqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactoryOne,
@ -297,13 +297,13 @@ public class DataSourceFactory {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(MAPPER_LOCATION));
// 其他可配置项(包括是否打印sql,是否开启驼峰命名等)
// 其他可配置项 (包括是否打印 sql,是否开启驼峰命名等)
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
configuration.setMapUnderscoreToCamelCase(true);
configuration.setLogImpl(StdOutImpl.class);
factoryBean.setConfiguration(configuration);
/* *
* 采用个如下方式配置属性的时候一定要保证已经进行数据源的配置(setDataSource)和数据源和MapperLocation配置(setMapperLocations)
* 采用个如下方式配置属性的时候一定要保证已经进行数据源的配置 (setDataSource) 和数据源和 MapperLocation 配置 (setMapperLocations)
* factoryBean.getObject().getConfiguration().setMapUnderscoreToCamelCase(true);
* factoryBean.getObject().getConfiguration().setLogImpl(StdOutImpl.class);
**/
@ -314,7 +314,7 @@ public class DataSourceFactory {
#### 3. 自定义sqlSessionTemplate的主要实现逻辑
这里主要覆盖重写了sqlSessionTemplategetSqlSessionFactory从ThreadLocal去获取实际使用的数据源AOP切面将实际使用的数据源存入ThreadLocal
这里主要覆盖重写了 sqlSessionTemplategetSqlSessionFactory ThreadLocal 去获取实际使用的数据源AOP 切面将实际使用的数据源存入 ThreadLocal
```java
/***
@ -340,12 +340,12 @@ public SqlSessionFactory getSqlSessionFactory() {
/**
* 这个方法的实现和父类的实现是基本一致的唯一不同的就是在getSqlSession方法传参中获取会话工厂的方式
* 这个方法的实现和父类的实现是基本一致的,唯一不同的就是在 getSqlSession 方法传参中获取会话工厂的方式
*/
private class SqlSessionInterceptor implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//在getSqlSession传参时候用我们重写的getSqlSessionFactory获取当前数据源对应的会话工厂
//在 getSqlSession 传参时候,用我们重写的 getSqlSessionFactory 获取当前数据源对应的会话工厂
final SqlSession sqlSession = getSqlSession(
CustomSqlSessionTemplate.this.getSqlSessionFactory(),
CustomSqlSessionTemplate.this.executorType,
@ -375,7 +375,7 @@ private class SqlSessionInterceptor implements InvocationHandler {
#### 4. 使用AOP动态切换数据源将当前使用的数据源名称保存到线程隔离的ThreadLocal中
这里我们直接对dao层接口进行切面如果第一个参数指明需要使用哪一个数据源就使用对应的数据源如果没有指定就使用默认的数据源。
这里我们直接对 dao 层接口进行切面,如果第一个参数指明需要使用哪一个数据源,就使用对应的数据源,如果没有指定,就使用默认的数据源。
注:使用切面来切换数据源是一种实现思路,而具体如何定义切入点可以按照自己的实际情况来定,你可以使用第一个参数指明数据源,也可以自定义注解来指定数据源,这个按照自己的实际使用方便来实现即可。
@ -436,7 +436,7 @@ public class DataSourceContextHolder {
```java
/**
* @author : heibaiying
* @description : JTA事务配置
* @description : JTA 事务配置
*/
@Configuration
@EnableTransactionManagement
@ -474,9 +474,9 @@ public class XATransactionManagerConfig {
### 3.1 测试数据库整合结果
这里我在mysqlmysql02的表中分别插入了一条数据
这里我在 mysqlmysql02 的表中分别插入了一条数据:
mysql数据库
mysql 数据库:
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/mysql01.png"/> </div>
@ -494,7 +494,7 @@ mysql02 数据库:
### 3.2 测试单数据库事务
这里因为没有复杂的业务逻辑,我直接将@Transactional加载controller层实际中最好加到service层
这里因为没有复杂的业务逻辑,我直接将@Transactional 加载 controller 层,实际中最好加到 service
```java
/**
@ -567,15 +567,15 @@ public class XATransactionController {
### 3.4 测试druid数据源是否整合成功
访问 http://localhost:8080/druid/index.html 可以在数据源监控页面看到两个数据源已配置成功同时配置都与我们在yml配置文件中的一致。
访问 http://localhost:8080/druid/index.html ,可以在数据源监控页面看到两个数据源已配置成功,同时配置都与我们在 yml 配置文件中的一致。
数据源1
数据源 1
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/druid-mysql01.png"/> </div>
数据源2
数据源 2
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/durud-mysql02.png"/> </div>
@ -593,11 +593,11 @@ url 监控情况:
### 4.1 XA 与 JTA
XA是由X/Open组织提出的分布式事务的规范。XA规范主要定义了(全局)事务管理器(Transaction Manager)(局部)资源管理器(Resource Manager)之间的接口。XA接口是双向的系统接口在事务管理器Transaction Manager以及一个或多个资源管理器Resource Manager之间形成通信桥梁。XA之所以需要引入事务管理器是因为在分布式系统中从理论上讲两台机器理论上无 法达到一致的状态,需要引入一个单点进行协调。
事务管理器控制着全局事务,管理事务生命周期,并协调资源。资源管理器负责控制和管理实际资源(如数据库或 JMS队列
XA 是由 X/Open 组织提出的分布式事务的规范。XA 规范主要定义了 (全局) 事务管理器 (Transaction Manager)(局部) 资源管理器 (Resource Manager) 之间的接口。XA 接口是双向的系统接口在事务管理器Transaction Manager以及一个或多个资源管理器Resource Manager之间形成通信桥梁。XA 之所以需要引入事务管理器是因为,在分布式系统中,从理论上讲,两台机器理论上无 法达到一致的状态,需要引入一个单点进行协调。
事务管理器控制着全局事务,管理事务生命周期,并协调资源。资源管理器负责控制和管理实际资源(如数据库或 JMS 队列)。
下图说明了事务管理器、资源管理器,与应用程序之间的关系。
**而 JTA 就是 XA 规范在java语言上的实现。JTA 采用两阶段提交实现分布式事务。**
**而 JTA 就是 XA 规范在 java 语言上的实现。JTA 采用两阶段提交实现分布式事务。**
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/XA.gif"/> </div>
@ -605,16 +605,16 @@ XA是由X/Open组织提出的分布式事务的规范。XA规范主要定义了(
### 4.2 两阶段提交
分布式事务必须满足传统事务的特性,即原子性,一致性,分离性和持久性。但是分布式事务处理过程中,某些节点(Server)可能发生故障,或 者由于网络发生故障而无法访问到某些节点。为了防止分布式系统部分失败时产生数据的不一致性。在分布式事务的控制中采用了两阶段提交协议Two- Phase Commit Protocol。即事务的提交分为两个阶段
分布式事务必须满足传统事务的特性,即原子性,一致性,分离性和持久性。但是分布式事务处理过程中,某些节点 (Server) 可能发生故障,或 者由于网络发生故障而无法访问到某些节点。为了防止分布式系统部分失败时产生数据的不一致性。在分布式事务的控制中采用了两阶段提交协议Two- Phase Commit Protocol。即事务的提交分为两个阶段
+ 预提交阶段(Pre-Commit Phase)
+ 预提交阶段 (Pre-Commit Phase)
+ 决策后阶段Post-Decision Phase
两阶段提交用来协调参与一个更新中的多个服务器的活动,以防止分布式系统部分失败时产生数据的不一致性。例如,如果一个更新操作要求位于三个不同结点上的记录被改变,且其中只要有一个结点失败,另外两个结点必须检测到这个失败并取消它们所做的改变。为了支持两阶段提交,一个分布式更新事务中涉及到的服务器必须能够相互通信。一般来说一个服务器会被指定为"控制"或"提交"服务器并监控来自其它服务器的信息。
在分布式更新期间,各服务器首先标志它们已经完成(但未提交)指定给它们的分布式事务的那一部分,并准备提交(以使它们的更新部分成为永久性的)。这是 两阶段提交的第一阶段。如果有一结点不能响应,那么控制服务器要指示其它结点撤消分布式事务的各个部分的影响。如果所有结点都回答准备好提交,控制服务器 则指示它们提交并等待它们的响应。等待确认信息阶段是第二阶段。在接收到可以提交指示后,每个服务器提交分布式事务中属于自己的那一部分,并给控制服务器 发回提交完成信息。
在一个分布式事务中必须有一个场地的Server作为协调者(coordinator),它能向 其它场地的Server发出请求并对它们的回答作出响应由它来控制一个分布式事务的提交或撤消。该分布式事务中涉及到的其它场地的Server称为参与者(Participant)。
在一个分布式事务中,必须有一个场地的 Server 作为协调者 (coordinator),它能向 其它场地的 Server 发出请求,并对它们的回答作出响应,由它来控制一个分布式事务的提交或撤消。该分布式事务中涉及到的其它场地的 Server 称为参与者 (Participant)。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/commit.png"/> </div>
@ -626,7 +626,7 @@ XA是由X/Open组织提出的分布式事务的规范。XA规范主要定义了(
(1) 协调者准备局部(即在本地)提交并在日志中写入"预提交"日志项,并包含有该事务的所有参与者的名字。
(2) 协调者询问参与者能否提交该事务。一个参与者可能由于多种原因不能提交。例如该Server提供的约束条(Constraints)的延迟检查不符合 限制条件时不能提交参与者本身的Server进程或硬件发生故障不能提交或者协调者访问不到某参与者网络故障这时协调者都认为是收到了一个 否定的回答。
(2) 协调者询问参与者能否提交该事务。一个参与者可能由于多种原因不能提交。例如,该 Server 提供的约束条 (Constraints) 的延迟检查不符合 限制条件时,不能提交;参与者本身的 Server 进程或硬件发生故障,不能提交;或者协调者访问不到某参与者(网络故障),这时协调者都认为是收到了一个 否定的回答。
(3) 如果参与者能够提交,则在其本身的日志中写入"准备提交"日志项,该日志项立即写入硬盘,然后给协调者发回,已准备好提交"的回答。
@ -652,19 +652,19 @@ XA是由X/Open组织提出的分布式事务的规范。XA规范主要定义了(
### 5.1 事务下多数据源无法切换
这里是主要是对上文提到为什么不重写sqlSessionTemplate会导致在事务下数据源切换失败的补充我们先看看sqlSessionTemplate源码中关于该类的定义
这里是主要是对上文提到为什么不重写 sqlSessionTemplate 会导致在事务下数据源切换失败的补充,我们先看看 sqlSessionTemplate 源码中关于该类的定义:
> sqlSessionTemplateSpring事务管理一起使用以确保使用的实际SqlSession是与当前Spring事务关联的,此外它还管理会话生命周期包括根据Spring事务配置根据需要关闭提交或回滚会话
> sqlSessionTemplateSpring 事务管理一起使用,以确保使用的实际 SqlSession 是与当前 Spring 事务关联的,此外它还管理会话生命周期,包括根据 Spring 事务配置根据需要关闭,提交或回滚会话
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/sqlSessionTemplate.png"/> </div>
这里最主要的是说明sqlSession是与当前是spring 事务是关联的。
这里最主要的是说明 sqlSession 是与当前是 spring 事务是关联的。
#### 1. sqlSession与事务关联导致问题
对于mybatis 来说是默认开启一级缓存的一级缓存是session级别的对于同一个session如果是相同的查询语句并且查询参数都相同第二次的查询就直接从一级缓存中获取。
对于 mybatis 来说,是默认开启一级缓存的,一级缓存是 session 级别的,对于同一个 session 如果是相同的查询语句并且查询参数都相同,第二次的查询就直接从一级缓存中获取。
这也就是说对于如下的情况由于sqlSession是与事务绑定的如果使用原生sqlSessionTemplate则第一次查询和第二次查询都是用的同一个sqlSession那么第二个查询数据库2的查询语句根本不会执行,会直接从一级缓存中获取查询结果。两次查询得到都是第一次查询的结果。
这也就是说,对于如下的情况,由于 sqlSession 是与事务绑定的,如果使用原生 sqlSessionTemplate则第一次查询和第二次查询都是用的同一个 sqlSession那么第二个查询数据库 2 的查询语句根本不会执行,会直接从一级缓存中获取查询结果。两次查询得到都是第一次查询的结果。
```java
@GetMapping("ts/db/programmers")
@ -682,7 +682,7 @@ public List<Programmer> getAllProgrammers() {
先说一下为什么会出现连接的复用:
我们可以在spring的源码中看到spring在通过`DataSourceUtils`类中去获取新的连接`doGetConnection`的时候,会通过`TransactionSynchronizationManager.getResource(dataSource)`方法去判断当前数据源是否有可用的连接,如果有就直接返回,如果没有就通过`fetchConnection`方法去获取。
我们可以在 spring 的源码中看到 spring 在通过 `DataSourceUtils` 类中去获取新的连接 `doGetConnection` 的时候,会通过 `TransactionSynchronizationManager.getResource(dataSource)` 方法去判断当前数据源是否有可用的连接,如果有就直接返回,如果没有就通过 `fetchConnection` 方法去获取。
```java
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
@ -734,37 +734,37 @@ public static Connection doGetConnection(DataSource dataSource) throws SQLExcept
}
```
这里主要的问题是`TransactionSynchronizationManager.getResource(dataSource)`dataSource参数是在哪里进行注入的这里可以沿着调用堆栈往上寻找可以看到是在这个参数是`SpringManagedTransaction`类中获取连接的时候传入的。
这里主要的问题是 `TransactionSynchronizationManager.getResource(dataSource)`dataSource 参数是在哪里进行注入的,这里可以沿着调用堆栈往上寻找,可以看到是在这个参数是 `SpringManagedTransaction` 类中获取连接的时候传入的。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/opneConnection.png"/> </div>
`SpringManagedTransaction`这类中的dataSource是如何得到赋值的这里可以进入这个类中查看只有在创建这个类的时候通过构造器为dataSource赋值那么是哪个方法创建了`SpringManagedTransaction`?
`SpringManagedTransaction` 这类中的 dataSource 是如何得到赋值的,这里可以进入这个类中查看,只有在创建这个类的时候通过构造器为 dataSource 赋值,那么是哪个方法创建了 `SpringManagedTransaction`?
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/springManagerTransaction.png"/> </div>
在构造器上打一个断点,沿着调用的堆栈往上寻找可以看到是`DefaultSqlSessionFactory`在创建`SpringManagedTransaction`中传入的,**这个数据源就是创建sqlSession`sqlSessionFactory`中数据源**。
在构造器上打一个断点,沿着调用的堆栈往上寻找可以看到是 `DefaultSqlSessionFactory` 在创建 `SpringManagedTransaction` 中传入的,**这个数据源就是创建 sqlSession`sqlSessionFactory` 中数据源**。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/DefaultSqlSessionFactory.png"/> </div>
**这里说明连接的复用是与我们创建sqlSession时候传入的sqlSessionFactory是否是同一个有关**
**这里说明连接的复用是与我们创建 sqlSession 时候传入的 sqlSessionFactory 是否是同一个有关**
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/getsqlSession.png"/> </div>
所以我们才重写了sqlSessionTemplate中的`getSqlSession`方法获取sqlSession时候传入正在使用的数据源对应的`sqlSessionFactory`,这样即便在同一个的事务中,由于传入的`sqlSessionFactory`中不同,就不会出现连接复用。
所以我们才重写了 sqlSessionTemplate 中的 `getSqlSession` 方法,获取 sqlSession 时候传入正在使用的数据源对应的 `sqlSessionFactory`,这样即便在同一个的事务中,由于传入的 `sqlSessionFactory` 中不同,就不会出现连接复用。
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/customSqlSessionTemplate.png"/> </div>
关于mybati-spring 的更多事务处理机制,推荐阅读博客[mybatis-spring事务处理机制分析](https://my.oschina.net/fifadxj/blog/785621)
关于 mybati-spring 的更多事务处理机制,推荐阅读博客[mybatis-spring 事务处理机制分析](https://my.oschina.net/fifadxj/blog/785621)
### 5.2 出现org.apache.ibatis.binding.BindingExceptionInvalid bound statement (not found)异常
出现这个异常的的原因是在创建SqlSessionFactory的时候`setMapperLocations`配置好之前调用了`factoryBean.getObject()`方法
出现这个异常的的原因是在创建 SqlSessionFactory 的时候,在 `setMapperLocations` 配置好之前调用了 `factoryBean.getObject()` 方法
```java
//一个错误的示范
@ -779,7 +779,7 @@ private SqlSessionFactory createSqlSessionFactory(DataSource dataSource) throws
}
```
上面这段代码没有任何编译问题导致这个错误不容易发现但是在调用sql时候就会出现异常。原因是`factoryBean.getObject()`方法被调用时就已经创建了SqlSessionFactory并且SqlSessionFactory只会被创建一次。此时还没有指定sql 文件的位置导致mybatis无法将接口与xml中的sql语句进行绑定所以出现BindingExceptionInvalid 绑定异常。
上面这段代码没有任何编译问题,导致这个错误不容易发现,但是在调用 sql 时候就会出现异常。原因是 `factoryBean.getObject()` 方法被调用时就已经创建了 SqlSessionFactory并且 SqlSessionFactory 只会被创建一次。此时还没有指定 sql 文件的位置,导致 mybatis 无法将接口与 xml 中的 sql 语句进行绑定,所以出现 BindingExceptionInvalid 绑定异常。
```java
@Override
@ -792,7 +792,7 @@ private SqlSessionFactory createSqlSessionFactory(DataSource dataSource) throws
}
```
正常绑定的情况下我们是可以在sqlSessionFactory中查看到绑定好的查询接口
正常绑定的情况下,我们是可以在 sqlSessionFactory 中查看到绑定好的查询接口:
<div align="center"> <img src="https://github.com/heibaiying/spring-samples-for-all/blob/master/pictures/sqlSessionFactory.png"/> </div>