statement 概述
前面接触的sqlsession 是一次会话,执行的门面,Executor 是执行器,我们也可以理解成对jdbc操作的一次封装,很简单的一句话就是:executor对statement进行封装,sqlsession 对executor进行封装,statement直接操作db了,
最原始的使用jdbc操作数据库
获取链接
DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/imooc", "root", "root");
//获取statement或者preparestatement 我们平时一般都是 preparestatement
conn.createStatement();
conn.prepareStatement(sql);
ResultSet rs = stmt.executeQuery("SELECT user_name, age FROM imooc_goddess");
//sql
String sql = "INSERT INTO imooc_goddess(user_name, sex, age, birthday, email, mobile,"+
"create_user, create_date, update_user, update_date, isdel)"
+"values("+"?,?,?,?,?,?,?,CURRENT_DATE(),?,CURRENT_DATE(),?)";
//预编译
PreparedStatement ptmt = conn.prepareStatement(sql); //预编译SQL,减少sql执行
ptmt.setString(8, g.getUpdate_user());
ptmt.setInt(9, g.getIsDel());
//执行
ptmt.execute();
上面的代码就是简单的举个例子,作为多年开发的你一定非常熟悉了,即便不熟悉看到这写也非常熟悉了
我们看到这些我们就知道statementhandler是处理statement,具体处理什么? 我感觉至少得有获取statement,参数如何处理,比如我们在mapper接口中写的对象如何进行映射,因为底层是setInt,setString这样的方法,结果集也是getInt,getString方法如何实现映射的这些今天在这一篇文章里面都率清楚?
原来一次没用解决的问题今天也顺便解决了:
1.mybatis接口不能重载,为什么?
2.接口参数不能用set集合?我记得当时提醒的不能转list?
今天带着我的两个问题去分析源码
结构
接口-》抽象类-〉一些默认的实现类非常熟悉的结构, 同时我们还看到routingstatementhandler 这个是路由器,方便创建那个一个statementhandler,里面基本没啥代码就一个switch
接口源码:
//sql 声明
// 声明Statement ,填充参数
// sql 执行
// 改,查,批处理
public interface StatementHandler {
//创建statement
Statement prepare(Connection connection, Integer transactionTimeout)
throws SQLException;
//预处理参数
void parameterize(Statement statement)
throws SQLException;
//批处理
void batch(Statement statement)
throws SQLException;
// 修改
int update(Statement statement)
throws SQLException;
//查询
<E> List<E> query(Statement statement, ResultHandler resultHandler)
throws SQLException;
//游标
<E> Cursor<E> queryCursor(Statement statement)
throws SQLException;
//动态sql
BoundSql getBoundSql();
ParameterHandler getParameterHandler();
}
执行顺序
源码分析:
org.apache.ibatis.executor.SimpleExecutor#doQuery
@Override
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);
//sql 预编译和参数设置
stmt = prepareStatement(handler, ms.getStatementLog());
//执行sql和结果集映射
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
statement创建
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
//statement 创建
stmt = handler.prepare(connection, transaction.getTimeout());
//设置参数
handler.parameterize(stmt);
return stmt;
}
参数转换过程
这里先补充一些我们接口中参数mybatis如何处理参数名称的
这样的错误是不是很面熟
mybatis 配置文件详解 中的参数处理
javabean =》 jdbc 参数
我是完全记不住这些规则我们就先看看这行规则的源码是如何实现的:
org.apache.ibatis.binding.MapperMethod#execute
} else {
//解析参数,因为SqlSession::selectOne方法参数只能传入一个,但是我们Mapper中可能传入多个参数,
//有可能是通过@Param注解指定参数名,所以这里需要将Mapper接口方法中的多个参数转化为一个ParamMap,
//也就是说如果是传入的单个封装实体,那么直接返回出来;如果传入的是多个参数,实际上都转换成了Map
Object param = method.convertArgsToSqlCommandParam(args);
// //可以看到动态代理最后还是使用SqlSession操作数据库的
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
这个方法是如何找到的现在先不管,等着后面介绍一篇mybatis启动时序图就把所有的知识点串起来了
Object param = method.convertArgsToSqlCommandParam(args);
public Object convertArgsToSqlCommandParam(Object[] args) {
return paramNameResolver.getNamedParams(args);
}
参数名称处理的核心类是 paramNameResolver
paramNameResolver 类解析
对names字段的解释
- Method(@Param(“M”) int a, @Param(“N”) int b)转化为map为{{0, “M”}, {1, “N”}}
- Method(int a, int b)转化为map为{{0, “0”}, {1, “1”}}
- aMethod(int a, RowBounds rb, int b)转化为map为{{0, “0”}, {2, “1”}}
public class ParamNameResolver {
public static final String GENERIC_NAME_PREFIX = "param";
private final boolean useActualParamName;
//存放参数的位置和对应的参数名
private final SortedMap<Integer, String> names;
是否使用param注解
private boolean hasParamAnnotation;
构造方法
public ParamNameResolver(Configuration config, Method method) {
this.useActualParamName = config.isUseActualParamName();
final Class<?>[] paramTypes = method.getParameterTypes();
final Annotation[][] paramAnnotations = method.getParameterAnnotations();
final SortedMap<Integer, String> map = new TreeMap<>();
int paramCount = paramAnnotations.length;
// get names from @Param annotations
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
if (isSpecialParameter(paramTypes[paramIndex])) {
// skip special parameters
continue;
}
String name = null;
for (Annotation annotation : paramAnnotations[paramIndex]) { //有@Param注释的
if (annotation instanceof Param) {
hasParamAnnotation = true;
name = ((Param) annotation).value();
break;
}
}
if (name == null) { //没有注释的
// @Param was not specified.
if (useActualParamName) {
name = getActualParamName(method, paramIndex);
}
if (name == null) {
// use the parameter index as the name ("0", "1", ...)
// gcode issue #71
name = String.valueOf(map.size());
}
}
map.put(paramIndex, name);
}
names = Collections.unmodifiableSortedMap(map);
}
构造方法的会经历如下的步骤
- 通过反射得到方法的参数类型和方法的参数注解注解,method.getParameterAnnotations()方法返回的是注解的二维数组,每一个方法的参数包含一个注解数组。
- 遍历所有的参数
- 首先判断这个参数的类型是否是特殊类型,RowBounds和ResultHandler,是的话跳过,咱不处理
- 判断这个参数是否是用来Param注解,如果使用的话name就是Param注解的值,并把name放到map中,键为参数在方法中的位置,value为Param的值
- 如果没有使用Param注解,判断是否开启了UseActualParamName,如果开启了,则使用java8的反射得到方法的名字,此处容易造成异常,
- 如果以上条件都不满足的话,则这个参数的名字为参数的下标
public Integer getList(@Param("list") Set<Integer> list, RowBounds rb , Integer a);
方法
此类有意非常重要的方法getNamedParams这个方法主要是将参数转换成map,为什么这么转换由于我们接口中参数会有多个,但是sqlSession中的接口参数只有一个,因此全部转换成map
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
if (args == null || paramCount == 0) { //没有参数的情况
return null;
} else if (!hasParamAnnotation && paramCount == 1) { //有一个参数并且没有注解
Object value = args[names.firstKey()];
return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
} else {
final Map<String, Object> param = new ParamMap<>();
int i = 0;
for (Map.Entry<Integer, String> entry : names.entrySet()) {
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)
final String genericParamName = GENERIC_NAME_PREFIX + (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 put一次,param put一次,这就是我们平时如果直接使用param1,param2 也可以的原因
public static Object wrapToMapIfCollection(Object object, String actualParamName) {
if (object instanceof Collection) {
ParamMap<Object> map = new ParamMap<>();
map.put("collection", object);
if (object instanceof List) {
map.put("list", object);
}
Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));
return map;
} else if (object != null && object.getClass().isArray()) {
ParamMap<Object> map = new ParamMap<>();
map.put("array", object);
Optional.ofNullable(actualParamName).ifPresent(name -> map.put(name, object));
return map;
}
return object;
}
集合参数转换逻辑:
collection, 如果是List key则为List, 数组则为 array
参数映射
上面我们了解到参数已经转化成包装到map中了,但是如何和java对象进行转换的
org.apache.ibatis.executor.statement.PreparedStatementHandler#parameterize
@Override
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}
这个方法借助于TypeHandler 将参数进行映射 最终是使用的 ps.setInt(i, parameter); 这种方法进行对statement进行参数设置
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType)
throws SQLException {
ps.setInt(i, parameter);
}
org.apache.ibatis.scripting.defaults.DefaultParameterHandler#setParameters
@Override
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
//获取sql中的变量 #{}
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);
}
}
}
}
}
结果集处理
ResultSetHandler & ResultContext & ResultHandler
ResultSetHandler : 结果集处理器
ResultContext:结果处理上下文,我目前理解就是结果集处理器和结果处理器中间对一个中专
ResultHandler:结果处理器,其实这个非常简单:就是从resultcontext中获取一个结果放到map或者list里面,
ps:这个地方真心感觉这种框架是指责单一啊,自己写的代码就是一堆,如果是我估计这3个能合并到一起了
eg:
public void SqlSessTest() throws Exception{
init();
SqlSession sqlSession = build.openSession();
ResultHandler handler = new ResultHandler() {
@Override
public void handleResult(ResultContext resultContext) {
int resultCount = resultContext.getResultCount();
if(resultCount==2) {
//resultContext.stop();
}
}
};
sqlSession.select("com.wfg.ActivityMapper.getList1",handler);
}
public interface ResultSetHandler {
<E> List<E> handleResultSets(Statement stmt) throws SQLException;
<E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;
void handleOutputParameters(CallableStatement cs) throws SQLException;
}
public interface ResultContext<T> {
T getResultObject();
int getResultCount();
boolean isStopped();
//停止转化
void stop();
}
public interface ResultHandler<T> {
//结果转化
void handleResult(ResultContext<? extends T> resultContext);
}
手动映射
eg:
<resultMap id="getActivityByMan" type="com.wfg.entity.ActivityEntity" >
<id column="id" property="id"/>
<result column="name" property="name"/>
</resultMap>
<select id="getActivityByMan" resultMap="getActivityByMan" >
select id , name from activity where id = #{id};
</select>
源码解析:
//填充属性(手动映射)
private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
boolean foundValues = false;
final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
for (ResultMapping propertyMapping : propertyMappings) {
String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
if (propertyMapping.getNestedResultMapId() != null) {
// the user added a column attribute to a nested result map, ignore it
column = null;
}
if (propertyMapping.isCompositeResult()
|| (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))
|| propertyMapping.getResultSet() != null) {
Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
// issue #541 make property optional
final String property = propertyMapping.getProperty();
if (property == null) {
continue;
} else if (value == DEFERRED) {
foundValues = true;
continue;
}
if (value != null) {
foundValues = true;
}
if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {
// gcode issue #377, call setter on nulls (value is not 'found')
metaObject.setValue(property, value);
}
}
}
return foundValues;
}
自动映射
eg:
<select id="getActivityByAuto" resultType="com.wfg.entity.ActivityEntity" >
select id , name from activity where id = #{id};
</select>
源码:
private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
boolean foundValues = false;
final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
for (ResultMapping propertyMapping : propertyMappings) {
String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
if (propertyMapping.getNestedResultMapId() != null) {
// the user added a column attribute to a nested result map, ignore it
column = null;
}
if (propertyMapping.isCompositeResult()
|| (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))
|| propertyMapping.getResultSet() != null) {
Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
// issue #541 make property optional
final String property = propertyMapping.getProperty();
if (property == null) {
continue;
} else if (value == DEFERRED) {
foundValues = true;
continue;
}
if (value != null) {
foundValues = true;
}
if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {
// gcode issue #377, call setter on nulls (value is not 'found')
metaObject.setValue(property, value);
}
}
}
return foundValues;
}
嵌套查询
嵌套子查询入口是在手动映射的获取属性值中进行触发的
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#getPropertyMappingValue
if (propertyMapping.getNestedQueryId() != null) { //嵌套
return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
} else if (propertyMapping.getResultSet() != null) {
addPendingChildRelation(rs, metaResultObject, propertyMapping); // TODO is that OK?
return DEFERRED;
} else {
final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
return typeHandler.getResult(rs, column);
}
循环依赖
结果集转换过程(重点)
这是最重要的一张处理结果集的图片,还是自己多debug一步一步的走,多写几个例子自己多走几遍;
从此处多走几遍debug
@Override
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);
//sql 预编译和参数设置
stmt = prepareStatement(handler, ms.getStatementLog());
//执行sql和结果集映射
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}