Mybatis原理初探
开篇略谈
谈到Mybatis,对于我们猿们来说是熟悉不过了。但是有没有兴趣去探一下其实现原理呢?是的,请往下看 ↓ come on...
Mybatis综述
Mybatis一个数据持久层轻量级框架,回顾我们原始的开发即没有持久层框架的年代。话不多说上代码 ↓
Connection con = DriverManager.getConnection(url, "...", "..."); // 首先我们得获得一个数据库连接 Statement stmt = con.createStatement(); // 不管你是获得statement还是preparedStatement,总之在项目越来越大得时候这些代码会有点累赘
// PreparedStatement prestmt = con.prepareStatement(...);
而我们得Mybatis则将其封装了起来,构成持久层框架,我们只要按照它的规定去配置就可以了,而无需在关注上面得代码,只需关注sql就行了,接下来入正题
Mybatis源码解析
提到Mybatis我们总会想起那个耳熟能详的东西sqlSession,是的,这是Mybatis的核心所在,sqlSession是由单例sqlSessionFactory创建而来的,而sqlSessionFactory又是由sqlSessionFactoryBuilder创建而来的,因此sqlSession得老祖宗就是它。在这里不管它的父辈,我们就来研究一下sqlSession的默认defaultSqlSession,多说无益看代码
// 可以看到 DefaultSqlSession 实现了SqlSession,这很容易让我们想起模板方法模式,请往下看 public class DefaultSqlSession implements SqlSession { // 下面是一些变量 private Configuration configuration; // 这个东西是核心关注点也是此次重点讲解点,它包含了Mybatis的所有配置信息,我们此次所要研究的就是configuration是如何将sql获取到的 private Executor executor; private boolean autoCommit; private boolean dirty; private List<Cursor<?>> cursorList; // 以下是对SqlSession方法的实现 @Override public <T> T selectOne(String statement) { return this.<T>selectOne(statement, null); } @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; } } // 这个是获取configuration的mapper的,也就是获取sql语句的,这是重点讲解的 @Override public <T> T getMapper(Class<T> type) { return configuration.<T>getMapper(type, this); }
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
// 获取数据库连接 @Override public Connection getConnection() { try { return executor.getTransaction().getConnection(); } catch (SQLException e) { throw ExceptionFactory.wrapException("Error getting a new connection. Cause: " + e, e); } } } // 而SqlSession又继承了Closeable,很明显Closeable就是关闭connection的,这里就不详细说明 public interface SqlSession extends Closeable { <T> T selectOne(String statement); <T> T selectOne(String statement, Object parameter); .......此处省略,详情可自己看源码 }
以上是DefaultSqlSession的方法介绍,此次要从addMapper方法入手弄明白Mybatis它是如何获取我们写的SQL语句的,come on
// 以下是mapperRegistry public class MapperRegistry { 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(); // 这是使用了MapperAnnotationBuilder的parse方法进行了解析,那么它是如何解析的呢,解析的又是什么? loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } } ..... 其他代码就省略了 }
接下来我们看它是如何解析的
public class MapperAnnotationBuilder { public void parse() { String resource = type.toString(); if (!configuration.isResourceLoaded(resource)) { // 首先会判断这个接口即Mapper是否加载过了 loadXmlResource(); // 这是对mapper的xml文件进行解析 configuration.addLoadedResource(resource); // 加载进去 assistant.setCurrentNamespace(type.getName()); parseCache(); parseCacheRef(); Method[] methods = type.getMethods(); // 获得当前Mapper接口的所有方法 for (Method method : methods) { try { // issue #237 if (!method.isBridge()) { parseStatement(method); // 对方法进行解析 } } catch (IncompleteElementException e) { configuration.addIncompleteMethod(new MethodResolver(this, method)); } } } parsePendingMethods(); } }
// 这个方法也是MapperAnotationBuilder的 void parseStatement(Method method) { Class<?> parameterTypeClass = getParameterType(method); // 获得参数类型 LanguageDriver languageDriver = getLanguageDriver(method); SqlSource sqlSource = getSqlSourceFromAnnotations(method, // 这里是获取sqlSource parameterTypeClass, languageDriver); if (sqlSource != null) { Options options = method.getAnnotation(Options.class); final String mappedStatementId = type.getName() + "." + method.getName(); Integer fetchSize = null; Integer timeout = null; StatementType statementType = StatementType.PREPARED; ResultSetType resultSetType = ResultSetType.FORWARD_ONLY; SqlCommandType sqlCommandType = getSqlCommandType(method); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; boolean flushCache = !isSelect; boolean useCache = isSelect; KeyGenerator keyGenerator; String keyProperty = "id"; String keyColumn = null; if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) { // first check for SelectKey annotation - that overrides everything else SelectKey selectKey = method.getAnnotation(SelectKey.class); if (selectKey != null) { keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver); keyProperty = selectKey.keyProperty(); } else if (options == null) { keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE; } else { keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE; keyProperty = options.keyProperty(); keyColumn = options.keyColumn(); } } else { keyGenerator = NoKeyGenerator.INSTANCE; } if (options != null) { if (FlushCachePolicy.TRUE.equals(options.flushCache())) { flushCache = true; } else if (FlushCachePolicy.FALSE.equals(options.flushCache())) { flushCache = false; } useCache = options.useCache(); fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348 timeout = options.timeout() > -1 ? options.timeout() : null; statementType = options.statementType(); resultSetType = options.resultSetType(); } String resultMapId = null; ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class); if (resultMapAnnotation != null) { String[] resultMaps = resultMapAnnotation.value(); StringBuilder sb = new StringBuilder(); for (String resultMap : resultMaps) { if (sb.length() > 0) { sb.append(","); } sb.append(resultMap); } resultMapId = sb.toString(); } else if (isSelect) { resultMapId = parseResultMap(method); } assistant.addMappedStatement( mappedStatementId, sqlSource, statementType, sqlCommandType, fetchSize, timeout, // ParameterMapID null, parameterTypeClass, resultMapId, getReturnType(method), resultSetType, flushCache, useCache, // TODO gcode issue #577 false, keyGenerator, keyProperty, keyColumn, // DatabaseID null, languageDriver, // ResultSets options != null ? nullOrEmpty(options.resultSets()) : null); } } private SqlSource getSqlSourceFromAnnotations(Method method, Class<?> parameterType, LanguageDriver languageDriver) { try { Class<? extends Annotation> sqlAnnotationType = getSqlAnnotationType(method); // 这里会判断方法是否使用了select等注解 Class<? extends Annotation> sqlProviderAnnotationType = getSqlProviderAnnotationType(method); // 是否使用了provider注解 if (sqlAnnotationType != null) { if (sqlProviderAnnotationType != null) { throw new BindingException("You cannot supply both a static SQL and SqlProvider to method named " + method.getName()); } Annotation sqlAnnotation = method.getAnnotation(sqlAnnotationType); final String[] strings = (String[]) sqlAnnotation.getClass().getMethod("value").invoke(sqlAnnotation); return buildSqlSourceFromStrings(strings, parameterType, languageDriver); } else if (sqlProviderAnnotationType != null) { Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType); return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation); } return null; } catch (Exception e) { throw new BuilderException("Could not find value method on SQL annotation. Cause: " + e, e); } } private SqlSource getSqlSourceFromAnnotations(Method method, Class<?> parameterType, LanguageDriver languageDriver) { try { Class<? extends Annotation> sqlAnnotationType = getSqlAnnotationType(method); Class<? extends Annotation> sqlProviderAnnotationType = getSqlProviderAnnotationType(method); if (sqlAnnotationType != null) { if (sqlProviderAnnotationType != null) { throw new BindingException("You cannot supply both a static SQL and SqlProvider to method named " + method.getName()); } Annotation sqlAnnotation = method.getAnnotation(sqlAnnotationType); final String[] strings = (String[]) sqlAnnotation.getClass().getMethod("value").invoke(sqlAnnotation); return buildSqlSourceFromStrings(strings, parameterType, languageDriver); } else if (sqlProviderAnnotationType != null) { Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType); return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation); } return null; } catch (Exception e) { throw new BuilderException("Could not find value method on SQL annotation. Cause: " + e, e); } }
从上面两个方法我们可以解释为什么我们使用provider和直接在mapper接口方法上加select语句会有效