手写mybatis执行流程
上一章将解析流程写完了,这一章我们将手写mybatis的执行流程。
在第一章节中,通过mybatis架构分析,我们知道SqlSession接口是mybatis在接口层专门给开发人员提供的对外操作接口,通过SqlSession接口开发人员就可以完成对数据库CRUD,所以执行流程的入口就是SqlSession,接下来我们将执行流程分解成两个子流程讲解并编写:
- 一个是 SqlSession的构建流程
- 一个是SqlSession的内部执行流程
1. SqlSession的构建流程
先来回顾一下mybatis的整体流程,看下面一张图:
从图中我们可以看到,SqlSession是通过SqlSessionFactory工厂获取的,图中其实少了一步,SqlSessionFactory本身的构建是通过SqlSessionFactoryBuilder构建的,所以流程是按照下面步骤来的:
- 配置文件 => SqlSessionFactoryBuilder => SqlSessionFactory => SqlSession
先看第一个类SqlSessionFactoryBuilder:
/**
* @Author yangtianhao
* @Date 2020/2/18 11:43 上午
* @Version 1.0
*/
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(InputStream inputStream) throws Exception {
XmlConfigurationBuilder xmlConfigurationBuilder = new XmlConfigurationBuilder(inputStream);
Configuration configuration = xmlConfigurationBuilder.parse();
return build(configuration);
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
}
很简单,build方法接收核心配置文件的输入流,然后使用我们在解析流程中说过的XmlConfigurationBuilder对配置文件进行解析,获取Configuration对象,然后交给SqlSessionFactory的实现类,就完成了会话工厂的创建。
再看SqlSessionFactory,本身是一个接口:
/**
* @Author yangtianhao
* @Date 2020/2/18 11:41 上午
* @Version 1.0
*/
public interface SqlSessionFactory {
SqlSession openSession();
}
提供一个获取SqlSession的方法。DefaultSqlSessionFactory是我们提供的一个默认实现类:
/**
* @Author yangtianhao
* @Date 2020/2/18 1:47 下午
* @Version 1.0
*/
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType());
}
private SqlSession openSessionFromDataSource(ExecutorType executorType) {
Connection connection = null;
try {
connection = configuration.getEnvironment().getDataSource().getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
Executor executor = configuration.newExecutor(executorType,connection);
return new DefaultSqlSession(configuration, executor);
}
}
需要关注这么几点:
1.return new DefaultSqlSession(configuration, executor)
DefaultSqlSession是我们提供的一个SqlSession默认实现类,我们知道SqlSession只是mybatis对外提供的操作接口,目的只是为了给开发者提供一套方便的API,底层真正执行Sql的操作是依靠Executor执行器的,所以SqlSession的构建,需要依赖执行器,和Configuration对象,一个用来执行,一个用来获取需要执行的MappedStatment信息。
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
private Executor executor;
public DefaultSqlSession(Configuration configuration, Executor executor) {
//configuration用来获取sql等信息
this.configuration = configuration;
//真正执行sql的执行器
this.executor = executor;
}
//其他暂时不关注...
2.Executor executor = configuration.newExecutor(executorType,connection);
执行器专门执行增删改查的,那么它最终要执行Sql,肯定需要知道数据库的连接对象,即Connection对象,所以构建Executor的时候暂时只传数据库连接对象即可(因为我们是简单手写,不支持事务,而在源码中连接是放在事务对象中进行管理的)。
另外我们知道Executor接口有两个实现,一个是基础执行器BaseExecutor(是一个抽象类,采用模板方法设计模式,提供了一级缓存功能,基础执行器下具体实现有简单执行器、可重用执行器、批量执行器…)、一个是缓存执行器CachingExecutor,专门提供二级缓存功能,而executorType是专门指定初始化哪种基本执行器的:
/**
* @Author yangtianhao
* @Date 2020/2/5 5:28 下午
* @Version 1.0
*/
public class Configuration {
//默认简单执行器
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
//默认支持二级缓存
protected boolean cacheEnabled = true;
//省略了一些不需要关注的代码....
//创建执行器
public Executor newExecutor(ExecutorType executorType, Connection connection) {
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, connection);
} else {
executor = new SimpleExecutor(this, connection);
}
if (cacheEnabled) {
executor = new CatchingExecutor(executor);
}
return executor;
}
//获取默认的执行器类型
public ExecutorType getDefaultExecutorType() {
return defaultExecutorType;
}
//其他暂不关注.....
可以看到executorType专门控制底层用哪种BaseExecutor,它有三种实现,SimpleExecutor,ReuseExecutor,BatchExecutor,我们只实现了一种简单执行器SimpleExecutor,而CachingExecutor是专门提供二级缓存功能的,看到cacheEnabled为true,则用CachingExecutor装饰BaseExecutor,这个是装饰设计模式。
那么通过SqlSessionFactoryBuilder加载配置文件,构建SqlSessionFactory,再通过SqlSessionFactory获取SqlSession,整个构建过程就是这样。
2. SqlSession的内部执行流程
SqlSession的构建过程说完,我们继续讲解SqlSession内部的执行流程,先看下面一张图:
这张图完美的把SqlSession内部的组件,以及各个组件之间的交互,所负责的功能展现了出来,可以看到,SqlSession是Mybatis工作的顶层外部接口,底层依靠Executor执行器来执行数据库增删改查,Executor执行器在执行过程中,又把内部执行过程中的各个重要步骤或者职责委托给了其他的组件(类或者接口),比如:
- 获取Sql,先从Configuration对象中获取指定的MappedStatment对象,再从MappedStatment对象中获取对应的SqlSource,最终调用SqlSource的getBoundSql方法,直接返回封装了可执行的Sql和参数映射信息的BoundSql对象,而SqlSource.getBoundSql底层会对Sql进行解析,这个在之前解析流程有说过。
- 获取Sql后,需要对Sql生成JDBC的Statement对象,并对Statment设置参数,执行查询或更新等,这一系列对Statment的操作,又委托给了StatmentHandler接口,针对不同的Statment,有不同的StatmentHandler实现去处理。
- SimpleStatementHandler:简单语句处理器(STATEMENT)
- PreparedStatementHandler:预处理语句处理器(PREPARED)
- CallableStatementHandler:存储过程语句处理器(CALLABLE)
- StatmentHandler在进行具体的为Statment设置参数的时候,又委托给了ParameterHandler处理(JDBC中的简单Statement是不需要StatmentHandler处理参数的,因为它在SqlSource解析过程中已经处理好了)
- StatmentHandler在执行完Sql拿到结果集后,需要对结果集处理封装成java集合,这个职责委托给了ResultSetHandler
- 另外在处理参数和结果集的时候,java数据类型和jdbc数据类型是需要相互转换的,转换的职责就交给了TypeHandler
接下来我们按照流程从上往下依次编写:
1. SqlSession
/**
* @Author yangtianhao
* @Date 2020/2/18 11:34 上午
* @Version 1.0
*/
public interface SqlSession {
//statement代表的是MappedStatment的Id
<T> T selectOne(String statement, Object parameter);
<E> List<E> selectList(String statement, Object parameter);
int insert(String statement, Object parameter);
}
因为我们mapper映射文件里的sql标签只支持insert和select,所以SqlSession目前就定义一个查询和插入。statement代表的是MappedStatment的Id。
提供一个默认实现类:
/**
* @Author yangtianhao
* @Date 2020/2/18 11:37 上午
* @Version 1.0
*/
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
private Executor executor;
//需要通过Configuration获取需要执行的MappedStatment
//底层具体执行增删改差,依靠Executor
public DefaultSqlSession(Configuration configuration, Executor executor) {
this.configuration = configuration;
this.executor = executor;
}
@Override
public <T> T selectOne(String statement, Object parameter) {
List<T> objects = this.selectList(statement, parameter);
if (CollectionUtils.isNotEmpty(objects)) {
return objects.get(0);
}
return null;
}
@Override
public <E> List<E> selectList(String statement, Object parameter) {
//从configuration中根据Id获取对应的MappedStatment
MappedStatement ms = configuration.getMappedStatment(statement);
if (ms == null) {
return null;
}
try {
//最终执行还是依靠执行器
return executor.query(ms, parameter);
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
@Override
public int insert(String statement, Object parameter) {
MappedStatement ms = configuration.getMappedStatment(statement);
if (ms == null) {
return 0;
}
try {
return executor.update(ms, parameter);
} catch (SQLException e) {
e.printStackTrace();
}
return 0;
}
}
代码中可以看出,SqlSession只干了两件事:
- 从configuration中获取MappedStatment
- 将MappedStatment传给执行器处理
2. Executor
我们再看Executor:
/**
* @Author yangtianhao
* @Date 2020/2/18 11:46 上午
* @Version 1.0
*/
public interface Executor {
int update(MappedStatement ms, Object parameter) throws SQLException;
<E> List<E> query(MappedStatement ms, Object parameter) throws SQLException;
<E> List<E> query(MappedStatement ms, Object parameter, BoundSql boundSql) throws SQLException;
}
主要是两个方法,update用来执行增删改,query用来执行查询。
我们知道Executor有两个实现类,一个BaseExecutor,一个CachingExecutor,先看BaseExecutor:
/**
* @Author yangtianhao
* @Date 2020/2/18 6:01 下午
* @Version 1.0
*/
public abstract class BaseExecutor implements Executor {
protected Configuration configuration;
protected Connection connection;
public BaseExecutor(Configuration configuration, Connection connection) {
this.configuration = configuration;
this.connection = connection;
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameter) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
return this.query(ms, parameter, boundSql);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, BoundSql boundSql) throws SQLException {
// 一级缓存处理 TODO
// 根据boundSql,parameter等生成一个唯一的缓存key,拿着key去缓存中找
// 如果缓存中有结果,直接返回结果,否则继续执行queryFronDatabase从数据库中查询
return queryFromDatabase(ms, boundSql, parameter);
}
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
return doUpdate(ms, parameter);
}
private <E> List<E> queryFromDatabase(MappedStatement ms, BoundSql boundSql, Object parameter) {
return doQuery(ms, boundSql, parameter);
}
protected abstract <E> List<E> doQuery(MappedStatement ms, BoundSql boundSql, Object parameter);
protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;
}
这是一个抽象类,采用模板设计模式,目的是让继承该类的执行器都具备一级缓存的功能,仔细看query方法,因为是简写,没有实现缓存,用TODO标识了,在这个地方可以统一做一级缓存处理,如果查询到直接返回,否则走queryFromDatabase方法,从数据库查询,在看queryFromDatabase方法,会走doQuery方法,该方法需要在子类中实现,所以如果从数据库查询,需要具体子类去实现,更新也是也一样的,具体实现走doUpdate方法,它有如下几个实现:
- SimpleExecutor 简单执行器
- BatchExecutor 批处理执行器
- ReuseExecutor 可重用执行器
我们只实现了SimpleExecutor:
/**
* @Author yangtianhao
* @Date 2020/2/18 1:49 下午
* @Version 1.0
*/
public class SimpleExecutor extends BaseExecutor {
public SimpleExecutor(Configuration configuration, Connection connection) {
super(configuration, connection);
}
@Override
protected <E> List<E> doQuery(MappedStatement ms, BoundSql boundSql, Object parameter) {
StatmentHandler statmentHandler = configuration.newStatementHandler(ms, parameter, boundSql);
try {
Statement statement = statmentHandler.prepare(connection);
statmentHandler.parameterize(statement);
return statmentHandler.query(statement);
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
@Override
protected int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
StatmentHandler statmentHandler = configuration.newStatementHandler(ms, parameter, boundSql);
Statement statement = statmentHandler.prepare(connection);
statmentHandler.parameterize(statement);
return statmentHandler.update(statement);
}
}
代码中可以看到,得到boundSql后,生成statment,对statment设置参数,执行stament都是委托给StatmentHandler处理的,StatmentHandler后面会说。
再看CatchingExecutor:
/**
* @Author yangtianhao
* @Date 2020/2/18 2:02 下午
* @Version 1.0
*/
public class CatchingExecutor implements Executor {
private Executor delegate;
public CatchingExecutor(Executor delegate) {
this.delegate = delegate;
}
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
return delegate.update(ms, parameter);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameter) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
return this.query(ms, parameter, boundSql);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, BoundSql boundSql) throws SQLException {
// 二级缓存 TODO
// 根据boundSql,parameter等生成一个唯一的缓存key,拿着key去缓存中找
// 如果缓存中有结果,直接返回结果,否则执行装饰的基本执行器
return delegate.query(ms, parameter, boundSql);
}
}
很简单,用的装饰模式,在构造的时候传入一个被装饰的执行器即可,具体传的就是上面说的BaseExecutor的子类,在执行查询的时候,先查询二级缓存,有的话直接返回结果,否则执行被装饰的那个执行器,即delegate。
所有执行器讲完了,那么是什么时候构建执行器的呢?
其实刚开始已经讲过了,在构建SqlSession的时候,就会对执行器初始化:
/**
* @Author yangtianhao
* @Date 2020/2/18 1:47 下午
* @Version 1.0
*/
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType());
}
private SqlSession openSessionFromDataSource(ExecutorType executorType) {
Connection connection = null;
try {
connection = configuration.getEnvironment().getDataSource().getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
Executor executor = configuration.newExecutor(executorType,connection);
return new DefaultSqlSession(configuration, executor);
}
}
openSessionFromDataSource()方法中可以看到,初始化就是在Executor executor = configuration.newExecutor(executorType,connection);
这一行
再看Configuration:
/**
* @Author yangtianhao
* @Date 2020/2/5 5:28 下午
* @Version 1.0
*/
public class Configuration {
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
protected boolean cacheEnabled = true;
//省略其他不需要关注的....
public Executor newExecutor(ExecutorType executorType, Connection connection) {
Executor executor;
if (ExecutorType.BATCH == executorType) {
//简单手写,只实现了SimpleExecutor,这边为了演示
executor = new BatchExecutor(this, connection);
} else {
executor = new SimpleExecutor(this, connection);
}
if (cacheEnabled) {
executor = new CatchingExecutor(executor);
}
return executor;
}
public ExecutorType getDefaultExecutorType() {
return defaultExecutorType;
}
}
configuration的newExecutor会根据传入的ExecutorType选择创建哪种BaseExecutor,默认就是SimpleExecutor简单执行器,另外二级缓存也是默认开启的,如果开启,就创建CatchingExecutor装饰基本执行器。
3. StatmentHandler
首先看下StatmentHandler接口:
/**
* @Author yangtianhao
* @Date 2020/2/18 4:45 下午
* @Version 1.0
*/
public interface StatmentHandler {
// 准备语句
Statement prepare(Connection connection) throws SQLException;
// 参数化
void parameterize(Statement statement) throws SQLException;
// update
int update(Statement statement) throws SQLException;
// select
<E> List<E> query(Statement statement) throws SQLException;
}
我们定义了4个方法(源码不只四个,我们需求只需要4个):
- prepare:创建Statment
- parameterize:对Statment设置参数
- update:执行statment的update
- query:执行statment的query
针对JDBC有三种类型Statment,对应有三个实现类处理各自的Statment:
- SimpleStatementHandler 简单语句处理器(STATEMENT)
- PreparedStatementHandler 预处理语句处理器(PREPARED)
- CallableStatementHandler 存储过程语句处理器(CALLABLE)
我们只实现了一种PreparedStatmentHanlder:
/**
* @Author yangtianhao
* @Date 2020/2/18 6:17 下午
* @Version 1.0
*/
public class PreparedStatementHandler implements StatmentHandler {
private BoundSql boundSql;
private ParameterHandle parameterHandle;
private ResultSetHandler resultSetHandler;
private Configuration configuration;
public PreparedStatementHandler(BoundSql boundSql, Object parameterObject, MappedStatement mappedStatement) {
this.boundSql = boundSql;
this.configuration = mappedStatement.getConfiguration();
this.parameterHandle = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
this.resultSetHandler = configuration.newResultSetHandler(mappedStatement);
}
@Override
public Statement prepare(Connection connection) throws SQLException {
String sql = boundSql.getSql();
return connection.prepareStatement(sql);
}
@Override
public void parameterize(Statement statement) throws SQLException {
parameterHandle.setParameters((PreparedStatement)statement);
}
@Override
public int update(Statement statement) throws SQLException {
PreparedStatement preparedStatement = (PreparedStatement)statement;
return preparedStatement.executeUpdate();
}
@Override
public <E> List<E> query(Statement statement) throws SQLException {
PreparedStatement preparedStatement = (PreparedStatement)statement;
preparedStatement.executeQuery();
return resultSetHandler.handleResultSets(preparedStatement);
}
}
除了基本的对PreparedStatment的创建、执行外,可以发现在构造的时候,会通过conection对象初始化ParameterHandler和ResultSetHandler,并在对Statment设置参数,处理结果集起委托给这两个组件。
StatmentHandler讲完了,那么是在什么时候构造的呢?在上面讲SimpleExecutor的时候,当执行doQuery或者doUpdate的时候我们能看到StatmentHandler statmentHandler = configuration.newStatementHandler(ms, parameter, boundSql);
,就是在这个时候构造的,并且也是在Connection中获取的:
public class Configuration {
//省略不关注的部分...
public StatmentHandler newStatementHandler(MappedStatement ms, Object parameter, BoundSql boundSql) {
String statementType = ms.getStatementType();
StatmentHandler statmentHandler = null;
switch (statementType) {
case "prepared":
statmentHandler = new PreparedStatementHandler(boundSql, parameter, ms);
break;
default:
//比较简单,我们只实现了一种
}
return statmentHandler;
}
}
4. ParameterHandler
StatmentHandler在执行parameterize方法的时候,是委托给parameterHandler处理的,我们看下ParameterHandler接口:
/**
* @Author yangtianhao
* @Date 2020/2/18 6:25 下午
* @Version 1.0
*/
public interface ParameterHandle {
// 设置参数
void setParameters(PreparedStatement ps) throws SQLException;
}
有一个默认的实现类:
/**
* @Author yangtianhao
* @Date 2020/2/18 6:26 下午
* @Version 1.0
*/
public class DefaultParameterHandler implements ParameterHandle {
private BoundSql boundSql;
private Object parameterObject;
private MappedStatement mappedStatement;
private Configuration configuration;
public DefaultParameterHandler(BoundSql boundSql, Object parameterObject, MappedStatement mappedStatement) {
this.boundSql = boundSql;
this.parameterObject = parameterObject;
this.mappedStatement = mappedStatement;
this.configuration = mappedStatement.getConfiguration();
}
@Override
public void setParameters(PreparedStatement ps) throws SQLException {
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (CollectionUtils.isNotEmpty(parameterMappings)) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
String propertyName = parameterMapping.getProperty();
Object value = null;
if (parameterObject == null) {
value = null;
} else if (SimpleTypeRegistry.isSimpleType(parameterObject.getClass())) {
value = parameterObject;
} else {
try {
// 写的比较简单,直接用反射取值,只支持一级
Field field = parameterObject.getClass().getDeclaredField(propertyName);
field.setAccessible(true);
value = field.get(parameterObject);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
// 另外如果值为null,并且获取jdbcType也为null的话,从
// configuration中获取一个默认的jdbcType,让statment设置为NULL值。
jdbcType = configuration.getJdbcTypeForNull();
}
typeHandler.setParameter(ps, i + 1, value, jdbcType);
}
}
}
}
只考虑了参数为null,简单数据类型,javaBean的情况,并且javaBean也只支持一级成员属性的处理(源码中对参数的处理是支持类似于ognl表达式那种类似的语法),整个流程是先从boundSql中获取参数映射列表信息List<ParameterMapping>,遍历处理,根据每个ParameterMapping的propertyName从传递的参数中取到值以后,再从ParameterMapping中获取指定的TypeHandler对Statment进行设值(TypeHandler是什么时候放入ParameterMapping的呢?其实它的初始化时机是在解析Sql的时候,即替换#{}为?的时候,但是在第二章中是没有讲的,等后面说TypeHandle会补上)
jdbcType = configuration.getJdbcTypeForNull();
这段代码的目的,是因为在解析sql的时候,如果映射文件中的sql,没有配置"#{base_duration,jdbcType=INTEGER}",类似于这样用jdbcType属性指定JdbcType,那么ParameterMapping中的jdbcType就会为null,是为了处理这个情况的,如果值为null,并且获取jdbcType也为null的话,就从configuration中获取一个默认的jdbcType,让statment设置为NULL值需要这个JdbcType。
ParameterHandle也讲完了,它构造时机其实上面也讲过了,在PreparedStatementHandler实例化的时候,构造函数中会初始化一个ParameterHandler:
public class PreparedStatementHandler implements StatmentHandler {
private BoundSql boundSql;
private ParameterHandle parameterHandle;
private ResultSetHandler resultSetHandler;
private Configuration configuration;
public PreparedStatementHandler(BoundSql boundSql, Object parameterObject, MappedStatement mappedStatement) {
this.boundSql = boundSql;
this.configuration = mappedStatement.getConfiguration();
//构造参数处理器
this.parameterHandle = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
this.resultSetHandler = configuration.newResultSetHandler(mappedStatement);
}
}
看到也是在configuration中获取的:
public class Configuration {
//略不关注的....
public ParameterHandle newParameterHandler(MappedStatement mappedStatement, Object parameterObject,
BoundSql boundSql) {
return new DefaultParameterHandler(boundSql, parameterObject, mappedStatement);
}
}
5. ResultSetHandler
StatmentHandler在执行query方法的时候,当statment执行完executeQuery后,会得到结果集,需要将其处理封装成java对象集合,这个是委托给ResultSetHandler处理的:
/**
* @Author yangtianhao
* @Date 2020/2/18 6:28 下午
* @Version 1.0
*/
public interface ResultSetHandler {
// 处理结果集
<E> List<E> handleResultSets(Statement stmt) throws SQLException;
}
源码处理挺复杂的,因为涉及到ResultMap,ResultHandler等,还考虑了ResultMap层层嵌套等很多复杂情况,我们只是简单的实现,我们定义的映射配置文件中,insert和select标签是没有ResultMap属性的,只有resultType属性,而源码在处理resultType的时候,会将resultType解析成一个ResultMap,该ResultMap对象中只有id,和javaType(笔者发现在转换的时候,生成的ResultMap对象 只有一个Id,和javaType,如果有错误,希望读者可以指出),而ResultSetHandler在处理结果集的时候,就会获取这个ResultMap,并且会有两个流程,一个是自动映射,即基于属性名自动映射列到 JavaBean 的属性上,再根据resultMap的propertyMapping信息进行指定的属性映射。这个我们会在TypeHandler中讲解。
先看下默认实现类:
/**
* @Author yangtianhao
* @Date 2020/2/18 6:32 下午
* @Version 1.0
*/
public class DefaultResultSetHandler implements ResultSetHandler {
private MappedStatement mappedStatement;
private Configuration configuration;
public DefaultResultSetHandler(MappedStatement mappedStatement) {
this.mappedStatement = mappedStatement;
this.configuration = mappedStatement.getConfiguration();
}
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
List<Object> result = new ArrayList<>();
// 获取对应的ResultMap
ResultMap resultMap = mappedStatement.getResultMap();
ResultSet resultSet = stmt.getResultSet();
while (resultSet.next()) {
try {
Object resultObject = resultMap.getType().newInstance();
// 自动映射,基于属性名自动映射列到 JavaBean 的属性上
applyAutomaticMappings(resultObject, resultSet);
if (CollectionUtils.isNotEmpty(resultMap.getResultMappings())) {
// 处理有映射关系的
for (ResultMapping propertyMapping : resultMap.getResultMappings()) {
String property = propertyMapping.getProperty();
TypeHandler typeHandler = propertyMapping.getTypeHandler();
String column = propertyMapping.getColumn();
Object value = typeHandler.getResult(resultSet, column);
Field declaredField = resultObject.getClass().getDeclaredField(property);
declaredField.setAccessible(true);
declaredField.set(resultObject, value);
}
}
result.add(resultObject);
} catch (Exception e) {
}
}
return result;
}
//自动映射,根据数据库列名,自动映射到javaType属性名一样的字段
private void applyAutomaticMappings(Object resultObject, ResultSet resultSet) throws SQLException {
ResultSetMetaData rsm = resultSet.getMetaData(); // 获得列集
int col = rsm.getColumnCount(); // 获得列的个数
String colName[] = new String[col];
// 取结果集中的表头名称, 放在colName数组中
for (int i = 0; i < col; i++) {
colName[i] = rsm.getColumnName(i + 1);
}
for (String s : colName) {
try {
Field declaredField = resultObject.getClass().getDeclaredField(s);
declaredField.setAccessible(true);
Class<?> type = resultObject.getClass().getDeclaredField(s).getType();
TypeHandler<?> typeHandler = configuration.getTypeHandlerRegistry().getTypeHandler(type);
Object value = typeHandler.getResult(resultSet, s);
declaredField.set(resultObject, value);
} catch (NoSuchFieldException e) {
//简单处理了,不打印报错信息,看的难受
//e.printStackTrace();
} catch (IllegalAccessException e) {
//e.printStackTrace();
}
}
}
}
上面代码演示处理结果集映射时,会先进行自动映射,在进行属性映射两个过程,做的事情和源码是一样的,但是考虑的远没有源码全面。重在理解吧。
6. TypeHandler
在StatmentHanlder对Statment设置参数,处理返回的结果集的时候,需要在javaType和JdbcType之间的进行转换,数据才能正确显示,这个职责就委托给了TypeHandler。
先看下接口:
/**
* @Author yangtianhao
* @Date 2020/2/26 9:38 下午
* @Version 1.0
*/
public interface TypeHandler<T> {
// 设置参数
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
// 取得结果,供普通select用
T getResult(ResultSet rs, String columnName) throws SQLException;
}
两个方法,一个对Statment进行设值,一个从结果集取值,泛型T就对应的JavaType,JdbcType就是对应的数据库的数据类型。
对于JdbcType,我们直接拷贝源码, 很简单,就是对java.sql.Type包装了一层,而java.sql.Type就是对应的数据库数据类型:
/**
* @Author yangtianhao
* @Date 2020/2/26 9:40 下午
* @Version 1.0
*/
public enum JdbcType {
// 就是包装一下java.sql.Types
ARRAY(Types.ARRAY), BIT(Types.BIT), TINYINT(Types.TINYINT), SMALLINT(Types.SMALLINT), INTEGER(Types.INTEGER),
BIGINT(Types.BIGINT), FLOAT(Types.FLOAT), REAL(Types.REAL), DOUBLE(Types.DOUBLE), NUMERIC(Types.NUMERIC),
DECIMAL(Types.DECIMAL), CHAR(Types.CHAR), VARCHAR(Types.VARCHAR), LONGVARCHAR(Types.LONGVARCHAR), DATE(Types.DATE),
TIME(Types.TIME), TIMESTAMP(Types.TIMESTAMP), BINARY(Types.BINARY), VARBINARY(Types.VARBINARY),
LONGVARBINARY(Types.LONGVARBINARY), NULL(Types.NULL), OTHER(Types.OTHER), BLOB(Types.BLOB), CLOB(Types.CLOB),
BOOLEAN(Types.BOOLEAN), CURSOR(-10), // Oracle
UNDEFINED(Integer.MIN_VALUE + 1000),
// 太周到了,还考虑jdk5兼容性,jdk6的常量都不是直接引用
NVARCHAR(Types.NVARCHAR), // JDK6
NCHAR(Types.NCHAR), // JDK6
NCLOB(Types.NCLOB), // JDK6
STRUCT(Types.STRUCT);
public final int TYPE_CODE;
private static Map<Integer, JdbcType> codeLookup = new HashMap<Integer, JdbcType>();
// 一开始就将数字对应的枚举型放入hashmap
static {
for (JdbcType type : JdbcType.values()) {
codeLookup.put(type.TYPE_CODE, type);
}
}
JdbcType(int code) {
this.TYPE_CODE = code;
}
public static JdbcType forCode(int code) {
return codeLookup.get(code);
}
}
因为我们测试的表只涉及varchar,datatime,bigint,char,所以我们就实现4个TypeHandler。
- BaseTypeHandler 基础handler,提供处理null的功能
- ObjectTypeHandler 不知道类型的时候就用Object
- StringTypeHandler 处理varchar和char
- IntegerTypeHandler 处理bigint
- DateTypeHandler 处理datatime
我们测试的表:
CREATE TABLE `user` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`creator` varchar(255) DEFAULT 'system' COMMENT '创建者',
`creation_time` datetime DEFAULT NULL COMMENT '创建时间',
`modifier` varchar(255) DEFAULT 'system' COMMENT '修改者',
`modification_time` datetime DEFAULT NULL COMMENT '修改时间',
`is_delete` char(1) DEFAULT 'n' COMMENT '是否删除',
`name` varchar(255) DEFAULT NULL COMMENT '用户姓名',
`gender` char(1) DEFAULT NULL COMMENT '性别',
`phone` varchar(255) DEFAULT NULL COMMENT '电话',
`address` varchar(255) DEFAULT NULL COMMENT '地址',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表';
先抽象一个BaseTypeHandler,用来处理NULL的情况:
/**
* @Author yangtianhao
* @Date 2020/2/26 11:32 下午
* @Version 1.0
*/
public abstract class BaseTypeHandler<T> implements TypeHandler<T> {
@Override
public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
// 特殊情况,设置NULL
if (parameter == null) {
if (jdbcType == null) {
// 如果没设置jdbcType,报错啦
throw new RuntimeException(
"JDBC requires that the JdbcType must be specified for all nullable parameters.");
}
try {
// 设成NULL
ps.setNull(i, jdbcType.TYPE_CODE);
} catch (SQLException e) {
throw new RuntimeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType
+ " . "
+ "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. "
+ "Cause: " + e, e);
}
} else {
// 非NULL情况
setNonNullParameter(ps, i, parameter, jdbcType);
}
}
@Override
public T getResult(ResultSet rs, String columnName) throws SQLException {
T result = getNullableResult(rs, columnName);
// 通过ResultSet.wasNull判断是否为NULL
if (rs.wasNull()) {
return null;
} else {
return result;
}
}
// 不为null就设置参数,通过具体子类去实现
protected abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType)
throws SQLException;
// 取得可能为null的结果,通过具体子类去实现
public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;
}
不知道类型的时候就用Object的处理方式:
/**
* @Author yangtianhao
* @Date 2020/2/27 12:27 上午
* @Version 1.0
*/
public class ObjectTypeHandler extends BaseTypeHandler<Object> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType)
throws SQLException {
ps.setObject(i, parameter);
}
@Override
public Object getNullableResult(ResultSet rs, String columnName) throws SQLException {
return rs.getObject(columnName);
}
}
处理varchar和char :
/**
* @Author yangtianhao
* @Date 2020/2/26 11:39 下午
* @Version 1.0
*/
public class StringTypeHandler extends BaseTypeHandler<String> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
throws SQLException {
ps.setString(i, parameter);
}
@Override
public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
return rs.getString(columnName);
}
}
处理bigint:
/**
* @Author yangtianhao
* @Date 2020/2/26 11:40 下午
* @Version 1.0
*/
public class IntegerTypeHandler extends BaseTypeHandler<Integer> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType)
throws SQLException {
ps.setInt(i, parameter);
}
@Override
public Integer getNullableResult(ResultSet rs, String columnName) throws SQLException {
return rs.getInt(columnName);
}
}
处理datetime:
/**
* @Author yangtianhao
* @Date 2020/2/26 11:41 下午
* @Version 1.0
*/
public class DateTypeHandler extends BaseTypeHandler<Date> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Date parameter, JdbcType jdbcType)
throws SQLException {
ps.setTimestamp(i, new Timestamp((parameter).getTime()));
}
@Override
public Date getNullableResult(ResultSet rs, String columnName) throws SQLException {
Timestamp sqlTimestamp = rs.getTimestamp(columnName);
if (sqlTimestamp != null) {
return new Date(sqlTimestamp.getTime());
}
return null;
}
}
具体实现有了,那么typeHandler是在什么时候注册的呢?又是如何和JavaType以及JdbcType绑上关系的呢?其实就是在实例化Configuration的时候,并且由TypeHandlerRegistry对象负责管理:
public class Configuration {
//省略不需要关注的代码.....
// 类型处理器注册机
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
public TypeHandlerRegistry getTypeHandlerRegistry() {
return typeHandlerRegistry;
}
}
看到在实例化Configuration对象的时候,就会实例化一个TypeHandlerRegistry对象,而在TypeHandlerRegistry对象构造的时候,会在构造方法中对TypeHandler进行注册,并绑定对和javaType、jdbcType的关系:
/**
* @Author yangtianhao
* @Date 2020/2/27 12:03 上午
* @Version 1.0
*/
public final class TypeHandlerRegistry {
// 枚举型map
private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP =
new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class);
private final Map<Type, Map<JdbcType, TypeHandler<?>>> TYPE_HANDLER_MAP =
new HashMap<Type, Map<JdbcType, TypeHandler<?>>>();
private final Map<Class<?>, TypeHandler<?>> ALL_TYPE_HANDLERS_MAP = new HashMap<Class<?>, TypeHandler<?>>();
public TypeHandlerRegistry() {
// 构造函数里注册系统内置的类型处理器
// 以下是为多个类型注册到同一个handler
register(Integer.class, new IntegerTypeHandler());
register(int.class, new IntegerTypeHandler());
register(Integer.class, JdbcType.INTEGER, new IntegerTypeHandler());
register(int.class, JdbcType.INTEGER, new IntegerTypeHandler());
// 以下是为同一个类型的多种变种注册到多个不同的handler
register(String.class, new StringTypeHandler());
register(String.class, JdbcType.CHAR, new StringTypeHandler());
register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
register(JdbcType.CHAR, new StringTypeHandler());
register(JdbcType.VARCHAR, new StringTypeHandler());
register(Date.class, new DateTypeHandler());
register(JdbcType.TIMESTAMP, new DateTypeHandler());
register(Object.class, new ObjectTypeHandler());
register(Object.class, JdbcType.OTHER, new ObjectTypeHandler());
}
public <T> TypeHandler<T> getTypeHandler(Class<T> type) {
return getTypeHandler((Type)type, null);
}
public <T> TypeHandler<T> getTypeHandler(Class<T> type, JdbcType jdbcType) {
return getTypeHandler((Type)type, jdbcType);
}
@SuppressWarnings("unchecked")
private <T> TypeHandler<T> getTypeHandler(Type type, JdbcType jdbcType) {
Map<JdbcType, TypeHandler<?>> jdbcHandlerMap = TYPE_HANDLER_MAP.get(type);
TypeHandler<?> handler = null;
if (jdbcHandlerMap != null) {
handler = jdbcHandlerMap.get(jdbcType);
if (handler == null) {
handler = jdbcHandlerMap.get(null);
}
}
return (TypeHandler<T>)handler;
}
public void register(JdbcType jdbcType, TypeHandler<?> handler) {
JDBC_TYPE_HANDLER_MAP.put(jdbcType, handler);
}
// java type + handler
public <T> void register(Class<T> javaType, TypeHandler<? extends T> typeHandler) {
register((Type)javaType, typeHandler);
}
private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
register(javaType, null, typeHandler);
}
// java type + jdbc type + handler
public <T> void register(Class<T> type, JdbcType jdbcType, TypeHandler<? extends T> handler) {
register((Type)type, jdbcType, handler);
}
private void register(Type javaType, JdbcType jdbcType, TypeHandler<?> handler) {
if (javaType != null) {
Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType);
if (map == null) {
map = new HashMap<JdbcType, TypeHandler<?>>();
TYPE_HANDLER_MAP.put(javaType, map);
}
map.put(jdbcType, handler);
}
ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler);
}
}
注册完以后,typeHandler又是在什么时候使用的呢?上面已经说过了,在ParameterHandler和ResultSetHandler处理参数和结果集的时候,会委托typeHandler进行设值和取值,我们具体分析一下两处的处理。
ParameterHandler处理参数的时候,从boundSql中获取ParameterMapping的集合,然后遍历处理,每次再从parameterMapping拿到TypeHandler,那么这个TypeHandler是怎么来的呢?其实是在解析流程的时候放进去的,但是在第二章我们并没有做这件事,现在我们补上,对原来代码进行改造。
当我们用SqlSource获取boundSql的时候,sql会经历拼接和解析两个过程,而在解析的时候,会将sql中对应的参数信息映射成ParameterMapping对象,也就是在这个时候,处理了TypeHandler,所以我们对关键代码进行改造,即SqlSourceParser(改造SqlSourceParser后,带来的代码其他影响就不一一展示了,主要也就是构造传入的参数多了后引起上层代码变动):
public class SqlSourceParser {
private Configuration configuration;
public SqlSourceParser(Configuration configuration) {
this.configuration = configuration;
}
public SqlSource parse(String sql, Class<?> parameterType) {
ParameterMappingTokenHandler parameterMappingTokenHandler =
new ParameterMappingTokenHandler(configuration, parameterType);
GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
String parseSql = genericTokenParser.parse(sql);
List<ParameterMapping> parameterMappingList = parameterMappingTokenHandler.getParameterMappingList();
return new StaticSqlSource(parseSql, parameterMappingList);
}
public class ParameterMappingTokenHandler implements TokenHandler {
private List<ParameterMapping> parameterMappingList = new ArrayList<>();
private Class<?> parameterType;
private Configuration configuration;
public ParameterMappingTokenHandler(Configuration configuration, Class<?> parameterType) {
this.configuration = configuration;
this.parameterType = parameterType;
}
@Override
public String handleToken(String content) {
String property = content;
// 像这样#{base_duration,jdbcType=INTEGER}里面配置了jdbcType,那么可以获取,并赋值,我们就不处理了
JdbcType jdbcType = null;
Class<?> propertyType;
if (parameterType == null) {
propertyType = Object.class;
} else if (SimpleTypeRegistry.isSimpleType(parameterType)) {
propertyType = parameterType;
} else {
try {
// 简单处理,只支持一级
Field declaredField = parameterType.getClass().getDeclaredField(content);
propertyType = declaredField.getType();
} catch (NoSuchFieldException e) {
e.printStackTrace();
propertyType = Object.class;
}
}
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
TypeHandler typeHandler = typeHandlerRegistry.getTypeHandler(propertyType, jdbcType);
parameterMappingList.add(new ParameterMapping(property, parameterType, jdbcType, typeHandler));
return "?";
}
public List<ParameterMapping> getParameterMappingList() {
return parameterMappingList;
}
}
}
重点关注ParameterMappingTokenHandler.handleToken方法,当然我们的处理考虑的不是很全面,只支持基本数据类型,或者java类型的一级,重在理解就可以了。可以看到,获取指定参数的类型后,在去typeHandlerRegistry获取对应的typeHandler进行赋值。
再看ResultSetHandler处理的时候,上面DefaultResultSetHandler已经演示过了,主要是先获取MappedStatment中的ResultMap,然后遍历ResultMap中的ResultMapping集合,每个ResultMapping都维护了参数映射关系,并且也维护了一个参数类型对应的TypeHandler,拉取部分代码:
if (CollectionUtils.isNotEmpty(resultMap.getResultMappings())) {
// 处理有映射关系的
for (ResultMapping propertyMapping : resultMap.getResultMappings()) {
String property = propertyMapping.getProperty();
//获取TypeHandler
TypeHandler typeHandler = propertyMapping.getTypeHandler();
String column = propertyMapping.getColumn();
Object value = typeHandler.getResult(resultSet, column);
Field declaredField = resultObject.getClass().getDeclaredField(property);
declaredField.setAccessible(true);
declaredField.set(resultObject, value);
}
}
另外想讲的是我们的映射文件只有resultType,所以我们改造了XMLStatmentBuilder,在处理resultType属性的时候,会转换成resultMap:
/**
* @Author yangtianhao
* @Date 2020/2/13 3:09 下午
* @Version 1.0
*/
public class XMLStatementBuilder {
private Element statmentELement;
private String namespace;
private Configuration configuration;
public XMLStatementBuilder(Element statmentElement, String namespace, Configuration configuration) {
this.statmentELement = statmentElement;
this.namespace = namespace;
this.configuration = configuration;
}
public MappedStatement parseStatementNode() throws ClassNotFoundException {
String id = namespace + "." + statmentELement.attributeValue("id");
String parameterType = statmentELement.attributeValue("parameterType");
String resultType = statmentELement.attributeValue("resultType");
Class<?> aClass = Class.forName(resultType);
// resultType转换resultMap,主要生成一个id以及获取Type即可.
ResultMap resultMap = new ResultMap(id + "-Inline", aClass);
String statmentType = statmentELement.attributeValue("statmentType");
XMLScriptBuilder xmlScriptBuilder = new XMLScriptBuilder(statmentELement, configuration, aClass);
SqlSource sqlSource = xmlScriptBuilder.parseScriptNode();
return new MappedStatement(id, parameterType, resultType, sqlSource, statmentType, configuration, resultMap);
}
}
其实此时是没有处理ResultMapping以及TypeHandler的,只有一个id和javaType,我看源码中也是这样就模仿了,而为什么没有ResultMapping也能完成映射处理,我想大概是因为当ResultSetHandler处理这个ResultMap的时候,会先进行自动映射的关系。当获取ResultMap的javaType进行自动映射时,会基于属性名自动映射列到 JavaBean 的属性上,这个时候每个属性都会根据它的数据类型,取对应的TypeHandler进行处理。
private void applyAutomaticMappings(Object resultObject, ResultSet resultSet) throws SQLException {
ResultSetMetaData rsm = resultSet.getMetaData(); // 获得列集
int col = rsm.getColumnCount(); // 获得列的个数
String colName[] = new String[col];
// 取结果集中的表头名称, 放在colName数组中
for (int i = 0; i < col; i++) {
colName[i] = rsm.getColumnName(i + 1);
}
for (String s : colName) {
try {
Field declaredField = resultObject.getClass().getDeclaredField(s);
declaredField.setAccessible(true);
Class<?> type = resultObject.getClass().getDeclaredField(s).getType();
TypeHandler<?> typeHandler = configuration.getTypeHandlerRegistry().getTypeHandler(type);
Object value = typeHandler.getResult(resultSet, s);
declaredField.set(resultObject, value);
} catch (NoSuchFieldException e) {
// e.printStackTrace();
} catch (IllegalAccessException e) {
// e.printStackTrace();
}
}
}
到此全部编写完成,最后测试代码:
对应映射配置文件:
<?xml version="1.0" encoding="UTF-8" ?>
<mapper xmlns="http://www.aiduoduo.site/schema/mybatis-mapper" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.aiduoduo.site/schema/mybatis-mapper site/aiduoduo/mybatis/builder/xml/xsd/mybatis-mapper.xsd"
namespace="site.aiduoduo.user">
<select id="selectAll" resultType="site.aiduoduo.mybatis.pojo.User" parameterType="site.aiduoduo.mybatis.pojo.User"
statmentType="prepared">
select * from user
</select>
<select id="selectByPhone" resultType="site.aiduoduo.mybatis.pojo.User"
parameterType="site.aiduoduo.mybatis.pojo.User" statmentType="prepared">
select * from user
<where>
<if test="phone != null">
and phone = #{phone}
</if>
</where>
</select>
<insert id="insert" parameterType="site.aiduoduo.mybatis.pojo.User" statmentType="prepared"
resultType="site.aiduoduo.mybatis.pojo.User">
insert into user(name,gender,phone,address) values(#{name},#{gender},#{phone},#{address});
</insert>
</mapper>
/**
* @Author yangtianhao
* @Date 2020/1/29 4:28 下午
* @Version 1.0
*/
public class FrameworkTest {
@Test
public void test2() throws Exception {
InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config-schema.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
User user1 = new User();
user1.setPhone("18115610044");
User user = sqlSession.selectOne("site.aiduoduo.user.selectByPhone", user1);
System.out.println(user);
}
结果:
User{name='貂蝉', gender=0, phone='18115610044', address='山西', creation_time=Thu Feb 27 15:50:35 CST 2020}