Configuration--mappers--addMappers(三-8-1)
来源:互联网 发布:电装机器人编程 编辑:程序博客网 时间:2024/06/10 09:38
本篇文章,我们来讲讲,当mapper标签加载的是一个类时,Configuration的addMappers方法的解析过程.
mapper标签引用类的情况:
<mappers> <mapper class="org.mybatis.builder.AuthorMapper"/> <package name="org.mybatis.builder"/></mappers>
可以看出,一种是单独引用一个类,一种是一次性引入一个包下所有类.而addMappers方法有两种形式的重载:
// Configuration中的成员变量MapperRegistry protected MapperRegistry mapperRegistry = new MapperRegistry(this); // 一次加载一个包下的类 public void addMappers(String packageName) { mapperRegistry.addMappers(packageName); } // 一次加载一个类 public <T> void addMapper(Class<T> type) { mapperRegistry.addMapper(type); }
从上述代码可以看出,Configuration中实际是使用了MapperRegistry的addMappers方法,我们先来看MapperRegistry中一次加载一个包下所有类的方法:
public void addMappers(String packageName) { addMappers(packageName, Object.class); } public void addMappers(String packageName, Class<?> superType) { // 找到该包下所有的Java类 ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>(); resolverUtil.find(new ResolverUtil.IsA(superType), packageName); Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses(); // 循环调用addMapper(Class<T> type) for (Class<?> mapperClass : mapperSet) { addMapper(mapperClass); } }
从上述代码中,我们可以看出,加载一个包下所有的类,最后还是循环调用一次加载一个类的方法:
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 { // MapperProxyFactory是Mapper的代理,之后用到的时候我们在讲 knownMappers.put(type, new MapperProxyFactory<T>(type)); // 构造一个MapperAnnotationBuilder去解析这个类 MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); parser.parse(); loadCompleted = true; } finally { // 若上面加载时出错,则将该类移除 if (!loadCompleted) { knownMappers.remove(type); } } } } public <T> boolean hasMapper(Class<T> type) { // knownMappers为MapperRegistry的一个Map结构,用来保存已经加载过的类信息和代理工厂的对应关系 return knownMappers.containsKey(type); } private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
从上述代码可以看出,对具体类的解析是在MapperAnnotationBuilder中完成的,我们先来看看MapperAnnotationBuilder的构造方法:
private final Set<Class<? extends Annotation>> sqlAnnotationTypes = new HashSet<>(); private final Set<Class<? extends Annotation>> sqlProviderAnnotationTypes = new HashSet<>(); public MapperAnnotationBuilder(Configuration configuration, Class<?> type) { String resource = type.getName().replace('.', '/') + ".java (best guess)"; this.assistant = new MapperBuilderAssistant(configuration, resource); this.configuration = configuration; this.type = type; sqlAnnotationTypes.add(Select.class); sqlAnnotationTypes.add(Insert.class); sqlAnnotationTypes.add(Update.class); sqlAnnotationTypes.add(Delete.class); sqlProviderAnnotationTypes.add(SelectProvider.class); sqlProviderAnnotationTypes.add(InsertProvider.class); sqlProviderAnnotationTypes.add(UpdateProvider.class); sqlProviderAnnotationTypes.add(DeleteProvider.class); }
接下来看比较重要的parse()方法:
public void parse() { String resource = type.toString(); // 是否已经加载过该resource(值形式类似于为:interface org.tree.study.mybatis.dao.UserDao) if (!configuration.isResourceLoaded(resource)) { // 加载类对应的xml文件 loadXmlResource(); // 将该resource加入已经加载过的Set中 configuration.addLoadedResource(resource); // 设置命名空间 assistant.setCurrentNamespace(type.getName()); // 加载CacheNamespace注解 parseCache(); // 加载CacheNamespaceRef注解 parseCacheRef(); // 加载类中所有的方法 Method[] methods = type.getMethods(); for (Method method : methods) { try { // 解析方法 parseStatement(method); } catch (IncompleteElementException e) { // 将异常解析的方法加入一个List中 configuration.addIncompleteMethod(new MethodResolver(this, method)); } } } // 重新处理异常解析的方法 parsePendingMethods(); }
上述代码逻辑比较复杂,但是总的来说,核心是去加载xml文件和加载该类上的注解(这也证明mybatis支持xml配置和注解配置两种方式).
我们先来看loadXmlResource()方法:
private void loadXmlResource() { // 看是否已经加载过该xml文件 if (!configuration.isResourceLoaded("namespace:" + type.getName())) { // 从这里我们可以看出,若是要使用加载类的方法,xml文件的路径需要和类的包名路径一致,并且xml文件名要和类名一致(如:org.tree.study.mybatis.dao.UserDao对应org/tree/study/mybatis/dao/UserDao.xml) String xmlResource = type.getName().replace('.', '/') + ".xml"; InputStream inputStream = null; try { inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource); } catch (IOException e) { // ignore, resource is not required } if (inputStream != null) { // 从这里我们看出,加载xml文件时,使用了XMLMapperBuilder,这正是我们要讲的另一种方式,这里的详细过程,我们下一节再讲 XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName()); xmlParser.parse(); } } }
parseCache()方法和parseCacheRef()方法涉及到缓存,我们之后再讲,我们主要来看parseStatement方法:
void parseStatement(Method method) { // 若该方法只有一个参数,则返回该参数本身Class对象,如果大于一个参数,则固定返回ParamMap.class Class<?> parameterTypeClass = getParameterType(method); // 该方法上是否定义了@Lang注解,否则获取一个默认的LanguageDriver LanguageDriver languageDriver = getLanguageDriver(method); // 通过方法上的注解生成一个SqlSource SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver); if (sqlSource != null) { Options options = method.getAnnotation(Options.class); // 这个mappedStatementId比较重要,就是每个语句的唯一标识 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() ? new Jdbc3KeyGenerator() : new NoKeyGenerator(); } else { keyGenerator = options.useGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator(); keyProperty = options.keyProperty(); keyColumn = options.keyColumn(); } } } else { keyGenerator = new NoKeyGenerator(); } if (options != null) { flushCache = options.flushCache(); 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); } // 最终调用addMappedStatement方法 assistant.addMappedStatement( mappedStatementId, sqlSource, statementType, sqlCommandType, fetchSize, timeout, null, // ParameterMapID parameterTypeClass, resultMapId, // ResultMapID getReturnType(method), resultSetType, flushCache, useCache, false, // TODO issue #577 keyGenerator, keyProperty, keyColumn, null, languageDriver, null); } }
上面的方法看起来比较复杂,主要就是解析各种注解,最终调用addMappedStatement方法,将一个方法封装成一个MappedStatement:
public MappedStatement addMappedStatement( String id, SqlSource sqlSource, StatementType statementType, SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class<?> parameterType, String resultMap, Class<?> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache, boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId, LanguageDriver lang, String resultSets) { if (unresolvedCacheRef) throw new IncompleteElementException("Cache-ref not yet resolved"); id = applyCurrentNamespace(id, false); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType); statementBuilder.resource(resource); statementBuilder.fetchSize(fetchSize); statementBuilder.statementType(statementType); statementBuilder.keyGenerator(keyGenerator); statementBuilder.keyProperty(keyProperty); statementBuilder.keyColumn(keyColumn); statementBuilder.databaseId(databaseId); statementBuilder.lang(lang); statementBuilder.resultOrdered(resultOrdered); statementBuilder.resulSets(resultSets); setStatementTimeout(timeout, statementBuilder); setStatementParameterMap(parameterMap, parameterType, statementBuilder); setStatementResultMap(resultMap, resultType, resultSetType, statementBuilder); setStatementCache(isSelect, flushCache, useCache, currentCache, statementBuilder); MappedStatement statement = statementBuilder.build(); // 最终调用该方法,将MappedStatement加入到Configuration中 configuration.addMappedStatement(statement); return statement; } public void addMappedStatement(MappedStatement ms) { mappedStatements.put(ms.getId(), ms); }
综上,我们可以看出,当我们使用class类去配置mybatis时,可以使用注解的方法,也可以使用xml文件,但是该xml文件需要和class类的路径相同.最终每条sql或者说每个接口方法都会被解析成一个mappedStatement加入到Configuration中.每个mappedStatement有其唯一的id(在该篇文章中,我们看到其中使用的id是class的类名+方法名).
- Configuration--mappers--addMappers(三-8-1)
- Configuration--mappers(三-8)
- Configuration--mappers--XMLMapperBuilder.parse(三-8-2)
- Configuration--properties(三-1)
- Configuration(三)
- Configuration--environments--dataSource(三-5-1)
- Configuration--typeAliases(三-2)
- Configuration--objectFactory(三-3)
- Configuration--settings(三-4)
- Configuration--environments(三-5)
- Configuration--databaseIdProvider(三-6)
- Configuration--typeHandlers(三-7)
- MyBatis Review——加载mappers映射文件的三种方式
- MyBatis总结——加载mappers映射文件的三种方式
- Configuration--environments--transactionManager(三-5-2)
- Chapter 8 Configuration Testing
- 1-Configuration Read
- configuration
- day12-java&oracle总结
- 深入理解Spring系列之六:bean初始化
- 在Android Framework层 C/C++代码中添加日志
- HDU 1150 Machine Schedule(二分匹配+匈牙利算法)
- Python学习日志(七)之输入输出
- Configuration--mappers--addMappers(三-8-1)
- 梯度树提升算法GBRT
- 变量替换和赋值及特殊的变量类型
- 原表达式转换为后缀表达式
- equals 和 == 的区别
- 深入理解Spring系列之七:web应用自动装配Spring配置
- WAMP的apache无法启动DocumentRoot must be a directory
- (吴恩达笔记 2-1)——支持向量机SVM
- Pie (二分)