一、框架设计
1. 接口层—和数据库交互的方式:以使用Mapper接口为例
将配置文件中的每一个<mapper>
节点抽象为一个 Mapper 接口,这个接口中声明的方法和跟Mapper.xml中的<select|update|delete|insert> 节点项对应,id值对应方法名称,parameterType 值对应方法的入参类型,而resultMap 值则对应返回值类型。
配置好后,调用SqlSession.getMapper(XXXMapper.class) 方法,MyBatis 会根据接口声明的方法信息,通过动态代理机制生成一个Mapper 实例,当调用接口方法时,根据这个方法的方法名和参数类型,确定Statement Id,底层还是通过 SqlSession.select/update( “statementId”, parameter) 等来实现对数据库的操作。
2. 数据处理层:可以说是MyBatis 的核心
a. 通过传入参数构建动态SQL语句;
b. SQL语句的执行以及封装查询结果集成List
2.1 参数映射和动态SQL语句生成
MyBatis 通过传入的参数值,使用 Ognl 来动态地构造SQL语句。
参数映射指的是java 数据和jdbc数据之间的转换:包括两个过程:查询阶段,将java类型的数据,转换成jdbc类型的数据,通过 preparedStatement.setXXX() 来设值;另一个就是对resultset查询结果集的jdbcType 数据转换成java 数据类型。
二、MyBatis的主要构件及其相互关系
mybatis底层还是采用原生jdbc来对数据库进行操作的,只是通过SqlSession, Executor, StatementHandler,ParameterHandler, ResultHandler和TypeHandler等几个处理器封装了这些过程.
- SqlSession :主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能,通常将它与ThreadLocal绑定,一个会话使用一个SqlSession,在使用完毕后需要close。
- Executor:MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护。
- StatementHandler 封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合。
- MappedStatement :维护了一条<select|update|delete|insert>节点的封装。
- Configuration:MyBatis所有的配置信息都维持在Configuration对象之中。
注: StatementHandler通过ParameterHandler与ResultHandler分别进行参数预编译 与结果处理。而ParameterHandler与ResultHandler都使用TypeHandler进行映射。
三、从MyBatis一次select 查询语句来分析MyBatis的架构设计
3.1、数据准备
i. 准备数据库数据,创建EMPLOYEES表,插入数据
ii. 配置Mybatis的配置文件,命名为Config.xml
iii. 创建Employee实体Bean 以及配置Mapper配置文件、相应接口
iv. 编写客户端代码
3.2、SqlSession 的工作过程分析:
a) 创建SqlSession对象:
SqlSession sqlSession = factory.openSession();
MyBatis封装了对数据库的访问,把对数据库的会话和事务控制放到了SqlSession对象中。
b) 为SqlSession传递一个配置的Sql语句 的Statement Id和参数,然后返回结果
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//1.根据Statement Id,在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();
}
}
每一条<select|update|delete|insert> 会生成一个对应的MappedStatement对象,然后会以key=“namespace.id” ,value为MappedStatement对象,维护到Configuration的一个Map中。以后需要使用的时候,只需要通过key值来获取就可。
c) 执行器Executor根据SqlSession传递的参数执行query()方法
Executor的作用是:
- 根据传递的参数,完成SQL的动态解析,生成BoundSql对象;
- 为查询创建缓存,以提高性能;
- 创建JDBC 的Statement 连接对象,传递给StatementHandler对象,返回List查询结果。
Executor.query()方法最后会创建一个StatementHandler对象,负责设置Statement 对象中的查询参数、处理JDBC返回的resultSet,将resultSet 加工为List 集合返回。
//SimpleExecutor类的doQuery()方法实现
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();
// 根据既有的参数,创建StatementHandler对象来执行查询操作
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 创建java.Sql.Statement对象,传递给StatementHandler对象
stmt = prepareStatement(handler, ms.getStatementLog());
// 调用StatementHandler.query()方法,返回List结果集
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
//getConnection方法经过重重调用最后会调用openConnection方法,从连接池中获得连接
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection);
//对创建的Statement对象设置参数,即设置SQL 语句中 ? 设置为指定的参数
handler.parameterize(stmt);
return stmt;
StatementHandler对象主要完成两个工作:
- 对JDBC的PreparedStatement 对象产生的占位符 ?进行设值。
- 通过List query(Statement statement, ResultHandler resultHandler)方法来完成执行Statement,将Statement对象返回的resultSet封装成List集合;
d) StatementHandler的parameterize(statement) 方法的实现:
public void parameterize(Statement statement) throws SQLException {
//使用ParameterHandler对象来完成对Statement的设值
parameterHandler.setParameters((PreparedStatement) statement);
}
ParameterHandler的setParameters(Statement)方法负责 根据我们输入的参数,对statement对象的 ? 占位符处进行赋值。
f) List query(Statement statement, ResultHandler resultHandler)方法的实现:
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
// 1.调用preparedStatemnt.execute()方法,然后将resultSet交给ResultSetHandler处理
PreparedStatement ps = (PreparedStatement) statement;
ps.execute(); //原生JDBC执行
//2. 使用ResultHandler来处理ResultSet
return resultSetHandler.<E> handleResultSets(ps);
}
ResultSetHandler的handleResultSets(Statement) 方法会将Statement语句执行后生成的resultSet 结果集转换成List 结果集。
四、接口方式:动态代理
先介绍一下MyBatis初始化时对接口的处理:MapperRegistry是Configuration中的一个属性,它内部维护一个HashMap用于存放mapper接口的工厂类,每个接口对应一个工厂类。
解析mappers标签时,当解析到接口时,会创建此接口对应的MapperProxyFactory对象,存入HashMap中,key = 接口的字节码对象,value = 此接口对应的MapperProxyFactory对象。
进入sqlSession.getMapper(UserMapper.class)中
//MapperRegistry中的getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//1. 从MapperRegistry中的HashMap中拿MapperProxyFactory
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
//2. 通过动态代理工厂生成示例。
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
//MapperProxyFactory类中的newInstance方法
public T newInstance(SqlSession sqlSession) {
// 3. 创建了JDK动态代理的Handler类
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
// 4. 调用了重载方法
return newInstance(mapperProxy);
}
//MapperProxy类,实现了InvocationHandler接口
public class MapperProxy<T> implements InvocationHandler, Serializable {
//省略部分源码
// 构造,传入了SqlSession,说明每个session中的代理对象的不同的!
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//判断调用是是不是Object中定义的方法,toString,hashCode这类非。是的话直接放行。
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 重点在这:MapperMethod最终调用了执行的方法
return mapperMethod.execute(sqlSession, args);
}
}
//5. 重载的方法,由动态代理创建新示例返回。
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}