上篇博客我们介绍了mybatis
的执行流程中查询的操作,同时介绍了两种SQL
语句。但是增删改没有讲,这篇博客我们就介绍一下mybatis
中的增删改的执行流程。首先我们书写以下的测试的代码,具体的代码如下:
public class TestInsertOrUpdateOrDelete {
public static void main(String[] args) throws IOException {
String resource = "mybatis.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
DemoMapper mapper = sqlSession.getMapper(DemoMapper.class);
System.out.println(mapper.insertUser("haha","123"));
System.out.println(mapper.updateUser(new Admin(177,"xixi","321")));
System.out.println(mapper.deleteUser(177));
sqlSession.close();
}
}
public class Admin {
private int id;
private String username;
private String password;
public Admin() {
}
public Admin(int id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
<?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="com.DemoMapper">
<insert id="insertUser" parameterType="string">
insert into admin values (#{username},#{password});
</insert>
<update id="updateUser" parameterType="com.Admin">
update admin set username = #{username},password = #{password} where id = #{id};
</update>
<delete id="deleteUser" parameterType="int">
delete * from admin where id = #{id}
</delete>
</mapper>
public interface DemoMapper {
int insertUser(@Param("username") String username, @Param("password") String password);
int updateUser(Admin admin);
int deleteUser(int id);
}
上面的测试基本上涵盖了所有的情况,以及注解的情况。这儿添加注解是因为我们之前没有使用过注解,所以这儿添加了注解,查看一下注解的情况下参数的包装。让我们随着代码的执行的流程一步步跟下去。具体的代码如下:
public class MapperProxy<T> implements InvocationHandler, Serializable {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
//你是不是调用的Object默认的方法
return method.invoke(this, args);
} else if (method.isDefault()) {
//对于默认方法的处理
if (privateLookupInMethod == null) {
return invokeDefaultMethodJava8(proxy, method, args);
} else {
return invokeDefaultMethodJava9(proxy, method, args);
}
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
}
这儿由于前面查询的时候已经介绍了,所以这儿我就不做过多的介绍,直接看mapperMethod.execute(sqlSession, args);
方法,具体的代码如下:
public class MapperMethod {
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
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;
}
}
这儿这个switch
选择,不用说,肯定是执行insert
中方法,至于这个Type
在什么时候封装,我们在前一篇博客中已经介绍过了。这个时候会执行method.convertArgsToSqlCommandParam(args);
方法,这个方法是直接对传过来的参数进行封装。我们直接跟进去看看,具体的代码如下:
public static class SqlCommand {
public Object convertArgsToSqlCommandParam(Object[] args) {
return paramNameResolver.getNamedParams(args);
}
}
public class ParamNameResolver {
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
if (args == null || paramCount == 0) {
return null;
} else if (!hasParamAnnotation && paramCount == 1) {
return args[names.firstKey()];
} else {
final Map<String, Object> param = new ParamMap<>();
int i = 0;
for (Map.Entry<Integer, String> entry : names.entrySet()) {
//names : arg0,arg1
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)
final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
// ensure not to overwrite parameter named with @Param
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
}
你会发现这儿的代码和前面的代码是一样的,这个时候由于我们给指定的参数的中加了@param
注解,所以这儿获取到的Map的内容如下:
{key:username value:haha}
{key:password value:123}
{key:param1 value:haha}
{key:param2 value:123}
这个执行参数的转换成Map
,然后执行rowCountResult(sqlSession.insert(command.getName(), param));
方法。这个时候会先执行sqlSession.insert(command.getName(), param)
方法,让我们继续跟进去看看对应的代码,具体的代码如下:
public class DefaultSqlSession implements SqlSession {
@Override
public int insert(String statement, Object parameter) {
return update(statement, parameter);
}
}
上面的代码会执行update
的方法。我们继续跟进,具体的代码如下:
public class DefaultSqlSession implements SqlSession {
@Override
public int update(String statement, Object parameter) {
try {
dirty = true;
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
}
上面代码会执行executor.update(ms, wrapCollection(parameter));
方法,执行这个方法之前,会执行 wrapCollection(parameter)
方法,具体的代码如下:
private Object wrapCollection(final Object object) {
if (object instanceof Collection) {
StrictMap<Object> map = new StrictMap<>();
map.put("collection", object);
if (object instanceof List) {
map.put("list", object);
}
return map;
} else if (object != null && object.getClass().isArray()) {
StrictMap<Object> map = new StrictMap<>();
map.put("array", object);
return map;
}
return object;
}
又是熟悉的代码,由于我们传入进来是map
,所以这儿都不会执行,所以会执行下面的代码
public class CachingExecutor implements Executor {
@Override
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
flushCacheIfRequired(ms);
return delegate.update(ms, parameterObject);
}
}
由于我们是执行新增的操作,所以这儿要刷新缓存,缓存的机制我们后面会讲,这儿直接跳过了。我们直接看delegate.update(ms, parameterObject);
方法,具体的代码如下:
public abstract class BaseExecutor implements Executor {
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
clearLocalCache();
return doUpdate(ms, parameter);
}
}
走来会清楚本地的缓存,然后执行doUpdate(ms, parameter);
方法,我们继续跟进,具体的代码如下:
public class SimpleExecutor extends BaseExecutor {
@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);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
}
上面的代码走来直接先获取对应的配置类,然后执行configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
方法,我们跟进去看看,具体的代码如下:
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
//代理StatementHandler 用你的插件去代理
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
上面代码会执行new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
代码,我们继续跟进,具体代码如下:
public class RoutingStatementHandler implements StatementHandler {
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
}
到这儿所有的东西已经准备好了,于是会执行new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
方法,我们继续跟进对应的代码具体如下:
public class PreparedStatementHandler extends BaseStatementHandler {
public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
}
}
可以发现调用的是父类BaseStatementHandler
的方法,具体的代码如下:
public abstract class BaseStatementHandler implements StatementHandler {
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
this.configuration = mappedStatement.getConfiguration();
this.executor = executor;
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds;
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.objectFactory = configuration.getObjectFactory();
if (boundSql == null) { // issue #435, get the key before calculating the statement
//生成主键
generateKeys(parameterObject);
boundSql = mappedStatement.getBoundSql(parameterObject);
}
this.boundSql = boundSql;
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}
}
这儿进行了一系列的参数的封装,最主要的方法还是if中判断,在这个判断中会自动生成主键,然后我们继续查看mappedStatement.getBoundSql(parameterObject);
方法,这儿会将参数进行绑定,具体的代码如下:
public final class MappedStatement {
public BoundSql getBoundSql(Object parameterObject) {
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings == null || parameterMappings.isEmpty()) {
boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
}
// check for nested result maps in parameter mappings (issue #30)
for (ParameterMapping pm : boundSql.getParameterMappings()) {
String rmId = pm.getResultMapId();
if (rmId != null) {
ResultMap rm = configuration.getResultMap(rmId);
if (rm != null) {
hasNestedResultMaps |= rm.hasNestedResultMaps();
}
}
}
return boundSql;
}
}
可以看到上面是进行SQL
语句转换。会将一些占位符进行转换。然后返回的SQL
就是正常的,同时参数的类型和值也放在这个对象中,最终返回,然后会执行prepareStatement(handler, ms.getStatementLog());
方法,具体的代码如下:
public class SimpleExecutor extends BaseExecutor {
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
//获取数据库连接
Connection connection = getConnection(statementLog);
//设置执行的超时的时间
stmt = handler.prepare(connection, transaction.getTimeout());
//进行参数的绑定
handler.parameterize(stmt);
return stmt;
}
}
上面代码handler.parameterize(stmt);
是进行的参数绑定,我们继续跟进去看看:
public class PreparedStatementHandler extends BaseStatementHandler {
@Override
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}
@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 (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
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 {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
}
上面进行一些SQL
的参数的绑定,和前面的查询是一样的。这个时候对应的SQL
语句已经生成,然后就需要执行对应的JDBC
的操作,就可以插入一条数据了。然后执行update
方法,直接跟进对应的代码,具体的代码如下:
public class RoutingStatementHandler implements StatementHandler {
@Override
public int update(Statement statement) throws SQLException {
return delegate.update(statement);
}
@Override
public int update(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
//执行底层的JDBC方法
ps.execute();
//获取受影响的函数
int rows = ps.getUpdateCount();
Object parameterObject = boundSql.getParameterObject();
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
return rows;
}
}
执行完,返回受影响的行数,然后关闭对应Statement
。这个时候整个insert
就执行完了。
然后我们再来看下update
的方法。这儿我只会讲不同的地方。前面都是一样,到了参数的转换的地方,由于是一个参数,所以会直接将这个对象进行返回。这个时候内容如下:
id = 177
username = "xixi"
password = "321"
你会发现最终调用如下的方法
@Override
public int update(String statement, Object parameter) {
try {
dirty = true;
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
和上面的新增最终的调用的方法是一样的。所以后面调用的流程是一样的,然后删除调用的方法也是这样。至此整个增删改就讲完了。后面会继续讲mybatis
其他的东西。