用了挺久的mybatis,但一直停留在用的层面上,以为不行的呀,得走出温馨区。
因此想本身看看mybatis的实现,而后模仿着写一个,哈哈,固然一开始不会要求完成度很高。
这一篇就先看下mybatis奥秘。这里参考的mybatis源码版本是3.4.5。sql
首先,先写一个mybatis简单使用的例子。数据库
// 使用 public static void main(String[] args) throws IOException { //根据配置文件建立一个SqlSessionFactory对象 String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 获取sqlSession对象 SqlSession session = sqlSessionFactory.openSession(); try{ // 获取接口的实现类实例 IUserMapper mapper = session.getMapper(IUserMapper.class); // 调用方法 User user = mapper.findById(1); System.out.println(user.getName()); }finally{ session.close(); } }
回忆一下,使用Mybatis的步骤就是apache
整个过程当中,玩家就只参与了配置参数,还有提供SQL这两步。因此这两步就是看mybatis怎么操做的入口,是进入mybatis地下城的大门。
配置参数这部分,使用框架时基本都有这个操做,比较常见。因此算是个分支剧情,而提供SQL算是mybatis的主线剧情,这里先通关主线剧情。缓存
IUserMapper mapper = session.getMapper(IUserMapper.class); User user = mapper.findById(1);
能够看到,在使用时,咱们获取到了咱们的接口的一个实现类实例,
燃鹅,咱们没有写这个接口的实现的呀。因此我以为是魔法的缘由,在这里要打个断点。session
在getMapper的方法上断点,咱们进入了DefaultSqlSession.getMapper(Class<T>),
因此默认咱们从SqlSessionFactory拿到的是一个DefaultSqlSession的实例。mybatis
/* 经过configuration的getMapper方法,传入咱们的接口类型以及SqlSession实例,返 回一个泛型。这里也就是咱们的IUserMapper接口的实现类的实例。*/ @Override public <T> T getMapper(Class<T> type) { return configuration.<T>getMapper(type, this); }
再进去是Configuration.getMapper(Class<T>, SqlSession)。app
/* 这里又从mapperRegistry里拿到对象, mapperRegistry是Configuration类的一个属性*/ public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); }
看一下MapperRegistry的getMapper里边是什么。
这里看到了使人激动的字眼,就是Proxy,
猜想咱们最终拿到的IUserMapper的实例是个代理对象框架
@SuppressWarnings("unchecked") public <T> T getMapper(Class<T> type, SqlSession sqlSession) { // 看了下,knownMappers是个Map对象,Map<Class<?>, MapperProxyFactory<?>> 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); } }
MapperProxyFactory的newInstance 一探究竟
其实名字叫xxFactory的确定是生产xx的,能够猜到返回的是个MapperProxyide
public T newInstance(SqlSession sqlSession) { /*这里new了一个MapperProxy,而后调用newInstance*/ final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); }
MapperProxy是个啥ui
/* 这个类实现了InvocationHandler,动态代理的接口。*/ public class MapperProxy<T> implements InvocationHandler, Serializable
看看newInstance(mapperProxy)作了啥。
使用Proxy构造实现咱们IUserMapper接口的代理类的实例!
@SuppressWarnings("unchecked") protected T newInstance(MapperProxy<T> mapperProxy) { return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); }
消化一下,开始的疑问是咱们没有提供IUserMapper的实现,可是经过SqlSession的getMapper方法能拿到一个IUserMapper的实现类的对象。
谜底就是最终返回了咱们接口的一个代理类的实例。
而MapperProxy实现了InvocationHandler接口,在咱们构造代理对象时传入了MapperProxy对象,
所以在调IUserMapper的全部方法时,都会进入到MapperProxy类的invoke方法。
其实不像上边那样操做,经过直接打印这个对象也能够看出来..
System.out.println(mapper); System.out.println(Proxy.isProxyClass(mapper.getClass())); // 打印结果,贴图片太丑了,就不贴结果图了。 org.apache.ibatis.binding.MapperProxy@e580929 true
通常使用动态代理,实现了InvocationHandler接口的类中都会持有被代理类的引用,这里也就是MapperProxy。而后在invoke方法里边先执行额外的操做,再调用被代理类的方法。在MapperProxy这个类里却没找到被代理类的引用。
public class MapperProxy<T> implements InvocationHandler, Serializable { private static final long serialVersionUID = -6424540398559729838L; private final SqlSession sqlSession; private final Class<T> mapperInterface; private final Map<Method, MapperMethod> methodCache; 。。。 }
因此穿山甲说了什么?
因此当咱们调用 IUserMapper 的 findById 时发生了什么?
这里就要看下MapperProxy的invoke方法了。
@Override 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); }
首先进行一个 if 判断,逻辑是 若是调的这个方法的提供类是Object类,那个就直接执行这个方法。
这里容易想偏,哪一个类不是Object的子类呀..
其实应该是 若是是Object中的方法,那就直接执行。
Object有哪些方法呢?toString这些。调mapper.toString()时,就直接被执行,不走下边的逻辑了。
if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); }
以后是第二个 if,逻辑是,若是这个方法的权限修饰符是public而且是由接口提供的,则执行invokeDefaultMethod方法。
好比在IUserMapper写了一个默认方法,执行这个方法isDefaultMethod就会返回true了。
这里咱们的方法的提供方是代理类,不是接口,因此返回了false。
else if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, args); } // isDefaultMethod private boolean isDefaultMethod(Method method) { return ((method.getModifiers() & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC) && method.getDeclaringClass().isInterface(); }
前两步都是过滤做用,下边的才是重点。
能够看到经过 cachedMapperMethod方法 拿到了一个 MapperMethod 对象。
看名字是从缓存里拿。而后就执行MapperMethod的execute方法。
final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args);
接着咱们看看怎么经过method参数拿到MapperMethod
这里就很简单了,Map里边有就直接返回,没有就新建。接口的一个方法就对应一个MapperMethod。
so easy ~
//cachedMapperMethod private MapperMethod cachedMapperMethod(Method method) { // methodCache 是个Map<Method, MapperMethod> MapperMethod mapperMethod = methodCache.get(method); if (mapperMethod == null) { mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()); methodCache.put(method, mapperMethod); } return mapperMethod; }
看下MapperMethod的构造过程,发现传入了接口信息,方法信息,还有配置信息。
主要工做是初始化 command 还有 method 字段。
command里边就保存方法的名称(com.mapper.IUserMapper.findById),还有对应的SQL类型(SELECT)。
method里边保存了方法的返回类型,是不是集合,是不是游标等信息。
看到这里,其实我一直在忽略Configuration这个类里边是什么东西,等要模仿再去看。
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) { this.command = new SqlCommand(config, mapperInterface, method); this.method = new MethodSignature(config, mapperInterface, method); }
咱们调用 mapper.findByid, 最终是经过MapperMethod执行execute获得结果。
因此接下来要看看execute方法中隐藏了什么秘密。
下边是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; }
能够看到 INSERT UPDATE 这些熟悉的字眼的了。
经过MapperMethod里的command的属性,进入不一样分支。
这里调用的是findById,进入了SELECT分支,最终执行了下边的语句,第一句是装配参数,第二句是执行查询。
Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param);
看下 sqlSession.selectOne(),里边调用了selectList的方法,而后将结果返回。
@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; } }
进入到selectList瞧瞧,感受流程要走完了,都已经开始select了。
这里又看到了使人激动的字眼,statement。感受已经在靠近JDBC啦。
有个MappedStatement的对象须要关注一下。
@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(); } }
回忆下基本JDBC是怎么用的。
/* 1 加载对应数据库驱动 Class.forName(driverClass); 2 获取数据库链接 Connection con = DriverManager.getConnection(jdbcUrl, user, password); 3 准备SQL语句 String sql = " ... "; 4 执行操做 Statement statement = con.createStatement(); statement.executeUpdate(sql); 5 释放资源 statement.close(); con.close();*/
拿到MappedStatement以后调用 executor的query方法,这个方法是CachingExecutor提供的。
能够看到,这里经过MappedStatement获取了咱们的SQL,而后生成一个缓存key,想起我记忆深处的mybatis一级二级缓存。
以后返回调用query方法的结果。
@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); }
仍是进入CachingExecutor的query方法。看来Executor这样的类就是真正执行数据库操做的类了。
看到先是从MappedStatement里边拿缓存,若是是空的,就调用delegate.query,
delegate是SimpleExecutor类型,顾名思义CachingExecutor委派了SimpleExecutor来进行数据库操做。
@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) { 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); }
继续看SimpleExecutor里边的query,看到SimpleExecutor里边并无query方法,
而是SimpleExecutor继承了BaseExecutor,query是BaseExecutor类提供的。
第一句断点进去以后,看到的是存起来的"executing a query",这是出异常时的堆栈信息。
emm..而后就是不少是否存在缓存是否使用缓存的代码。
咱直接看queryFromDatabase(),这个命名明显告诉玩家,BOSS就在前面了。
@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; }
一样是BaseExecutor 提供的 queryFromDatabase()方法。
首先put进去了一个缓存,key是咱们以前的缓存键,值是一个默认的值,感受是占位的意思。
而后执行doQuery方法,看到do开头的方法,就知道不简单。doGet doPost
doQuery是个抽象方法,咱们得去SimpleExecutor看实现。
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; }
SimpleExecutor.doQuery
来啦! Statement!并且还有咱们熟悉的prepareStatement字眼。哈哈 都是JDBC呀
最后看到是由一个Handler来执行的,看一看这个Handler。
@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); } }
先是进入到了RoutingStatementHandler,而后RoutingStatementHandler委托给了PreparedStatementHandler,下边是PreparedStatementHandler的query。
看到想看的东西了,ps.execute()
以后将结果交给resultSetHandler处理。
@Override public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; ps.execute(); return resultSetHandler.<E> handleResultSets(ps); }
答:使用的是咱们的接口的代理类的实例。
在构造代理类的实例时,
咱们传入了实现了InvocationHandler接口的MapperProxy实例,
当代理对象调用方法时,会进入MapperProxy的invoke方法。
在invoke方法中经过Method对象找MapperMethod,
而后执行MapperMethod对象的execute方法。
在这里,代理的做用是,让咱们知道哪一个接口的哪一个方法被使用了。MapperProxy 对应了咱们的一个接口,
MapperMethod 对应接口里的一个方法,
MappedStatement 对应一条SQL
MapperProxy: 定义代理对象调用方法时执行的动做。
即在invoke()里拿到调用的方法对应的MapperMethod,而后调用MapperMethod的execute。MapperMethod: 对应咱们接口里的方法,持有SqlCommand(command)和MethodSignature(method),
能够知道方法的全名以及对应的SQL的类型。MappedStatement: 保存的SQL的信息。
SqlSession: 玩家获取Mapper的地方。伪装执行SQL,实际交给了Executor。
Executor: 真正执行数据库操做。
大体知道流程是什么样的,接着就能够模仿着写一写了...emm...感受没这么简单。