Mybatis

第03篇:Mybatis核心类详细介绍

2026-01-299 min readJava 专题系列
Mybatis

!!!info 核心类介绍 前面我们知道Mybatis的解析原理,知道了在 ConfigurationMapperBuilderAssistant 出现了很多核心的类。 正是由这些类来实现了,Mybatis的核心功能。所以要想完全搞懂 Mybatis,这些类就必须要进行深入的研究,废话不多少,直接就开始吧。 !!!

其实这里面的每个类要都能单独拆出来一篇进行详细说明,但是这里我们只取其精华,知道他的作用,及如何使用。和能借鉴的地方就可以了。

一、Configuration

属性解释
TypeAliasRegistrykey是一个别名,value是一个class对象
Properties variables配置文件中占位符的变量配置
InterceptorChain interceptorChain拦截链,用于拦截方法,实现插件
ObjectFactory objectFactory对象实例化统一的工厂方法
ObjectWrapperFactory objectWrapperFactory扩展使用,允许用户自定义包装对象ObjectWrapper
ReflectorFactory reflectorFactory反射工厂,用于生成一个反射信息对象
Environment environment环境信息包含(事务管理器和数据源)
TypeHandlerRegistry typeHandlerRegistry数据库返回数据类型转换成Java对象的处理器,或是Java数据类型转换jdbc数据类型的处理器
MapperRegistry mapperRegistryMapper生成的处理类,包含代理的逻辑

1.1 TypeAliasRegistry

key是别名,value是对应的Class<?>

这个在什么时候用的呢? 前面我们通过解析xml,发现很多的dtd约束,文件的值类型都是 CDATA 即 字符串。 但是这些字符串最终是要解析成指定的字节码的。 怎么知道字符串对应的是哪个java类呢? 那么这个功能就交给 TypeAliasRegistry。允许你将一个java类注册一个别名。这样你就可以在配置文件中用别名 来替换java类了。

java
1 @Test 2 public void TypeAliasRegistry() { 3 TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry(); 4 System.out.println(typeAliasRegistry.resolveAlias("byte")); 5 }

1.2 Properties

这个java类就不用介绍了,在Configuration 就是存储的配置信息,允许你在mybatis中任意地方使用${}进行访问数据。

比如你可以这样用? 配置一个全局的limit限制数量

properties
1datasource.driver-class-name=com.mysql.cj.jdbc.Driver 2datasource.url=jdbc:mysql://127.0.0.1:3306/test 3datasource.username=root 4datasource.password=123456 5datasource.globalLimit=1000
java
1public interface TUserMapper { 2 @Select("select * from t_user where uid = ${id} limit ${datasource.globalLimit} ") 3 List<TUser> selectById(Long id); 4}

1.3 InterceptorChain

内容较多,开单独的篇幅进行介绍; 第07篇:Mybatis的插件设计分析

从名字就可以看到是一个拦截链; 主要是实现插件的功能。核心思路是, 通过拦截类的方法来实现插件。

MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)
java
1public class InterceptorChain { 2 3 private final List<Interceptor> interceptors = new ArrayList<>(); 4 5 public Object pluginAll(Object target) { 6 for (Interceptor interceptor : interceptors) { 7 target = interceptor.plugin(target); 8 } 9 return target; 10 } 11 12 public void addInterceptor(Interceptor interceptor) { 13 interceptors.add(interceptor); 14 } 15 16 public List<Interceptor> getInterceptors() { 17 return Collections.unmodifiableList(interceptors); 18 } 19 20} 21 22public interface Interceptor { 23 24 Object intercept(Invocation invocation) throws Throwable; 25 26 default Object plugin(Object target) { 27 return Plugin.wrap(target, this); 28 } 29 30 default void setProperties(Properties properties) { 31 // NOP 32 } 33 34}

1.4 ObjectFactory 对象工厂

在Mybatis中或者说是orm框架中, 使用到反射的地方较多。那么就一定会遇到实例化的问题。具体如何实例化。就是使用对象工厂。 之所以提供个工厂, 小编个人认为还是为了扩展使用。但是实际中一般不会扩展这个类。因为该有的功能默认的就已经具备了。

java
1public interface ObjectFactory { 2 3 // 配置信息 4 default void setProperties(Properties properties) {} 5 // 根据空构造来实例化 6 <T> T create(Class<T> type); 7 // 根据构造参数来实例化 8 <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs); 9 // 判断是否是Collection子类 10 <T> boolean isCollection(Class<T> type); 11 12} 13

1.5 ObjectWrapperFactory 对象包装工厂

他的作用主要是提供外面的扩展,允许用户自己去创建包装对象。实际框架中不会用到这个对象。我们只要知道他的作用是什么行。 我们重点说一下 ObjectWrapper 。

ObjectWrapper的主要作用是,提供统一的属性操作方法。主要在MetaObject被使用,如下。

java
1public class MetaObject { 2 3 private final Object originalObject; 4 private final ObjectWrapper objectWrapper; 5 private final ObjectFactory objectFactory; 6 private final ObjectWrapperFactory objectWrapperFactory; 7 private final ReflectorFactory reflectorFactory; 8 9 private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) { 10 this.originalObject = object; 11 this.objectFactory = objectFactory; 12 this.objectWrapperFactory = objectWrapperFactory; 13 this.reflectorFactory = reflectorFactory; 14 15 if (object instanceof ObjectWrapper) { 16 this.objectWrapper = (ObjectWrapper) object; 17 } else if (objectWrapperFactory.hasWrapperFor(object)) { 18 this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object); 19 } else if (object instanceof Map) { 20 this.objectWrapper = new MapWrapper(this, (Map) object); 21 } else if (object instanceof Collection) { 22 this.objectWrapper = new CollectionWrapper(this, (Collection) object); 23 } else { 24 this.objectWrapper = new BeanWrapper(this, object); 25 } 26 } 27}

我们看到普通的对象,被包装成 ObjectWrapper后就可以使用通用的API来获取和修改对象数值型,以及可以获取属性值的类型信息,如下面的例子。

java
1 @Test 2 public void objectWrapper(){ 3 // 读取配置信息(为什么路径前不用加/,因为是相对路径。maven编译后的资源文件和class文件都是在一个包下,所以不用加/就是当前包目录) 4 InputStream mapperInputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("mybatisConfig.xml"); 5 // 生成SqlSession工厂,SqlSession从名字上看就是,跟数据库交互的会话信息,负责将sql提交到数据库进行执行 6 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(mapperInputStream, "development"); 7 // 获取Mybatis配置信息 8 Configuration configuration = sqlSessionFactory.getConfiguration(); 9 Map<String,String> map = new HashMap<>(); 10 map.put("name","孙悟空"); 11 MetaObject metaObject = MetaObject.forObject(map, configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory()); 12 System.out.println(metaObject.getValue("name")); 13 // 复制 14 metaObject.setValue("age",18); 15 // {name=孙悟空, age=18} 16 System.out.println(map); 17 18 TUser tUser = new TUser(); 19 tUser.setName("唐三藏"); 20 MetaObject tUserMetaObject = MetaObject.forObject(tUser, configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory()); 21 // 唐三藏 22 System.out.println(tUserMetaObject.getValue("name")); 23 24 List<TUser> users = new ArrayList<>(); 25 users.add(tUser); 26 MetaObject tUserMetaObjects = MetaObject.forObject(users, configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory()); 27 tUserMetaObjects.add(new TUser()); 28 // [TUser(tokenId=null, uid=null, name=唐三藏), TUser(tokenId=null, uid=null, name=null)] 29 System.out.println(tUserMetaObjects.getOriginalObject()); 30 }

1.6 ReflectorFactory 反射工厂

从名字看就是反射的工厂,主要是为了生成 Reflector 对象。Reflector 对反射的信息进行了缓存。用的时候直接从缓存中获取。

java
1public interface ReflectorFactory { 2 3 boolean isClassCacheEnabled(); 4 5 void setClassCacheEnabled(boolean classCacheEnabled); 6 7 Reflector findForClass(Class<?> type); 8}

1.7 Environment 环境

这里面的环境属性,是比较重要。因为他直接决定了你要跟那个数据库交互。以及事务如何处理。

java
1public final class Environment { 2 private final String id; 3 private final TransactionFactory transactionFactory; 4 private final DataSource dataSource; 5}
java
1private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { 2 Transaction tx = null; 3 try { 4 final Environment environment = configuration.getEnvironment(); 5 final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); 6 tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); 7 final Executor executor = configuration.newExecutor(tx, execType); 8 return new DefaultSqlSession(configuration, executor, autoCommit); 9 } catch (Exception e) { 10 closeTransaction(tx); // may have fetched a connection so lets call close() 11 throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); 12 } finally { 13 ErrorContext.instance().reset(); 14 } 15 }

1.8 TypeHandlerRegistry

TypeHandler + Registry, 从名字来看又是一个类型注册器用于反射使用。看来mybatis中用于反射的工具类是在太多了。那么TypeHandler究竟有什么用呢? TypeHandler 是对Statement和ResultSet负责。 ResultSet 是从数据库获取的数据的载体,Statement 是准备向数据库提交数据的载体。TypeHandler 的作用就是 根据数据类型, 处理跟数据的输入和输出信息。看下面接口。

java
1public interface TypeHandler<T> { 2 3 void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException; 4 5 /** 6 * Gets the result. 7 * 8 * @param rs 9 * the rs 10 * @param columnName 11 * Colunm name, when configuration <code>useColumnLabel</code> is <code>false</code> 12 * @return the result 13 * @throws SQLException 14 * the SQL exception 15 */ 16 T getResult(ResultSet rs, String columnName) throws SQLException; 17 18 T getResult(ResultSet rs, int columnIndex) throws SQLException; 19 20 T getResult(CallableStatement cs, int columnIndex) throws SQLException; 21 22}

这里举一个例子,比如name这个字段在数据库是varchar类型,但是java对象中name是一个Name对象。那么如何处理呢? 我们自定义一个处理器。

java
1public class NameTypeHandler implements TypeHandler<Name> { 2 3 @Override 4 public void setParameter(PreparedStatement ps, int i, Name parameter, JdbcType jdbcType) throws SQLException { 5 ps.setString(i, parameter.getFirstName() + "-" + parameter.getSurname()); 6 } 7 8 @Override 9 public Name getResult(ResultSet rs, String columnName) throws SQLException { 10 String name = rs.getString(columnName); 11 String[] split = name.split("-"); 12 return new Name(split[0], split[1]); 13 } 14}

然后在配置文件中声明注册器,用于将java对象转换成jdbc数据库字段类型。同时也将数据库查询到的jdbc类型转换成java对象。

xml
1 <configuration> 2 <typeHandlers> 3 <typeHandler handler="orm.example.dal.type.NameTypeHandler" javaType="orm.example.dal.model.Name"></typeHandler> 4 </typeHandlers> 5 </configuration> 6 <mapper> 7 <insert id="insert" parameterType="orm.example.dal.model.T2User"> 8 <!-- 9 WARNING - @mbggenerated 10 This element is automatically generated by MyBatis Generator, do not modify. 11 This element was generated on Sun Mar 27 23:01:23 CST 2022. 12 --> 13 insert into T_USER (token_id, uid, name) 14 values (#{tokenId,jdbcType=CHAR}, #{uid,jdbcType=INTEGER}, #{name,javaType=orm.example.dal.model.Name }) 15 </insert> 16 </mapper>

我们执行下面代码,可以看到我们将数据类型转换成了jdbc存到了数据库,同时执行查询时候又将jdbc类型转换成了java对象。这就是它的作用。

java
1 @Test 2 public void test() { 3 // 读取配置信息(为什么路径前不用加/,因为是相对路径。maven编译后的资源文件和class文件都是在一个包下,所以不用加/就是当前包目录) 4 InputStream mapperInputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("mybatisConfig.xml"); 5 // 生成SqlSession工厂,SqlSession从名字上看就是,跟数据库交互的会话信息,负责将sql提交到数据库进行执行 6 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(mapperInputStream, "development"); 7 // 获取Mybatis配置信息 8 Configuration configuration = sqlSessionFactory.getConfiguration(); 9 configuration.getTypeHandlerRegistry().register(new NameTypeHandler()); 10 // 参数: autoCommit,从名字上看就是是否自动提交事务 11 SqlSession sqlSession = sqlSessionFactory.openSession(false); 12 // 获取Mapper 13 T2UserMapper mapper = configuration.getMapperRegistry().getMapper(T2UserMapper.class, sqlSession); 14 T2User tUser = new T2User(); 15 Name name = new Name("孙","悟空"); 16 tUser.setName(name); 17 tUser.setTokenId("西天取经"); 18 mapper.insert(tUser); 19 // 获取插入的数据: T2User(tokenId=西天取经, uid=32, name=Name(surname=悟空, firstName=孙)) 20 System.out.println(mapper.selectByPrimaryKey("西天取经")); 21 // 数据插入后,执行查询,然后回滚数据 22 sqlSession.rollback(); 23 }

1.9 MapperRegistry

看到Registry又知道了,这货又是一个类似Map的工具类。肯定是跟Mapper有关系。下面代码关键在于13和17行。 Mybatis中获取Mapper对象都是从 MapperRegistry中获取的。

  • line(13) new MapperProxyFactory<>(type) 接口生成代理对象
  • line(17) MapperAnnotationBuilder 用于解析Mybatis支持的注解,并添加到 Configuration

这两个类比较重要我们开单独的篇幅进行说明。

java
1public class MapperRegistry { 2 3 private final Configuration config; 4 private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>(); 5 6 public <T> void addMapper(Class<T> type) { 7 if (type.isInterface()) { 8 if (hasMapper(type)) { 9 throw new BindingException("Type " + type + " is already known to the MapperRegistry."); 10 } 11 boolean loadCompleted = false; 12 try { 13 knownMappers.put(type, new MapperProxyFactory<>(type)); 14 // It's important that the type is added before the parser is run 15 // otherwise the binding may automatically be attempted by the 16 // mapper parser. If the type is already known, it won't try. 17 MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); 18 parser.parse(); 19 loadCompleted = true; 20 } finally { 21 if (!loadCompleted) { 22 knownMappers.remove(type); 23 } 24 } 25 } 26 } 27}

1.10 SqlSession

SqlSession相当于一千个桥梁,负责将方法参数,发送给数据库,并且将数据库返回值组装成方法的返回值。

在SqlSession中有几个比较重要的类,如下图。他们负责不同的逻辑。 分别处理入参(ParameterHandler),处理出参(ResultSetHandler),生成Jdbc(StatementHandler),处理缓存相关(Executor)。 是一个非常重要的一个类。后面我们的学习中会经常看到。

java
1public interface SqlSession extends Closeable { 2 3 <T> T selectOne(String statement); 4 5 <T> T selectOne(String statement, Object parameter); 6 7 int insert(String statement); 8 9 int insert(String statement, Object parameter); 10 11 int update(String statement); 12 13 int update(String statement, Object parameter); 14 15 int delete(String statement); 16 17 int delete(String statement, Object parameter); 18 19 void commit(); 20 21 void commit(boolean force); 22 23 void rollback(); 24 25 void rollback(boolean force); 26 27 List<BatchResult> flushStatements(); 28 29 @Override 30 void close(); 31 32 void clearCache(); 33 34 Configuration getConfiguration(); 35 36 <T> T getMapper(Class<T> type); 37 38 Connection getConnection(); 39}

我们看增删改查的方法入参无非2个。1个是statement,1个是入参。 其中statement主要是为了获取 MappedStatement。如下

java
1private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) { 2 try { 3 MappedStatement ms = configuration.getMappedStatement(statement); 4 return executor.query(ms, wrapCollection(parameter), rowBounds, handler); 5 } catch (Exception e) { 6 throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); 7 } finally { 8 ErrorContext.instance().reset(); 9 } 10 }

另外一个入参是为了组装sql信息。MappedStatement#getBoundSql 获取sql信息。

java
1 @Override 2 public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { 3 BoundSql boundSql = ms.getBoundSql(parameter); 4 CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); 5 return query(ms, parameter, rowBounds, resultHandler, key, boundSql); 6 }

二、MapperBuilderAssistant

属性解释
MapperBuilderAssistantMapper构建辅助工具类(缓存配置)
CacheRefResolver决定如何使用缓存
ParameterMapping参数映射类
ResultMapResolver返回值映射
Map<String, XNode> sqlFragmentssql片段
MappedStatementMapper方法的所有信息(出参,入参)

2.1 MapperBuilderAssistant

Mapper构建工具类,下面小编列举了几个方法。可以看出来基本都是用于处理sql结果集向java对象转换使用,和对Mapper方法签名分析生成sql的工具。 下面我们一个一个来看看。

java
1public class MapperBuilderAssistant extends BaseBuilder { 2 // 确定使用那个缓存 3 public Cache useCacheRef(String namespace); 4 // 生成2级缓存对象 5 public Cache useNewCache(...); 6 // 每个参数的信息 7 public ParameterMapping buildParameterMapping(); 8 // 生成结构集 9 public ResultMap addResultMap(); 10 // 鉴别器 11 public Discriminator buildDiscriminator(); 12 // 生成Mapper签名 13 public MappedStatement addMappedStatement(); 14 // 获取方言处理器 15 public LanguageDriver getLanguageDriver(Class<? extends LanguageDriver> langClass); 16}

2.1.1 Cache

Mybatis 缓存的接口定义,用于缓存查询sql的结果。Mybatis中一级缓存和二级缓存是一个面试经常会考的问题。这个类我们也单独开一篇私聊。

2.1.2 ParameterMapping & ResultMapping

从名字中能看到就是对Mapper中方法的入参和出参的映射关系类。

java
1public class ParameterMapping { 2 private Configuration configuration; 3 private String property; 4 private ParameterMode mode; 5 private Class<?> javaType = Object.class; 6 private JdbcType jdbcType; 7 private Integer numericScale; 8 private TypeHandler<?> typeHandler; 9 private String resultMapId; 10 private String jdbcTypeName; 11 private String expression; 12}

如图所示,会对方法的每个参数,生成一个 ParameterMapping对象。存储了java类型和db的类型的映射关系。

2.1.3 ResultMap

从名字看就是对jdbc结果集向Mapper返回值的映射关系,用于将jdbc数据重新映射成Java对象。

java
1 @Test 2 public void resultSet(){ 3 // 读取配置信息(为什么路径前不用加/,因为是相对路径。maven编译后的资源文件和class文件都是在一个包下,所以不用加/就是当前包目录) 4 InputStream mapperInputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("mybatisConfig.xml"); 5 // 生成SqlSession工厂,SqlSession从名字上看就是,跟数据库交互的会话信息,负责将sql提交到数据库进行执行 6 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(mapperInputStream, "development"); 7 // 获取Mybatis配置信息 8 Configuration configuration = sqlSessionFactory.getConfiguration(); 9 SqlSession sqlSession = sqlSessionFactory.openSession(false); 10 TUserMapper mapper = configuration.getMapper(TUserMapper.class,sqlSession); 11 System.out.println(mapper.selectAll()); 12 MappedStatement mappedStatement = configuration.getMappedStatement("orm.example.dal.mapper.TUserMapper.selectAll"); 13 List<ResultMap> resultMaps = mappedStatement.getResultMaps(); 14 System.out.println(resultMaps); 15 }

2.1.4 LanguageDriver

主要用于生成 SqlSource,动态sql(XMLLanguageDriver)或者静态sql(RawLanguageDriver)

java
1 2public interface LanguageDriver { 3 4 ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql); 5 6 SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType); 7 8 SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType); 9 10}

动态sql可以处理下面这些标签

java
1 private void initNodeHandlerMap() { 2 nodeHandlerMap.put("trim", new TrimHandler()); 3 nodeHandlerMap.put("where", new WhereHandler()); 4 nodeHandlerMap.put("set", new SetHandler()); 5 nodeHandlerMap.put("foreach", new ForEachHandler()); 6 nodeHandlerMap.put("if", new IfHandler()); 7 nodeHandlerMap.put("choose", new ChooseHandler()); 8 nodeHandlerMap.put("when", new IfHandler()); 9 nodeHandlerMap.put("otherwise", new OtherwiseHandler()); 10 nodeHandlerMap.put("bind", new BindHandler()); 11 }

2.2 CacheRefResolver

确定每个Mapper配置的缓存

java
1public class CacheRefResolver { 2 private final MapperBuilderAssistant assistant; 3 private final String cacheRefNamespace; 4 5 public CacheRefResolver(MapperBuilderAssistant assistant, String cacheRefNamespace) { 6 this.assistant = assistant; 7 this.cacheRefNamespace = cacheRefNamespace; 8 } 9 10 public Cache resolveCacheRef() { 11 return assistant.useCacheRef(cacheRefNamespace); 12 } 13}

2.3 MappedStatement

可以说关于Mapper所有的信息都在这个类里面,包括sql信息、入参及返回值类型、sql类型(SqlCommandType)、是否使用缓存、 是否刷新缓存、StatementType类型。

java
1public final class MappedStatement { 2 // mapper/TUserMapper.xml 3 private String resource; 4 // 全局配置 5 private Configuration configuration; 6 // orm.example.dal.mapper.TUserMapper.insert 7 private String id; 8 // 9 private Integer fetchSize; 10 // 超时时间 11 private Integer timeout; 12 // StatementType.PREPARED 13 private StatementType statementType; 14 // ResultSetType.DEFAULT(-1), 15 private ResultSetType resultSetType; 16 // RawSqlSource 17 private SqlSource sqlSource; 18 private Cache cache; 19 private ParameterMap parameterMap; 20 private List<ResultMap> resultMaps; 21 private boolean flushCacheRequired; 22 private boolean useCache; 23 private boolean resultOrdered; 24 // SqlCommandType( UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH) 25 private SqlCommandType sqlCommandType; 26 // 生成id 27 private KeyGenerator keyGenerator; 28 private String[] keyProperties; 29 private String[] keyColumns; 30 private boolean hasNestedResultMaps; 31 private String databaseId; 32 private Log statementLog; 33 private LanguageDriver lang; 34 private String[] resultSets; 35}

生成主要有2种方法。

  1. xml的方式 XMLStatementBuilder
  2. 通过注解的方式 MapperAnnotationBuilder
java
1public class XMLMapperBuilder extends BaseBuilder 2 public void parse() { 3 // 如果有资源文件先解析xml,并保存到Configuration#addMappedStatement 4 if (!configuration.isResourceLoaded(resource)) { 5 // XMLStatementBuilder进行解析 6 configurationElement(parser.evalNode("/mapper")); 7 configuration.addLoadedResource(resource); 8 // 同时使用MapperAnnotationBuilder类解析 9 bindMapperForNamespace(); 10 } 11 12 parsePendingResultMaps(); 13 parsePendingCacheRefs(); 14 parsePendingStatements(); 15 } 16} 17 18public class MapperAnnotationBuilder{ 19 // 只有包含了下面注解的方法才会被解析 20 private static final Set<Class<? extends Annotation>> statementAnnotationTypes = Stream 21 .of(Select.class, Update.class, Insert.class, Delete.class, SelectProvider.class, UpdateProvider.class, 22 InsertProvider.class, DeleteProvider.class) 23 .collect(Collectors.toSet()); 24 25 public void parseStatement(Method method) { 26 final Class<?> parameterTypeClass = getParameterType(method); 27 final LanguageDriver languageDriver = getLanguageDriver(method); 28 // 判断是否包含了上面的注解 29 getAnnotationWrapper(method, true, statementAnnotationTypes) 30 .ifPresent(statementAnnotation -> {}) 31 } 32}

Mapper配置文件在解析的时候首先,回去解析xml,然后解析注解。如果两种方式都存在那么就会提示错误。

!!!danger Caused by: java.lang.IllegalArgumentException: Mapped Statements collection already contains value for orm.example.dal.mapper.TUserMapper.selectAll. please check mapper/TUserMapper.xml and orm/example/dal/mapper/TUserMapper.java (best guess) at org.apache.ibatis.session.Configuration$StrictMap.put(Configuration.java:1014) at org.apache.ibatis.session.Configuration$StrictMap.put(Configuration.java:970) !!!

原因就在 StrictMap。

java
1public V put(String key, V value) { 2 if (containsKey(key)) { 3 throw new IllegalArgumentException(name + " already contains value for " + key 4 + (conflictMessageProducer == null ? "" : conflictMessageProducer.apply(super.get(key), value))); 5 } 6 if (key.contains(".")) { 7 final String shortKey = getShortName(key); 8 if (super.get(shortKey) == null) { 9 super.put(shortKey, value); 10 } else { 11 super.put(shortKey, (V) new Ambiguity(shortKey)); 12 } 13 } 14 return super.put(key, value); 15}

三、可以借鉴的知识点

3.1 包装器模式

ObjectWrapper

ObjectWrapper的主要作用是,提供统一的属性操作方法。主要在MetaObject被使用,如下。

java
1 @Test 2 public void objectWrapper() { 3 TUser mock = JMockData.mock(TUser.class); 4 MetaObject metaObject = MetaObject.forObject(mock, new DefaultObjectFactory(), new DefaultObjectWrapperFactory(), new DefaultReflectorFactory()); 5 boolean name = metaObject.hasGetter("name"); 6 if (name) { 7 // iuslA4Xp 8 System.out.println(metaObject.getValue("name")); 9 } 10 11 Map<String,Object> map = new HashMap<>(); 12 map.put("age",18); 13 MetaObject metaMap = MetaObject.forObject(map, new DefaultObjectFactory(), new DefaultObjectWrapperFactory(), new DefaultReflectorFactory()); 14 boolean age = metaMap.hasGetter("age"); 15 if (age) { 16 // 18 17 System.out.println(metaMap.getValue("age")); 18 } 19 }

3.2 MetaClass

反射工具类

java
1 @Test 2 public void metaClass()throws Exception{ 3 MetaClass metaClass = MetaClass.forClass(TUser.class, new DefaultReflectorFactory()); 4 TUser blankUser = new TUser(); 5 metaClass.getSetInvoker("name").invoke(blankUser,new Object[]{"孙悟空"}); 6 // 孙悟空 7 System.out.println(blankUser.getName()); 8 }