MyBatis-进阶1
接入门的实例,我们知道MyBatis可以使用注解和配置文件实现接口和sql语句的绑定。
那么一个接口方法同时使用注解和xml配置会怎么样。
@Select("select * from user_tb where id=#{id}") User getOneUser(int id);
<select id="getOneUser" resultType="User"> select * from user_tb where id+1=#{id} </select>
如果传入id=12,查出来的User.id=12,说明注解覆盖xml配置,查出来的User.id=11,说明xml配置覆盖注解
结果是:
Exception in thread "main" org.apache.ibatis.exceptions.PersistenceException: ### Error building SqlSession. ### The error may exist in com/xh/mybatisLearn/dao/UserMapper.java (best guess) ### Cause: org.apache.ibatis.builder.BuilderException: Error parsing SQL Mapper Configuration. Cause: java.lang.IllegalArgumentException: Mapped Statements collection already contains value for com.xh.mybatisLearn.dao.UserMapper.getOneUser at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30) at org.apache.ibatis.session.SqlSessionFactoryBuilder.build(SqlSessionFactoryBuilder.java:80) at org.apache.ibatis.session.SqlSessionFactoryBuilder.build(SqlSessionFactoryBuilder.java:64) at com.xh.mybatisLearn.Test.getSqlSessionFactory(Test.java:29) at com.xh.mybatisLearn.Test.main(Test.java:34)
竟然抛异常啦,去掉任何一个都是可以的。其实这个可以理解,因为实在没有理由这么干。
我现在想看看到时是哪个方法抛出异常的怎么办?
我的办法是边debug边下断点:
比如第一次在执行A()抛异常,那么就在A处下断点,下次运行到A的时候进入,然后B()抛异常,在B()下断点。。。。
如果经过的方法很多可以去掉之前的一些断点,只保留关键的方法(后期需要分析)
可能有人喜欢边debug边读源码,但我偏好先不读代码,找到抛出异常的源头,回头在根据断点一步步看源码,这样脉络更清晰。
异常是由:Configuration.class抛出
public V put(String key, V value) { if(this.containsKey(key)) { throw new IllegalArgumentException(this.name + " already contains value for " + key); } else {
key:com.xh.mybatisLearn.dao.UserMapper.getOneUser
value:MappedStatement对象
原来是这里保证了每个接口方法只有一个MappedStatement对象。
public void addMappedStatement(MappedStatement ms) { this.mappedStatements.put(ms.getId(), ms); }
Configuration有这个属性:
protected final Map<String, MappedStatement> mappedStatements;
++++++++++++++++++++++++++
debug关键步骤:
XMLConfigBuilder.class解析节点
private void mapperElement(XNode parent) throws Exception { if(parent != null) { Iterator i$ = parent.getChildren().iterator(); while(true) { while(i$.hasNext()) { XNode child = (XNode)i$.next(); String resource; if("package".equals(child.getName())) { resource = child.getStringAttribute("name"); this.configuration.addMappers(resource); } else { resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); XMLMapperBuilder mapperParser; InputStream mapperInterface1; if(resource != null && url == null && mapperClass == null) { ErrorContext.instance().resource(resource); mapperInterface1 = Resources.getResourceAsStream(resource); mapperParser = new XMLMapperBuilder(mapperInterface1, this.configuration, resource, this.configuration.getSqlFragments()); mapperParser.parse();<--------------------- } else if(resource == null && url != null && mapperClass == null) {
MapperBuilderAssistant.class:
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(this.unresolvedCacheRef) { throw new IncompleteElementException("Cache-ref not yet resolved"); } else { id = this.applyCurrentNamespace(id, false); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; org.apache.ibatis.mapping.MappedStatement.Builder statementBuilder = (new org.apache.ibatis.mapping.MappedStatement.Builder(this.configuration, id, sqlSource, sqlCommandType)).resource(this.resource).fetchSize(fetchSize).timeout(timeout).statementType(statementType).keyGenerator(keyGenerator).keyProperty(keyProperty).keyColumn(keyColumn).databaseId(databaseId).lang(lang).resultOrdered(resultOrdered).resultSets(resultSets).resultMaps(this.getStatementResultMaps(resultMap, resultType, id)).resultSetType(resultSetType).flushCacheRequired(((Boolean)this.valueOrDefault(Boolean.valueOf(flushCache), Boolean.valueOf(!isSelect))).booleanValue()).useCache(((Boolean)this.valueOrDefault(Boolean.valueOf(useCache), Boolean.valueOf(isSelect))).booleanValue()).cache(this.currentCache); ParameterMap statementParameterMap = this.getStatementParameterMap(parameterMap, parameterType, id); if(statementParameterMap != null) { statementBuilder.parameterMap(statementParameterMap); } MappedStatement statement = statementBuilder.build(); this.configuration.addMappedStatement(statement);<--------------------- return statement; } }
MapperRegistry.class:
public <T> void addMapper(Class<T> type) { if(type.isInterface()) { if(this.hasMapper(type)) { throw new BindingException("Type " + type + " is already known to the MapperRegistry."); } boolean loadCompleted = false; try { this.knownMappers.put(type, new MapperProxyFactory(type)); MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type); parser.parse();<--------------------- loadCompleted = true;
MapperAnnotationBuilder.class:
this.assistant.addMappedStatement(mappedStatementId, sqlSource, statementType, sqlCommandType, fetchSize, timeout, (String)null, parameterTypeClass, var26, this.getReturnType(method), resultSetType, flushCache, useCache, false, (KeyGenerator)keyGenerator, keyProperty, keyColumn, (String)null, languageDriver, options != null?this.nullOrEmpty(options.resultSets()):null);
this.configuration.addMappedStatement(statement);
最后回到:Configuration.class: