Mybatis工作流程及其原理與解析

Mybatis簡介:

MyBatis 是一款優(yōu)秀的持久層框架笙什,它支持定制化 SQL、存儲過程以及高級映射睦擂。MyBatis 避免了幾乎所有的 JDBC 代碼和手動設置參數(shù)以及獲取結果集得湘。MyBatis 可以使用簡單的 XML 或注解來配置和映射原生信息,將接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java對象)映射成數(shù)據(jù)庫中的記錄顿仇。本文將通過debug的方式來了解其工作原理淘正。

Mybatis核心類:

SqlSessionFactory:每個基于 MyBatis 的應用都是以一個 SqlSessionFactory 的實例為中心的摆马。SqlSessionFactory 的實例可以通過 SqlSessionFactoryBuilder 獲得。而 SqlSessionFactoryBuilder 則可以從 XML 配置文件或通過Java的方式構建出 SqlSessionFactory 的實例鸿吆。SqlSessionFactory 一旦被創(chuàng)建就應該在應用的運行期間一直存在囤采,建議使用單例模式或者靜態(tài)單例模式。一個SqlSessionFactory對應配置文件中的一個環(huán)境(environment)惩淳,如果你要使用多個數(shù)據(jù)庫就配置多個環(huán)境分別對應一個SqlSessionFactory蕉毯。

SqlSession:SqlSession是一個接口,它有2個實現(xiàn)類思犁,分別是DefaultSqlSession(默認使用)以及SqlSessionManager代虾。SqlSession通過內(nèi)部存放的執(zhí)行器(Executor)來對數(shù)據(jù)進行CRUD。此外SqlSession不是線程安全的激蹲,因為每一次操作完數(shù)據(jù)庫后都要調(diào)用close對其進行關閉棉磨,官方建議通過try-finally來保證總是關閉SqlSession。

Executor:Executor(執(zhí)行器)接口有兩個實現(xiàn)類学辱,其中BaseExecutor有三個繼承類分別是BatchExecutor(重用語句并執(zhí)行批量更新)乘瓤,ReuseExecutor(重用預處理語句prepared statements),SimpleExecutor(普通的執(zhí)行器)策泣。以上三個就是主要的Executor衙傀。通過下圖可以看到Mybatis在Executor的設計上面使用了裝飾者模式,我們可以用CachingExecutor來裝飾前面的三個執(zhí)行器目的就是用來實現(xiàn)緩存萨咕。

image

MappedStatement:MappedStatement就是用來存放我們SQL映射文件中的信息包括sql語句统抬,輸入?yún)?shù),輸出參數(shù)等等任洞。一個SQL節(jié)點對應一個MappedStatement對象蓄喇。

Mybatis工作流程:

image

閱讀全文有驚喜哦7⑶帧=惶汀!

下面將通過debug方式對Mybatis進行一步步解析刃鳄。首先貼出我的mybatis-config.xml文件以及Mapper.xml文件盅弛。

PUBLIC "-//mybatis.org//DTD Config 3.0//EN"

"http://mybatis.org/dtd/mybatis-3-config.dtd">

PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"

"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

select * from user

User where id = #{id}

insert into User (username,birthday,sex,address)

values (#{name},#{birthday},#{sex},#{address})

update User set username = #{username},birthday = #{birthday},

sex = #{sex},address = #{address} where id = #{id}

delete from User where id = #{id}

select * from User where sex = #{param1}

and username like #{param2}

and address = #{parma3}

select count(*) from user where username like #{username}

username like #{pattern}

and sex = #{sex}

and address = #{address}

where id in

{id}

image

第一步通過SqlSessionFactoryBuilder創(chuàng)建SqlSessionFactory:

首先在SqlSessionFactoryBuilder的build()方法中可以看到MyBatis內(nèi)部定義了一個類XMLConfigBuilder用來解析配置文件mybatis-config.xml。針對配置文件中的每一個節(jié)點進行解析并將數(shù)據(jù)存放到Configuration這個對象中叔锐,緊接著使用帶有Configuration的構造方法發(fā)返回一個DefautSqlSessionFactory挪鹏。

public SqlSessionFactory build(InputStream inputStream) {

return build(inputStream, null, null);

}

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {

try {

XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);

//解析mybatis-config.xml

return build(parser.parse());

} catch (Exception e) {

throw ExceptionFactory.wrapException("Error building SqlSession.", e);

} finally {

ErrorContext.instance().reset();

try {

inputStream.close();

} catch (IOException e) {

// Intentionally ignore. Prefer previous error.

}

}

}

//返回SqlSessionFactory,默認使用的是實現(xiàn)類DefaultSqlSessionFactory

public SqlSessionFactory build(Configuration config) {

return new DefaultSqlSessionFactory(config);

}

public Configuration parse() {

if (parsed) {

throw new BuilderException("Each XMLConfigBuilder can only be used once.");

}

parsed = true;

//獲取根節(jié)點configuration

parseConfiguration(parser.evalNode("/configuration"));

return configuration;

}

//開始解析mybatis-config.xml,并把解析后的數(shù)據(jù)存放到configuration中

private void parseConfiguration(XNode root) {

try {

//保存mybatis-config.xml中的標簽setting,本例中開啟全局緩存cacheEnabled愉烙,設置默認執(zhí)行器defaultExecutorType=REUSE

Properties settings = settingsAsPropertiess(root.evalNode("settings"));

//issue #117 read properties first

//解析是否配置了外部properties讨盒,例如本例中配置的jdbc.propertis

propertiesElement(root.evalNode("properties"));

//查看是否配置了VFS,默認沒有步责,本例也沒有使用

loadCustomVfs(settings);

//查看是否用了類型別名返顺,減少完全限定名的冗余禀苦,本例中使用了別名User代替了com.ctc.Model.User

typeAliasesElement(root.evalNode("typeAliases"));

//查看是否配置插件來攔截映射語句的執(zhí)行,例如攔截Executor的Update方法遂鹊,本例沒有使用

pluginElement(root.evalNode("plugins"))

//查看是否配置了ObjectFactory振乏,默認情況下使用對象的無參構造方法或者是帶有參數(shù)的構造方法峡钓,本例沒有使用

objectFactoryElement(root.evalNode("objectFactory"));

//查看是否配置了objectWrapperFatory,這個用來或者ObjectWapper怨愤,可以訪問:對象,Collection涂召,Map屬性舟陆。本例沒有使用

objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));

//查看是否配置了reflectorFactory,mybatis的反射工具误澳,提供了很多反射方法。本例沒有使用

reflectorFactoryElement(root.evalNode("reflectorFactory"));

//放入?yún)?shù)到configuration對象中

settingsElement(settings);

// read it after objectFactory and objectWrapperFactory issue #631

//查看數(shù)據(jù)庫環(huán)境配置

environmentsElement(root.evalNode("environments"));

//查看是否使用多種數(shù)據(jù)庫秦躯,本例沒有使用

databaseIdProviderElement(root.evalNode("databaseIdProvider"));

//查看是否配置了新的類型處理器脓匿,如果跟處理的類型跟默認的一致就會覆蓋。本例沒有使用

typeHandlerElement(root.evalNode("typeHandlers"));

//查看是否配置SQL映射文件,有四種配置方式宦赠,resource陪毡,url,class以及自動掃包package勾扭。本例使用package

mapperElement(root.evalNode("mappers"));

} catch (Exception e) {

throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);

}

}

第二步通過SqlSessionFactory創(chuàng)建SqlSession:

@Override

public SqlSession openSession() {

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

}

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

Transaction tx = null;

try {

//拿到前文從mybatis中解析到的數(shù)據(jù)庫環(huán)境配置

final Environment environment = configuration.getEnvironment();

final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);

//拿到jdbc的事務管理器毡琉,有兩種一種是jbc,一種的managed。本例使用的是JdbcTransaction

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

//從mybatis配置文件可以看到本例使用了REUSE妙色,因此返回的是ReuseExecutor并把事務傳入對象中

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

return new DefaultSqlSession(configuration, executor, autoCommit);

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

}

}

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {

executorType = executorType == null ? defaultExecutorType : executorType;

executorType = executorType == null ? ExecutorType.SIMPLE : executorType;

Executor executor;

if (ExecutorType.BATCH == executorType) {

executor = new BatchExecutor(this, transaction);

} else if (ExecutorType.REUSE == executorType) {

executor = new ReuseExecutor(this, transaction);

} else {

executor = new SimpleExecutor(this, transaction);

}

if (cacheEnabled) {

executor = new CachingExecutor(executor);

}

executor = (Executor) interceptorChain.pluginAll(executor);

return executor;

}

//返回一個SqlSession桅滋,默認使用DefaultSqlSession

public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {

this.configuration = configuration;

this.executor = executor;

this.dirty = false;

this.autoCommit = autoCommit;

}

第三步通過SqlSession拿到Mapper對象的代理:

@Override

public T getMapper(Class type) {

return configuration.getMapper(type, this);

}

public T getMapper(Class type, SqlSession sqlSession) {

//前文解析Mybatis-config.xml的時候,在解析標簽mapper就是用configuration對象的mapperRegistry存放數(shù)據(jù)

return mapperRegistry.getMapper(type, sqlSession);

}

@SuppressWarnings("unchecked")

public T getMapper(Class type, SqlSession sqlSession) {

//knownMapper是一個HashMap在存放mapperRegistry的過程中身辨,以每個Mapper對象的類型為Key, MapperProxyFactory 為value保存丐谋。

//例如本例中保存的就是Key:com.ctc.mapper.UserMapper,value就是保存了key的MapperProxyFactory對象

final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);

if (mapperProxyFactory == null) {

throw new BindingException("Type " + type + " is not known to the MapperRegistry.");

}

try {

return mapperProxyFactory.newInstance(sqlSession);

} catch (Exception e) {

throw new BindingException("Error getting mapper instance. Cause: " + e, e);

}

}

public T newInstance(SqlSession sqlSession) {

//生成一個mapperProxy對象,這個對象實現(xiàn)了InvocationHandler, Serializable煌珊。就是JDK動態(tài)代理中的方法調(diào)用處理器

final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);

return newInstance(mapperProxy);

}

public MapperProxy(SqlSession sqlSession, Class mapperInterface, Map methodCache) {

this.sqlSession = sqlSession;

this.mapperInterface = mapperInterface;

this.methodCache = methodCache;

}

@SuppressWarnings("unchecked")

protected T newInstance(MapperProxy mapperProxy) {

//通過JDK動態(tài)代理生成一個Mapper的代理号俐,在本例中的就是UserMapper的代理類,它實現(xiàn)了UserMapper接口

return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);

}

第四步通過MapperProxy調(diào)用Maper中相應的方法:

@Override

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

//判斷當前調(diào)用的method是不是Object中聲明的方法定庵,如果是的話直接執(zhí)行吏饿。

if (Object.class.equals(method.getDeclaringClass())) {

try {

return method.invoke(this, args);

} catch (Throwable t) {

throw ExceptionUtil.unwrapThrowable(t);

}

}

final MapperMethod mapperMethod = cachedMapperMethod(method);

return mapperMethod.execute(sqlSession, args);

}

//把當前請求放入一個HashMap中,一旦下次還是同樣的方法進來直接返回蔬浙。

private MapperMethod cachedMapperMethod(Method method) {

MapperMethod mapperMethod = methodCache.get(method);

if (mapperMethod == null) {

mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());

methodCache.put(method, mapperMethod);

}

return mapperMethod;

}

public Object execute(SqlSession sqlSession, Object[] args) {

Object result;

switch (command.getType()) {

case INSERT: {

Object param = method.convertArgsToSqlCommandParam(args);

result = rowCountResult(sqlSession.insert(command.getName(), param));

break;

}

case UPDATE: {

Object param = method.convertArgsToSqlCommandParam(args);

result = rowCountResult(sqlSession.update(command.getName(), param));

break;

}

case DELETE: {

Object param = method.convertArgsToSqlCommandParam(args);

result = rowCountResult(sqlSession.delete(command.getName(), param));

break;

}

case SELECT:

if (method.returnsVoid() && method.hasResultHandler()) {

executeWithResultHandler(sqlSession, args);

result = null;

} else if (method.returnsMany()) {

result = executeForMany(sqlSession, args);

} else if (method.returnsMap()) {

result = executeForMap(sqlSession, args);

} else if (method.returnsCursor()) {

result = executeForCursor(sqlSession, args);

} else {

//本次案例會執(zhí)行selectOne

Object param = method.convertArgsToSqlCommandParam(args);

result = sqlSession.selectOne(command.getName(), param);

}

break;

case FLUSH:

result = sqlSession.flushStatements();

break;

default:

throw new BindingException("Unknown execution method for: " + command.getName());

}

if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {

throw new BindingException("Mapper method '" + command.getName()

  • " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");

}

return result;

}

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

}

}

@Override

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

try {

MappedStatement ms = configuration.getMappedStatement(statement);

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

}

}

//這邊調(diào)用的是CachingExecutor類的query,還記得前文解析mybatis-config.xml的時候我們指定了REUSE但是因為在配置文件中開啟了緩存

//所以ReuseExecutor被CachingExecotur裝飾猪落,新增了緩存的判斷,最后還是會調(diào)用ReuseExecutor

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

}

@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, parameterObject, boundSql);

@SuppressWarnings("unchecked")

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

if (list == null) {

//如果緩存中沒有數(shù)據(jù)則查詢數(shù)據(jù)庫

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

}

MyBatis 是一款優(yōu)秀的持久層框架畴博,它支持定制化 SQL笨忌、存儲過程以及高級映射。MyBatis 避免了幾乎所有的 JDBC 代碼和手動設置參數(shù)以及獲取結果集俱病。MyBatis 可以使用簡單的 XML 或注解來配置和映射原生信息官疲,將接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java對象)映射成數(shù)據(jù)庫中的記錄杂曲。本文將通過debug的方式來了解其工作原理。

Mybatis核心類:

SqlSessionFactory:每個基于 MyBatis 的應用都是以一個 SqlSessionFactory 的實例為中心的袁余。SqlSessionFactory 的實例可以通過 SqlSessionFactoryBuilder 獲得擎勘。而 SqlSessionFactoryBuilder 則可以從 XML 配置文件或通過Java的方式構建出 SqlSessionFactory 的實例。SqlSessionFactory 一旦被創(chuàng)建就應該在應用的運行期間一直存在颖榜,建議使用單例模式或者靜態(tài)單例模式棚饵。一個SqlSessionFactory對應配置文件中的一個環(huán)境(environment),如果你要使用多個數(shù)據(jù)庫就配置多個環(huán)境分別對應一個SqlSessionFactory掩完。

SqlSession:SqlSession是一個接口噪漾,它有2個實現(xiàn)類,分別是DefaultSqlSession(默認使用)以及SqlSessionManager且蓬。SqlSession通過內(nèi)部存放的執(zhí)行器(Executor)來對數(shù)據(jù)進行CRUD欣硼。此外SqlSession不是線程安全的,因為每一次操作完數(shù)據(jù)庫后都要調(diào)用close對其進行關閉恶阴,官方建議通過try-finally來保證總是關閉SqlSession诈胜。

Executor:Executor(執(zhí)行器)接口有兩個實現(xiàn)類,其中BaseExecutor有三個繼承類分別是BatchExecutor(重用語句并執(zhí)行批量更新)冯事,ReuseExecutor(重用預處理語句prepared statements)焦匈,SimpleExecutor(普通的執(zhí)行器)。以上三個就是主要的Executor昵仅。通過下圖可以看到Mybatis在Executor的設計上面使用了裝飾者模式缓熟,我們可以用CachingExecutor來裝飾前面的三個執(zhí)行器目的就是用來實現(xiàn)緩存。

image

MappedStatement:MappedStatement就是用來存放我們SQL映射文件中的信息包括sql語句摔笤,輸入?yún)?shù)够滑,輸出參數(shù)等等。一個SQL節(jié)點對應一個MappedStatement對象吕世。

Mybatis工作流程:

image

下面將通過debug方式對Mybatis進行一步步解析彰触。首先貼出我的mybatis-config.xml文件以及Mapper.xml文件。

PUBLIC "-//mybatis.org//DTD Config 3.0//EN"

"http://mybatis.org/dtd/mybatis-3-config.dtd">

PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"

"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

select * from user

User where id = #{id}

insert into User (username,birthday,sex,address)

values (#{name},#{birthday},#{sex},#{address})

update User set username = #{username},birthday = #{birthday},

sex = #{sex},address = #{address} where id = #{id}

delete from User where id = #{id}

select * from User where sex = #{param1}

and username like #{param2}

and address = #{parma3}

select count(*) from user where username like #{username}

username like #{pattern}

and sex = #{sex}

and address = #{address}

where id in

{id}

image

第一步通過SqlSessionFactoryBuilder創(chuàng)建SqlSessionFactory:

首先在SqlSessionFactoryBuilder的build()方法中可以看到MyBatis內(nèi)部定義了一個類XMLConfigBuilder用來解析配置文件mybatis-config.xml寞冯。針對配置文件中的每一個節(jié)點進行解析并將數(shù)據(jù)存放到Configuration這個對象中渴析,緊接著使用帶有Configuration的構造方法發(fā)返回一個DefautSqlSessionFactory。

public SqlSessionFactory build(InputStream inputStream) {

return build(inputStream, null, null);

}

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {

try {

XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);

//解析mybatis-config.xml

return build(parser.parse());

} catch (Exception e) {

throw ExceptionFactory.wrapException("Error building SqlSession.", e);

} finally {

ErrorContext.instance().reset();

try {

inputStream.close();

} catch (IOException e) {

// Intentionally ignore. Prefer previous error.

}

}

}

//返回SqlSessionFactory吮龄,默認使用的是實現(xiàn)類DefaultSqlSessionFactory

public SqlSessionFactory build(Configuration config) {

return new DefaultSqlSessionFactory(config);

}

public Configuration parse() {

if (parsed) {

throw new BuilderException("Each XMLConfigBuilder can only be used once.");

}

parsed = true;

//獲取根節(jié)點configuration

parseConfiguration(parser.evalNode("/configuration"));

return configuration;

}

//開始解析mybatis-config.xml,并把解析后的數(shù)據(jù)存放到configuration中

private void parseConfiguration(XNode root) {

try {

//保存mybatis-config.xml中的標簽setting,本例中開啟全局緩存cacheEnabled,設置默認執(zhí)行器defaultExecutorType=REUSE

Properties settings = settingsAsPropertiess(root.evalNode("settings"));

//issue #117 read properties first

//解析是否配置了外部properties咆疗,例如本例中配置的jdbc.propertis

propertiesElement(root.evalNode("properties"));

//查看是否配置了VFS漓帚,默認沒有,本例也沒有使用

loadCustomVfs(settings);

//查看是否用了類型別名午磁,減少完全限定名的冗余尝抖,本例中使用了別名User代替了com.ctc.Model.User

typeAliasesElement(root.evalNode("typeAliases"));

//查看是否配置插件來攔截映射語句的執(zhí)行毡们,例如攔截Executor的Update方法,本例沒有使用

pluginElement(root.evalNode("plugins"))

//查看是否配置了ObjectFactory昧辽,默認情況下使用對象的無參構造方法或者是帶有參數(shù)的構造方法衙熔,本例沒有使用

objectFactoryElement(root.evalNode("objectFactory"));

//查看是否配置了objectWrapperFatory,這個用來或者ObjectWapper,可以訪問:對象搅荞,Collection红氯,Map屬性。本例沒有使用

objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));

//查看是否配置了reflectorFactory,mybatis的反射工具咕痛,提供了很多反射方法痢甘。本例沒有使用

reflectorFactoryElement(root.evalNode("reflectorFactory"));

//放入?yún)?shù)到configuration對象中

settingsElement(settings);

// read it after objectFactory and objectWrapperFactory issue #631

//查看數(shù)據(jù)庫環(huán)境配置

environmentsElement(root.evalNode("environments"));

//查看是否使用多種數(shù)據(jù)庫,本例沒有使用

databaseIdProviderElement(root.evalNode("databaseIdProvider"));

//查看是否配置了新的類型處理器茉贡,如果跟處理的類型跟默認的一致就會覆蓋塞栅。本例沒有使用

typeHandlerElement(root.evalNode("typeHandlers"));

//查看是否配置SQL映射文件,有四種配置方式,resource腔丧,url放椰,class以及自動掃包package。本例使用package

mapperElement(root.evalNode("mappers"));

} catch (Exception e) {

throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);

}

}

第二步通過SqlSessionFactory創(chuàng)建SqlSession:

@Override

public SqlSession openSession() {

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

}

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

Transaction tx = null;

try {

//拿到前文從mybatis中解析到的數(shù)據(jù)庫環(huán)境配置

final Environment environment = configuration.getEnvironment();

final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);

//拿到jdbc的事務管理器愉粤,有兩種一種是jbc,一種的managed庄敛。本例使用的是JdbcTransaction

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

//從mybatis配置文件可以看到本例使用了REUSE,因此返回的是ReuseExecutor并把事務傳入對象中

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

return new DefaultSqlSession(configuration, executor, autoCommit);

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

}

}

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {

executorType = executorType == null ? defaultExecutorType : executorType;

executorType = executorType == null ? ExecutorType.SIMPLE : executorType;

Executor executor;

if (ExecutorType.BATCH == executorType) {

executor = new BatchExecutor(this, transaction);

} else if (ExecutorType.REUSE == executorType) {

executor = new ReuseExecutor(this, transaction);

} else {

executor = new SimpleExecutor(this, transaction);

}

if (cacheEnabled) {

executor = new CachingExecutor(executor);

}

executor = (Executor) interceptorChain.pluginAll(executor);

return executor;

}

//返回一個SqlSession科汗,默認使用DefaultSqlSession

public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {

this.configuration = configuration;

this.executor = executor;

this.dirty = false;

this.autoCommit = autoCommit;

}

第三步通過SqlSession拿到Mapper對象的代理:

@Override

public T getMapper(Class type) {

return configuration.getMapper(type, this);

}

public T getMapper(Class type, SqlSession sqlSession) {

//前文解析Mybatis-config.xml的時候藻烤,在解析標簽mapper就是用configuration對象的mapperRegistry存放數(shù)據(jù)

return mapperRegistry.getMapper(type, sqlSession);

}

@SuppressWarnings("unchecked")

public T getMapper(Class type, SqlSession sqlSession) {

//knownMapper是一個HashMap在存放mapperRegistry的過程中,以每個Mapper對象的類型為Key, MapperProxyFactory 為value保存头滔。

//例如本例中保存的就是Key:com.ctc.mapper.UserMapper,value就是保存了key的MapperProxyFactory對象

final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);

if (mapperProxyFactory == null) {

throw new BindingException("Type " + type + " is not known to the MapperRegistry.");

}

try {

return mapperProxyFactory.newInstance(sqlSession);

} catch (Exception e) {

throw new BindingException("Error getting mapper instance. Cause: " + e, e);

}

}

public T newInstance(SqlSession sqlSession) {

//生成一個mapperProxy對象怖亭,這個對象實現(xiàn)了InvocationHandler, Serializable。就是JDK動態(tài)代理中的方法調(diào)用處理器

final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);

return newInstance(mapperProxy);

}

public MapperProxy(SqlSession sqlSession, Class mapperInterface, Map methodCache) {

this.sqlSession = sqlSession;

this.mapperInterface = mapperInterface;

this.methodCache = methodCache;

}

@SuppressWarnings("unchecked")

protected T newInstance(MapperProxy mapperProxy) {

//通過JDK動態(tài)代理生成一個Mapper的代理坤检,在本例中的就是UserMapper的代理類兴猩,它實現(xiàn)了UserMapper接口

return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);

}

第四步通過MapperProxy調(diào)用Maper中相應的方法:

@Override

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

//判斷當前調(diào)用的method是不是Object中聲明的方法,如果是的話直接執(zhí)行早歇。

if (Object.class.equals(method.getDeclaringClass())) {

try {

return method.invoke(this, args);

} catch (Throwable t) {

throw ExceptionUtil.unwrapThrowable(t);

}

}

final MapperMethod mapperMethod = cachedMapperMethod(method);

return mapperMethod.execute(sqlSession, args);

}

//把當前請求放入一個HashMap中倾芝,一旦下次還是同樣的方法進來直接返回。

private MapperMethod cachedMapperMethod(Method method) {

MapperMethod mapperMethod = methodCache.get(method);

if (mapperMethod == null) {

mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());

methodCache.put(method, mapperMethod);

}

return mapperMethod;

}

public Object execute(SqlSession sqlSession, Object[] args) {

Object result;

switch (command.getType()) {

case INSERT: {

Object param = method.convertArgsToSqlCommandParam(args);

result = rowCountResult(sqlSession.insert(command.getName(), param));

break;

}

case UPDATE: {

Object param = method.convertArgsToSqlCommandParam(args);

result = rowCountResult(sqlSession.update(command.getName(), param));

break;

}

case DELETE: {

Object param = method.convertArgsToSqlCommandParam(args);

result = rowCountResult(sqlSession.delete(command.getName(), param));

break;

}

case SELECT:

if (method.returnsVoid() && method.hasResultHandler()) {

executeWithResultHandler(sqlSession, args);

result = null;

} else if (method.returnsMany()) {

result = executeForMany(sqlSession, args);

} else if (method.returnsMap()) {

result = executeForMap(sqlSession, args);

} else if (method.returnsCursor()) {

result = executeForCursor(sqlSession, args);

} else {

//本次案例會執(zhí)行selectOne

Object param = method.convertArgsToSqlCommandParam(args);

result = sqlSession.selectOne(command.getName(), param);

}

break;

case FLUSH:

result = sqlSession.flushStatements();

break;

default:

throw new BindingException("Unknown execution method for: " + command.getName());

}

if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {

throw new BindingException("Mapper method '" + command.getName()

  • " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");

}

return result;

}

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

}

}

@Override

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

try {

MappedStatement ms = configuration.getMappedStatement(statement);

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

}

}

//這邊調(diào)用的是CachingExecutor類的query,還記得前文解析mybatis-config.xml的時候我們指定了REUSE但是因為在配置文件中開啟了緩存

//所以ReuseExecutor被CachingExecotur裝飾箭跳,新增了緩存的判斷晨另,最后還是會調(diào)用ReuseExecutor

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

}

@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, parameterObject, boundSql);

@SuppressWarnings("unchecked")

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

if (list == null) {

//如果緩存中沒有數(shù)據(jù)則查詢數(shù)據(jù)庫

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

}

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市谱姓,隨后出現(xiàn)的幾起案子借尿,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件路翻,死亡現(xiàn)場離奇詭異狈癞,居然都是意外死亡,警方通過查閱死者的電腦和手機茂契,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進店門蝶桶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人掉冶,你說我怎么就攤上這事真竖。” “怎么了郭蕉?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵疼邀,是天一觀的道長。 經(jīng)常有香客問我召锈,道長旁振,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任涨岁,我火速辦了婚禮拐袜,結果婚禮上,老公的妹妹穿的比我還像新娘梢薪。我一直安慰自己蹬铺,他們只是感情好,可當我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布秉撇。 她就那樣靜靜地躺著甜攀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪琐馆。 梳的紋絲不亂的頭發(fā)上规阀,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天,我揣著相機與錄音瘦麸,去河邊找鬼谁撼。 笑死,一個胖子當著我的面吹牛滋饲,可吹牛的內(nèi)容都是我干的厉碟。 我是一名探鬼主播,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼屠缭,長吁一口氣:“原來是場噩夢啊……” “哼箍鼓!你這毒婦竟也來了?” 一聲冷哼從身側響起勿她,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤袄秩,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后逢并,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體之剧,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年砍聊,在試婚紗的時候發(fā)現(xiàn)自己被綠了背稼。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡玻蝌,死狀恐怖蟹肘,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情俯树,我是刑警寧澤帘腹,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站许饿,受9級特大地震影響阳欲,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜陋率,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一球化、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧瓦糟,春花似錦筒愚、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至劲蜻,卻和暖如春陆淀,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背斋竞。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工倔约, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人坝初。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓浸剩,卻偏偏與公主長得像,于是被迫代替她去往敵國和親鳄袍。 傳聞我的和親對象是個殘疾皇子绢要,可洞房花燭夜當晚...
    茶點故事閱讀 42,834評論 2 345

推薦閱讀更多精彩內(nèi)容

  • Mybatis簡介: MyBatis 是一款優(yōu)秀的持久層框架,它支持定制化 SQL拗小、存儲過程以及高級映射重罪。MyBa...
    Java機械師閱讀 4,479評論 0 1
  • MyBatis 是支持定制化 SQL、存儲過程以及高級映射的優(yōu)秀的持久層框架,其主要就完成2件事情: 封裝JDBC...
    慕容小偉閱讀 1,010評論 0 2
  • 1. 簡介 1.1 什么是 MyBatis 剿配? MyBatis 是支持定制化 SQL搅幅、存儲過程以及高級映射的優(yōu)秀的...
    笨鳥慢飛閱讀 5,429評論 0 4
  • mybatis 1. what is mybatis 2. how to use mybatis 1. 編程...
    天外云卷閱讀 321評論 0 1
  • ORM框架Mybatis幾個重要的流程: SqlSessionFactoryBuilder,用來創(chuàng)建SqlSess...
    小雪的筆記閱讀 421評論 0 1