- 3 关键对象总结与回顾
- 4 SQL语句的执行流程
- 5 执行期主要类总结
3 关键对象总结与回顾
3.1 SqlSource
SqlSource是XML文件或者注解方法中映射语句的实现时表示,通过SqlSourceBuilder.parse()方法创建,SqlSourceBuilder中符号解析器将mybatis中的查询参数#{}转换为?,并记录了参数的顺序。它只有一个方法getBoundSql用于获取映射语句对象的各个组成部分,它的定义如下:
/**
* Represents the content of a mapped statement read from an XML file or an annotation.
* It creates the SQL that will be passed to the database out of the input parameter received from the user.
* 代表从XML文件或者注解读取的映射语句的内容,它创建的SQL会被传递给数据库。
* @author Clinton Begin
*/
public interface SqlSource {
BoundSql getBoundSql(Object parameterObject);
}
根据SQL语句的类型不同,mybatis提供了多种SqlSource的具体实现,如下所示:
- StaticSqlSource:最终静态SQL语句的封装,其他类型的SqlSource最终都委托给StaticSqlSource。
- RawSqlSource:原始静态SQL语句的封装,在加载时就已经确定了SQL语句,没有、等动态标签和${} SQL拼接,比动态SQL语句要快,因为不需要运行时解析SQL节点。
- DynamicSqlSource:动态SQL语句的封装,在运行时需要根据参数处理、等标签或者${} SQL拼接之后才能生成最后要执行的静态SQL语句。
- ProviderSqlSource:当SQL语句通过指定的类和方法获取时(使用@XXXProvider注解),需要使用本类,它会通过反射调用相应的方法得到SQL语句。
3.2 SqlNode
SqlNode接口主要用来处理CRUD节点下的各类动态标签比如if、choose、for-each,对每个动态标签,mybatis都提供了对应的SqlNode实现,这些动态标签可以相互嵌套且实现上采用单向链表进行应用,这样后面如果需要增加其他动态标签,就只需要新增对应的SqlNode实现就能支持。mybatis使用OGNL表达式语言。对sqlNode的调用在SQL执行期间的DynamicSqlSource.getBoundSql()方法中,SQL执行过程我们后面会讲解。
当前版本的SqlNode有下列实现:
其中MixedSqlNode代表了所有具体SqlNode的集合,其他分别代表了一种类型的SqlNode。下面对每个SqlNode的实现做简单的分析:
ChooseSqlNode
public class ChooseSqlNode implements SqlNode {
private final SqlNode defaultSqlNode;
private final List<SqlNode> ifSqlNodes;
public ChooseSqlNode(List<SqlNode> ifSqlNodes, SqlNode defaultSqlNode) {
this.ifSqlNodes = ifSqlNodes;
this.defaultSqlNode = defaultSqlNode;
}
@Override
public boolean apply(DynamicContext context) {
// 遍历所有when分支节点,只要遇到第一个为true就返回
for (SqlNode sqlNode : ifSqlNodes) {
if (sqlNode.apply(context)) {
return true;
}
}
// 全部when都为false时,走otherwise分支
if (defaultSqlNode != null) {
defaultSqlNode.apply(context);
return true;
}
return false;
}
}
ForEachSqlNode
public class ForEachSqlNode implements SqlNode {
public static final String ITEM_PREFIX = "__frch_";
private final ExpressionEvaluator evaluator;
private final String collectionExpression;
private final SqlNode contents;
private final String open;
private final String close;
private final String separator;
private final String item;
private final String index;
private final Configuration configuration;
public ForEachSqlNode(Configuration configuration, SqlNode contents, String collectionExpression, String index, String item, String open, String close, String separator) {
this.evaluator = new ExpressionEvaluator();
this.collectionExpression = collectionExpression;
this.contents = contents;
this.open = open;
this.close = close;
this.separator = separator;
this.index = index;
this.item = item;
this.configuration = configuration;
}
@Override
public boolean apply(DynamicContext context) {
Map<String, Object> bindings = context.getBindings();
// 将Map/Array/List统一包装为迭代器接口
final Iterable<?> iterable = evaluator.evaluateIterable(collectionExpression, bindings);
if (!iterable.iterator().hasNext()) {
return true;
}
boolean first = true;
applyOpen(context);
int i = 0;
// 遍历集合
for (Object o : iterable) {
DynamicContext oldContext = context;
if (first || separator == null) {
context = new PrefixedContext(context, "");
} else {
context = new PrefixedContext(context, separator);
}
int uniqueNumber = context.getUniqueNumber();
// Issue #709
if (o instanceof Map.Entry) { //Map条目处理
@SuppressWarnings("unchecked")
Map.Entry<Object, Object> mapEntry = (Map.Entry<Object, Object>) o;
applyIndex(context, mapEntry.getKey(), uniqueNumber);
applyItem(context, mapEntry.getValue(), uniqueNumber);
} else { // List条目处理
applyIndex(context, i, uniqueNumber);
applyItem(context, o, uniqueNumber);
}
// 子节点SqlNode处理,很重要的一个逻辑就是将#{item.XXX}转换为#{__frch_item_N.XXX},这样在JDBC设置参数的时候就能够找到对应的参数值了
contents.apply(new FilteredDynamicContext(configuration, context, index, item, uniqueNumber));
if (first) {
first = !((PrefixedContext) context).isPrefixApplied();
}
context = oldContext;
i++;
}
applyClose(context);
context.getBindings().remove(item);
context.getBindings().remove(index);
return true;
}
private void applyIndex(DynamicContext context, Object o, int i) {
if (index != null) {
context.bind(index, o);
context.bind(itemizeItem(index, i), o);
}
}
private void applyItem(DynamicContext context, Object o, int i) {
if (item != null) {
context.bind(item, o);
context.bind(itemizeItem(item, i), o);
}
}
private void applyOpen(DynamicContext context) {
if (open != null) {
context.appendSql(open);
}
}
private void applyClose(DynamicContext context) {
if (close != null) {
context.appendSql(close);
}
}
private static String itemizeItem(String item, int i) {
return new StringBuilder(ITEM_PREFIX).append(item).append("_").append(i).toString();
}
private static class FilteredDynamicContext extends DynamicContext {
private final DynamicContext delegate;
private final int index;
private final String itemIndex;
private final String item;
public FilteredDynamicContext(Configuration configuration,DynamicContext delegate, String itemIndex, String item, int i) {
super(configuration, null);
this.delegate = delegate;
this.index = i;
this.itemIndex = itemIndex;
this.item = item;
}
@Override
public Map<String, Object> getBindings() {
return delegate.getBindings();
}
@Override
public void bind(String name, Object value) {
delegate.bind(name, value);
}
@Override
public String getSql() {
return delegate.getSql();
}
@Override
public void appendSql(String sql) {
GenericTokenParser parser = new GenericTokenParser("#{", "}", new TokenHandler() {
@Override
// 将#{item.XXX}转换为#{__frch_item_N.XXX}
public String handleToken(String content) {
String newContent = content.replaceFirst("^\\s*" + item + "(?![^.,:\\s])", itemizeItem(item, index));
if (itemIndex != null && newContent.equals(content)) {
newContent = content.replaceFirst("^\\s*" + itemIndex + "(?![^.,:\\s])", itemizeItem(itemIndex, index));
}
return new StringBuilder("#{").append(newContent).append("}").toString();
}
});
delegate.appendSql(parser.parse(sql));
}
@Override
public int getUniqueNumber() {
return delegate.getUniqueNumber();
}
}
private class PrefixedContext extends DynamicContext {
private final DynamicContext delegate;
private final String prefix;
private boolean prefixApplied;
public PrefixedContext(DynamicContext delegate, String prefix) {
super(configuration, null);
this.delegate = delegate;
this.prefix = prefix;
this.prefixApplied = false;
}
public boolean isPrefixApplied() {
return prefixApplied;
}
@Override
public Map<String, Object> getBindings() {
return delegate.getBindings();
}
@Override
public void bind(String name, Object value) {
delegate.bind(name, value);
}
@Override
public void appendSql(String sql) {
if (!prefixApplied && sql != null && sql.trim().length() > 0) {
delegate.appendSql(prefix);
prefixApplied = true;
}
delegate.appendSql(sql);
}
@Override
public String getSql() {
return delegate.getSql();
}
@Override
public int getUniqueNumber() {
return delegate.getUniqueNumber();
}
}
}
IfSqlNode
public class IfSqlNode implements SqlNode {
private final ExpressionEvaluator evaluator; //表达式执行器
private final String test; //条件表达式
private final SqlNode contents;
public IfSqlNode(SqlNode contents, String test) {
this.test = test;
this.contents = contents;
this.evaluator = new ExpressionEvaluator();
}
@Override
public boolean apply(DynamicContext context) {
if (evaluator.evaluateBoolean(test, context.getBindings())) {
contents.apply(context);
return true;
}
return false;
}
}
ExpressionEvaluator的定义如下:
public class ExpressionEvaluator {
// 布尔表达式解析,对于返回值为数字的if表达式,0为假,非0为真
public boolean evaluateBoolean(String expression, Object parameterObject) {
Object value = OgnlCache.getValue(expression, parameterObject);
if (value instanceof Boolean) {
return (Boolean) value;
}
if (value instanceof Number) {
return new BigDecimal(String.valueOf(value)).compareTo(BigDecimal.ZERO) != 0;
}
return value != null;
}
// 循环表达式解析,主要用于foreach标签
public Iterable<?> evaluateIterable(String expression, Object parameterObject) {
Object value = OgnlCache.getValue(expression, parameterObject);
if (value == null) {
throw new BuilderException("The expression '" + expression + "' evaluated to a null value.");
}
if (value instanceof Iterable) {
return (Iterable<?>) value;
}
if (value.getClass().isArray()) {
// the array may be primitive, so Arrays.asList() may throw
// a ClassCastException (issue 209). Do the work manually
// Curse primitives! :) (JGB)
int size = Array.getLength(value);
List<Object> answer = new ArrayList<Object>();
for (int i = 0; i < size; i++) {
Object o = Array.get(value, i);
answer.add(o);
}
return answer;
}
if (value instanceof Map) {
return ((Map) value).entrySet();
}
throw new BuilderException("Error evaluating expression '" + expression + "'. Return value (" + value + ") was not iterable.");
}
}
StaticTextSqlNode
静态文本节点不做任何处理,直接将本文本节点的内容追加到已经解析了的SQL文本的后面。
public class StaticTextSqlNode implements SqlNode {
private final String text;
public StaticTextSqlNode(String text) {
this.text = text;
}
@Override
public boolean apply(DynamicContext context) {
context.appendSql(text);
return true;
}
}
TextSqlNode
TextSqlNode主要是用来将${}转换为实际的参数值,并返回拼接后的SQL语句,为了防止SQL注入,可以通过标签来创建OGNL上下文变量。
public class TextSqlNode implements SqlNode {
private final String text;
private final Pattern injectionFilter;
public TextSqlNode(String text) {
this(text, null);
}
public TextSqlNode(String text, Pattern injectionFilter) {
this.text = text;
this.injectionFilter = injectionFilter;
}
public boolean isDynamic() {
DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
GenericTokenParser parser = createParser(checker);
parser.parse(text);
return checker.isDynamic();
}
@Override
public boolean apply(DynamicContext context) {
GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));
context.appendSql(parser.parse(text));
return true;
}
private GenericTokenParser createParser(TokenHandler handler) {
return new GenericTokenParser("${", "}", handler);
}
private static class BindingTokenParser implements TokenHandler {
private DynamicContext context;
private Pattern injectionFilter;
public BindingTokenParser(DynamicContext context, Pattern injectionFilter) {
this.context = context;
this.injectionFilter = injectionFilter;
}
// 将${}中的值替换为查询参数中实际的值并返回,在StaticTextSqlNode中,#{}返回的是?
@Override
public String handleToken(String content) {
Object parameter = context.getBindings().get("_parameter");
if (parameter == null) {
context.getBindings().put("value", null);
} else if (SimpleTypeRegistry.isSimpleType(parameter.getClass())) {
context.getBindings().put("value", parameter);
}
Object value = OgnlCache.getValue(content, context.getBindings());
String srtValue = (value == null ? "" : String.valueOf(value)); // issue #274 return "" instead of "null"
checkInjection(srtValue);
return srtValue;
}
private void checkInjection(String value) {
if (injectionFilter != null && !injectionFilter.matcher(value).matches()) {
throw new ScriptingException("Invalid input. Please conform to regex" + injectionFilter.pattern());
}
}
}
private static class DynamicCheckerTokenParser implements TokenHandler {
private boolean isDynamic;
public DynamicCheckerTokenParser() {
// Prevent Synthetic Access
}
public boolean isDynamic() {
return isDynamic;
}
@Override
public String handleToken(String content) {
this.isDynamic = true;
return null;
}
}
}
VarDeclSqlNode
public class VarDeclSqlNode implements SqlNode {
private final String name;
private final String expression;
public VarDeclSqlNode(String var, String exp) {
name = var;
expression = exp;
}
@Override
public boolean apply(DynamicContext context) {
final Object value = OgnlCache.getValue(expression, context.getBindings());
// 直接将ognl表达式加到当前映射语句的上下文中,这样就可以直接获取到了
context.bind(name, value);
return true;
}
}
DynamicContext.bind方法的实现如下:
private final ContextMap bindings;
public void bind(String name, Object value) {
bindings.put(name, value);
}
TrimSqlNode
public class TrimSqlNode implements SqlNode {
private final SqlNode contents;
private final String prefix;
private final String suffix;
private final List<String> prefixesToOverride; // 要trim多个文本的话,|分隔即可
private final List<String> suffixesToOverride; // 要trim多个文本的话,|分隔即可
private final Configuration configuration;
public TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, String prefixesToOverride, String suffix, String suffixesToOverride) {
this(configuration, contents, prefix, parseOverrides(prefixesToOverride), suffix, parseOverrides(suffixesToOverride));
}
protected TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, List<String> prefixesToOverride, String suffix, List<String> suffixesToOverride) {
this.contents = contents;
this.prefix = prefix;
this.prefixesToOverride = prefixesToOverride;
this.suffix = suffix;
this.suffixesToOverride = suffixesToOverride;
this.configuration = configuration;
}
@Override
public boolean apply(DynamicContext context) {
FilteredDynamicContext filteredDynamicContext = new FilteredDynamicContext(context);
// trim节点只有在至少有一个子节点不为空的时候才有意义
boolean result = contents.apply(filteredDynamicContext);
// 所有子节点处理完成之后,filteredDynamicContext.delegate里面就包含解析后的静态SQL文本了,此时就可以处理前后的trim了
filteredDynamicContext.applyAll();
return result;
}
private static List<String> parseOverrides(String overrides) {
if (overrides != null) {
final StringTokenizer parser = new StringTokenizer(overrides, "|", false);
final List<String> list = new ArrayList<String>(parser.countTokens());
while (parser.hasMoreTokens()) {
list.add(parser.nextToken().toUpperCase(Locale.ENGLISH));
}
return list;
}
return Collections.emptyList();
}
private class FilteredDynamicContext extends DynamicContext {
private DynamicContext delegate;
private boolean prefixApplied;
private boolean suffixApplied;
private StringBuilder sqlBuffer;
public FilteredDynamicContext(DynamicContext delegate) {
super(configuration, null);
this.delegate = delegate;
this.prefixApplied = false;
this.suffixApplied = false;
this.sqlBuffer = new StringBuilder();
}
public void applyAll() {
sqlBuffer = new StringBuilder(sqlBuffer.toString().trim());
String trimmedUppercaseSql = sqlBuffer.toString().toUpperCase(Locale.ENGLISH);
if (trimmedUppercaseSql.length() > 0) {
applyPrefix(sqlBuffer, trimmedUppercaseSql);
applySuffix(sqlBuffer, trimmedUppercaseSql);
}
delegate.appendSql(sqlBuffer.toString());
}
@Override
public Map<String, Object> getBindings() {
return delegate.getBindings();
}
@Override
public void bind(String name, Object value) {
delegate.bind(name, value);
}
@Override
public int getUniqueNumber() {
return delegate.getUniqueNumber();
}
@Override
public void appendSql(String sql) {
sqlBuffer.append(sql);
}
@Override
public String getSql() {
return delegate.getSql();
}
// 处理前缀
private void applyPrefix(StringBuilder sql, String trimmedUppercaseSql) {
if (!prefixApplied) {
prefixApplied = true;
if (prefixesToOverride != null) {
for (String toRemove : prefixesToOverride) {
if (trimmedUppercaseSql.startsWith(toRemove)) {
sql.delete(0, toRemove.trim().length());
break;
}
}
}
if (prefix != null) {
sql.insert(0, " ");
sql.insert(0, prefix);
}
}
}
// 处理后缀
private void applySuffix(StringBuilder sql, String trimmedUppercaseSql) {
if (!suffixApplied) {
suffixApplied = true;
if (suffixesToOverride != null) {
for (String toRemove : suffixesToOverride) {
if (trimmedUppercaseSql.endsWith(toRemove) || trimmedUppercaseSql.endsWith(toRemove.trim())) {
int start = sql.length() - toRemove.trim().length();
int end = sql.length();
sql.delete(start, end);
break;
}
}
}
if (suffix != null) {
sql.append(" ");
sql.append(suffix);
}
}
}
}
}
SetSqlNode
SetSqlNode直接委托给TrimSqlNode处理。参见TrimSqlNode。
WhereSqlNode
WhereSqlNode直接委托给TrimSqlNode处理。参见TrimSqlNode。
3.3 BaseBuilder
从整个设计角度来说,BaseBuilder的目的是为了统一解析的使用,但在实现上却出入较大。首先,BaseBuilder是所有解析类的MapperBuilderAssistant、XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder等的父类。如下所示:
BaseBuilder中提供类型处理器、JDBC类型、结果集类型、别名等的解析,因为在mybatis配置文件、mapper文件解析、SQL映射语句解析、基于注解的mapper文件解析过程中,都会频繁的遇到类型处理相关的解析。但是BaseBuilder也没有定义需要子类实现的负责解析的抽象接口,虽然XMLMapperBuilder、XMLConfigBuilder的解析入口是parse方法,XMLStatementBuilder的入口是parseStatementNode,不仅如此,MapperBuilderAssistant继承了BaseBuilder,而不是MapperAnnotationBuilder,实际上MapperAnnotationBuilder才是解析Mapper接口的主控类。
所以从实现上来说,BaseBuilder如果要作为具体Builder类的抽象父类,那就应该定义一个需要子类实现的parse接口,要么就用组合代替继承。
3.4 AdditionalParameter
额外参数主要是维护一些在加载时无法确定的参数,比如标签中的参数在加载时就无法尽最大努力确定,必须通过运行时执行org.apache.ibatis.scripting.xmltags.DynamicSqlSource.getBoundSql()中的SqlNode.apply()才能确定真正要执行的SQL语句,以及额外参数。比如,对于下列的foreach语句,它的AdditionalParameter内容为:
{frch_index_0=0, item=2, frch_index_1=1, _parameter=org.mybatis.internal.example.pojo.UserReq@5ccddd20, index=1, frch_item_1=2, _databaseId=null, frch_item_0=1}
其中_parameter和_databaseId在DynamicContext构造器中硬编码,其他值通过调用ForEachSqlNode.apply()计算得到。与此相对应,此时SQL语句在应用ForeachSqlNode之后,对参数名也进行重写,如下所示:
select lfPartyId,author as authors,subject,comments,title,partyName from LfParty where partyName = #{partyName} AND partyName like #{partyName} and lfPartyId in ( #{__frch_item_0.prop} , #{__frch_item_1} )
然后通过SqlSourceBuilder.parse()调用ParameterMappingTokenHandler计算出该sql的ParameterMapping列表,最后构造出StaticSqlSource。
3.5 TypeHandler
当MyBatis将一个Java对象作为输入/输出参数执行CRUD语句操作时,它会创建一个PreparedStatement对象,并且调用setXXX()为占位符设置相应的参数值。XXX可以是Int,String,Date等Java内置类型,或者用户自定义的类型。在实现上,MyBatis是通过使用类型处理器(type handler)来确定XXX是具体什么类型的。MyBatis对于下列类型使用内建的类型处理器:所有的基本数据类型、基本类型的包裹类型、byte[] 、java.util.Date、java.sql.Date、java,sql.Time、java.sql.Timestamp、java 枚举类型等。对于用户自定义的类型,我们可以创建一个自定义的类型处理器。要创建自定义类型处理器,只要实现TypeHandler接口即可,TypeHandler接口的定义如下:
public interface TypeHandler<T> {
void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
T getResult(ResultSet rs, String columnName) throws SQLException;
T getResult(ResultSet rs, int columnIndex) throws SQLException;
T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}
虽然我们可以直接实现TypeHandler接口,但是在实践中,我们一般选择继承BaseTypeHandler,BaseTypeHandler为TypeHandler提供了部分骨架代码,使得用户使用方便,几乎所有mybatis内置类型处理器都继承于BaseTypeHandler。下面我们实现一个最简单的自定义类型处理器MobileTypeHandler。
public class MobileTypeHandler extends BaseTypeHandler<Mobile> {
@Override
public Mobile getNullableResult(ResultSet rs, String columnName)
throws SQLException {
// mobile字段是VARCHAR类型,所以使用rs.getString
return new Mobile(rs.getString(columnName));
}
@Override
public Mobile getNullableResult(ResultSet rs, int columnIndex)
throws SQLException {
return new Mobile(rs.getString(columnIndex));
}
@Override
public Mobile getNullableResult(CallableStatement cs, int columnIndex)
throws SQLException {
return new Mobile(cs.getString(columnIndex));
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i,
Mobile param, JdbcType jdbcType) throws SQLException {
ps.setString(i, param.getFullNumber());
}
}
我们实现了自定义的类型处理器后,只要在mybatis配置文件mybatis-config.xml中注册就可以使用了,如下:
<typeHandlers>
<typeHandler handler="org.mybatis.internal.example.MobileTypeHandler" />
</typeHandlers>
上述完成之后,当我们在parameterType或者resultType或者resultMap中遇到Mobile类型的属性时,就会调用MobileTypeHandler进行代理出入参的设置和获取。
3.6 对象包装器工厂ObjectWrapperFactory
ObjectWrapperFactory是一个对象包装器工厂,用于对返回的结果对象进行二次处理,它主要在org.apache.ibatis.executor.resultset.DefaultResultSetHandler.getRowValue方法中创建对象的MetaObject时作为参数设置进去,这样MetaObject中的objectWrapper属性就可以被设置为我们自定义的ObjectWrapper实现而不是mybatis内置实现,如下所示:
private MetaObject(Object object, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
this.originalObject = object;
this.objectFactory = objectFactory;
this.objectWrapperFactory = objectWrapperFactory;
this.reflectorFactory = reflectorFactory;
if (object instanceof ObjectWrapper) {
this.objectWrapper = (ObjectWrapper) object;
} else if (objectWrapperFactory.hasWrapperFor(object)) { // 如果有自定义的ObjectWrapperFactory,就不会总是返回false了,这样对于特定类就启用了的我们自定义的ObjectWrapper
this.objectWrapper = objectWrapperFactory.getWrapperFor(this, object);
} else if (object instanceof Map) {
this.objectWrapper = new MapWrapper(this, (Map) object);
} else if (object instanceof Collection) {
this.objectWrapper = new CollectionWrapper(this, (Collection) object);
} else {
this.objectWrapper = new BeanWrapper(this, object);
}
}
典型的下划线转驼峰,我们就可以使用ObjectWrapperFactory来统一处理(当然,在实际中,我们一般不会这么做,而是通过设置mapUnderscoreToCamelCase来实现)。ObjectWrapperFactory 接口如下:
public interface ObjectWrapperFactory {
boolean hasWrapperFor(Object object);
ObjectWrapper getWrapperFor(MetaObject metaObject, Object object);
}
通过实现这个接口,可以判断当object是特定类型时,返回true,然后在下面的getWrapperFor中返回一个可以处理key为驼峰的ObjectWrapper 实现类即可。ObjectWrapper类可以说是对象反射信息的facade模式,它的定义如下:
public interface ObjectWrapper {
Object get(PropertyTokenizer prop);
void set(PropertyTokenizer prop, Object value);
String findProperty(String name, boolean useCamelCaseMapping);
String[] getGetterNames();
String[] getSetterNames();
Class<?> getSetterType(String name);
Class<?> getGetterType(String name);
boolean hasSetter(String name);
boolean hasGetter(String name);
MetaObject instantiatePropertyValue(String name, PropertyTokenizer prop, ObjectFactory objectFactory);
boolean isCollection();
void add(Object element);
<E> void addAll(List<E> element);
}
当然,我们不需要从头实现ObjectWrapper接口,可以选择继承BeanWrapper或者MapWrapper。比如对于Map类型,我们可以继承MapWrapper,让参数useCamelCaseMapping起作用。MapWrapper默认的findProperty方法并没有做驼峰转换处理,如下::
@Override
public String findProperty(String name, boolean useCamelCaseMapping) {
return name;
}
我们可以改成:
public class CamelMapWrapper extends MapWrapper {
public CamelMapWrapper(MetaObject metaObject, Map<String, Object> map) {
super(metaObject, map);
}
@Override
public String findProperty(String name, boolean useCamelCaseMapping) {
if (useCamelCaseMapping
&& ((name.charAt(0) >= 'A' && name.charAt(0) <= 'Z')
|| name.indexOf("_") >= 0)) {
return underlineToCamelhump(name);
}
return name;
}
/**
* 将下划线风格替换为驼峰风格
*/
public String underlineToCamelhump(String inputString) {
StringBuilder sb = new StringBuilder();
boolean nextUpperCase = false;
for (int i = 0; i < inputString.length(); i++) {
char c = inputString.charAt(i);
if (c == '_') {
if (sb.length() > 0) {
nextUpperCase = true;
}
} else {
if (nextUpperCase) {
sb.append(Character.toUpperCase(c));
nextUpperCase = false;
} else {
sb.append(Character.toLowerCase(c));
}
}
}
return sb.toString();
}
}
同时,创建一个自定义的objectWrapperFactory如下:
public class CustomWrapperFactory implements ObjectWrapperFactory {
@Override
public boolean hasWrapperFor(Object object) {
return object != null && object instanceof Map;
}
@Override
public ObjectWrapper getWrapperFor(MetaObject metaObject, Object object) {
return new CamelMapWrapper(metaObject, (Map) object);
}
}
然后,在 MyBatis 配置文件中配置上objectWrapperFactory:
<objectWrapperFactory type="org.mybatis.internal.example.CustomWrapperFactory"/>
同样,useCamelCaseMapping最终是通过mapUnderscoreToCamelCase设置注入进来的,所以settings要加上这个设置:
<setting name="mapUnderscoreToCamelCase" value="true"/>
此时,如果resultType是map类型的话,就可以看到key已经是驼峰式而不是columnName了。
注意:mybatis提供了一个什么都不做的默认实现DefaultObjectWrapperFactory。
3.7 MetaObject
MetaObject是一个对象包装器,其性质上有点类似ASF提供的commons类库,其中包装了对象的元数据信息,对象本身,对象反射工厂,对象包装器工厂等。使得根据OGNL表达式设置或者获取对象的属性更为便利,也可以更加方便的判断对象中是否包含指定属性、指定属性是否具有getter、setter等。主要的功能是通过其ObjectWrapper类型的属性完成的,它包装了操作对象元数据以及对象本身的主要接口,操作标准对象的实现是BeanWrapper。BeanWrapper类型有个MetaClass类型的属性,MetaClass中有个Reflector属性,其中包含了可读、可写的属性、方法以及构造器信息。
3.8 对象工厂ObjectFactory
MyBatis 每次创建结果对象的新实例时,都会使用一个对象工厂(ObjectFactory)实例来完成。 默认的对象工厂DefaultObjectFactory仅仅是实例化目标类,要么通过默认构造方法,要么在参数映射存在的时候通过参数构造方法来实例化。如果想覆盖对象工厂的默认行为比如给某些属性设置默认值(有些时候直接修改对象不可行,或者由于不是自己拥有的代码或者改动太大),则可以通过创建自己的对象工厂来实现。ObjectFactory接口定义如下:
public interface ObjectFactory {
/**
* Sets configuration properties.
* @param properties configuration properties
*/
void setProperties(Properties properties);
/**
* Creates a new object with default constructor.
* @param type Object type
* @return
*/
<T> T create(Class<T> type);
/**
* Creates a new object with the specified constructor and params.
* @param type Object type
* @param constructorArgTypes Constructor argument types
* @param constructorArgs Constructor argument values
* @return
*/
<T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs);
/**
* Returns true if this object can have a set of other objects.
* It's main purpose is to support non-java.util.Collection objects like Scala collections.
*
* @param type Object type
* @return whether it is a collection or not
* @since 3.1.0
*/
<T> boolean isCollection(Class<T> type);
}
从这个接口定义可以看出,它包含了两种通过反射机制构造实体类对象的方法,一种是通过无参构造函数,一种是通过带参数的构造函数。同时,为了使工厂类能设置其他属性,还提供了setProperties()方法。
要自定义对象工厂类,我们可以实现ObjectFactory这个接口,但是这样我们就需要自己去实现一些在DefaultObjectFactory已经实现好了的东西,所以也可以继承这个DefaultObjectFactory类,这样可以使得实现起来更为简单。例如,我们希望给Order对象的属性hostname设置为本地机器名,可以像下面这么实现:
public class CustomObjectFactory extends DefaultObjectFactory{
private static String hostname;
static {
InetAddress addr = InetAddress.getLocalHost();
String ip=addr.getHostAddress().toString(); //获取本机ip
hostName=addr.getHostName().toString(); //获取本机计算机名称
}
private static final long serialVersionUID = 1128715667301891724L;
@Override
public <T> T create(Class<T> type) {
T result = super.create(type);
if(type.equals(Order.class)){
((Order)result).setIp(hostname);
}
return result;
}
}
接下来,在配置文件中配置对象工厂类为我们创建的对象工厂类CustomObjectFactory。
<objectFactory type="org.mybatis.internal.example.CustomObjectFactory"></objectFactory>
此时执行代码,就会发现返回的Order对象中ip字段的值为本机名。
3.9 MappedStatement
mapper文件或者mapper接口中每个映射语句都对应一个MappedStatement实例,它包含了所有运行时需要的信息比如结果映射、参数映射、是否需要刷新缓存等。MappedStatement定义如下:
public final class MappedStatement {
private String resource;
private Configuration configuration;
private String id;
private Integer fetchSize;
private Integer timeout;
private StatementType statementType;
private ResultSetType resultSetType;
private SqlSource sqlSource;
private Cache cache;
private ParameterMap parameterMap;
private List resultMaps;
private boolean flushCacheRequired;
private boolean useCache;
private boolean resultOrdered;
private SqlCommandType sqlCommandType;
private KeyGenerator keyGenerator;
private String[] keyProperties;
private String[] keyColumns;
private boolean hasNestedResultMaps;
private String databaseId;
private Log statementLog;
private LanguageDriver lang;
private String[] resultSets;
…
public MappedStatement build() {
assert mappedStatement.configuration != null;
assert mappedStatement.id != null;
assert mappedStatement.sqlSource != null;
assert mappedStatement.lang != null;
mappedStatement.resultMaps = Collections.unmodifiableList(mappedStatement.resultMaps);
return mappedStatement;
}
}
…
}
唯一值得注意的是resultMaps被设计为只读,这样应用可以查看但是不能修改。
3.10 ParameterMapping
每个参数映射<>标签都被创建为一个ParameterMapping实例,其中包含和结果映射类似的信息,如下:
public class ParameterMapping {
private Configuration configuration;
private String property;
private ParameterMode mode;
private Class<?> javaType = Object.class;
private JdbcType jdbcType;
private Integer numericScale;
private TypeHandler<?> typeHandler;
private String resultMapId;
private String jdbcTypeName;
private String expression;
private ParameterMapping() {
}
…
}
3.11 KeyGenerator
package org.apache.ibatis.executor.keygen;
public interface KeyGenerator {
// before key generator 主要用于oracle等使用序列机制的ID生成方式
void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter);
// after key generator 主要用于mysql等使用自增机制的ID生成方式
void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter);
}
3.12 各种Registry
mybatis将类型处理器,类型别名,mapper定义,语言驱动器等各种信息包装在Registry中维护,如下所示:
public class Configuration {
...
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
protected final InterceptorChain interceptorChain = new InterceptorChain();
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
...
}
各Registry中提供了相关的方法,比如TypeHandlerRegistry中包含了判断某个java类型是否有类型处理器以及获取类型处理器的方法,如下:
public boolean hasTypeHandler(TypeReference<?> javaTypeReference, JdbcType jdbcType) {
return javaTypeReference != null && getTypeHandler(javaTypeReference, jdbcType) != null;
}
public <T> TypeHandler<T> getTypeHandler(Class<T> type) {
return getTypeHandler((Type) type, null);
}
3.13 LanguageDriver
从3.2版本开始,mybatis提供了LanguageDriver接口,我们可以使用该接口自定义SQL的解析方式。先来看下LanguageDriver接口中的3个方法:
public interface LanguageDriver {
/**
* Creates a {@link ParameterHandler} that passes the actual parameters to the the JDBC statement.
* 创建一个ParameterHandler对象,用于将实际参数赋值到JDBC语句中
*
* @param mappedStatement The mapped statement that is being executed
* @param parameterObject The input parameter object (can be null)
* @param boundSql The resulting SQL once the dynamic language has been executed.
* @return
* @author Frank D. Martinez [mnesarco]
* @see DefaultParameterHandler
*/
ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);
/**
* Creates an {@link SqlSource} that will hold the statement read from a mapper xml file.
* It is called during startup, when the mapped statement is read from a class or an xml file.
* 将XML中读入的语句解析并返回一个sqlSource对象
*
* @param configuration The MyBatis configuration
* @param script XNode parsed from a XML file
* @param parameterType input parameter type got from a mapper method or specified in the parameterType xml attribute. Can be null.
* @return
*/
SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType);
/**
* Creates an {@link SqlSource} that will hold the statement read from an annotation.
* It is called during startup, when the mapped statement is read from a class or an xml file.
* 将注解中读入的语句解析并返回一个sqlSource对象
*
* @param configuration The MyBatis configuration
* @param script The content of the annotation
* @param parameterType input parameter type got from a mapper method or specified in the parameterType xml attribute. Can be null.
* @return
*/
SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType);
}
实现了LanguageDriver之后,可以在配置文件中指定该实现类作为SQL的解析器,在XML中我们可以使用 lang 属性来进行指定,如下:
<typeAliases>
<typeAlias type="org.sample.MyLanguageDriver" alias="myLanguage"/>
</typeAliases>
<select id="selectBlog" lang="myLanguage">
SELECT * FROM BLOG
</select>
除了可以在语句级别指定外,也可以全局设置,如下:
<settings>
<setting name="defaultScriptingLanguage" value="myLanguage"/>
</settings>
对于mapper接口,也可以使用@Lang注解,如下所示:
public interface Mapper {
@Lang(MyLanguageDriver.class)
@Select("SELECT * FROM users")
List<User> selectUser();
}
LanguageDriver的默认实现类为XMLLanguageDriver。Mybatis默认是XML语言,所以我们来看看XMLLanguageDriver的实现:
public class XMLLanguageDriver implements LanguageDriver {
// 创建参数处理器,返回默认的实现
@Override
public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
}
// 根据XML定义创建SqlSource
@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
// 解析注解中的SQL语句
@Override
public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
// issue #3
if (script.startsWith("<script>")) {
XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver());
return createSqlSource(configuration, parser.evalNode("/script"), parameterType);
} else {
// issue #127
script = PropertyParser.parse(script, configuration.getVariables());
TextSqlNode textSqlNode = new TextSqlNode(script);
if (textSqlNode.isDynamic()) {
return new DynamicSqlSource(configuration, textSqlNode);
} else {
return new RawSqlSource(configuration, script, parameterType);
}
}
}
}
如上所示,LanguageDriver将实际的实现根据采用的底层不同,委托给了具体的Builder,对于XML配置,委托给XMLScriptBuilder。对于使用Velocity模板的解析器,委托给SQLScriptSource解析具体的SQL。
注:mybatis-velocity还提供了VelocityLanguageDriver和FreeMarkerLanguageDriver,可参见:
3.14 ResultMap
ResultMap类维护了每个标签中的详细信息,比如id映射、构造器映射、属性映射以及完整的映射列表、是否有嵌套的resultMap、是否有鉴别器、是否有嵌套查询,如下所示:
public class ResultMap {
private Configuration configuration;
private String id;
private Class<?> type;
private List<ResultMapping> resultMappings;
private List<ResultMapping> idResultMappings;
private List<ResultMapping> constructorResultMappings;
private List<ResultMapping> propertyResultMappings;
private Set<String> mappedColumns;
private Set<String> mappedProperties;
private Discriminator discriminator;
private boolean hasNestedResultMaps;
private boolean hasNestedQueries;
private Boolean autoMapping;
...
}
ResultMap除了作为一个ResultMap的数据结构表示外,本身并没有提供额外的功能。
3.15 ResultMapping
ResultMapping代表下的映射,如下:
public class ResultMapping {
private Configuration configuration;
private String property;
private String column;
private Class<?> javaType;
private JdbcType jdbcType;
private TypeHandler<?> typeHandler;
private String nestedResultMapId;
private String nestedQueryId;
private Set<String> notNullColumns;
private String columnPrefix;
// 标记是否构造器属性,是否ID属性
private List<ResultFlag> flags;
private List<ResultMapping> composites;
private String resultSet;
private String foreignColumn;
private boolean lazy;
...
}
3.16 Discriminator
每个鉴别器节点都表示为一个Discriminator,如下所示:
public class Discriminator {
// 所属的属性节点<result>
private ResultMapping resultMapping;
// 内部的if then映射
private Map<String, String> discriminatorMap;
...
}
3.17 Configuration
Configuration是mybatis所有配置以及mapper文件的元数据容器。无论是解析mapper文件还是运行时执行SQL语句,都需要依赖与mybatis的环境和配置信息,比如databaseId、类型别名等。mybatis实现将所有这些信息封装到Configuration中并提供了一系列便利的接口方便各主要的调用方使用,这样就避免了各种配置和元数据信息到处散落的凌乱。
3.18 ErrorContext
ErrorContext定义了一个mybatis内部统一的日志规范,记录了错误信息、发生错误涉及的资源文件、对象、逻辑过程、SQL语句以及出错原因,但是它不会影响运行,如下所示:
public class ErrorContext {
private static final String LINE_SEPARATOR = System.getProperty("line.separator","\n");
private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<ErrorContext>();
private ErrorContext stored;
private String resource;
private String activity;
private String object;
private String message;
private String sql;
private Throwable cause;
...
public ErrorContext reset() {
resource = null;
activity = null;
object = null;
message = null;
sql = null;
cause = null;
LOCAL.remove();
return this;
}
@Override
public String toString() {
StringBuilder description = new StringBuilder();
// message
if (this.message != null) {
description.append(LINE_SEPARATOR);
description.append("### ");
description.append(this.message);
}
// resource
if (resource != null) {
description.append(LINE_SEPARATOR);
description.append("### The error may exist in ");
description.append(resource);
}
// object
if (object != null) {
description.append(LINE_SEPARATOR);
description.append("### The error may involve ");
description.append(object);
}
// activity
if (activity != null) {
description.append(LINE_SEPARATOR);
description.append("### The error occurred while ");
description.append(activity);
}
// activity
if (sql != null) {
description.append(LINE_SEPARATOR);
description.append("### SQL: ");
description.append(sql.replace('\n', ' ').replace('\r', ' ').replace('\t', ' ').trim());
}
// cause
if (cause != null) {
description.append(LINE_SEPARATOR);
description.append("### Cause: ");
description.append(cause.toString());
}
return description.toString();
}
}
3.19 BoundSql
/**
* An actual SQL String got from an {@link SqlSource} after having processed any dynamic content.
* The SQL may have SQL placeholders "?" and an list (ordered) of an parameter mappings
* with the additional information for each parameter (at least the property name of the input object to read
* the value from).
* </br>
* Can also have additional parameters that are created by the dynamic language (for loops, bind...).
*
* SqlSource中包含的SQL处理动态内容之后的实际SQL语句,SQL中会包含?占位符,也就是最终给JDBC的SQL语句,以及他们的参数信息
* @author Clinton Begin
*/
public class BoundSql {
// sql文本
private final String sql;
// 静态参数说明
private final List<ParameterMapping> parameterMappings;
// 运行时参数对象
private final Object parameterObject;
// 额外参数,也就是for loops、bind生成的
private final Map<String, Object> additionalParameters;
// 额外参数的facade模式包装
private final MetaObject metaParameters;
public BoundSql(Configuration configuration, String sql, List<ParameterMapping> parameterMappings, Object parameterObject) {
this.sql = sql;
this.parameterMappings = parameterMappings;
this.parameterObject = parameterObject;
this.additionalParameters = new HashMap<String, Object>();
this.metaParameters = configuration.newMetaObject(additionalParameters);
}
public String getSql() {
return sql;
}
public List<ParameterMapping> getParameterMappings() {
return parameterMappings;
}
public Object getParameterObject() {
return parameterObject;
}
public boolean hasAdditionalParameter(String name) {
String paramName = new PropertyTokenizer(name).getName();
return additionalParameters.containsKey(paramName);
}
public void setAdditionalParameter(String name, Object value) {
metaParameters.setValue(name, value);
}
public Object getAdditionalParameter(String name) {
return metaParameters.getValue(name);
}
}
4 SQL语句的执行流程
4.1 传统JDBC用法
在原生jdbc中,我们要执行一个sql语句,它的流程是这样的:
- 注册驱动;
- 获取jdbc连接;
- 创建参数化预编译SQL;
- 绑定参数;
- 发送SQL给数据库进行执行;
- 对于查询,获取结果集到应用;
我们先回顾下典型JDBC的用法:
package org.mybatis.internal.example;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.Statement;
public class JdbcHelloWord {
/**
* 入口函数
* @param arg
*/
public static void main(String arg[]) {
try {
Connection con = null; //定义一个MYSQL链接对象
Class.forName("com.mysql.jdbc.Driver").newInstance(); //MYSQL驱动
con = DriverManager.getConnection("jdbc:mysql://10.7.12.4:3306/lfBase?useUnicode=true", "lfBase", "eKffQV6wbh3sfQuFIG6M"); //链接本地MYSQL
//更新一条数据
String updateSql = "UPDATE LfParty SET remark1 = 'mybatis internal example' WHERE lfPartyId = ?";
PreparedStatement pstmt = con.prepareStatement(updateSql);
pstmt.setString(1, "1");
long updateRes = pstmt.executeUpdate();
System.out.print("UPDATE:" + updateRes);
//查询数据并输出
String sql = "select lfPartyId,partyName from LfParty where lfPartyId = ?";
PreparedStatement pstmt2 = con.prepareStatement(sql);
pstmt2.setString(1, "1");
ResultSet rs = pstmt2.executeQuery();
while (rs.next()) { //循环输出结果集
String lfPartyId = rs.getString("lfPartyId");
String partyName = rs.getString("partyName");
System.out.print("\r\n\r\n");
System.out.print("lfPartyId:" + lfPartyId + "partyName:" + partyName);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
4.2 mybatis执行SQL语句
同样的,在mybatis中,要执行sql语句,首先要拿到代表JDBC底层连接的一个对象,这在mybatis中的实现就是SqlSession。mybatis提供了下列实现:
获取SqlSession的API如下:
SqlSession session = SqlSessionFactory.openSession();
try {
User user = (User) session.selectOne("org.mybatis.internal.example.mapper.UserMapper.getUser", 1);
System.out.println("sql from xml:" + user.getLfPartyId() + "," + user.getPartyName());
UserMapper2 mapper = session.getMapper(UserMapper2.class);
List<User> users = mapper.getUser2(293);
System.out.println("sql from mapper:" + users.get(0).getLfPartyId() + "," + users.get(0).getPartyName());
} finally {
session.close();
}
同样,首先调用SqlSessionFactory.openSession()拿到一个session,然后在session上执行各种CRUD操作。简单来说,SqlSession就是jdbc连接的代表,openSession()就是获取jdbc连接(当然其背后可能是从jdbc连接池获取);session中的各种selectXXX方法或者调用mapper的具体方法就是集合了JDBC调用的第3、4、5、6步。SqlSession接口的定义如下:
可知,绝大部分的方法都是泛型方法,也可以说采用了模板方法实现。
4.2.1 获取openSession
获取openSession的总体流程为:
我们先来看openSession的具体实现。mybatis提供了两个SqlSessionFactory实现:SqlSessionManager和DefaultSqlSessionFactory,默认返回的是DefaultSqlSessionFactory,它们的区别我们后面会讲到。我们先来看下SqlSessionFactory的接口定义:
public interface SqlSessionFactory {
SqlSession openSession();
SqlSession openSession(boolean autoCommit);
SqlSession openSession(Connection connection);
SqlSession openSession(TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType);
SqlSession openSession(ExecutorType execType, boolean autoCommit);
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType, Connection connection);
Configuration getConfiguration();
}
主要有多种形式的重载,除了使用默认设置外,可以指定自动提交模式、特定的jdbc连接、事务隔离级别,以及指定的执行器类型。关于执行器类型,mybatis提供了三种执行器类型:SIMPLE, REUSE, BATCH。后面我们会详细分析每种类型的执行器的差别以及各自的适用场景。我们以最简单的无参方法切入(按照一般的套路,如果定义了多个重载的方法或者构造器,内部实现一定是设置作者认为最合适的默认值,然后调用次多参数的方法,直到最后),它的实现是这样的:
public class DefaultSqlSessionFactory implements SqlSessionFactory {
...
@Override
public SqlSession openSession() {
// 使用默认的执行器类型(默认是SIMPLE),默认隔离级别,非自动提交 委托给openSessionFromDataSource方法
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
...
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
// 获取事务管理器, 支持从数据源或者直接获取
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 从数据源创建一个事务, 同样,数据源必须配置, mybatis内置了JNDI、POOLED、UNPOOLED三种类型的数据源,其中POOLED对应的实现为org.apache.ibatis.datasource.pooled.PooledDataSource,它是mybatis自带实现的一个同步、线程安全的数据库连接池 一般在生产中,我们会使用dbcp或者druid连接池
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
...
private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
// 如果没有配置environment或者environment的事务管理器为空,则使用受管的事务管理器
// 除非什么都没有配置,否则在mybatis-config里面,至少要配置一个environment,此时事务工厂不允许为空
// 对于jdbc类型的事务管理器,则返回JdbcTransactionFactory,其内部操作mybatis的JdbcTransaction实现(采用了Facade模式),后者对jdbc连接操作
if (environment == null || environment.getTransactionFactory() == null) {
return new ManagedTransactionFactory();
}
return environment.getTransactionFactory();
}
}
我们来看下transactionFactory.newTransaction的实现,还是以jdbc事务为例子。
public class JdbcTransactionFactory implements TransactionFactory {
...
@Override
public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
return new JdbcTransaction(ds, level, autoCommit);
}
}
newTransaction的实现逻辑很简单,但是此时返回的事务不一定是有底层连接的。
拿到事务后,根据事务和执行器类型创建一个真正的执行器实例。获取执行器的逻辑如下:
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);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
如果没有配置执行器类型,默认是简单执行器。如果启用了缓存,则使用缓存执行器。
拿到执行器之后,new一个DefaultSqlSession并返回,这样一个SqlSession就创建了,它从逻辑上代表一个封装了事务特性的连接,如果在此期间发生异常,则调用关闭事务(因为此时事务底层的连接可能已经持有了,否则会导致连接泄露)。
DefaultSqlSession的构造很简单,就是简单的属性赋值:
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
private Executor executor;
private boolean autoCommit;
// 含义是TODO
private boolean dirty;
private List<Cursor<?>> cursorList;
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}
...
}
具体的对外API我们后面专门讲解。
根据sql语句使用xml进行维护或者在注解上配置,sql语句执行的入口分为两种:
第一种,调用org.apache.ibatis.session.SqlSession的crud方法比如selectList/selectOne传递完整的语句id直接执行;
第二种,先调用SqlSession的getMapper()方法得到mapper接口的一个实现,然后调用具体的方法。除非早期,现在实际开发中,我们一般采用这种方式。
4.2.2 sql语句执行方式一
我们先来看第一种形式的sql语句执行也就是SqlSession.getMapper除外的形式。这里还是以带参数的session.selectOne为例子。其实现代码为:
@Override
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
List<T> list = this.<T>selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
...
@Override
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
selectOne调用在内部将具体实现委托给selectList,如果返回的行数大于1就抛异常。我们先看下selectList的总体流程:
selectList的第三个参数是RowBounds.DEFAULT,我们来看下什么是RowBounds。
public class RowBounds {
public static final int NO_ROW_OFFSET = 0;
public static final int NO_ROW_LIMIT = Integer.MAX_VALUE;
public static final RowBounds DEFAULT = new RowBounds();
private int offset;
private int limit;
public RowBounds() {
this.offset = NO_ROW_OFFSET;
this.limit = NO_ROW_LIMIT;
}
}
从定义可知,RowBounds是一个分页查询的参数封装,默认是不分页。
第三个selectList重载首先使用应用调用方传递的语句id判断configuration.mappedStatements里面是否有这个语句,如果没有将会抛出IllegalArgumentException异常,执行结束。否则将获取到的映射语句对象连同其他参数一起将具体实现委托给执行器Executor的query方法。
在这里对查询参数parameter进行了一次封装,封装逻辑wrapCollection主要是判断参数是否为数组或集合类型类型,是的话将他们包装到StrictMap中。同时设置结果处理器为null。
我们现在来看下Executor的query方法:
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 首先根据传递的参数获取BoundSql对象,对于不同类型的SqlSource,对应的getBoundSql实现不同,具体参见SqlSource详解一节 TODO
BoundSql boundSql = ms.getBoundSql(parameter);
// 创建缓存key
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
// 委托给重载的query
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
@SuppressWarnings("unchecked")
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// 如果需要刷新缓存(默认DML需要刷新,也可以语句层面修改), 且queryStack(应该是用于嵌套查询的场景)=0
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
// 如果查询不需要应用结果处理器,则先从缓存获取,这样可以避免数据库查询。我们后面会分析到localCache是什么时候被设置进去的
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 不管是因为需要应用结果处理器还是缓存中没有,都从数据库中查询
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
...
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// 根据映射语句id,分页信息,jdbc规范化的预编译sql,所有映射参数的值以及环境id的值,计算出缓存Key
CacheKey cacheKey = new CacheKey();
cacheKey.update(ms.getId());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
cacheKey.update(boundSql.getSql());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
// mimic DefaultParameterHandler logic
for (ParameterMapping parameterMapping : parameterMappings) {
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) {
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);
}
cacheKey.update(value);
}
}
if (configuration.getEnvironment() != null) {
// issue #176
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
private void handleLocallyCachedOutputParameters(MappedStatement ms, CacheKey key, Object parameter, BoundSql boundSql) {
// 只处理存储过程和函数调用的出参, 因为存储过程和函数的返回不是通过ResultMap而是ParameterMap来的,所以只要把缓存的非IN模式参数取出来设置到parameter对应的属性上即可
if (ms.getStatementType() == StatementType.CALLABLE) {
final Object cachedParameter = localOutputParameterCache.getObject(key);
if (cachedParameter != null && parameter != null) {
final MetaObject metaCachedParameter = configuration.newMetaObject(cachedParameter);
final MetaObject metaParameter = configuration.newMetaObject(parameter);
for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
if (parameterMapping.getMode() != ParameterMode.IN) {
final String parameterName = parameterMapping.getProperty();
final Object cachedValue = metaCachedParameter.getValue(parameterName);
metaParameter.setValue(parameterName, cachedValue);
}
}
}
}
}
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
// 一开始放个占位符进去,这个还真不知道用意是什么???
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
// doQuery是个抽象方法,每个具体的执行器都要自己去实现,我们先看SIMPLE的
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
// 把真正的查询结果放到缓存中去
localCache.putObject(key, list);
// 如果是存储过程类型,则把查询参数放到本地出参缓存中, 所以第一次一定为空
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
我们先来看下缓存key的定义:
package org.apache.ibatis.cache;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import org.apache.ibatis.reflection.ArrayUtil;
/**
* @author Clinton Begin
*/
public class CacheKey implements Cloneable, Serializable {
private static final long serialVersionUID = 1146682552656046210L;
public static final CacheKey NULL_CACHE_KEY = new NullCacheKey();
private static final int DEFAULT_MULTIPLYER = 37;
private static final int DEFAULT_HASHCODE = 17;
private int multiplier;
private int hashcode;
private long checksum;
private int count;
private List<Object> updateList;
public CacheKey() {
this.hashcode = DEFAULT_HASHCODE;
this.multiplier = DEFAULT_MULTIPLYER;
this.count = 0;
this.updateList = new ArrayList<Object>();
}
public CacheKey(Object[] objects) {
this();
updateAll(objects);
}
public int getUpdateCount() {
return updateList.size();
}
public void update(Object object) {
// ArrayUtil提供了可以计算包括数组的对象的hashCode, toString, equals方法
int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);
count++;
checksum += baseHashCode;
baseHashCode *= count;
hashcode = multiplier * hashcode + baseHashCode;
updateList.add(object);
}
public void updateAll(Object[] objects) {
for (Object o : objects) {
update(o);
}
}
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (!(object instanceof CacheKey)) {
return false;
}
final CacheKey cacheKey = (CacheKey) object;
if (hashcode != cacheKey.hashcode) {
return false;
}
if (checksum != cacheKey.checksum) {
return false;
}
if (count != cacheKey.count) {
return false;
}
for (int i = 0; i < updateList.size(); i++) {
Object thisObject = updateList.get(i);
Object thatObject = cacheKey.updateList.get(i);
if (!ArrayUtil.equals(thisObject, thatObject)) {
return false;
}
}
return true;
}
...
@Override
public CacheKey clone() throws CloneNotSupportedException {
CacheKey clonedCacheKey = (CacheKey) super.clone();
clonedCacheKey.updateList = new ArrayList<Object>(updateList);
return clonedCacheKey;
}
}
我们来看下SIMPLE执行器的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();
// 根据上下文参数和具体的执行器new一个StatementHandler, 其中包含了所有必要的信息,比如结果处理器、参数处理器、执行器等等,主要有三种类型的语句处理器UNPREPARE、PREPARE、CALLABLE。默认是PREPARE类型,通过mapper语句上的statementType属性进行设置,一般除了存储过程外不应该设置
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 这一步是真正和JDBC打交道
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
// 获取JDBC连接
Connection connection = getConnection(statementLog);
// 调用语句处理器的prepare方法
stmt = handler.prepare(connection, transaction.getTimeout());
// 设置参数
handler.parameterize(stmt);
return stmt;
}
下面我们来看下PREPARE处理处理的prepare实现,从上述可知,具体的处理器继承了BaseStatementHandler,
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
// 首先实例化语句,因为PREPARE和非PREPARE不同,所以留给具体子类实现
statement = instantiateStatement(connection);
// 设置语句超时时间
setStatementTimeout(statement, transactionTimeout);
// 设置fetch大小
setFetchSize(statement);
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}
重点来看PREPARE语句处理器的初始化语句过程:
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();
// 只处理Jdbc3KeyGenerator,因为它代表的是自增,另外一个是SelectKeyGenerator用于不支持自增的情况
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
return connection.prepareStatement(sql, keyColumnNames);
}
} else if (mappedStatement.getResultSetType() != null) {
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
} else {
return connection.prepareStatement(sql);
}
}
基本上就是把我们在MAPPER中定义的属性转换为JDBC标准的调用。
接下去再来看参数是如何MAPPER中定义的参数是如何转换为JDBC参数的,PreparedStatementHandler.parameterize将具体实现委托给了ParameterHandler.setParameters()方法,ParameterHandler目前只有一种实现DefaultParameterHandler。
@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();
// 计算参数值的优先级是 先判断是不是属于语句的AdditionalParameter;其次参数是不是null;然后判断是不是属于注册类型;都不是,那估计参数一定是object或者map了,这就要借助于MetaObject获取属性值了;
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 {
// jdbc下标从1开始,由具体的类型处理器进行参数的设置, 对于每个jdbcType, mybatis都提供了一个对应的Handler,具体可参考上文TypeHandler详解, 其内部调用的是PrepareStatement.setXXX进行设置。
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
} catch (SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
我们在mapper中定义的所有ParameterType、ParameterMap、内嵌参数映射等在最后都在这里被作为ParameterMapping转换为JDBC参数。
Configuration中newStatementHandler的定义如下:
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) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
回到主体逻辑SimpleExecutor.doQuery,创建了Statement具体实现的实例后,调用SimpleExecutor.query进行具体的查询,查询的主体逻辑如下:
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.<E> handleResultSets(ps);
}
从上述具体逻辑的实现可以看出,内部调用PreparedStatement完成具体查询后,将ps的结果集传递给对应的结果处理器进行处理。查询结果的映射是mybatis作为ORM框架提供的最有价值的功能,同时也可以说是最复杂的逻辑之一。下面我们来专门分析mybatis查询结果集的处理。
mybatis结果集处理
对于结果集处理,mybatis默认提供了DefaultResultSetHandler,如下所示:
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List<Object> multipleResults = new ArrayList<Object>();
int resultSetCount = 0;
// 返回jdbc ResultSet的包装形式,主要是将java.sql.ResultSetMetaData做了Facade模式,便于使用
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
// 绝大部分情况下一个查询只有一个ResultMap, 除非多结果集查询
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
// 至少执行一次
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
// 根据resultMap的定义将resultset打包到应用端的multipleResults中
handleResultSet(rsw, resultMap, multipleResults, null);
// 循环直到处理完所有的结果集,一般情况下,一个execute只会返回一个结果集,除非语句比如存储过程返回多个resultSet
rsw = getNextResultSet(stmt);
// 清空嵌套结果集
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
// 处理关联或集合
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
// 如果一个映射语句的resultSet数量比jdbc resultset多的话,多的部分就是嵌套结果集
while (rsw != null && resultSetCount < resultSets.length) {
// nextResultMaps初始化的时候为空,它是在处理主查询每行记录的时候写进去的,所以此时就可以得到主记录是哪个属性
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
// 得到子结果集的resultMap之后,就可以进行填充了
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
return collapseSingleResultList(multipleResults);
}
// 因为调用handleResultSet的只有handleResultSets,按说其中第一个调用永远不会出现parentMapping==null的情况,只有第二个调用才会出现这种情况,而且应该是连续的,因为第二个调用就是为了处理嵌套resultMap。所以在handleRowValues处理resultMap的时候,一定是主的先处理,嵌套的后处理,这样整个逻辑就比较清晰了。这里需要补个流程图。
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
try {
// 处理嵌套/关联的resultMap(collection或association)
if (parentMapping != null) {
// 处理非主记录 resultHandler传递null,RowBounds传递默认值,parentMapping不为空
handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
} else {
// 处理主记录,这里有个疑问???问什么resultHandler不为空,就不需要添加到multipleREsults中
if (resultHandler == null) {
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
// 处理主记录,resultHander不为空,rowBounds不使用默认值,parentMapping传递null
handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
multipleResults.add(defaultResultHandler.getResultList());
} else {
handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
}
}
} finally {
// issue #228 (close resultsets)
closeResultSet(rsw.getResultSet());
}
}
// 处理每一行记录,不管是主查询记录还是关联嵌套的查询结果集,他们的入参上通过resultHandler,rowBounds,parentMapping区分,具体见上述调用
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
// 具体实现上,处理嵌套记录和主记录的逻辑不一样
if (resultMap.hasNestedResultMaps()) {
ensureNoRowBounds();
checkResultHandler();
// 含有嵌套resultMap的列处理,通常是collection或者association
handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
} else {
handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
}
}
// 处理主记录
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
throws SQLException {
DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();
// 设置从指定行开始,这是mybatis进行内部分页,不是依赖服务器端的分页实现
skipRows(rsw.getResultSet(), rowBounds);
// 确保没有超过mybatis逻辑分页限制,同时结果集中还有记录没有fetch
while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
// 解析鉴别器,得到嵌套的最深的鉴别器对应的ResultMap,如果没有鉴别器,就返回最顶层的ResultMap
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
// 这个时候resultMap是非常干净的,没有嵌套任何其他东西了,但是这也是最关键的地方,将ResultSet记录转换为业务层配置的对象类型或者Map类型
Object rowValue = getRowValue(rsw, discriminatedResultMap);
storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
}
}
private void storeObject(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue, ResultMapping parentMapping, ResultSet rs) throws SQLException {
if (parentMapping != null) {
// 如果不是主记录,则链接到主记录
linkToParents(rs, parentMapping, rowValue);
} else {
// 否则让ResultHander(默认是DefaultResultHander处理,直接添加到List<Object>中)
callResultHandler(resultHandler, resultContext, rowValue);
}
}
@SuppressWarnings("unchecked" /* because ResultHandler<?> is always ResultHandler<Object>*/)
private void callResultHandler(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue) {
resultContext.nextResultObject(rowValue);
((ResultHandler<Object>) resultHandler).handleResult(resultContext);
}
// 非主记录需要连接到主记录,这里涉及到collection和association的resultMap实现,我们来看下
// MULTIPLE RESULT SETS
private void linkToParents(ResultSet rs, ResultMapping parentMapping, Object rowValue) throws SQLException {
CacheKey parentKey = createKeyForMultipleResults(rs, parentMapping, parentMapping.getColumn(), parentMapping.getForeignColumn());
// 判断当前的主记录是否有待关联的子记录,是通过pendingRelations Map进行维护的,pendingRelations是在addPendingChildRelation中添加主记录的
List<PendingRelation> parents = pendingRelations.get(parentKey);
if (parents != null) {
for (PendingRelation parent : parents) {
if (parent != null && rowValue != null) {
linkObjects(parent.metaObject, parent.propertyMapping, rowValue);
}
}
}
}
// 集合与非集合的处理逻辑对外封装在一起,这样便于用户使用, 内部通过判断resultMapping中的类型确定是否为集合类型。无论是否为集合类型,最后都添加到parent的metaObject所封装的原始Object对应的属性上
private void linkObjects(MetaObject metaObject, ResultMapping resultMapping, Object rowValue) {
final Object collectionProperty = instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject);
if (collectionProperty != null) {
final MetaObject targetMetaObject = configuration.newMetaObject(collectionProperty);
targetMetaObject.add(rowValue);
} else {
metaObject.setValue(resultMapping.getProperty(), rowValue);
}
}
private Object instantiateCollectionPropertyIfAppropriate(ResultMapping resultMapping, MetaObject metaObject) {
final String propertyName = resultMapping.getProperty();
Object propertyValue = metaObject.getValue(propertyName);
if (propertyValue == null) {
Class<?> type = resultMapping.getJavaType();
if (type == null) {
type = metaObject.getSetterType(propertyName);
}
try {
if (objectFactory.isCollection(type)) {
propertyValue = objectFactory.create(type);
metaObject.setValue(propertyName, propertyValue);
return propertyValue;
}
} catch (Exception e) {
throw new ExecutorException("Error instantiating collection property for result '" + resultMapping.getProperty() + "'. Cause: " + e, e);
}
} else if (objectFactory.isCollection(propertyValue.getClass())) {
return propertyValue;
}
return null;
}
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
// 有三个createResultObject重载,这三个重载完成的功能从最里面到最外面分别是:
1、使用构造器创建目标对象类型;
先判断是否有目标对象类型的处理器,有的话,直接调用类型处理器(这里为什么一定是原生类型)创建目标对象
如果没有,判断是否有构造器,有的话,使用指定的构造器创建目标对象(构造器里面如果嵌套了查询或者ResultMap,则进行处理)
如果结果类型是接口或者具有默认构造器,则使用ObjectFactory创建默认目标对象
最后判断是否可以应用自动映射,默认是对非嵌套查询,只要没有明确设置AutoMappingBehavior.NONE就可以,对于嵌套查询,AutoMappingBehavior.FULL就可以。自动映射的逻辑是先找目标对象上具有@AutomapConstructor注解的构造器,然后根据ResultSet返回的字段清单找匹配的构造器,如果找不到,就报错
2、如果此时创建的对象不为空,且不需要应用结果对象处理器,判断有没有延迟加载且具有嵌套查询的属性,如果有的话,则为对象创建一个代理,额外存储后面fetch的时候进行延迟加载所需的信息。返回对象。
3、如果此时创建的对象不为空,且不需要应用结果对象处理器,如果对象需要自动映射,则先进行自动映射(创建自动映射列表的过程为:先找到在ResultSet、不在ResultMap中的列,如果在目标对象上可以找到属性且可以类型可以处理,则标记为可以自动映射;然后进行自动映射处理,如果遇到无法处理的属性,则根据autoMappingUnknownColumnBehavior进行处理,默认忽略),其次进行属性映射处理
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, null);
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
final MetaObject metaObject = configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
if (shouldApplyAutomaticMappings(resultMap, false)) {
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
}
// 处理属性映射,这里会识别出哪些属性需要nestQuery,哪些是nest ResultMap
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
foundValues = lazyLoader.size() > 0 || foundValues;
rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
}
return rowValue;
}
//
// PROPERTY MAPPINGS
//
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 == DEFERED) {
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;
}
private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
if (propertyMapping.getNestedQueryId() != null) {
// 处理嵌套query类型的属性
return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
} else if (propertyMapping.getResultSet() != null) {
// 处理resultMap类型的属性,主要是:1、维护记录cacheKey和父属性/记录对Map的关联关系,便于在处理嵌套ResultMap时很快可以找到所有需要处理嵌套结果集的父属性;2、维护父属性和对应resultSet的关联关系。这两者都是为了在处理嵌套结果集是方便
addPendingChildRelation(rs, metaResultObject, propertyMapping); // TODO is that OK?
return DEFERED;
} else {
final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
return typeHandler.getResult(rs, column);
}
}
对于嵌套resultmap的处理,它的实现是这样的:
//
// HANDLE NESTED RESULT MAPS
//
private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
final DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();
// mybatis 分页处理
skipRows(rsw.getResultSet(), rowBounds);
// 前一次处理的记录,只有在映射语句的结果集无序的情况下有意义
Object rowValue = previousRowValue;
// 一直处理直到超出分页边界或者结果集处理完
while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
//同主记录,先解析到鉴别器的最底层的ResultMap
final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
// 创建当前处理记录的rowKey,规则见下文
final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null);
// 根据rowKey获取嵌套结果对象map中对应的值,因为在处理主记录时存储进去了,具体见上面addPending的流程图,所以partialObject一般不会为空
Object partialObject = nestedResultObjects.get(rowKey);
// issue #577 && #542
// 根据映射语句的结果集是否有序走不同的逻辑
if (mappedStatement.isResultOrdered()) {
// 对于有序结果集的映射语句,如果嵌套结果对象map中不包含本记录,则清空嵌套结果对象,因为此时嵌套结果对象之前的记录已经没有意义了
if (partialObject == null && rowValue != null) {
nestedResultObjects.clear();
// 添加记录到resultHandler的list属性中 或 如果是非主记录,添加到主记录对应属性的list或者object中
storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
}
rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
} else { // 正常逻辑走这里
rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
if (partialObject == null) {
storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
}
}
}
if (rowValue != null && mappedStatement.isResultOrdered() && shouldProcessMoreRows(resultContext, rowBounds)) {
storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
previousRowValue = null;
} else if (rowValue != null) {
previousRowValue = rowValue;
}
}
//
// GET VALUE FROM ROW FOR NESTED RESULT MAP
// 为嵌套resultMap创建rowValue,和非嵌套记录的接口分开
// getRowValue和applyNestedResultMappings存在递归调用,直到处理到不包含任何嵌套结果集的最后一层resultMap为止
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap/*主记录的resultMap*/, CacheKey combinedKey, String columnPrefix, Object partialObject/*嵌套resultMap的记录*/) throws SQLException {
final String resultMapId = resultMap.getId();
Object rowValue = partialObject;
if (rowValue != null) { // 此时rowValue不应该空
final MetaObject metaObject = configuration.newMetaObject(rowValue);
putAncestor(rowValue, resultMapId);
applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, false);
ancestorObjects.remove(resultMapId);
} else {
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
final MetaObject metaObject = configuration.newMetaObject(rowValue);
// 判断是否至少找到了一个不为null的属性值
boolean foundValues = this.useConstructorMappings;
if (shouldApplyAutomaticMappings(resultMap, true)) {
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
}
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
putAncestor(rowValue, resultMapId);
foundValues = applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, true) || foundValues;
ancestorObjects.remove(resultMapId);
foundValues = lazyLoader.size() > 0 || foundValues;
rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
}
if (combinedKey != CacheKey.NULL_CACHE_KEY) {
nestedResultObjects.put(combinedKey, rowValue);
}
}
return rowValue;
}
private void putAncestor(Object resultObject, String resultMapId) {
ancestorObjects.put(resultMapId, resultObject);
}
//
// NESTED RESULT MAP (JOIN MAPPING)
//
private boolean applyNestedResultMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String parentPrefix, CacheKey parentRowKey, boolean newObject) {
boolean foundValues = false;
for (ResultMapping resultMapping : resultMap.getPropertyResultMappings()) {
final String nestedResultMapId = resultMapping.getNestedResultMapId();
if (nestedResultMapId != null && resultMapping.getResultSet() == null) { // 仅仅处理嵌套resultMap的属性
try {
final String columnPrefix = getColumnPrefix(parentPrefix, resultMapping);
// 得到嵌套resultMap的实际定义
final ResultMap nestedResultMap = getNestedResultMap(rsw.getResultSet(), nestedResultMapId, columnPrefix);
if (resultMapping.getColumnPrefix() == null) {
// try to fill circular reference only when columnPrefix
// is not specified for the nested result map (issue #215)
// 不管循环嵌套resultMap的情况
Object ancestorObject = ancestorObjects.get(nestedResultMapId);
if (ancestorObject != null) {
if (newObject) {
linkObjects(metaObject, resultMapping, ancestorObject); // issue #385
}
continue;
}
}
final CacheKey rowKey = createRowKey(nestedResultMap, rsw, columnPrefix);
final CacheKey combinedKey = combineKeys(rowKey, parentRowKey);
Object rowValue = nestedResultObjects.get(combinedKey);
boolean knownValue = rowValue != null; // 第一次一定是null
// 为嵌套resultMap属性创建对象或者集合
instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject); // mandatory
// 至少有一个字段不为null
if (anyNotNullColumnHasValue(resultMapping, columnPrefix, rsw)) {
// 为嵌套resultMap创建记录
rowValue = getRowValue(rsw, nestedResultMap, combinedKey, columnPrefix, rowValue);
if (rowValue != null && !knownValue) { //获取到记录,就绑定到主记录
linkObjects(metaObject, resultMapping, rowValue/*嵌套resultMap的记录*/);
foundValues = true;
}
}
} catch (SQLException e) {
throw new ExecutorException("Error getting nested result map values for '" + resultMapping.getProperty() + "'. Cause: " + e, e);
}
}
}
return foundValues;
}
对于嵌套查询的属性处理,它的实现是这样的:
private Object getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
// 获取queryId
final String nestedQueryId = propertyMapping.getNestedQueryId();
final String property = propertyMapping.getProperty();
// 根据嵌套queryId获取映射语句对象
final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, propertyMapping, nestedQueryParameterType, columnPrefix);
Object value = null;
if (nestedQueryParameterObject != null) {
final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT, nestedBoundSql);
final Class<?> targetType = propertyMapping.getJavaType();
if (executor.isCached(nestedQuery, key)) {
executor.deferLoad(nestedQuery, metaResultObject, property, key, targetType);
value = DEFERED;
} else {
final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, nestedQueryParameterObject, targetType, key, nestedBoundSql);
if (propertyMapping.isLazy()) {
lazyLoader.addLoader(property, metaResultObject, resultLoader);
value = DEFERED;
} else {
value = resultLoader.loadResult();
}
}
}
return value;
}
private Object prepareParameterForNestedQuery(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType, String columnPrefix) throws SQLException {
if (resultMapping.isCompositeResult()) {
return prepareCompositeKeyParameter(rs, resultMapping, parameterType, columnPrefix);
} else {
return prepareSimpleKeyParameter(rs, resultMapping, parameterType, columnPrefix);
}
}
private Object prepareSimpleKeyParameter(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType, String columnPrefix) throws SQLException {
final TypeHandler<?> typeHandler;
if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
typeHandler = typeHandlerRegistry.getTypeHandler(parameterType);
} else {
typeHandler = typeHandlerRegistry.getUnknownTypeHandler();
}
return typeHandler.getResult(rs, prependPrefix(resultMapping.getColumn(), columnPrefix));
}
private Object prepareCompositeKeyParameter(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType, String columnPrefix) throws SQLException {
final Object parameterObject = instantiateParameterObject(parameterType);
final MetaObject metaObject = configuration.newMetaObject(parameterObject);
boolean foundValues = false;
for (ResultMapping innerResultMapping : resultMapping.getComposites()) {
final Class<?> propType = metaObject.getSetterType(innerResultMapping.getProperty());
final TypeHandler<?> typeHandler = typeHandlerRegistry.getTypeHandler(propType);
final Object propValue = typeHandler.getResult(rs, prependPrefix(innerResultMapping.getColumn(), columnPrefix));
// issue #353 & #560 do not execute nested query if key is null
if (propValue != null) {
metaObject.setValue(innerResultMapping.getProperty(), propValue);
foundValues = true;
}
}
return foundValues ? parameterObject : null;
}
selectMap实现
看完selectList/selectOne的实现,我们来看下selectMap的实现,需要注意的是,这个selectMap并不等价于方法public List selectList,它返回的格式直接是Map,Key是查询记录的某个字段,一般应该唯一,Value是查询记录本身,也就是Map<object.prop1,object>。
selectMap的入口为:
@Override
public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
final List<? extends V> list = selectList(statement, parameter, rowBounds);
final DefaultMapResultHandler<K, V> mapResultHandler = new DefaultMapResultHandler<K, V>(mapKey,
configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory());
final DefaultResultContext<V> context = new DefaultResultContext<V>();
for (V o : list) {
context.nextResultObject(o);
mapResultHandler.handleResult(context);
}
return mapResultHandler.getMappedResults();
}
从方法签名上,我们可以看到,和selectList不同,selectMap多了一个参数mapKey,mapKey就是用来指定返回类型中作为key的那个字段名,具体的核心逻辑委托给了selectList方法,只是在返回结果后,mapResultHandler进行了二次处理。DefaultMapResultHandler是众多ResultHandler的实现之一。DefaultMapResultHandler.handleResult()的功能就是把List转换为Map<object.prop1,object>格式。
update/insert/delete实现
看完select的实现,我们再来看update的实现。update操作的整体流程为:
具体实现代码如下:
@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();
}
}
首先设置了字段dirty=true(dirty主要用在非自动提交模式下,用于判断是否需要提交或回滚,在强行提交模式下,如果dirty=true,则需要提交或者回滚,代表可能有pending的事务),然后调用执行器实例的update()方法,如下:
@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();
// 调用具体执行器实现的doUpdate方法
return doUpdate(ms, parameter);
}
我们以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);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
其中的逻辑可以发现,和selectList的实现非常相似,先创建语句处理器,然后创建Statement实例,最后调用语句处理的update,语句处理器里面调用jdbc对应update的方法execute()。和selectList的不同之处在于:
- 在创建语句处理器期间,会根据需要调用KeyGenerator.processBefore生成前置id;
- 在执行完成execute()方法后,会根据需要调用KeyGenerator.processAfter生成后置id;
通过分析delete/insert,我们会发现他们内部都委托给update实现了,所以我们就不做重复的分析了。
4.2.3 SQL语句执行方式二 SqlSession.getMapper实现
通过SqlSession.getMapper执行CRUD语句的流程为:
我们现在来看下SqlSession的getMapper()是如何实现的。DefaultSqlSession将具体创建Mapper实现的任务委托给了Configuration的getMapper泛型方法,如下所示:
public class DefaultSqlSession {
...
@Override
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
}
public class Configuration {
...
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
}
最后调用MapperRegistry.getMapper得到Mapper的实现代理,如下所示:
public class MapperRegistry {
...
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
}
MapperRegistry又将创建代理的任务委托给MapperProxyFactory,MapperProxyFactory首先为Mapper接口创建了一个实现了InvocationHandler方法调用处理器接口的代理类MapperProxy,并实现invoke接口(其中为mapper各方法执行sql的具体逻辑),最后才调用JDK的
java.lang.reflect.Proxy为Mapper接口创建动态代理类并返回。如下所示:
public class MapperProxyFactory<T> {
...
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
这样当我们应用层执行List users = mapper.getUser2(293);的时候,JVM会首先调用MapperProxy.invoke,如下:
具体实现代码如下:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
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);
return mapperMethod.execute(sqlSession, args);
}
MapperMethod.execute实现如下:
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);
}
break;
case FLUSH: // 主要用于BatchExecutor和CacheExecutor的场景,SimpleExecutor模式不适用
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;
}
对非查询类SQL,首先将请求参数转换为mybatis内部的格式,然后调用sqlSession实例对应的方法,这就和第一种方式的SQL逻辑一样的。
对于查询类SQL,根据返回类型是void/many/map/one/cursor分别调用不同的实现入口,但主体逻辑都类似,除少数特别处理外,都是调用sqlSession.selectXXX,这里我们就不一一讲解。
4.3 动态sql
准确的说,只要mybatis的的crud语句中包含了、等标签或者${}之后,就已经算是动态sql了,所以只要在mybatis加载mapper文件期间被解析为非StaticSqlSource,就会被当做动态sql处理,在执行selectXXX或者update/insert/delete期间,就会调用对应的SqlNode接口和TextSqlNode.isDynamic()处理各自的标签以及${},并最终将每个sql片段处理到StaticTextSqlNode并生成最终的参数化静态SQL语句为止。所以,可以说,在绝大部分非PK查询的情况下,我们都是在使用动态SQL。
4.4 存储过程与函数调用实现
如果MappedStatement.StatementType类型为CALLABLE,在Executor.doQuery方法中创建语句处理器的时候,就会返回CallableStatementHandler实例,随后在调用语句处理器的初始化语句和设置参数 方法时,调用jdbc对应存储过程的prepareCall方法,如下:
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();
if (mappedStatement.getResultSetType() != null) {
return connection.prepareCall(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
} else {
return connection.prepareCall(sql);
}
}
@Override
public void parameterize(Statement statement) throws SQLException {
registerOutputParameters((CallableStatement) statement);
parameterHandler.setParameters((CallableStatement) statement);
}
private void registerOutputParameters(CallableStatement cs) throws SQLException {
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
for (int i = 0, n = parameterMappings.size(); i < n; i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() == ParameterMode.OUT || parameterMapping.getMode() == ParameterMode.INOUT) {
if (null == parameterMapping.getJdbcType()) {
throw new ExecutorException("The JDBC Type must be specified for output parameter. Parameter: " + parameterMapping.getProperty());
} else {
if (parameterMapping.getNumericScale() != null && (parameterMapping.getJdbcType() == JdbcType.NUMERIC || parameterMapping.getJdbcType() == JdbcType.DECIMAL)) {
cs.registerOutParameter(i + 1, parameterMapping.getJdbcType().TYPE_CODE, parameterMapping.getNumericScale());
} else {
if (parameterMapping.getJdbcTypeName() == null) {
cs.registerOutParameter(i + 1, parameterMapping.getJdbcType().TYPE_CODE);
} else {
cs.registerOutParameter(i + 1, parameterMapping.getJdbcType().TYPE_CODE, parameterMapping.getJdbcTypeName());
}
}
}
}
}
}
4.5 mybatis事务实现
mybatis的事务管理模式分为两种,自动提交和手工提交,DefaultSqlSessionFactory的openSession中重载中,提供了一个参数用于控制是否自动提交事务,该参数最终被传递给 java.sql.Connection.setAutoCommit()方法用于控制是否自动提交事务(默认情况下,连接是自动提交的),如下所示:
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
如上所示,返回的事务传递给了执行器,因为执行器是在事务上下文中执行,所以对于自动提交模式,实际上mybatis不需要去关心。只有非自动管理模式,mybatis才需要关心事务。对于非自动提交模式,通过sqlSession.commit()或sqlSession.rollback()发起,在进行提交或者回滚的时候会调用isCommitOrRollbackRequired判断是否应该提交或者回滚事务,如下所示:
private boolean isCommitOrRollbackRequired(boolean force) {
return (!autoCommit && dirty) || force;
}
只有非自动提交模式且执行过DML操作或者设置强制提交才会认为应该进行事务提交或者回滚操作。
对于不同的执行器,在提交和回滚执行的逻辑不一样,因为每个执行器在一级、二级、语句缓存上的差异:
- 对于简单执行器,除了清空一级缓存外,什么都不做;
- 对于REUSE执行器,关闭每个缓存的Statement以释放服务器端语句处理器,然后清空缓存的语句;
- 对于批量处理器,则执行每个批处理语句的executeBatch()方法以便真正执行语句,然后关闭Statement;
上述逻辑执行完成后,会执行提交/回滚操作。对于缓存执行器,在提交/回滚完成之后,会将TransactionCache中的entriesMissedInCache和entriesToAddOnCommit列表分别移动到语句对应的二级缓存中或清空掉。
4.6 缓存
只要实现org.apache.ibatis.cache.Cache接口的任何类都可以当做缓存,Cache接口很简单:
public interface Cache {
/**
* @return The identifier of this cache
*/
String getId();
/**
* @param key Can be any object but usually it is a {@link CacheKey}
* @param value The result of a select.
*/
void putObject(Object key, Object value);
/**
* @param key The key
* @return The object stored in the cache.
*/
Object getObject(Object key);
/**
* As of 3.3.0 this method is only called during a rollback
* for any previous value that was missing in the cache.
* This lets any blocking cache to release the lock that
* may have previously put on the key.
* A blocking cache puts a lock when a value is null
* and releases it when the value is back again.
* This way other threads will wait for the value to be
* available instead of hitting the database.
*
*
* @param key The key
* @return Not used
*/
Object removeObject(Object key);
/**
* Clears this cache instance
*/
void clear();
/**
* Optional. This method is not called by the core.
*
* @return The number of elements stored in the cache (not its capacity).
*/
int getSize();
/**
* Optional. As of 3.2.6 this method is no longer called by the core.
*
* Any locking needed by the cache must be provided internally by the cache provider.
*
* @return A ReadWriteLock
*/
ReadWriteLock getReadWriteLock();
}
mybatis提供了基本实现org.apache.ibatis.cache.impl.PerpetualCache,内部采用原始HashMap实现。第二个需要知道的方面是mybatis有一级缓存和二级缓存。一级缓存是SqlSession级别的缓存,不同SqlSession之间的缓存数据区域(HashMap)是互相不影响,MyBatis默认支持一级缓存,不需要任何的配置,默认情况下(一级缓存的有效范围可通过参数localCacheScope参数修改,取值为SESSION或者STATEMENT),在一个SqlSession的查询期间,只要没有发生commit/rollback或者调用close()方法,那么mybatis就会先根据当前执行语句的CacheKey到一级缓存中查找,如果找到了就直接返回,不到数据库中执行。其实现在代码BaseExecutor.query()中,如下所示:
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
// 如果在一级缓存中就直接获取
==list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}==
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
// 如果设置了一级缓存是STATEMENT级别而非默认的SESSION级别,一级缓存就去掉了
clearLocalCache();
}
}
return list;
}
二级缓存是mapper级别的缓存,多个SqlSession去操作同一个mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession。二级缓存默认不启用,需要通过在Mapper中明确设置cache,它的实现在CachingExecutor的query()方法中,如下所示:
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, parameterObject, boundSql);
@SuppressWarnings("unchecked")
// 如果二级缓存中找到了记录就直接返回,否则到DB查询后进行缓存
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
在mybatis的缓存实现中,缓存键CacheKey的格式为:cacheKey=ID + offset + limit + sql + parameterValues + environmentId。对于本书例子中的语句,其CacheKey为:
-1445574094:212285810:org.mybatis.internal.example.mapper.UserMapper.getUser:0:2147483647:select lfPartyId,partyName from LfParty where partyName = ? AND partyName like ? and lfPartyId in ( ?, ?):p2:p2:1:2:development
- 对于一级缓存,commit/rollback都会清空一级缓存。
- 对于二级缓存,DML操作或者显示设置语句层面的flushCache属性都会使得二级缓存失效。
在二级缓存容器的具体回收策略实现上,有下列几种:
- LRU – 最近最少使用的:移除最长时间不被使用的对象,也是默认的选项,其实现类是org.apache.ibatis.cache.decorators.LruCache。
- FIFO – 先进先出:按对象进入缓存的顺序来移除它们,其实现类是org.apache.ibatis.cache.decorators.FifoCache。
- SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象,其实现类是org.apache.ibatis.cache.decorators.SoftCache。
- WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象,其实现类是org.apache.ibatis.cache.decorators.WeakCache。
在缓存的设计上,Mybatis的所有Cache算法都是基于装饰器/Composite模式对PerpetualCache扩展增加功能。
对于模块化微服务系统来说,应该来说mybatis的一二级缓存对业务数据都不适合,尤其是对于OLTP系统来说,CRM/BI这些不算,如果要求数据非常精确的话,也不是特别合适。对这些要求数据准确的系统来说,尽可能只使用mybatis的ORM特性比较靠谱。但是有一部分数据如果前期没有很少的设计缓存的话,是很有价值的,比如说对于一些配置类数据比如数据字典、系统参数、业务配置项等很少变化的数据。
5 执行期主要类总结
mybatis在执行期间,主要有四大核心接口对象:
- 执行器Executor,执行器负责整个SQL执行过程的总体控制。
- 参数处理器ParameterHandler,参数处理器负责PreparedStatement入参的具体设置。
- 语句处理器StatementHandler,语句处理器负责和JDBC层具体交互,包括prepare语句,执行语句,以及调用ParameterHandler.parameterize()设置参数。
- 结果集处理器ResultSetHandler,结果处理器负责将JDBC查询结果映射到java对象。
5.1 执行器Executor
什么是执行器?所有我们在应用层通过sqlSession执行的各类selectXXX和增删改操作在做了动态sql和参数相关的封装处理后,都被委托给具体的执行器去执行,包括一、二级缓存的管理,事务的具体管理,Statement和具体JDBC层面优化的实现等等。所以执行器比较像是sqlSession下的各个策略工厂实现,用户通过配置决定使用哪个策略工厂。只不过执行器在一个mybatis配置下只有一个,这可能无法适应于所有的情况,尤其是哪些微服务做得不是特别好的中小型公司,因为这些系统通常混搭了OLTP和ETL功能。先来看下执行器接口的定义:
public interface Executor {
ResultHandler NO_RESULT_HANDLER = null;
int update(MappedStatement ms, Object parameter) throws SQLException;
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
<E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;
List<BatchResult> flushStatements() throws SQLException;
void commit(boolean required) throws SQLException;
void rollback(boolean required) throws SQLException;
CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);
boolean isCached(MappedStatement ms, CacheKey key);
void clearLocalCache();
void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);
Transaction getTransaction();
void close(boolean forceRollback);
boolean isClosed();
void setExecutorWrapper(Executor executor);
}
mybatis提供了下列类型的执行器:
从上述可以看出,mybatis提供了两种类型的执行器,缓存执行器与非缓存执行器(使用哪个执行器是通过配置文件中settings下的属性defaultExecutorType控制的,默认是SIMPLE),是否使用缓存执行器则是通过执行cacheEnabled控制的,默认是true。
缓存执行器不是真正功能上独立的执行器,而是非缓存执行器的装饰器模式。
我们先来看非缓存执行器。非缓存执行器又分为三种,这三种类型的执行器都基于基础执行器BaseExecutor,基础执行器完成了大部分的公共功能,如下所示:
package org.apache.ibatis.executor;
...
public abstract class BaseExecutor implements Executor {
protected Transaction transaction;
protected Executor wrapper;
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
// mybatis的二级缓存 PerpetualCache实际上内部使用的是常规的Map
protected PerpetualCache localCache;
// 用于存储过程出参
protected PerpetualCache localOutputParameterCache;
protected Configuration configuration;
protected int queryStack;
// transaction的底层连接是否已经释放
private boolean closed;
protected BaseExecutor(Configuration configuration, Transaction transaction) {
this.transaction = transaction;
this.deferredLoads = new ConcurrentLinkedQueue<DeferredLoad>();
this.localCache = new PerpetualCache("LocalCache");
this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
this.closed = false;
this.configuration = configuration;
this.wrapper = this;
}
@Override
public Transaction getTransaction() {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
return transaction;
}
// 关闭本执行器相关的transaction
@Override
public void close(boolean forceRollback) {
try {
try {
rollback(forceRollback);
} finally {
if (transaction != null) {
transaction.close();
}
}
} catch (SQLException e) {
// Ignore. There's nothing that can be done at this point.
log.warn("Unexpected exception on closing transaction. Cause: " + e);
} finally {
transaction = null;
deferredLoads = null;
localCache = null;
localOutputParameterCache = null;
closed = true;
}
}
@Override
public boolean isClosed() {
return closed;
}
// 更新操作
@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);
}
@Override
public List<BatchResult> flushStatements() throws SQLException {
return flushStatements(false);
}
public List<BatchResult> flushStatements(boolean isRollBack) throws SQLException {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
return doFlushStatements(isRollBack);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
@SuppressWarnings("unchecked")
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
@Override
public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
return doQueryCursor(ms, parameter, rowBounds, boundSql);
}
@Override
public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
DeferredLoad deferredLoad = new DeferredLoad(resultObject, property, key, localCache, configuration, targetType);
if (deferredLoad.canLoad()) {
deferredLoad.load();
} else {
deferredLoads.add(new DeferredLoad(resultObject, property, key, localCache, configuration, targetType));
}
}
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
CacheKey cacheKey = new CacheKey();
cacheKey.update(ms.getId());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
cacheKey.update(boundSql.getSql());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
// mimic DefaultParameterHandler logic
for (ParameterMapping parameterMapping : parameterMappings) {
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) {
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);
}
cacheKey.update(value);
}
}
if (configuration.getEnvironment() != null) {
// issue #176
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
@Override
public boolean isCached(MappedStatement ms, CacheKey key) {
return localCache.getObject(key) != null;
}
@Override
public void commit(boolean required) throws SQLException {
if (closed) {
throw new ExecutorException("Cannot commit, transaction is already closed");
}
clearLocalCache();
flushStatements();
if (required) {
transaction.commit();
}
}
@Override
public void rollback(boolean required) throws SQLException {
if (!closed) {
try {
clearLocalCache();
flushStatements(true);
} finally {
if (required) {
transaction.rollback();
}
}
}
}
@Override
public void clearLocalCache() {
if (!closed) {
localCache.clear();
localOutputParameterCache.clear();
}
}
// 接下去的4个方法由子类进行实现
protected abstract int doUpdate(MappedStatement ms, Object parameter)
throws SQLException;
protected abstract List<BatchResult> doFlushStatements(boolean isRollback)
throws SQLException;
protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
throws SQLException;
protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)
throws SQLException;
protected void closeStatement(Statement statement) {
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
// ignore
}
}
}
/**
* Apply a transaction timeout.
* @param statement a current statement
* @throws SQLException if a database access error occurs, this method is called on a closed <code>Statement</code>
* @since 3.4.0
* @see StatementUtil#applyTransactionTimeout(Statement, Integer, Integer)
*/
protected void applyTransactionTimeout(Statement statement) throws SQLException {
StatementUtil.applyTransactionTimeout(statement, statement.getQueryTimeout(), transaction.getTimeout());
}
private void handleLocallyCachedOutputParameters(MappedStatement ms, CacheKey key, Object parameter, BoundSql boundSql) {
if (ms.getStatementType() == StatementType.CALLABLE) {
final Object cachedParameter = localOutputParameterCache.getObject(key);
if (cachedParameter != null && parameter != null) {
final MetaObject metaCachedParameter = configuration.newMetaObject(cachedParameter);
final MetaObject metaParameter = configuration.newMetaObject(parameter);
for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
if (parameterMapping.getMode() != ParameterMode.IN) {
final String parameterName = parameterMapping.getProperty();
final Object cachedValue = metaCachedParameter.getValue(parameterName);
metaParameter.setValue(parameterName, cachedValue);
}
}
}
}
}
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
@Override
public void setExecutorWrapper(Executor wrapper) {
this.wrapper = wrapper;
}
private static class DeferredLoad {
private final MetaObject resultObject;
private final String property;
private final Class<?> targetType;
private final CacheKey key;
private final PerpetualCache localCache;
private final ObjectFactory objectFactory;
private final ResultExtractor resultExtractor;
// issue #781
public DeferredLoad(MetaObject resultObject,
String property,
CacheKey key,
PerpetualCache localCache,
Configuration configuration,
Class<?> targetType) {
this.resultObject = resultObject;
this.property = property;
this.key = key;
this.localCache = localCache;
this.objectFactory = configuration.getObjectFactory();
this.resultExtractor = new ResultExtractor(configuration, objectFactory);
this.targetType = targetType;
}
public boolean canLoad() {
return localCache.getObject(key) != null && localCache.getObject(key) != EXECUTION_PLACEHOLDER;
}
public void load() {
@SuppressWarnings( "unchecked" )
// we suppose we get back a List
List<Object> list = (List<Object>) localCache.getObject(key);
Object value = resultExtractor.extractObjectFromList(list, targetType);
resultObject.setValue(property, value);
}
}
}
我们先来看下BaseExecutor的属性,从上述BaseExecutor的定义可以看出:
- 执行器在特定的事务上下文下执行;
- 具有本地缓存和本地出参缓存(任何时候,只要事务提交或者回滚或者执行update或者查询时设定了刷新缓存,都会清空本地缓存和本地出参缓存);
- 具有延迟加载任务;
BaseExecutor实现了大部分通用功能本地缓存管理、事务提交、回滚、超时设置、延迟加载等,但是将下列4个方法留给了具体的子类实现:
protected abstract int doUpdate(MappedStatement ms, Object parameter)
throws SQLException;
protected abstract List<BatchResult> doFlushStatements(boolean isRollback)
throws SQLException;
protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
throws SQLException;
protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)
throws SQLException;
从功能上来说,这三种执行器的差别在于:
- ExecutorType.SIMPLE:这个执行器类型不做特殊的事情。它为每个语句的每次执行创建一个新的预处理语句。
- ExecutorType.REUSE:这个执行器类型会复用预处理语句。
- ExecutorType.BATCH:这个执行器会批量执行所有更新语句,也就是jdbc addBatch API的facade模式。
所以这三种类型的执行器可以说时应用于不同的负载场景下,除了SIMPLE类型外,另外两种要求对系统有较好的架构设计,当然也提供了更多的回报。
5.4.1 SIMPLE执行器
我们先来看SIMPLE各个方法的实现,
public class SimpleExecutor extends BaseExecutor {
public SimpleExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
@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);
}
}
@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 handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
@Override
protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql);
Statement stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>queryCursor(stmt);
}
@Override
public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
return Collections.emptyList();
}
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;
}
}
简单执行器的实现非常的简单,我们就不展开详述了。下面俩看REUSE执行器。
5.4.2 REUSE执行器
我们来看下REUSE执行器中和SIMPLE执行器不同的地方:
public class ReuseExecutor extends BaseExecutor {
private final Map<String, Statement> statementMap = new HashMap<String, Statement>();
public ReuseExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
Statement stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
}
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
Statement stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
}
@Override
protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql);
Statement stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>queryCursor(stmt);
}
@Override
public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
for (Statement stmt : statementMap.values()) {
closeStatement(stmt);
}
statementMap.clear();
return Collections.emptyList();
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
BoundSql boundSql = handler.getBoundSql();
String sql = boundSql.getSql();
if (hasStatementFor(sql)) {
stmt = getStatement(sql);
applyTransactionTimeout(stmt);
} else {
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
putStatement(sql, stmt);
}
handler.parameterize(stmt);
return stmt;
}
private boolean hasStatementFor(String sql) {
try {
return statementMap.keySet().contains(sql) && !statementMap.get(sql).getConnection().isClosed();
} catch (SQLException e) {
return false;
}
}
private Statement getStatement(String s) {
return statementMap.get(s);
}
private void putStatement(String sql, Statement stmt) {
statementMap.put(sql, stmt);
}
}
从实现可以看出,REUSE和SIMPLE在doUpdate/doQuery上有个差别,不再是每执行一个语句就close掉了,而是尽可能的根据SQL文本进行缓存并重用,但是由于数据库服务器端通常对每个连接以及全局的语句(oracle称为游标)handler的数量有限制,oracle中是open_cursors参数控制,mysql中是mysql_stmt_close参数控制,这就会导致如果sql都是靠if各种拼接出来,日积月累可能会导致数据库资源耗尽。其是否有足够价值,视创建Statement语句消耗的资源占整体资源的比例、以及一共有多少完全不同的Statement数量而定,一般来说,纯粹的OLTP且非自动生成的sqlmap,它会比SIMPLE执行器更好。
5.4.3 BATCH执行器
BATCH执行器的实现代码如下:
public class BatchExecutor extends BaseExecutor {
public static final int BATCH_UPDATE_RETURN_VALUE = Integer.MIN_VALUE + 1002;
// 存储在一个事务中的批量DML的语句列表
private final List<Statement> statementList = new ArrayList<Statement>();
// 存放DML语句对应的参数对象,包括自动/手工生成的key
private final List<BatchResult> batchResultList = new ArrayList<BatchResult>();
// 最新提交执行的SQL语句
private String currentSql;
// 最新提交执行的语句
private MappedStatement currentStatement;
public BatchExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
@Override
public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
final Configuration configuration = ms.getConfiguration();
final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, null, null);
final BoundSql boundSql = handler.getBoundSql();
final String sql = boundSql.getSql();
final Statement stmt;
// 如果最新执行的一条语句和前面一条语句相同,就不创建新的语句了,直接用缓存的语句,只是把参数对象添加到该语句对应的BatchResult中
// 否则的话,无论是否在未提交之前,还有pending的语句,都新插入一条语句到list中
if (sql.equals(currentSql) && ms.equals(currentStatement)) {
int last = statementList.size() - 1;
stmt = statementList.get(last);
applyTransactionTimeout(stmt);
handler.parameterize(stmt);//fix Issues 322
BatchResult batchResult = batchResultList.get(last);
batchResult.addParameterObject(parameterObject);
} else {
Connection connection = getConnection(ms.getStatementLog());
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt); //fix Issues 322
currentSql = sql;
currentStatement = ms;
statementList.add(stmt);
batchResultList.add(new BatchResult(ms, sql, parameterObject));
}
// handler.parameterize(stmt);
// 调用jdbc的addBatch方法
handler.batch(stmt);
return BATCH_UPDATE_RETURN_VALUE;
}
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
throws SQLException {
Statement stmt = null;
try {
flushStatements();
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameterObject, rowBounds, resultHandler, boundSql);
Connection connection = getConnection(ms.getStatementLog());
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
@Override
protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException {
flushStatements();
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql);
Connection connection = getConnection(ms.getStatementLog());
Statement stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return handler.<E>queryCursor(stmt);
}
@Override
public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
try {
List<BatchResult> results = new ArrayList<BatchResult>();
if (isRollback) {
return Collections.emptyList();
}
for (int i = 0, n = statementList.size(); i < n; i++) {
Statement stmt = statementList.get(i);
applyTransactionTimeout(stmt);
BatchResult batchResult = batchResultList.get(i);
try {
batchResult.setUpdateCounts(stmt.executeBatch());
MappedStatement ms = batchResult.getMappedStatement();
List<Object> parameterObjects = batchResult.getParameterObjects();
KeyGenerator keyGenerator = ms.getKeyGenerator();
if (Jdbc3KeyGenerator.class.equals(keyGenerator.getClass())) {
Jdbc3KeyGenerator jdbc3KeyGenerator = (Jdbc3KeyGenerator) keyGenerator;
jdbc3KeyGenerator.processBatch(ms, stmt, parameterObjects);
} else if (!NoKeyGenerator.class.equals(keyGenerator.getClass())) { //issue #141
for (Object parameter : parameterObjects) {
keyGenerator.processAfter(this, ms, stmt, parameter);
}
}
// Close statement to close cursor #1109
closeStatement(stmt);
} catch (BatchUpdateException e) {
StringBuilder message = new StringBuilder();
message.append(batchResult.getMappedStatement().getId())
.append(" (batch index #")
.append(i + 1)
.append(")")
.append(" failed.");
if (i > 0) {
message.append(" ")
.append(i)
.append(" prior sub executor(s) completed successfully, but will be rolled back.");
}
throw new BatchExecutorException(message.toString(), e, results, batchResult);
}
results.add(batchResult);
}
return results;
} finally {
for (Statement stmt : statementList) {
closeStatement(stmt);
}
currentSql = null;
statementList.clear();
batchResultList.clear();
}
}
}
批量执行器是JDBC Statement.addBatch的实现,对于批量insert而言比如导入大量数据的ETL,驱动器如果支持的话,能够大幅度的提高DML语句的性能(首先最重要的是,网络交互就大幅度减少了),比如对于mysql而言,在5.1.13以上版本的驱动,在连接字符串上rewriteBatchedStatements参数也就是jdbc:mysql://192.168.1.100:3306/test?rewriteBatchedStatements=true后,性能可以提高几十倍,参见 https://www.cnblogs.com/kxdblog/p/4056010.html 以及 http://blog.sina.com.cn/s/blog_68b4c68f01013yog.html 。因为BatchExecutor对于每个statementList中的语句,都执行executeBatch()方法,因此最差的极端情况是交叉执行不同的DML SQL语句,这种情况退化为原始的方式。比如下列形式就是最差的情况:
for(int i=0;i<100;i++) {
session.update("insertUser", userReq);
session.update("insertUserProfile", userReq);
}
5.4.4 缓存执行器CachingExecutor的实现
public class CachingExecutor implements Executor {
private final Executor delegate;
private final TransactionalCacheManager tcm = new TransactionalCacheManager();
public CachingExecutor(Executor delegate) {
this.delegate = delegate;
delegate.setExecutorWrapper(this);
}
@Override
public Transaction getTransaction() {
return delegate.getTransaction();
}
@Override
public void close(boolean forceRollback) {
try {
//issues #499, #524 and #573
if (forceRollback) {
tcm.rollback();
} else {
tcm.commit();
}
} finally {
delegate.close(forceRollback);
}
}
@Override
public boolean isClosed() {
return delegate.isClosed();
}
@Override
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
flushCacheIfRequired(ms);
return delegate.update(ms, parameterObject);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
@Override
public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
flushCacheIfRequired(ms);
return delegate.queryCursor(ms, parameter, rowBounds);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
// 首先判断是否启用了二级缓存
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
// 然后判断缓存中是否有对应的缓存条目(正常情况下,执行DML操作会清空缓存,也可以语句层面明确明确设置),有的话则返回,这样就不用二次查询了
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
@Override
public List<BatchResult> flushStatements() throws SQLException {
return delegate.flushStatements();
}
@Override
public void commit(boolean required) throws SQLException {
delegate.commit(required);
tcm.commit();
}
@Override
public void rollback(boolean required) throws SQLException {
try {
delegate.rollback(required);
} finally {
if (required) {
tcm.rollback();
}
}
}
// 存储过程不支持二级缓存
private void ensureNoOutParams(MappedStatement ms, BoundSql boundSql) {
if (ms.getStatementType() == StatementType.CALLABLE) {
for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
if (parameterMapping.getMode() != ParameterMode.IN) {
throw new ExecutorException("Caching stored procedures with OUT params is not supported. Please configure useCache=false in " + ms.getId() + " statement.");
}
}
}
}
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql);
}
@Override
public boolean isCached(MappedStatement ms, CacheKey key) {
return delegate.isCached(ms, key);
}
@Override
public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) {
delegate.deferLoad(ms, resultObject, property, key, targetType);
}
@Override
public void clearLocalCache() {
delegate.clearLocalCache();
}
private void flushCacheIfRequired(MappedStatement ms) {
Cache cache = ms.getCache();
if (cache != null && ms.isFlushCacheRequired()) {
tcm.clear(cache);
}
}
@Override
public void setExecutorWrapper(Executor executor) {
throw new UnsupportedOperationException("This method should not be called");
}
}
缓存执行器相对于其他执行器的差别在于,首先是在query()方法中判断是否使用二级缓存(也就是mapper级别的缓存)。虽然mybatis默认启用了CachingExecutor,但是如果在mapper层面没有明确设置二级缓存的话,就退化为SimpleExecutor了。二级缓存的维护由TransactionalCache(事务化缓存)负责,当在TransactionalCacheManager(事务化缓存管理器)中调用putObject和removeObject方法的时候并不是马上就把对象存放到缓存或者从缓存中删除,而是先把这个对象放到entriesToAddOnCommit和entriesToRemoveOnCommit这两个HashMap之中的一个里,然后当执行commit/rollback方法时再真正地把对象存放到缓存或者从缓存中删除,具体可以参见TransactionalCache.commit/rollback方法。
还有一个差别是使用了TransactionalCacheManager管理事务,其他逻辑就一样了。
5.2 参数处理器ParameterHandler
ParameterHandler的接口定义如下:
public interface ParameterHandler {
Object getParameterObject();
void setParameters(PreparedStatement ps)
throws SQLException;
}
ParameterHandler只有一个默认实现DefaultParameterHandler,它的代码如下:
public class DefaultParameterHandler implements ParameterHandler {
private final TypeHandlerRegistry typeHandlerRegistry;
private final MappedStatement mappedStatement;
private final Object parameterObject;
private final BoundSql boundSql;
private final Configuration configuration;
public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
this.mappedStatement = mappedStatement;
this.configuration = mappedStatement.getConfiguration();
this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
this.parameterObject = parameterObject;
this.boundSql = boundSql;
}
@Override
public Object getParameterObject() {
return parameterObject;
}
// 设置PreparedStatement的入参
@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 e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
} catch (SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
}
ParameterHandler的实现很简单,上面在执行语句的时候详细解释了每个步骤,这里就不重复了。
5.3 语句处理器StatementHandler
先来看下StatementHandler的定义:
public interface StatementHandler {
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;
BoundSql getBoundSql();
ParameterHandler getParameterHandler();
}
从接口可以看出,StatementHandler主要包括prepare语句、给语句设置参数、执行语句获取要执行的SQL语句本身。mybatis包含了三种类型的StatementHandler实现:
分别用于JDBC对应的PrepareStatement,Statement以及CallableStatement。BaseStatementHandler是这三种类型语句处理器的抽象父类,封装了一些实现细节比如设置超时时间、结果集每次提取大小等操作,代码如下:
public abstract class BaseStatementHandler implements StatementHandler {
protected final Configuration configuration;
protected final ObjectFactory objectFactory;
protected final TypeHandlerRegistry typeHandlerRegistry;
protected final ResultSetHandler resultSetHandler;
protected final ParameterHandler parameterHandler;
protected final Executor executor;
protected final MappedStatement mappedStatement;
protected final RowBounds rowBounds;
protected BoundSql boundSql;
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
// 首先执行SelectKey对应的SQL语句把ID生成
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);
}
@Override
public BoundSql getBoundSql() {
return boundSql;
}
@Override
public ParameterHandler getParameterHandler() {
return parameterHandler;
}
// prepare SQL语句
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
// 创建Statement
statement = instantiateStatement(connection);
setStatementTimeout(statement, transactionTimeout);
setFetchSize(statement);
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}
// 不同类型语句的初始化过程不同,比如Statement语句直接调用JDBC java.sql.Connection.createStatement,而PrepareStatement则是调用java.sql.Connection.prepareStatement
protected abstract Statement instantiateStatement(Connection connection) throws SQLException;
// 设置JDBC语句超时时间,注:数据库服务器端也可以设置语句超时时间。mysql通过参数max_statement_time设置,oracle截止12.2c不支持
protected void setStatementTimeout(Statement stmt, Integer transactionTimeout) throws SQLException {
Integer queryTimeout = null;
if (mappedStatement.getTimeout() != null) {
queryTimeout = mappedStatement.getTimeout();
} else if (configuration.getDefaultStatementTimeout() != null) {
queryTimeout = configuration.getDefaultStatementTimeout();
}
if (queryTimeout != null) {
stmt.setQueryTimeout(queryTimeout);
}
StatementUtil.applyTransactionTimeout(stmt, queryTimeout, transactionTimeout);
}
// fetchSize设置每次从服务器端提取的行数,默认不同数据库实现不同,mysql一次性提取全部,oracle默认10。正确设置fetchSize可以避免OOM并且对性能有一定的影响,尤其是在网络延时较大的情况下
protected void setFetchSize(Statement stmt) throws SQLException {
Integer fetchSize = mappedStatement.getFetchSize();
if (fetchSize != null) {
stmt.setFetchSize(fetchSize);
return;
}
Integer defaultFetchSize = configuration.getDefaultFetchSize();
if (defaultFetchSize != null) {
stmt.setFetchSize(defaultFetchSize);
}
}
protected void closeStatement(Statement statement) {
try {
if (statement != null) {
statement.close();
}
} catch (SQLException e) {
//ignore
}
}
protected void generateKeys(Object parameter) {
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
ErrorContext.instance().store();
keyGenerator.processBefore(executor, mappedStatement, null, parameter);
ErrorContext.instance().recall();
}
}
5.4 结果集处理器ResultSetHandler
结果集处理器,顾名知义,就是用了对查询结果集进行处理的,目标是将JDBC结果集映射为业务对象。其接口定义如下:
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;
}
接口中定义的三个接口分别用于处理常规查询的结果集,游标查询的结果集以及存储过程调用的出参设置。和参数处理器一样,结果集处理器也只有一个默认实现DefaultResultSetHandler。结果集处理器的功能包括对象的实例化、属性自动匹配计算、常规属性赋值、嵌套ResultMap的处理、嵌套查询的处理、鉴别器结果集的处理等,每个功能我们在分析SQL语句执行selectXXX的时候都详细的讲解过了,具体可以参见selectXXX部分。