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);
}
}