Mybatis源码分析1-如何获取Mapper实现类
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Mybatis源码分析1-如何获取Mapper实现类相关的知识,希望对你有一定的参考价值。
参考技术A 入口:一般会在DAO层配置中,比如下面是我的配置在这里面,我们配置了id为sqlSessionFactory的bean。那么这也是我们的入口
由此可以看出,id为sqlSessionFactory的bean默认是DefaultSqlSessionFactory。Mybatis的官方推荐,SqlSessionFactory最好是做成单例的,不需要频繁创建。
通过api获取session的方式
那么我们就看DefaultSqlSessionFactory.openSession方法,如下
再看DefaultSqlSession.getMapper方法
这个 configuration 对象是在哪里创建,又是哪里传入的呢?其实是从一开始的 SqlSessionFactoryBean 中创建好后,赋值给 SqlSessionFactoryBuilder 的build的方法参数,一路传到了 DefaultSqlSession 中。也就是说 configuration 是隶属于 DefaultSqlSessionFactory 的,生命周期很长。
那 configuration 究竟有什么呢?
原来,所有的Mapper接口被扫描后,都会被存储在Configuration的mapperRegistry里的Map中。而且 key = Mapper class value = 创建当前Mapper的工厂 。
接着看 configuration.getMapper(..) 方法
那我们知道了 knownMappers.get(type) 返回的是一个创建Mapper的工厂 MapperProxyFactory 。
接下来我们看看 MapperProxyFactory 是如何创建的。直接看 MapperProxyFactory 的 newInstance(SqlSession sqlSession) 方法
可以清楚的看到,通过jdk的动态代理创建了代理类。那么我们需要关注一下,代理类的InvocationHandler做了哪些事情
这里需要注意methodCache这个Map,不是保存在本类里的。看MapperProxy的有参构造传进来的。那么谁创建的MapperProxy呢?上面我们说了是Mapper对应的Mapper工厂MapperProxyFactory。这个对象也是保存在 Configuration 中,生命周期很长。
这就解释了,为什么 官网上推荐,让Mapper的实现类 用后就可以舍弃(做局部变量),但是这样每次都去创建难道不耗时,答案是不耗时,因为核心的 MapperMethod 都已经被缓存起来了。
Mybatis之Mapper调用源码分析
Mybatis之Mapper调用源码分析
这一篇是承接前面两篇的,分别为:Mybatis源码解析之配置加载(一), Mybatis源码解析之配置加载(二),前面两篇讲了在Mybatis启动时如何加载配置,这一节就讲在运行时,如何通过session获取Mapper代理类,从而实现对数据库的查询操作。
程序
首先展示下之前写的程序,main程序在第一篇中有展示,这里就不再完全的贴出来了,获取session并获取mapper类代码如下:
SqlSession session = sessionFactory.openSession();
UserMapper userMapper = session.getMapper(UserMapper.class);
//执行查询返回一个唯一user对象的sql
User user = userMapper.getUser(1);
System.out.println(user);
获取session在这就不讲了,就是将configuration中的配置信息取出,然后实例化对应Executor类以及事务等相关信息,这里就直接从session中获取mapper类开始说。
session对象调用getMapper方法获取Mapper代理类,我们进入到getMapper方法。
@Override
public <T> T getMapper(Class<T> type)
return configuration.<T>getMapper(type, this);
public <T> T getMapper(Class<T> type, SqlSession sqlSession)
return mapperRegistry.getMapper(type, sqlSession);
public <T> T getMapper(Class<T> type, SqlSession sqlSession)
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) 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);
在第三个getMapper方法中,从knownMappers中取出对应代理工厂类,knownMappers是一个HashMap对象,从第二篇文章中的addMapper方法得知,在加载mapper接口时,每一个Mapper接口类都被添加进knownMappers中,具体可以看以下一段代码,
public <T> void addMapper(Class<T> type)
if (type.isInterface())
if (hasMapper(type))
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
boolean loadCompleted = false;
try
knownMappers.put(type, new MapperProxyFactory<T>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
finally
if (!loadCompleted)
knownMappers.remove(type);
继续回到之前的代码,在获取到MapperProxyFactory对象后,进行了实例化操作,继续分析,此处进入到newInstance方法中。
public T newInstance(SqlSession sqlSession)
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
protected T newInstance(MapperProxy<T> mapperProxy)
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] mapperInterface , mapperProxy);
此处根据mapperInterface获取到Mapper的代理类对象,就是MapperProxy类。
当我们的演示程序main程序开始调用mapper中的方法时,即如下代码:
//执行查询返回一个唯一user对象的sql
User user = userMapper.getUser(1);
对应我们转到MapperProxy类的invoke方法。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
try
if (Object.class.equals(method.getDeclaringClass()))
return method.invoke(this, args);
else if (isDefaultMethod(method))
return invokeDefaultMethod(proxy, method, args);
catch (Throwable t)
throw ExceptionUtil.unwrapThrowable(t);
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
我们直接看最下面的mapperMethod.execute(sqlSession, args),进入到execute方法中。
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
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;
我们的演示程序中调用的方法为SELECT类型,同时我们要求返回的为resultMap,所以应该是最后一种方法调用,即else中的
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
其中的Object param = method.convertArgsToSqlCommandParam(args)
是将参数转换成kv结构的map返回,这里面就不再多看,直接到selectOne方法中。
@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;
转到了session中的selectOne方法,session中的selectOne还是调用了selectList方法,继续转到selectList方法。
@Override
public <E> List<E> selectList(String statement, Object parameter)
return this.selectList(statement, parameter, RowBounds.DEFAULT);
@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();
selectList最终调用了**MappedStatement ms =configuration.getMappedStatement(statement);从之前的文章分析得知此处getMappedStatement实际上就是从mappedStatements.get(id)**中取出对应SQL,这里传进去的statement就是方法名,也就是id,因此此处也与前面串联起来。
接下来就是Executor执行sql的过程,我们进入到query方法中。
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
根据parameter获取到BoundSql,继续找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 (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;
这个query方法中只看list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql)这一行,其他的在日后分析cache的时候再回来继续看源码。
我们转到queryFromDatabase方法。
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;
转向doQuery()方法,当然doQuery有多个实现类,这里简单点直接看SimpleExecutor,simpleExecutor中的doQuery实现如下:
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);
这里的stmt = prepareStatement(handler, ms.getStatementLog());也不再详细讲述了,就是对参数的组装,以后讲其他的在此会涉及到,以后再说,直接看query方法。
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException
String sql = boundSql.getSql();
statement.execute(sql);
return resultSetHandler.<E>handleResultSets(statement);
上述代码中最终是到了Statemet进行execute查询方法,而Statement对象即为java.sql包下的Statement对象,最终还是转换成了Statement对象查询,然后将返回结果进行了处理,从而返回一个list结果,然后有了上述代码中的selectOne方法的size判断。
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;
如果返回多条则报错,如果返回一条则直接返回结果。
Mybatis调用方法源码分析就到这了,其中漏了很多重要的步骤的讲述,比如plugin的使用,参数的组装等等,这个到以后分析其他的时候再来讲述。
以上是关于Mybatis源码分析1-如何获取Mapper实现类的主要内容,如果未能解决你的问题,请参考以下文章