//1.通过输入流解析xml配置文件
InputStream inputstream = Resources.getResourceAsStream("xxx.xml")
SqlsessionFactory sqlsessionfactory = new SqlsessionFactoryBuilder().build(inputstream);
//2.获取和数据库的链接,创建会话
SqlSession openSession = sqlsessionfactory.openSession();
//3.获取接口的实现类对象,会为接口自动的创建一个代理对象mapper,代理对象会去执行增删改查方法
xxxMapper mapper = openSession.getMapper(xxxMapper.class)
//4.执行增删改的方法
mybatis源码解析-SqlsessionFactory
mybatis源码解析-获取Sqlsession
mybatis源码解析-getMapper
一个mybatis运行前的步骤我们都分析的差不多了,现在来看一下具体的执行过程
查询如何实现
我们假如有一个方法叫做getXXXById(),我们便顺理成章的进入了这个方法内部,我们之前提到过,
openSession.getMapper(xxxMapper.class)
返回的是
MapperProxy
对象,也就是mapper的代理对象,我们看下代码:
1.1
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
if (this.isDefaultMethod(method)) {
return this.invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
MapperMethod mapperMethod = this.cachedMapperMethod(method);
return mapperMethod.execute(this.sqlSession, args);
}
首先这个
invoke
方法会先做判断:
if (Object.class.equals(method.getDeclaringClass()))
它的意思是判断通过代理对象执行的方法是不是Object方法,或者是不是mapper接口中的方法,如果是Object的方法比如toString就直接执行下面的代码了。入如果是mapper接口中的方法,则会将当前方法通过
MapperMethod mapperMethod = this.cachedMapperMethod(method);
封装成
MapperMethod
,然后通过
mapperMethod.execute(this.sqlSession, args);
来执行,这个参数中的
sqlsession
是我们
MapperProxy
封装好了的,
Object[] args
是我们查询方法的参数,我们来看一下
MapperMethod
这个类:
1.2
public class MapperMethod {
private final MapperMethod.SqlCommand command;
private final MapperMethod.MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new MapperMethod.SqlCommand(config, mapperInterface, method);
this.method = new MapperMethod.MethodSignature(config, mapperInterface, method);
}
这是这个类的基本信息,其中
MapperMethod.SqlCommand command
是这个类中的静态内部类,在构造方法中就进行了解析,
目的是
判断要运行的方法是增删改查的那种类型,然后看这个类中的
execute
方法:
public Object execute(SqlSession sqlSession, Object[] args) {
Object param;
Object result;
switch(this.command.getType()) {
case INSERT:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
break;
case UPDATE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
break;
case DELETE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
break;
case SELECT:
if (this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if (this.method.returnsMany()) {
result = this.executeForMany(sqlSession, args);
} else if (this.method.returnsMap()) {
result = this.executeForMap(sqlSession, args);
} else if (this.method.returnsCursor()) {
result = this.executeForCursor(sqlSession, args);
} else {
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);
}
break;
我们要运行的是根据id查询对象,所以是
select
类型的,进入了
select
之后会根据返回值类型进行判断,我们的情况都不满足,直接进入else语句,这里面的
method.convertArgsToSqlCommandParam(args)
是将Object类型的参数转为我们需要的类型,我们不妨细看一下:
看到这里是不是很熟悉,和
mybatis参数处理过程及原理
里面讲得一样,这里就不多说了,参数转换完成后,就会执行
sqlSession.selectOne(this.command.getName(), param);
这个方法,其中参数
this.command.getName()
就是我们在mapper.xml中写的select标签的唯一标识,即方法所在接口的全限定类名,我们进入这个
selectOne方法
:
1.3
这个方法是DefaultSqlSession实现的
public <T> T selectOne(String statement, Object parameter) {
List<T> list = this.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;
}
}
我们发现,无论是查询单个还是多个,都调用了
selectList
方法,我们来看一看这个方法,参数
statement
是我们对应mapper接口中的一个方法,有唯一标识,
parameter
是方法的参数
1.4
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
List var5;
try {
MappedStatement ms = this.configuration.getMappedStatement(statement);
var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception var9) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + var9, var9);
} finally {
ErrorContext.instance().reset();
}
return var5;
}
这里需要注意了,
MappedStatement ms = this.configuration.getMappedStatement(statement);
就是根据
configuration
全局配置对象来获取之前被封装的
MappedStatement
对象,而这个对象正是封装了增删改查标签中的信息,包括我们配置的SQL,SQL的id,缓存信息,resultMap等,每个标签都有一个该对象封装,这个在本票文章开始的
mybatis源码解析-SqlsessionFactory
有详细介绍,
每个增删改查标签都有一个封装它的MappedStatement对象
,他们通过map封装,这里的
selectList
方法通过唯一标识
statement
找到了对应的接口方法
我们接着往下走,
完成了
MappedStatement
的查询,会将这个作为参数传递给
this.executor.query
方法,我们来看一下这个方法:其中有个参数
this.wrapCollection(parameter)
,我们看一下这个封装参数的方法:
private Object wrapCollection(Object object) {
DefaultSqlSession.StrictMap map;
if (object instanceof Collection) {
map = new DefaultSqlSession.StrictMap();
map.put("collection", object);
if (object instanceof List) {
map.put("list", object);
}
return map;
} else if (object != null && object.getClass().isArray()) {
map = new DefaultSqlSession.StrictMap();
map.put("array", object);
return map;
} else {
return object;
}
}
是不是有一种豁然开朗的感觉?是的,就是这段代码的作用我们才可以将List类型的集合封装为list或者是collection,好了,现在跳过这段代码,接着往下走,
看query方法
,注意了,这里的
this.executor.query
是我们在获取SqlSession步骤中创建Executor时的Executor子类
CachingExecutor
:
1.5
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);
return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
注意第一句的
BoundSql
,我们进入
ms.getBoundSql
看一下:
这个类提供
三个主要的属性
:sql,parameterMappings,parameterObject,我们这里简单介绍一下:
- sql就是我们卸载增删改标签里的sql语句
- parameterObject就是我们写在接口里方法的参数
-
parameterMappings,它是一个
List
,它的每一个参数都是
ParameterMapping
对象,这个对象会描述我们的参数,包括属性,名称,表达式,javatype等重要信息,我们一般不需要去改变它,通过它可以实现参数和SQL的结合
回到query方法,里面还有一个二级缓存key:
CacheKey key
,这里就不多说它了,然后它再次调用本类中的另一个重载的query方法:
1.6
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) {
this.flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
this.ensureNoOutParams(ms, boundSql);
List<E> list = (List)this.tcm.getObject(cache, key);
if (list == null) {
list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
this.tcm.putObject(cache, key, list);
}
return list;
}
}
return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
在之前介绍获取sqlsession的文章中我们提到,通过openSession创建的Executor也就是
executor = new SimpleExecutor(this, transaction);
这段代码被
executor = new CachingExecutor((Executor)executor);
封装了,真正起作用的是
CachingExecutor
类中的
delegate
,如果没有缓存,这个执行器进行查询
this.delegate.query
,我们进入这个query方法:
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 (this.closed) {
throw new ExecutorException("Executor was closed.");
} else {
if (this.queryStack == 0 && ms.isFlushCacheRequired()) {
this.clearLocalCache();
}
List list;
try {
++this.queryStack;
list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
if (list != null) {
this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
--this.queryStack;
}
if (this.queryStack == 0) {
Iterator var8 = this.deferredLoads.iterator();
while(var8.hasNext()) {
BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)var8.next();
deferredLoad.load();
}
this.deferredLoads.clear();
if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
this.clearLocalCache();
}
}
return list;
}
}
我们发现会执行**list = resultHandler == null ? (List)this.localCache.getObject(key) : null;**这行代码,这是执行了二级缓存之后再执行的本地缓存,这也应证了mybatis会先查二级,再查一级缓存,如果一级二级都为
null
,就会执行
queryFromDatabase
方法,我们进入这个方法:
1.7
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);
List list;
try {
list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
this.localCache.removeObject(key);
}
this.localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
this.localOutputParameterCache.putObject(key, parameter);
}
return list;
}
这个方法会现在本地缓存中设置一个key,再查出数据放进本地缓存,我们看
doQuery
方法,这个方法的参数我们都很熟悉了,直接进入该方法了
1.8
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
List var9;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = this.prepareStatement(handler, ms.getStatementLog());
var9 = handler.query(stmt, resultHandler);
} finally {
this.closeStatement(stmt);
}
return var9;
}
这个方法就很有意思了,里面有
Statement
原生JDBC,而且出现了
StatementHandler
,这是SqlSession下的一个重要的对象,我们之前说的Executor也是一个,它的作用是创建出statement(prepareStatement)对象,起到承上启下的作用,我们进入这个方法看一下:
1.9
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
StatementHandler statementHandler = (StatementHandler)this.interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
我们在mapper中写select标签时有一个属性:
默认采取预编译模式,那么在这个方法中就会创建
PreparedStatementHandler
它是
StatementHandler
的子类