天天看點

第03篇:Mybatis核心類詳細介紹

第03篇:Mybatis核心類詳細介紹

作者: 西魏陶淵明

部落格: ​​https://blog.springlearn.cn/​​

西魏陶淵明

莫笑少年江湖夢,誰不少年夢江湖

核心類介紹

前面我們知道Mybatis的解析原理,知道了在 ​​

​Configuration​

​​ 、​

​MapperBuilderAssistant​

​​ 出現了很多核心的類。

正是由這些類來實作了,Mybatis的核心功能。是以要想完全搞懂 Mybatis,這些類就必須要進行深入的研究,廢話不多少,直接就開始吧。

其實這裡面的每個類要都能單獨拆出來一篇進行詳細說明,但是這裡我們隻取其精華,知道他的作用,及如何使用。和能借鑒的地方就可以了。

一、Configuration

屬性 解釋
TypeAliasRegistry key是一個别名,value是一個class對象
Properties variables 配置檔案中占位符的變量配置
InterceptorChain interceptorChain 攔截鍊,用于攔截方法,實作插件
ObjectFactory objectFactory 對象執行個體化統一的工廠方法
ObjectWrapperFactory objectWrapperFactory 擴充使用,允許使用者自定義包裝對象ObjectWrapper
ReflectorFactory reflectorFactory 反射工廠,用于生成一個反射資訊對象
Environment environment 環境資訊包含(事務管理器和資料源)
TypeHandlerRegistry typeHandlerRegistry 資料庫傳回資料類型轉換成Java對象的處理器,或是Java資料類型轉換jdbc資料類型的處理器
MapperRegistry mapperRegistry Mapper生成的處理類,包含代理的邏輯

1.1 TypeAliasRegistry

key是别名,value是對應的Class<?>

這個在什麼時候用的呢? 前面我們通過解析xml,發現很多的dtd限制,檔案的值類型都是 CDATA 即 字元串。 但是這些字元串最終是要解析成指定的位元組碼的。

怎麼知道字元串對應的是哪個java類呢? 那麼這個功能就交給 ​​

​TypeAliasRegistry​

​​。允許你将一個java類注冊一個别名。這樣你就可以在配置檔案中用别名

來替換java類了。

@Test
    public void TypeAliasRegistry() {
        TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
        System.out.println(typeAliasRegistry.resolveAlias("byte"));
    }      

1.2 Properties

這個java類就不用介紹了,在​

​Configuration​

​ 就是存儲的配置資訊,允許你在mybatis中任意地方使用${}進行通路資料。

比如你可以這樣用? 配置一個全局的limit限制數量

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

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)
public class InterceptorChain {

  private final List<Interceptor> interceptors = new ArrayList<>();

  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }

  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }

  public List<Interceptor> getInterceptors() {
    return Collections.unmodifiableList(interceptors);
  }

}

public interface Interceptor {

  Object intercept(Invocation invocation) throws Throwable;

  default Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }

  default void setProperties(Properties properties) {
    // NOP
  }

}      

1.4 ObjectFactory 對象工廠

在Mybatis中或者說是orm架構中, 使用到反射的地方較多。那麼就一定會遇到執行個體化的問題。具體如何執行個體化。就是使用對象工廠。

之是以提供個工廠, 小編個人認為還是為了擴充使用。但是實際中一般不會擴充這個類。因為該有的功能預設的就已經具備了。

public interface ObjectFactory {
  
  // 配置資訊
  default void setProperties(Properties properties) {}
  // 根據空構造來執行個體化
  <T> T create(Class<T> type);
  // 根據構造參數來執行個體化
  <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs);
  // 判斷是否是Collection子類
  <T> boolean isCollection(Class<T> type);

}      

1.5 ObjectWrapperFactory 對象包裝工廠

他的作用主要是提供外面的擴充,允許使用者自己去建立包裝對象。實際架構中不會用到這個對象。我們隻要知道他的作用是什麼行。

我們重點說一下 ObjectWrapper 。

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

public class MetaObject {

  private final Object originalObject;
  private final ObjectWrapper objectWrapper;
  private final ObjectFactory objectFactory;
  private final ObjectWrapperFactory objectWrapperFactory;
  private final ReflectorFactory reflectorFactory;

  private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
    this.originalObject = object;
    this.objectFactory = objectFactory;
    this.objectWrapperFactory = objectWrapperFactory;
    this.reflectorFactory = reflectorFactory;

    if (object instanceof ObjectWrapper) {
      this.objectWrapper = (ObjectWrapper) object;
    } else if (objectWrapperFactory.hasWrapperFor(object)) {
      this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);
    } else if (object instanceof Map) {
      this.objectWrapper = new MapWrapper(this, (Map) object);
    } else if (object instanceof Collection) {
      this.objectWrapper = new CollectionWrapper(this, (Collection) object);
    } else {
      this.objectWrapper = new BeanWrapper(this, object);
    }
  }
}      

我們看到普通的對象,被包裝成 ObjectWrapper後就可以使用通用的API來擷取和修改對象數值型,以及可以擷取屬性值的類型資訊,如下面的例子。

第03篇:Mybatis核心類詳細介紹
@Test
    public void objectWrapper(){
        // 讀取配置資訊(為什麼路徑前不用加/,因為是相對路徑。maven編譯後的資源檔案和class檔案都是在一個包下,是以不用加/就是目前包目錄)
        InputStream mapperInputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("mybatisConfig.xml");
        // 生成SqlSession工廠,SqlSession從名字上看就是,跟資料庫互動的會話資訊,負責将sql送出到資料庫進行執行
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(mapperInputStream, "development");
        // 擷取Mybatis配置資訊
        Configuration configuration = sqlSessionFactory.getConfiguration();
        Map<String,String> map = new HashMap<>();
        map.put("name","孫悟空");
        MetaObject metaObject = MetaObject.forObject(map, configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory());
        System.out.println(metaObject.getValue("name"));
        // 複制
        metaObject.setValue("age",18);
        // {name=孫悟空, age=18}
        System.out.println(map);

        TUser tUser = new TUser();
        tUser.setName("唐三藏");
        MetaObject tUserMetaObject = MetaObject.forObject(tUser, configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory());
        // 唐三藏
        System.out.println(tUserMetaObject.getValue("name"));

        List<TUser> users = new ArrayList<>();
        users.add(tUser);
        MetaObject tUserMetaObjects = MetaObject.forObject(users, configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory());
        tUserMetaObjects.add(new TUser());
        // [TUser(tokenId=null, uid=null, name=唐三藏), TUser(tokenId=null, uid=null, name=null)]
        System.out.println(tUserMetaObjects.getOriginalObject());
    }      

1.6 ReflectorFactory 反射工廠

從名字看就是反射的工廠,主要是為了生成 Reflector 對象。Reflector 對反射的資訊進行了緩存。用的時候直接從緩存中擷取。

public interface ReflectorFactory {

  boolean isClassCacheEnabled();

  void setClassCacheEnabled(boolean classCacheEnabled);

  Reflector findForClass(Class<?> type);
}      

1.7 Environment 環境

這裡面的環境屬性,是比較重要。因為他直接決定了你要跟那個資料庫互動。以及事務如何處理。

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

1.8 TypeHandlerRegistry

TypeHandler + Registry, 從名字來看又是一個類型注冊器用于反射使用。看來mybatis中用于反射的工具類是在太多了。那麼TypeHandler究竟有什麼用呢?

TypeHandler 是對Statement和ResultSet負責。

ResultSet 是從資料庫擷取的資料的載體,Statement 是準備向資料庫送出資料的載體。TypeHandler 的作用就是

根據資料類型, 處理跟資料的輸入和輸出資訊。看下面接口。

public interface TypeHandler<T> {

  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

  /**
   * Gets the result.
   *
   * @param rs
   *          the rs
   * @param columnName
   *          Colunm name, when configuration <code>useColumnLabel</code> is <code>false</code>
   * @return the result
   * @throws SQLException
   *           the SQL exception
   */
  T getResult(ResultSet rs, String columnName) throws SQLException;

  T getResult(ResultSet rs, int columnIndex) throws SQLException;

  T getResult(CallableStatement cs, int columnIndex) throws SQLException;

}      

這裡舉一個例子,比如name這個字段在資料庫是varchar類型,但是java對象中name是一個Name對象。那麼如何處理呢?

我們自定義一個處理器。

public class NameTypeHandler implements TypeHandler<Name> {

    @Override
    public void setParameter(PreparedStatement ps, int i, Name parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i, parameter.getFirstName() + "-" + parameter.getSurname());
    }

    @Override
    public Name getResult(ResultSet rs, String columnName) throws SQLException {
        String name = rs.getString(columnName);
        String[] split = name.split("-");
        return new Name(split[0], split[1]);
    }
}      

然後在配置檔案中聲明注冊器,用于将java對象轉換成jdbc資料庫字段類型。同時也将資料庫查詢到的jdbc類型轉換成java對象。

<configuration>
        <typeHandlers>
            <typeHandler handler="orm.example.dal.type.NameTypeHandler" javaType="orm.example.dal.model.Name"></typeHandler>
        </typeHandlers>
    </configuration>    
    <mapper>
         <insert id="insert" parameterType="orm.example.dal.model.T2User">
            <!--
              WARNING - @mbggenerated
              This element is automatically generated by MyBatis Generator, do not modify.
              This element was generated on Sun Mar 27 23:01:23 CST 2022.
            -->
            insert into T_USER (token_id, uid, name)
            values (#{tokenId,jdbcType=CHAR}, #{uid,jdbcType=INTEGER}, #{name,javaType=orm.example.dal.model.Name })
        </insert>
    </mapper>      

我們執行下面代碼,可以看到我們将資料類型轉換成了jdbc存到了資料庫,同時執行查詢時候又将jdbc類型轉換成了java對象。這就是它的作用。

@Test
    public void test() {
        // 讀取配置資訊(為什麼路徑前不用加/,因為是相對路徑。maven編譯後的資源檔案和class檔案都是在一個包下,是以不用加/就是目前包目錄)
        InputStream mapperInputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("mybatisConfig.xml");
        // 生成SqlSession工廠,SqlSession從名字上看就是,跟資料庫互動的會話資訊,負責将sql送出到資料庫進行執行
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(mapperInputStream, "development");
        // 擷取Mybatis配置資訊
        Configuration configuration = sqlSessionFactory.getConfiguration();
        configuration.getTypeHandlerRegistry().register(new NameTypeHandler());
        // 參數: autoCommit,從名字上看就是是否自動送出事務
        SqlSession sqlSession = sqlSessionFactory.openSession(false);
        // 擷取Mapper
        T2UserMapper mapper = configuration.getMapperRegistry().getMapper(T2UserMapper.class, sqlSession);
        T2User tUser = new T2User();
        Name name = new Name("孫","悟空");
        tUser.setName(name);
        tUser.setTokenId("西天取經");
        mapper.insert(tUser);
        // 擷取插入的資料: T2User(tokenId=西天取經, uid=32, name=Name(surname=悟空, firstName=孫))
        System.out.println(mapper.selectByPrimaryKey("西天取經"));
        // 資料插入後,執行查詢,然後復原資料
        sqlSession.rollback();
    }      

1.9 MapperRegistry

看到Registry又知道了,這貨又是一個類似Map的工具類。肯定是跟Mapper有關系。下面代碼關鍵在于13和17行。

Mybatis中擷取Mapper對象都是從 MapperRegistry中擷取的。

  • line(13)​

    ​new MapperProxyFactory<>(type)​

    ​ 接口生成代理對象
  • line(17)​

    ​MapperAnnotationBuilder​

    ​​ 用于解析Mybatis支援的注解,并添加到​

    ​Configuration​

這兩個類比較重要我們開單獨的篇幅進行說明。

public class MapperRegistry {

  private final Configuration config;
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
  
  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<>(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();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }
}      

1.10 SqlSession

SqlSession相當于一千個橋梁,負責将方法參數,發送給資料庫,并且将資料庫傳回值組裝成方法的傳回值。

在SqlSession中有幾個比較重要的類,如下圖。他們負責不同的邏輯。

分别處理入參(ParameterHandler),處理出參(ResultSetHandler),生成Jdbc(StatementHandler),處理緩存相關(Executor)。

是一個非常重要的一個類。後面我們的學習中會經常看到。

第03篇:Mybatis核心類詳細介紹
public interface SqlSession extends Closeable {

  <T> T selectOne(String statement);

  <T> T selectOne(String statement, Object parameter);

  int insert(String statement);

  int insert(String statement, Object parameter);

  int update(String statement);

  int update(String statement, Object parameter);

  int delete(String statement);

  int delete(String statement, Object parameter);

  void commit();

  void commit(boolean force);

  void rollback();

  void rollback(boolean force);

  List<BatchResult> flushStatements();

  @Override
  void close();

  void clearCache();

  Configuration getConfiguration();

  <T> T getMapper(Class<T> type);

  Connection getConnection();
}      

我們看增删改查的方法入參無非2個。1個是statement,1個是入參。

其中statement主要是為了擷取 MappedStatement。如下

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

另外一個入參是為了組裝sql資訊。MappedStatement#getBoundSql 擷取sql資訊。

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

二、MapperBuilderAssistant

屬性 解釋
MapperBuilderAssistant Mapper建構輔助工具類(緩存配置)
CacheRefResolver 決定如何使用緩存
ParameterMapping 參數映射類
ResultMapResolver 傳回值映射
Map<String, XNode> sqlFragments sql片段
MappedStatement Mapper方法的所有資訊(出參,入參)

2.1 MapperBuilderAssistant

Mapper建構工具類,下面小編列舉了幾個方法。可以看出來基本都是用于處理sql結果集向java對象轉換使用,和對Mapper方法簽名分析生成sql的工具。

下面我們一個一個來看看。

public class MapperBuilderAssistant extends BaseBuilder {
    // 确定使用那個緩存
    public Cache useCacheRef(String namespace);
    // 生成2級緩存對象
    public Cache useNewCache(...);
    // 每個參數的資訊
    public ParameterMapping buildParameterMapping();
    // 生成結構集
    public ResultMap addResultMap();
    // 鑒别器
    public Discriminator buildDiscriminator();
    // 生成Mapper簽名
    public MappedStatement addMappedStatement();
    // 擷取方言處理器
    public LanguageDriver getLanguageDriver(Class<? extends LanguageDriver> langClass);
}      

2.1.1 Cache

Mybatis 緩存的接口定義,用于緩存查詢sql的結果。Mybatis中一級緩存和二級緩存是一個面試經常會考的問題。這個類我們也單獨開一篇私聊。

2.1.2 ParameterMapping & ResultMapping

從名字中能看到就是對Mapper中方法的入參和出參的映射關系類。

public class ParameterMapping {
  private Configuration configuration;
  private String property;
  private ParameterMode mode;
  private Class<?> javaType = Object.class;
  private JdbcType jdbcType;
  private Integer numericScale;
  private TypeHandler<?> typeHandler;
  private String resultMapId;
  private String jdbcTypeName;
  private String expression;
}      

如圖所示,會對方法的每個參數,生成一個 ParameterMapping對象。存儲了java類型和db的類型的映射關系。

第03篇:Mybatis核心類詳細介紹

2.1.3 ResultMap

從名字看就是對jdbc結果集向Mapper傳回值的映射關系,用于将jdbc資料重新映射成Java對象。

@Test
    public void resultSet(){
        // 讀取配置資訊(為什麼路徑前不用加/,因為是相對路徑。maven編譯後的資源檔案和class檔案都是在一個包下,是以不用加/就是目前包目錄)
        InputStream mapperInputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("mybatisConfig.xml");
        // 生成SqlSession工廠,SqlSession從名字上看就是,跟資料庫互動的會話資訊,負責将sql送出到資料庫進行執行
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(mapperInputStream, "development");
        // 擷取Mybatis配置資訊
        Configuration configuration = sqlSessionFactory.getConfiguration();
        SqlSession sqlSession = sqlSessionFactory.openSession(false);
        TUserMapper mapper = configuration.getMapper(TUserMapper.class,sqlSession);
        System.out.println(mapper.selectAll());
        MappedStatement mappedStatement = configuration.getMappedStatement("orm.example.dal.mapper.TUserMapper.selectAll");
        List<ResultMap> resultMaps = mappedStatement.getResultMaps();
        System.out.println(resultMaps);
    }      
第03篇:Mybatis核心類詳細介紹

2.1.4 LanguageDriver

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

public interface LanguageDriver {
 
  ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);

  SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType);
 
  SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType);

}      

動态sql可以處理下面這些标簽

private void initNodeHandlerMap() {
    nodeHandlerMap.put("trim", new TrimHandler());
    nodeHandlerMap.put("where", new WhereHandler());
    nodeHandlerMap.put("set", new SetHandler());
    nodeHandlerMap.put("foreach", new ForEachHandler());
    nodeHandlerMap.put("if", new IfHandler());
    nodeHandlerMap.put("choose", new ChooseHandler());
    nodeHandlerMap.put("when", new IfHandler());
    nodeHandlerMap.put("otherwise", new OtherwiseHandler());
    nodeHandlerMap.put("bind", new BindHandler());
  }      

2.2 CacheRefResolver

确定每個Mapper配置的緩存

public class CacheRefResolver {
  private final MapperBuilderAssistant assistant;
  private final String cacheRefNamespace;

  public CacheRefResolver(MapperBuilderAssistant assistant, String cacheRefNamespace) {
    this.assistant = assistant;
    this.cacheRefNamespace = cacheRefNamespace;
  }

  public Cache resolveCacheRef() {
    return assistant.useCacheRef(cacheRefNamespace);
  }
}      

2.3 MappedStatement

可以說關于Mapper所有的資訊都在這個類裡面,包括sql資訊、入參及傳回值類型、sql類型(SqlCommandType)、是否使用緩存、

是否重新整理緩存、StatementType類型。

public final class MappedStatement {
  // mapper/TUserMapper.xml
  private String resource;
  // 全局配置
  private Configuration configuration;
  // orm.example.dal.mapper.TUserMapper.insert
  private String id;
  // 
  private Integer fetchSize;
  // 逾時時間
  private Integer timeout;
  // StatementType.PREPARED
  private StatementType statementType;
  // ResultSetType.DEFAULT(-1),
  private ResultSetType resultSetType;
  // RawSqlSource
  private SqlSource sqlSource;
  private Cache cache;
  private ParameterMap parameterMap;
  private List<ResultMap> resultMaps;
  private boolean flushCacheRequired;
  private boolean useCache;
  private boolean resultOrdered;
  // SqlCommandType( UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH)
  private SqlCommandType sqlCommandType;
  // 生成id
  private KeyGenerator keyGenerator;
  private String[] keyProperties;
  private String[] keyColumns;
  private boolean hasNestedResultMaps;
  private String databaseId;
  private Log statementLog;
  private LanguageDriver lang;
  private String[] resultSets;
}      

生成主要有2種方法。

  1. xml的方式 XMLStatementBuilder
  2. 通過注解的方式 MapperAnnotationBuilder
public class XMLMapperBuilder extends BaseBuilder 
    public void parse() {
        // 如果有資源檔案先解析xml,并儲存到Configuration#addMappedStatement
        if (!configuration.isResourceLoaded(resource)) {
          // XMLStatementBuilder進行解析
          configurationElement(parser.evalNode("/mapper"));
          configuration.addLoadedResource(resource);
          // 同時使用MapperAnnotationBuilder類解析
          bindMapperForNamespace();
        }
    
        parsePendingResultMaps();
        parsePendingCacheRefs();
        parsePendingStatements();
      }
}  

public class MapperAnnotationBuilder{
      // 隻有包含了下面注解的方法才會被解析
      private static final Set<Class<? extends Annotation>> statementAnnotationTypes = Stream
      .of(Select.class, Update.class, Insert.class, Delete.class, SelectProvider.class, UpdateProvider.class,
          InsertProvider.class, DeleteProvider.class)
      .collect(Collectors.toSet());
      
     public  void parseStatement(Method method) {
        final Class<?> parameterTypeClass = getParameterType(method);
        final LanguageDriver languageDriver = getLanguageDriver(method);
        // 判斷是否包含了上面的注解
        getAnnotationWrapper(method, true, statementAnnotationTypes)
        .ifPresent(statementAnnotation -> {})
     }   
}      

Mapper配置檔案在解析的時候首先,回去解析xml,然後解析注解。如果兩種方式都存在那麼就會提示錯誤。

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.ConfigurationStrictMap.put(Configuration.java:970)

原因就在 StrictMap。

public V put(String key, V value) {
      if (containsKey(key)) {
        throw new IllegalArgumentException(name + " already contains value for " + key
            + (conflictMessageProducer == null ? "" : conflictMessageProducer.apply(super.get(key), value)));
      }
      if (key.contains(".")) {
        final String shortKey = getShortName(key);
        if (super.get(shortKey) == null) {
          super.put(shortKey, value);
        } else {
          super.put(shortKey, (V) new Ambiguity(shortKey));
        }
      }
      return super.put(key, value);
}      

三、可以借鑒的知識點

3.1 包裝器模式

ObjectWrapper

@Test
    public void objectWrapper() {
        TUser mock = JMockData.mock(TUser.class);
        MetaObject metaObject = MetaObject.forObject(mock, new DefaultObjectFactory(), new DefaultObjectWrapperFactory(), new DefaultReflectorFactory());
        boolean name = metaObject.hasGetter("name");
        if (name) {
            // iuslA4Xp
            System.out.println(metaObject.getValue("name"));
        }

        Map<String,Object> map = new HashMap<>();
        map.put("age",18);
        MetaObject metaMap = MetaObject.forObject(map, new DefaultObjectFactory(), new DefaultObjectWrapperFactory(), new DefaultReflectorFactory());
        boolean age = metaMap.hasGetter("age");
        if (age) {
            // 18 
            System.out.println(metaMap.getValue("age"));
        }
    }      

3.2 MetaClass

@Test
    public void metaClass()throws Exception{
        MetaClass metaClass = MetaClass.forClass(TUser.class, new DefaultReflectorFactory());
        TUser blankUser = new TUser();
        metaClass.getSetInvoker("name").invoke(blankUser,new Object[]{"孫悟空"});
        // 孫悟空
        System.out.println(blankUser.getName());
    }