mybatis如何由spring管理数据源(mybatis和spring的交互流程)

本文章比较枯燥,源码居多。都是本人一步一步debug出来的,如果有问题欢迎指出。为了体现流程连贯性,所以由很多无用步骤。读者可以一边看一边debug。如果简单可以自行略过。

在前面的章节中我们已经知道mybatis在初始化过程。(org.mybatis.spring.SqlSessionFactoryBean的afterPropertiesSet())
在初始化mybatis的时候会将所有配置封装到Configuration类中,由JVM加载到内存中。这样做的好处是内存级操作是最快的,无需重复读取配置文件信息。Configuration类几乎包含所有的mytatis配置信息。sqlSessionFactoryBuilder也将会以此作为参数,创建SqlSessionFactory(实现类DefaultSqlSessionFactory)。
在创建SqlSessionFactory时我们可能记得有这样一段代码:


public void afterPropertiesSet() throws Exception {

notNull(dataSource, "Property ‘dataSource‘ is required");

notNull(sqlSessionFactoryBuilder, "Property ‘sqlSessionFactoryBuilder‘ is required");

state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),

"Property ‘configuration‘ and ‘configLocation‘ can not specified with together");

this.sqlSessionFactory = buildSqlSessionFactory();

}

protected SqlSessionFactory buildSqlSessionFactory() throws IOException {

Configuration configuration;

XMLConfigBuilder xmlConfigBuilder = null;

if (this.configuration != null) {

configuration = this.configuration;

if (configuration.getVariables() == null) {

configuration.setVariables(this.configurationProperties);

} else if (this.configurationProperties != null) {

configuration.getVariables().putAll(this.configurationProperties);

}

} else if (this.configLocation != null) {

xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);

configuration = xmlConfigBuilder.getConfiguration();

} else {

if (LOGGER.isDebugEnabled()) {

LOGGER.debug("Property `configuration` or ‘configLocation‘ not specified, using default MyBatis Configuration");

}

configuration = new Configuration();

configuration.setVariables(this.configurationProperties);

}

... ...

//当我们不指定transactionFactory时会创建SpringManagedTransactionFactory。这个是mybatis由spring管理事务的桥梁

if (this.transactionFactory == null) {

this.transactionFactory = new SpringManagedTransactionFactory();

}

configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));

在不指定transactionFactory的时候会创建默认SpringManagedTransactionFactory。SpringManagedTransactionFactory用于创建Transaction。Transaction是ibatis底层的一个数据源链接connection的包装类。管理数据库连接的声明周期,包括creation, preparation, commit/rollback and close。mytatis将此Transaction作为参数,创建sql执行的Executor。(mytatis的sql执行者是Executor)


/**

* Wraps a database connection.

* Handles the connection lifecycle that comprises: its creation, preparation, commit/rollback and close.

*

* @author Clinton Begin

*/

public interface Transaction {

/**

* Retrieve inner database connection

* @return DataBase connection

* @throws SQLException

*/

Connection getConnection() throws SQLException;

那么SpringManagedTransactionFactory在项目中是如何流转的,又是如何和spring进行沟通的呢?接着往下走。
在mytatis中是通过sqlSession开启会话的,它相当于一个connection链接。我们既可以通过sqlSession直接查询也可以通过sqlSession获取映射器,通过动态代理方式查询。
无论哪种方式,总归都要获取sqlSession。在上面初始化的过程中,我们知到创建的是DefaultSqlSessionFactory。进入源码看一看有啥


public class DefaultSqlSessionFactory implements SqlSessionFactory {

private final Configuration configuration;

public DefaultSqlSessionFactory(Configuration configuration) {

this.configuration = configuration;

}

@Override

public SqlSession openSession() {

return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);

}

@Override

public SqlSession openSession(ExecutorType execType) {

return openSessionFromDataSource(execType, null, false);

}

省略略干方法... ...

@Override

public SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level) {

return openSessionFromDataSource(execType, level, false);

}

@Override

public SqlSession openSession(ExecutorType execType, boolean autoCommit) {

return openSessionFromDataSource(execType, null, autoCommit);

}

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {

Transaction tx = null;

try {

final Environment environment = configuration.getEnvironment();//1

final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);//2

tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);//3

final Executor executor = configuration.newExecutor(tx, execType);//4

return new DefaultSqlSession(configuration, executor, autoCommit);//5

} catch (Exception e) {

closeTransaction(tx); // may have fetched a connection so lets call close()

throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);

} finally {

ErrorContext.instance().reset();

}

}

}

我们可以看见这个类中提供了茫茫多openSession的方法。通过数据源创建session最终调用的都是openSessionFromDataSource。这个方法很简单。
1、通过全局configuration获取当前环境
2、再通过环境拿到当初创建的SpringManagedTransactionFactory。
3、再通过SpringManagedTransactionFactory创建Transaction(SpringManagedTransaction)
4、创建sql执行器Executor(SimpleExecutor)
5、创建sqlsession会话(DefaultSqlSession)。
在会话创建完成后,就是进行具体的数据库操作,获取链接,查询,映射器解析结果,返回等操作。在DefaultSqlSession我们可以看到很多查询方法,具体用哪个mytatis经过解析自己选择,我们不做分析。拿一个案例getById做一下简单分析。(值得注意的是mybatis并没有在创建session会话时就建立数据库连接的)
当一个查询getById进入系统后,经过前面的初始化,创建会话,参数解析等步骤后进入DefaultSqlSession的public <T> T selectOne(String statement, Object parameter)方法


public class DefaultSqlSession implements SqlSession {

@Override

public <T> T selectOne(String statement) {

return this.<T>selectOne(statement, null);

}

//1

@Override

public <T> T selectOne(String statement, Object parameter) {

// Popular vote was to return null on 0 results and throw exception on too many.

List<T> list = this.<T>selectList(statement, parameter);

if (list.size() == 1) {

return list.get(0);

} else if (list.size() > 1) {

throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());

} else {

return null;

}

}

}


public class DefaultSqlSession implements SqlSession {

//2

@Override

public <E> List<E> selectList(String statement, Object parameter) {

return this.selectList(statement, parameter, RowBounds.DEFAULT);

}

//3

@Override

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {

try {

MappedStatement ms = configuration.getMappedStatement(statement);

//executor执行器具体执行sql

return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);

} catch (Exception e) {

throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);

} finally {

ErrorContext.instance().reset();

}

}

}


public class CachingExecutor implements Executor {

//4

@Override

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {

BoundSql boundSql = ms.getBoundSql(parameterObject);

CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);

return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);

}

//5

@Override

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)

throws SQLException {

Cache cache = ms.getCache();

//第一次是没有缓存的走下边的

if (cache != null) {

flushCacheIfRequired(ms);

if (ms.isUseCache() && resultHandler == null) {

ensureNoOutParams(ms, boundSql);

@SuppressWarnings("unchecked")

List<E> list = (List<E>) tcm.getObject(cache, key);

if (list == null) {

list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);

tcm.putObject(cache, key, list); // issue #578 and #116

}

return list;

}

}

return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);

}

}


public abstract class BaseExecutor implements Executor {

//6

@SuppressWarnings("unchecked")

@Override

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {

ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());

if (closed) {

throw new ExecutorException("Executor was closed.");

}

if (queryStack == 0 && ms.isFlushCacheRequired()) {

clearLocalCache();

}

List<E> list;

try {

queryStack++;

list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;

if (list != null) {

handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);

} else {

list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);

}

} finally {

queryStack--;

}

if (queryStack == 0) {

for (DeferredLoad deferredLoad : deferredLoads) {

deferredLoad.load();

}

// issue #601

deferredLoads.clear();

if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {

// issue #482

clearLocalCache();

}

}

return list;

}

//7

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {

List<E> list;

localCache.putObject(key, EXECUTION_PLACEHOLDER);

try {

list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);

} finally {

localCache.removeObject(key);

}

localCache.putObject(key, list);

if (ms.getStatementType() == StatementType.CALLABLE) {

localOutputParameterCache.putObject(key, parameter);

}

return list;

}

}


public class SimpleExecutor extends BaseExecutor {

//8

@Override

public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {

Statement stmt = null;

try {

Configuration configuration = ms.getConfiguration();

StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);

stmt = prepareStatement(handler, ms.getStatementLog());

return handler.<E>query(stmt, resultHandler);

} finally {

closeStatement(stmt);

}

}

//9

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {

Statement stmt;

//获取数据库连接

Connection connection = getConnection(statementLog);

stmt = handler.prepare(connection, transaction.getTimeout());

handler.parameterize(stmt);

return stmt;

}

}

在执行器SimpleExecutor中我们看到一个getConnection的方法。这是具体获取数据源连接的过程。我们记得在开启会话的时候第三步是创建执行器,参数是SpringManagedTransaction和执行器类型simple。所以继续往下跟,看SpringManagedTransaction如何拿到数据库连接的


public abstract class BaseExecutor implements Executor {

//10

protected Connection getConnection(Log statementLog) throws SQLException {

//transaction是一个接口,创建会话时指定的是SpringManagedTransaction

Connection connection = transaction.getConnection();

if (statementLog.isDebugEnabled()) {

return ConnectionLogger.newInstance(connection, statementLog, queryStack);

} else {

return connection;

}

}

}


public class SpringManagedTransaction implements Transaction {

@Override

public Connection getConnection() throws SQLException {

//如果Transaction包装类中有数据源就返回,没有就创建

if (this.connection == null) {

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);

if (LOGGER.isDebugEnabled()) {

LOGGER.debug(

"JDBC Connection ["

+ this.connection

+ "] will"

+ (this.isConnectionTransactional ? " " : " not ")

+ "be managed by Spring");

}

}

}


public abstract class DataSourceUtils {

public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {

try {

return doGetConnection(dataSource);

}

catch (SQLException ex) {

throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection", ex);

}

catch (IllegalStateException ex) {

throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection: " + ex.getMessage());

}

}

public static Connection doGetConnection(DataSource dataSource) throws SQLException {

Assert.notNull(dataSource, "No DataSource specified");

ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);

if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {

conHolder.requested();

if (!conHolder.hasConnection()) {

logger.debug("Fetching resumed JDBC Connection from DataSource");

conHolder.setConnection(fetchConnection(dataSource));

}

return conHolder.getConnection();

}

// Else we either got no holder or an empty thread-bound holder here.

logger.debug("Fetching JDBC Connection from DataSource");

Connection con = fetchConnection(dataSource);

if (TransactionSynchronizationManager.isSynchronizationActive()) {

logger.debug("Registering transaction synchronization for JDBC Connection");

// Use same Connection for further JDBC actions within the transaction.

// Thread-bound object will get removed by synchronization at transaction completion.

ConnectionHolder holderToUse = conHolder;

if (holderToUse == null) {

holderToUse = new ConnectionHolder(con);

}

else {

holderToUse.setConnection(con);

}

holderToUse.requested();

TransactionSynchronizationManager.registerSynchronization(

new ConnectionSynchronization(holderToUse, dataSource));

holderToUse.setSynchronizedWithTransaction(true);

if (holderToUse != conHolder) {

TransactionSynchronizationManager.bindResource(dataSource, holderToUse);

}

}

return con;

}

private static Connection fetchConnection(DataSource dataSource) throws SQLException {

//使用指定的dataSource创建链接。具体步骤需要看使用的是什么线程池技术。如果是druid就看druid源码。如果是c3p0就看c3p0的源码。

Connection con = dataSource.getConnection();

if (con == null) {

throw new IllegalStateException("DataSource returned null from getConnection(): " + dataSource);

}

return con;

}

}

在dataSource.getConnection()中具体需要看用的什么线程池技术(一般都会用到线程池)。我们自己的项目使用的是druid连接池。由于流量大所以加入了主从复制多数据源,再继承spring的AbstractRoutingDataSource作为包装。使用Aspect动态切换数据源。所以我们看我项目自己的包装类即可。配置和源码如下:


//数据源包装类

public class MultipleDataSource extends AbstractRoutingDataSource {

private static final ThreadLocal<String> dataSourceKey = new InheritableThreadLocal<>();

public static void setDataSourceKey(String dataSource) {

dataSourceKey.set(dataSource);

}

//注意这个重写的方法。它是后面从数据源Map中获取key的方法

@Override

protected Object determineCurrentLookupKey() {

return dataSourceKey.get();

}

}

AbstractRoutingDataSource源码如下。AbstractRoutingDataSource实现了InitializingBean接口,spring在完成对象创建和成员变量赋值后,会调用afterPropertiesSet()方法。这一点和druid数据源创建很类似。在这个方法中会做一些校验和其他成员变量的赋值工作。将所有的数据源以key=value的方式放到一个Map中。


public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {

@Nullable

private Map<Object, Object> targetDataSources;

@Nullable

private Object defaultTargetDataSource;

@Nullable

private Map<Object, DataSource> resolvedDataSources;

@Nullable

private DataSource resolvedDefaultDataSource;

@Override

public void afterPropertiesSet() {

if (this.targetDataSources == null) {

throw new IllegalArgumentException("Property ‘targetDataSources‘ is required");

}

this.resolvedDataSources = new HashMap<>(this.targetDataSources.size());

this.targetDataSources.forEach((key, value) -> {

Object lookupKey = resolveSpecifiedLookupKey(key);

DataSource dataSource = resolveSpecifiedDataSource(value);

this.resolvedDataSources.put(lookupKey, dataSource);

});

if (this.defaultTargetDataSource != null) {

this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);

}

}

@Override

public Connection getConnection() throws SQLException {

return determineTargetDataSource().getConnection();

}

protected DataSource determineTargetDataSource() {

Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");

Object lookupKey = determineCurrentLookupKey();

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 + "]");

}

return dataSource;

}

@Nullable

protected abstract Object determineCurrentLookupKey();

}

回到上面的问题我们看我项目自己的包装类MultipleDataSource中并没有getConnection() 方法,调用的是父类AbstractRoutingDataSource的getConnection()。接着调用determineTargetDataSource(),再次方法中调用determineCurrentLookupKey();这个方法是在包装类中重写的方法。它的返回值是从ThreadLocal副本中获取。在每个线程链接mysql之前,都会用Aspect动态在ThreadLocal副本保存一个数据源key,当getConnection() 时会根据这个key,从数据源Map(afterPropertiesSet()方法设置的)中get出来对应的数据源,这样就能动态的从spring中获取指定的数据源。

原文地址:https://www.cnblogs.com/Houz/p/11957908.html

时间: 2024-10-28 22:22:30

mybatis如何由spring管理数据源(mybatis和spring的交互流程)的相关文章

Spring 管理数据源

Spring 管理数据源 不管通过何种持久化技术,都必须通过数据连接访问数据库,在Spring中,数据连接是通过数据源获得的.在以往的应用中,数据源一般是Web应用服务器提供的.在Spring中,你不但可以通过JNDI获取应用服务器的数据源,也可以直接在Spring容器中配置数据源,此外,你还可以通过代码的方式创建一个数据源,以便进行无依赖的单元测试配置一个数据源. Spring在第三方依赖包中包含了两个数据源的实现类包,其一是Apache的DBCP,其二是 C3P0.可以在Spring配置文件

Mybaits 源码解析 (十二)----- Mybatis的事务如何被Spring管理?Mybatis和Spring事务中用的Connection是同一个吗?

不知道一些同学有没有这种疑问,为什么Mybtis中要配置dataSource,Spring的事务中也要配置dataSource?那么Mybatis和Spring事务中用的Connection是同一个吗?我们常用配置如下 <!--会话工厂 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name=&qu

Spring 配置数据源

配置一个数据源     Spring在第三方依赖包中包含了两个数据源的实现类包,其一是Apache的DBCP,其二是 C3P0.可以在Spring配置文件中利用这两者中任何一个配置数据源. DBCP数据源     DBCP类包位于 /lib/jakarta-commons/commons-dbcp.jar,DBCP 是一个依赖 Jakarta commons-pool对象池机制的数据库连接池,所以在类路径下还必须包括/lib/jakarta- commons /commons-pool.jar.

Spring多数据源分布式事务管理/springmvc+spring+atomikos[jta]+druid+mybatis

项目进行读写分离及分库分表,在一个业务中,在一个事务中处理时候将切换多个数据源,需要保证同一事务多个数据源数据的一致性.此处使用atomikos来实现:最后附源码: 1:spring3.0之后不再支持jtom[jta]了,第三方开源软件atomikos(http://www.atomikos.com/)来实现. 2:org.springframework.transaction.jta.JotmFactoryBean类,spring-tx-2.5.6.jar中有此类,spring-tx-3.0.

Spring Boot 整合 Mybatis 实现 Druid 多数据源详解

一.多数据源的应用场景 目前,业界流行的数据操作框架是 Mybatis,那 Druid 是什么呢? Druid 是 Java 的数据库连接池组件.Druid 能够提供强大的监控和扩展功能.比如可以监控 SQL ,在监控业务可以查询慢查询 SQL 列表等.Druid 核心主要包括三部分: 1. DruidDriver 代理 Driver,能够提供基于 Filter-Chain 模式的插件体系. 2. DruidDataSource 高效可管理的数据库连接池 3. SQLParser 当业务数据量达

spring boot配置mybatis和事务管理

spring boot配置mybatis和事务管理 一.spring boot与mybatis的配置 1.首先,spring boot 配置mybatis需要的全部依赖如下: <!-- Spring Boot 启动父依赖 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId>

Spring Profile和Mybatis进行多个数据源(H2和Mysql)的切换

总结: 最近在做WebMagic的后台,遇到一个问题:后台用到了数据库,本来理想情况下是用Mysql,但是为了做到开箱即用,也整合了一个嵌入式数据库H2.这里面就有个问题了,如何用一套代码,提供对Mysql和H2两种方案的支持?博主收集了一些资料,也调试了很久,终于找到一套可行方案,记录下来.代码贴的有点多,主要是为了以后方便自己查找. H2的使用 H2是一个嵌入式,纯Java实现的数据库,它各方面都要好于Java的sqlitejdbc.它可以使用内存模式,也可以使用磁盘模式.具体使用可以看攻略

Spring、Spring MVC、MyBatis整合文件配置详解

使用SSM框架做了几个小项目了,感觉还不错是时候总结一下了.先总结一下SSM整合的文件配置.其实具体的用法最好还是看官方文档. Spring:http://spring.io/docs MyBatis:http://mybatis.github.io/mybatis-3/ 基本的组织结构和用法就不说了,前面的博客和官方文档上都非常的全面.jar包可以使用Maven来组织管理.来看配置文件. web.xml的配置                                           

Spring+SpringMVC+MyBatis深入学习及搭建(三)——MyBatis全局配置文件解析

转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/6874672.html 前面有写到Spring+SpringMVC+MyBatis深入学习及搭建(二)——MyBatis原始Dao开发和mapper代理开发 MyBatis的全局配置文件SqlMapConfig.xml,配置内容和顺序如下: properties(属性) setting(全局配置参数) typeAliases(类名别名) typeHandlers(类名处理器) objectFactory(对