1. 前言
上一篇讲了mapper的注册与获取,这一节一起看看MapperMethod类
2. 正文
2.1 案例
@Test
public void test(){
SqlSessionFactory sqlSessionFactory = SqlSessionFactoryUtil.getSqlSessionFactory();
SqlSession sqlSession = sqlSessionFactory.openSession();
PersonDao personDao = sqlSession.getMapper(PersonDao.class);
Person p = new Person();
p.setAddress("广东省");
p.setAge(12);
p.setEmail("157538651@qq.com");
p.setName("chen");
p.setPhone("15345634565");
personDao.insert(p);
System.out.println(p.toString());
sqlSession.commit();
sqlSession.close();
}
2.2 MapperMethod#execute
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {@1
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);@2
result = rowCountResult(sqlSession.insert(command.getName(), param));@3
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
@1 判断你要执行的语句类型走不同是case
@2 获取你对象中的参数,上面案例中就是person的对象值
@3 调动sqlSession的insert方法,然后通过rowCountResult封装insert返回的对象,最终返回result,核心的流程就是这一行,进去看下具体的实现
2.3 常见的接口及实现类
在看具体实现之前,先理下几个核心的接口和实现类
2.3.1 SqlSession
首先是SqlSession,里面包含了经常操作数据库用到的一些方法,比如insert/update/selectOne/select/selectList 等等,默认有2个实现类,默认是DefaultSqlSession
2.3.2 Executor 执行器
BaseExecutor是抽象类,下面有3个要重点关注下:
SimpleExecutor 简单的执行器,就是执行完Statement会及时关掉,也是mybatis默认的执行器。
ReuseExecutor 复用的执行器,会把用过的Statement 用map存起来,key=sql,value=Statement
BatchExecutor 批量的执行器,为了提高性能,一次性操作多个Statement ,执行完,也会及时关闭。
这三个执行器,可以在mybatis 中配置,一种方式是在mybatis-config.xml 配置,另外一种是在spring xml 中配置sqlSession的时候配置。
还有一个特殊的,就是CachingExecutor ,它是把MappedStatement 缓存起来了。
注意:他里面持有了一个特殊的对象,private final Executor delegate;
还记得之前获取session的时候,构建的执行器吧,就是下面这个代码
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);@1
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);@2
} else {
executor = new SimpleExecutor(this, transaction);@3
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);@4
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
@1、@2、@3 对象构建好后,只要是开启了缓存(默认开启),则会对executor 加一层包装,从而变成CachingExecutor,到这里就豁然开朗了吧,也就是上面案例中CachingExecutor 中的delegate 其实就是SimpleExecutor(默认是简单的执行器);
2.3.2 StatementHandler
这几个子类其实就是对应jdbc里面的几种Statement
SimpleStatementHandler 简单SQL的处理;
PreparedStatementHandler 预编译SQL处理;
CallableStatementHandler JDBC中CallableStatement,执行存储过程相关的接口
RoutingStatementHandler 是核心,也是负责把上面三个handle串起来的关键类,主要负责上面三个handle的创建和调用,所以他也和CachingExecutor类似,持有一个
private final StatementHandler delegate;
2.3.3 KeyGenerator
KeyGenerator的功能是生成数据库主键和将insert 生成的主键设置到pojo中
重点关注2个实现类:Jdbc3KeyGenerator、SelectKeyGenerator
Jdbc3KeyGenerator表示自增的,也就是数据库自增后如果需要知道值,比如mysql 表主键自增。
这个是将自增结果回填到对象中是从返回的Statement中获取id值。配置方式:在 insert 标签中配置了keyProperty="id" useGeneratedKeys="true"
属性
<insert id="insert" parameterType="Person" keyProperty="id" useGeneratedKeys="true">
INSERT INTO person (name, age, phone, email, address)
VALUES(#{name},#{age},#{phone},#{email},#{address})
</insert>
SelectKeyGenerator 是通过insert后,再查询一次,来获取id的值,Oracle 需要结合sequence 来设置主键,就可以用SelectKeyGenerator方式配置。配置方式:在insert标签内,配置selectKey标签
<insert id="insert" parameterType="Person" keyProperty="id" useGeneratedKeys="true">
<selectKey resultType="int" keyProperty="id" order="BEFORE">
SELECT LAST_INSERT_ID() AS id
</selectKey>
INSERT INTO person (name, age, phone, email, address)
VALUES(#{name},#{age},#{phone},#{email},#{address})
</insert>
BEFORE 表示在插入之前,先查询一次获取到id
2.4 核心流程
2.4.1 继续回到上面2.2
Object param = method.convertArgsToSqlCommandParam(args);@2
result = rowCountResult(sqlSession.insert(command.getName(), param));@3
进入到insert中
@Override
public int insert(String statement, Object parameter) {
return update(statement, parameter);
}
@Override
public int update(String statement, Object parameter) {
try {
dirty = true;
MappedStatement ms = configuration.getMappedStatement(statement);@1
return executor.update(ms, wrapCollection(parameter));@2
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
@1 从config中获取mapper的信息
@2 这里的executor是CachingExecutor,原因上面解释过了。进入到CachingExecutor#update
2.4.2 CachingExecutor#update
@Override
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
flushCacheIfRequired(ms);@1
return delegate.update(ms, parameterObject);@2
}
private void flushCacheIfRequired(MappedStatement ms) {
Cache cache = ms.getCache();
if (cache != null && ms.isFlushCacheRequired()) {
tcm.clear(cache);
}
}
@1 刚刚说过的, MappedStatement 是有缓存的,这里需要进行是否需要clear,isFlushCacheRequired 参数是在创建缓存的时候,就需要设置的。
@2 调用抽象类BaseExecutor的update方法
2.4.3 BaseExecutor#update
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());@1
if (closed) {
throw new ExecutorException("Executor was closed.");
}
clearLocalCache();@2
return doUpdate(ms, parameter);@3
}
@1 记录上下文信息,用于抛异常的时候,获取信息。内部通过ThreadLocal来存储
@2 清除本地缓存
@3 调用子类的doUpdate方法
2.4.4 SimpleExecutor#doUpdate
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);@1
stmt = prepareStatement(handler, ms.getStatementLog());@2
return handler.update(stmt);@3
} finally {
closeStatement(stmt);
}
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);@4
return stmt;
}
@1 构建StatementHandler 对象,这里会根据配置的Statement拦截器,执行所有的拦截后,返回StatementHandler
@2 构建Statement对象,进入到prepareStatement方法,里面有@4,这里是设置参数处理handle
最终的执行方法如下:
DefaultParameterHandler#setParameters
@Override
public void setParameters(PreparedStatement ps) {
//记录上下文
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
//获取需要填充的参数的个数(就是?的个数)
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
//?是输入参数,进入if
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
//判断SQL中是否包含此属性
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
//取值
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
//获取参数类型
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
//给pre 设置值
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
进入PreparedStatementHandle update 方法
2.4.5 PreparedStatementHandle#update
@Override
public int update(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();@1
int rows = ps.getUpdateCount();@2
Object parameterObject = boundSql.getParameterObject();@3
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();@4
keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);@5
return rows;
}
@1 执行jdbc execute
@2 返回受影响的记录条数
@3 获取插入的对象,上述案例中的person对象
@4 获取主键生成器,设置person id字段的值为刚刚插入的主键
2.4.6 继续回到
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
接下来是sqlSession.insert 返回受影响的记录条数,然后通过rowCountResult 返回一个通用的result.
处理逻辑如下:
private Object rowCountResult(int rowCount) {
final Object result;
if (method.returnsVoid()) {
result = null;
} else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
result = rowCount;
} else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
result = (long)rowCount;
} else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
result = rowCount > 0;
} else {
throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());
}
return result;
}
根据方法的要求的返回类型,处理返回值。
3. 总结
insert 时序图
- 发起insert 请求,获取mapper代理对象
- 调用代理对象的invoke
- 执行MapperMethod的execute,根据sql语句类型,执行不同case
- DefaultSqlSession#insert 底层调用的也是DefaultSqlSession#update(可以参考DefaultSqlSession.java 183行),调用执行器的update方法
- 获取默认开启了缓存,所以是调用CachingExecutor#update
- 调用抽象类的BaseExecutor#update
- 调用子类的具体实现SimpleExecutor#doUpdate
- 执行后,逐步返回受影响的记录条数
4. 参考
[1] https://blog.csdn.net/Roger_CoderLife/article/details/88835765
[2] https://blog.csdn.net/yangliuhbhd/article/details/80982254