Junhc

岂止于博客

MyBatis工作原理

MyBatis的框架设计

MyBatis的两种开发模式
基于传统方式StatementId方式

传递StatementId和查询参数给SqlSession对象,使用SqlSession对象完成和数据库的交互
MyBatis提供了非常方便和简单的API,供用户实现对数据库的增删改查数据操作,以及对数据库连接信息和MyBatis自身配置信息的维护操作

使用传统方式对映射器配置文件的namespace命名没有任何要求,只要statement全限定名不重复就可以

public interface UserDao {
    public User findUserById(Long id);
}

public class UserDaoImpl implements UserDao {
    private SqlSessionFactory sqlSessionFactory;

    public UserDaoImpl(SqlSessionFactory sqlSessionFactory) {
        this.sqlSessionFactory = sqlSessionFactory;
    }

    public User findUserById(Long id) {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 全限定名 = namespace + StatementId
        return sqlSession.selectOne("userDao.findUserById", id);
    }
}
...
public static void main(String[] args) {
    ...
    InputStream config = Resources.getResourceAsStream("mybatis-config.xml");
		SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
		SqlSessionFactory factory = builder.build(config);
    UserDao userDao = new UserDaoImpl(factory);
    User user = userDao.findUserById(1L);
}
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="xxx.xxx.xxx.UserDao">
    <select id="findUserById" parameterType="java.lang.Long" resultType="xxx.xxx.xxx.User">
            select * from user where id = #{id}
    </select>
</mapper>
基于Mapper接口动态代理方式(推荐)

MyBatis将配置文件中的每一个<mapper>节点抽象为一个Mapper接口,而这个接口中声明的方法和跟<mapper>节点中的<select|update|delete|insert>节点项对应,即<select|update|delete|insert>节点的id值为Mapper接口中的方法名称,parameterType值表示Mapper对应方法的入参类型,而resultMap值则对应了Mapper接口表示的返回值类型或者返回结果集的元素类型

使用Mapper接口方式是有要求的,映射器配置文件的namespace必须和Mapper接口的全限定名一致

public interface UserMapper {
    public User findUserById(Long id);
}
...
public static void main(String[] args) {
    ...
    InputStream config = Resources.getResourceAsStream("mybatis-config.xml");
		SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
		SqlSessionFactory factory = builder.build(config);
    SqlSession sqlSession = factory.openSession();
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    User user = userMapper.findUserById(1L);
}
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="xxx.xxx.xxx.UserMapper">
    <select id="findUserById" parameterType="java.lang.Long" resultType="xxx.xxx.xxx.User">
        select * from user where id = #{id}
    </select>
</mapper>
核心部件
  • Configuration

    MyBatis所有的配置信息都保存在Configuration对象之中,配置文件中的大部分配置都会存储到该类中

  • SqlSession

    作为MyBatis工作的主要顶层API,表示和数据库交互时的会话,完成必要数据库增删改查功能

  • Executor

    MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护

  • StatementHandler

    封装了JDBC Statement操作,负责对JDBC statement的操作,如设置参数等

  • ParameterHandler

    负责对用户传递的参数转换成JDBC Statement所对应的数据类型

  • ResultSetHandler

    负责将JDBC返回的ResultSet结果集对象转换成List类型的集合

  • TypeHandler

    负责JAVA数据类型和JDBC数据类型(也可以说是数据表列类型)之间的映射和转换

  • MappedStatement

    MappedStatement维护一条<select|update|delete|insert>节点的封装

  • SqlSource

    负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回

  • BoundSql

    表示动态生成的SQL语句以及相应的参数信息

// DefaultSqlSession.java
public <E> List<E> selectList(String statement, Object parameter) {
  return this.selectList(statement, parameter, RowBounds.DEFAULT);
}

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  try {
    // 1. 根据StatementId,在Mybatis配置对象Configuration中查找和配置文件相对应的MappedStatement
    MappedStatement ms = configuration.getMappedStatement(statement);
    // 2. 将查询任务委托给MyBatis的执行器Executor
    List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    return result;
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

MyBatis在初始化的时候,会将MyBatis的配置信息全部加载到内存中,使用org.apache.ibatis.session.Configuration实例来维护。
使用者可以使用sqlSession.getConfiguration()方法来获取。
MyBatis的配置文件中配置信息的组织格式和内存中对象的组织格式几乎完全对应的。

// BaseExecutor.java
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
	  // 1. 根据具体传入的参数,动态地生成需要执行的SQL语句,用BoundSql对象表示  
    BoundSql boundSql = ms.getBoundSql(parameter);
    // 2. 为当前的查询创建一个缓存Key
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) throw new ExecutorException("Executor was closed.");
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
    	  // 3. 缓存中没有值,直接从数据库中读取数据  
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      deferredLoads.clear(); // issue #601
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        clearLocalCache(); // issue #482
      }
    }
    return list;
  }

  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      // 4. 执行查询,返回List结果,然后将查询的结果放入缓存之中
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      localCache.removeObject(key);
    }
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }    
  // SimpleExecutor.java
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      // 5. 根据既有的参数,创建StatementHandler对象来执行查询操作
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      // 6. 创建java.Sql.Statement对象,传递给StatementHandler对象
      stmt = prepareStatement(handler, ms.getStatementLog());
      // 7. 调用StatementHandler.query()方法,返回List结果集
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }
你必须要知道的