MyBatisの動作原理と内部構造の解析

MyBatisは優れた永続層フレームワークであり、カスタムSQL、ストアドプロシージャ、高度なマッピングをサポートしています。JDBCコードのほとんどすべてと手動パラメータ設定、結果取得を回避し、XMLまたはアノテーションで設定を管理します。
@Slf4j
public class MyBatisExample {

  @Test
  public void verifyCache() throws IOException {
    String configPath = "mybatis-config.xml";
    InputStream configStream = Resources.getResourceAsStream(configPath);
    SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(configStream);
    SqlSession session = sessionFactory.openSession();
    session.selectOne("com.example.mapper.UserMapper.retrieveUser", 3);
  }
}
XML設定ファイルの例:
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
  <typeAliases>
    <typeAlias type="com.example.model.User" alias="user"/>
  </typeAliases>
  
  <environments default="dev">
    <environment id="dev">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/db"/>
        <property name="username" value="user"/>
        <property name="password" value="pass"/>
      </dataSource>
    </environment>
  </environments>
  
  <mappers>
    <mapper resource="mapping/UserMapper.xml"/>
  </mappers>
</configuration>
<?xml version="1.0" encoding="UTF-8" ?>
<mapper namespace="com.example.mapper.UserMapper">
  <select id="retrieveUser" parameterType="int" resultType="user">
    SELECT * FROM users WHERE id = #{id}
  </select>
</mapper>
SqlSessionFactoryの構築プロセスではXML設定の解析が行われます:
public SqlSessionFactory build(InputStream input, String env, Properties props) {
  XMLConfigBuilder parser = new XMLConfigBuilder(input, env, props);
  return this.build(parser.parse());
}
設定解析の主要メソッド:
public Configuration parse() {
  parseConfiguration(parser.evalNode("/configuration"));
  return this.configuration;
}

private void parseConfiguration(XNode root) {
  propertiesElement(root.evalNode("properties"));
  typeAliasesElement(root.evalNode("typeAliases"));
  environmentsElement(root.evalNode("environments"));
  mapperElement(root.evalNode("mappers"));
}
セッション開始時のトランザクション処理:
private SqlSession openSessionFromDataSource() {
  Environment env = configuration.getEnvironment();
  TransactionFactory txFactory = getTransactionFactory(env);
  Transaction tx = txFactory.newTransaction(env.getDataSource(), null, false);
  Executor executor = configuration.newExecutor(tx, ExecutorType.SIMPLE);
  return new DefaultSqlSession(configuration, executor, false);
}
クエリ実行時の処理フロー:
public <T> T selectOne(String statementId, Object param) {
  List<T> results = selectList(statementId, param);
  if (results.size() == 1) return results.get(0);
  if (results.size() > 1) throw new TooManyResultsException();
  return null;
}
キャッシュキーの生成ロジック:
public CacheKey createCacheKey(MappedStatement ms, Object param, BoundSql boundSql) {
  CacheKey key = new CacheKey();
  key.update(ms.getId());
  key.update(boundSql.getSql());
  for (ParameterMapping mapping : boundSql.getParameterMappings()) {
    String prop = mapping.getProperty();
    Object value = getParameterValue(prop, param, boundSql);
    key.update(value);
  }
  return key;
}
クエリ実行とキャッシュの相互作用:
public <E> List<E> query(MappedStatement ms, Object param, CacheKey key, BoundSql boundSql) {
  if (cache != null) {
    List<E> cached = (List<E>) tcm.getObject(cache, key);
    if (cached != null) return cached;
    List<E> fresh = delegate.query(ms, param, key, boundSql);
    tcm.putObject(cache, key, fresh);
    return fresh;
  }
  return delegate.query(ms, param, key, boundSql);
}
データベースからの直接取得処理:
private <E> List<E> queryFromDatabase(MappedStatement ms, Object param, CacheKey key, BoundSql boundSql) {
  localCache.putObject(key, EXECUTION_PLACEHOLDER);
  List<E> results = doQuery(ms, param, boundSql);
  localCache.putObject(key, results);
  return results;
}

タグ: MyBatis SQLマッピング 一级キャッシュ ソースコード解析 ORM

7月3日 18:45 投稿