1.JDBC参数处理
使用JDBC操作数据库时,以PreparedStatement为例:
PreparedStatement statement = connection.prepareStatement(sql);
statement.setInt(0, 0);
statement.setByte(1, (byte) 0);
statement.setBoolean(2, false);
statement.setBigDecimal(3, new BigDecimal(25000.98));
statement.setString(4, "员工,经理");
预编译SQL,对预编译SQL设置参数。对于不同的数据库字段类型,调用的setXXX方法也不一样。
因此mybatis把这些方法封装成不同的TypeHandler,我们可以指定javaType与jdbcType来选择调用。如:
LongTypeHandler、StringTypeHandler、BooleanTypeHandler等等。
决定TypeHandler的使用因素有:
-
mapper.xml文件中指定的参数类型parameterType属性,只有静态SQL有用。对于if、choose、when等是不使用的,或者${userId}
<select id="getUserByList" parameterType="list" resultMap="resultMap">
-
mapper.xml文件的参数
#{userId, jdbcType=BIGINT}
-
查询参数
UserPO simpleGetUser(@Param("userId") Long userId, @Param("userName") String userName);
2.解析xml文件中的参数设置
2.1解析parameterType
XMLStatementBuilder解析parameterType部分代码
public void parseStatementNode() {
// ...
// 参数类型
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
// 解析sql文件时会用到parameterTypeClass
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
// ...
}
XMLLanguageDriver解析为sql的代码
@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
return builder.parseScriptNode();
}
XMLScriptBuilder解析sql动态标签的代码
发现只有当mapperStatement语句不是动态SQL时,才会用到parameterType
public SqlSource parseScriptNode() {
// 解析动态标签后 组成的sql节点片段
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource;
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
2.2解析#{}
解析#{},主要分为两种
- 静态SQL(RawSqlSource)
- 动态SQL(DynamicSqlSource)
2.2.1RawSqlSource
public class RawSqlSource implements SqlSource {
private final SqlSource sqlSource;
public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
this(configuration, getSql(configuration, rootSqlNode), parameterType);
}
public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
// 如果没有指定parameterType属性就使用Object
Class<?> clazz = parameterType == null ? Object.class : parameterType;
// 解析SQL中的参数,需要使用parameterType
sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());
}
private static String getSql(Configuration configuration, SqlNode rootSqlNode) {
DynamicContext context = new DynamicContext(configuration, null);
rootSqlNode.apply(context);
return context.getSql();
}
@Override
public BoundSql getBoundSql(Object parameterObject) {
return sqlSource.getBoundSql(parameterObject);
}
}
2.2.2DynamicSqlSource
/**
* 动态SQL源码
*/
public class DynamicSqlSource implements SqlSource {
private final Configuration configuration;
private final SqlNode rootSqlNode;
public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
this.configuration = configuration;
this.rootSqlNode = rootSqlNode;
}
@Override
public BoundSql getBoundSql(Object parameterObject) {
DynamicContext context = new DynamicContext(configuration, parameterObject);
rootSqlNode.apply(context);
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
// 根据实际传入的参数来确定parameterType与静态SQL不同的地方
// mapper接口,多个参数会被转为Map
Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
context.getBindings().forEach(boundSql::setAdditionalParameter);
return boundSql;
}
}
2.2.3SqlSourceBuilder
SqlSourceBuilder.parse 解析xml文件中的#{},使用 ? 替换
//替换#{}中间的部分,如何替换,逻辑在ParameterMappingTokenHandler
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
String sql;
// SQL是否缩小空格
if (configuration.isShrinkWhitespacesInSql()) {
sql = parser.parse(removeExtraWhitespaces(originalSql));
} else {
// 把SQL中#{XXX} 替换为 ? SQL预编译是使用
sql = parser.parse(originalSql);
}
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
2.2.4ParameterMappingTokenHandler
ParameterMappingTokenHandler参数映射解析处理器,重要部分。
对于#{}的解析及使用占位符替换的逻辑都在这里
private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {
private List<ParameterMapping> parameterMappings = new ArrayList<>();
private Class<?> parameterType;
private MetaObject metaParameters;
public ParameterMappingTokenHandler(Configuration configuration, Class<?> parameterType, Map<String, Object> additionalParameters) {
super(configuration);
this.parameterType = parameterType;
this.metaParameters = configuration.newMetaObject(additionalParameters);
}
public List<ParameterMapping> getParameterMappings() {
return parameterMappings;
}
@Override
public String handleToken(String content) {
// content 内容为#{userId, jdbcType=BIGINT} 中的 userId, jdbcType=BIGINT
parameterMappings.add(buildParameterMapping(content));
// 如何替换很简单,永远是一个问号,但是参数的信息要记录在parameterMappings里面供后续使用
return "?";
}
// 构建参数映射
// #{userName,jdbcType=VARCHAR,javaType=string,typeHandler=org.apache.ibatis.type.StringTypeHandler} 为例
private ParameterMapping buildParameterMapping(String content) {
// 先解析参数映射,就是转化成一个hashmap
Map<String, String> propertiesMap = parseParameterMapping(content);
String property = propertiesMap.get("property");
Class<?> propertyType;
// 这里分支比较多,需要逐个理解
if (metaParameters.hasGetter(property)) { // issue #448 get type from additional params
propertyType = metaParameters.getGetterType(property);
} else if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
propertyType = parameterType;
} else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) {
propertyType = java.sql.ResultSet.class;
} else if (property == null || Map.class.isAssignableFrom(parameterType)) {
propertyType = Object.class;
} else {
// 为javaBean对象,通过getter方法获取属性类型
MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());
if (metaClass.hasGetter(property)) {
propertyType = metaClass.getGetterType(property);
} else {
propertyType = Object.class;
}
}
ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
Class<?> javaType = propertyType;
String typeHandlerAlias = null;
for (Map.Entry<String, String> entry : propertiesMap.entrySet()) {
String name = entry.getKey();
String value = entry.getValue();
if ("javaType".equals(name)) {
// #{}指定了javaType则以此为准,而不是以parameterType或者ParameterObject字段的属性类型为准
javaType = resolveClass(value);
builder.javaType(javaType);
} else if ("jdbcType".equals(name)) {
builder.jdbcType(resolveJdbcType(value));
} else if ("mode".equals(name)) {
builder.mode(resolveParameterMode(value));
} else if ("numericScale".equals(name)) {
builder.numericScale(Integer.valueOf(value));
} else if ("resultMap".equals(name)) {
builder.resultMapId(value);
} else if ("typeHandler".equals(name)) {
// #{}指定了typeHandler
typeHandlerAlias = value;
} else if ("jdbcTypeName".equals(name)) {
builder.jdbcTypeName(value);
} else if ("property".equals(name)) {
// Do Nothing
} else if ("expression".equals(name)) {
throw new BuilderException("Expression based parameters are not supported yet");
} else {
throw new BuilderException("An invalid property '" + name + "' was found in mapping #{" + content + "}. Valid properties are " + PARAMETER_PROPERTIES);
}
}
if (typeHandlerAlias != null) {
// #{}指定了typeHandler,根据TypeHandler获取,如果为空则又以javaType获取
builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));
}
// 最终构建ParameterMapping,如果TypeHandler还为空,则以javaType与jdbcType再去获取TypeHandler
return builder.build();
}
// builder.build()时会用到,如果javaType为Object时,为UnknownTypeHandler
private void resolveTypeHandler() {
// 没有指定typeHandler,则根据javaType,jdbcType 去typeHandlerRegistry 里面获取
if (parameterMapping.typeHandler == null && parameterMapping.javaType != null) {
Configuration configuration = parameterMapping.configuration;
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
parameterMapping.typeHandler = typeHandlerRegistry.getTypeHandler(parameterMapping.javaType, parameterMapping.jdbcType);
}
}
private Map<String, String> parseParameterMapping(String content) {
try {
return new ParameterExpression(content);
} catch (BuilderException ex) {
throw ex;
} catch (Exception ex) {
throw new BuilderException("Parsing error was found in mapping #{" + content + "}. Check syntax #{property|(expression), var1=value1, var2=value2, ...} ", ex);
}
}
}
2.3参数解析总结
解析构造parameterMappings,在setParameter时使用
-
如果是静态SQL,会使用xml配置的属性parameterType。如果没有指定则会被设置为Object.class
-
如果是动态SQL,会使用ParameterObject参数来获取字段类型
-
properType的顺序为:
- parameterType有相应的hasTypeHandler(Class<?> javaType, JdbcType jdbcType)
- 如果没有通过反射获取ParameterObject的字段类型
- #{}中指定了javaType则以此为准,而不是以parameterType或者ParameterObject字段的属性类型为准
-
解析#{}中指定了jdbcType
-
解析#{}中指定的TypeHandler
-
如果#{}中指定的TypeHandler没有在注册其中找到,则根据javaType,jdbcType(可能为空)去typeHandlerRegistry 里面获取。
-
javaType为Object,jdbcType为空时则为UnknownTypeHandler
3.ParamNameResolver
参数装换即将JAVA 方法中的普通参数,封装转换成Map,以便map中的key和sql的参数引用相对应。
UserPO simpleGetUser(@Param("userId") Long userId, @Param("userName") String userName);
<select id="simpleGetUser" resultMap="resultMap">
select
<include refid="base_column"/>
from tb_user
where user_id = #{userId, jdbcType=BIGINT}
<if test="userName != null and userName != ''">
and USER_NAME = #{userName, jdbcType=VARCHAR}
</if>
</select>
- 单个参数的情况下且没有设置@param注解会直接转换,勿略SQL中的引用名称。
- 多个参数情况:优先采用@Param中设置的名称,如果没有则用参数序号代替 即"param1、parm2…"
- 如果javac编译时设置了 -parameters 编译参数,也可以直接获取源码中的变量名称作为key
以上所有转换逻辑均在ParamNameResolver中实现
3.1ParamNameResolver
根据Mapper接口初始化接口方法中的@Param 参数。
- 设置@Param,则key为注解值
- 没有设置@Param,开启useActualParamName,则为(“arg0” , “arg1” , … “argn”)
- 没有设置@Param,没有开启useActualParamName,则为(“0”, “1”, …“n”)
public ParamNameResolver(Configuration config, Method method) {
this.useActualParamName = config.isUseActualParamName();
final Class<?>[] paramTypes = method.getParameterTypes();
// paramAnnotations(涉及方法参数使用注解[][] 第一个表示参数的个数,第二个表示每个参数列表里对应的注解)
final Annotation[][] paramAnnotations = method.getParameterAnnotations();
final SortedMap<Integer, String> map = new TreeMap<>();
int paramCount = paramAnnotations.length;
// get names from @Param annotations
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
// 去除特殊的参数(RowBounds、ResultHandler)
if (isSpecialParameter(paramTypes[paramIndex])) {
// skip special parameters
continue;
}
String name = null;
// 获取@Param注解参数的名字及参数位置
for (Annotation annotation : paramAnnotations[paramIndex]) {
if (annotation instanceof Param) {
hasParamAnnotation = true;
name = ((Param) annotation).value();
break;
}
}
if (name == null) {
// @Param was not specified.
// useActualParamName为true,返回arg0-argn
if (useActualParamName) {
name = getActualParamName(method, paramIndex);
}
if (name == null) {
// use the parameter index as the name ("0", "1", ...)
// gcode issue #71
name = String.valueOf(map.size());
}
}
map.put(paramIndex, name);
}
names = Collections.unmodifiableSortedMap(map);
}
3.2getNamedParams
把参数名称与参数值封装进Map,在设置参数时使用。
- 只有一个参数,没有使用@Param注解
- collection或者array数组转为map,key为[collection,list,array]
- 非集合,直接使用参数本身
- 多个参数,转为Map
- 使用@Param注解,key为注解值
- 没有使用@Param注解,key为(“arg0” , “arg1” , … “argn”)或者(“0”, “1”, …“n”),与ParamNameResolver初始化有关
- 同时会有key为param1,param2,param3…
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
if (args == null || paramCount == 0) {
return null;
} else if (!hasParamAnnotation && paramCount == 1) {
// 没有使用@Param注解,且只有一个参数。
Object value = args[names.firstKey()];
// 把collection或者array数组转为map.key为[collection,list,array],不然直接返回参数本身
return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
} else {
// 多个参数,转为map 此时的@Param注解 起到作用,key为注解的值。同时把param+参数序号 也存入map。value为具体的参数值
// 单个参数使用@Param注解
final Map<String, Object> param = new ParamMap<>();
int i = 0;
for (Map.Entry<Integer, String> entry : names.entrySet()) {
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)
final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
// ensure not to overwrite parameter named with @Param
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
4.DefaultParameterHandler
构建DefaultParameterHandler需要MappedStatement、Object(参数)、BoundSql
在构建PreparedStatementHandler等jdbc处理器时,会在BaseStatementHandler构造器中创建ParameterHandler
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// ...
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
// ...
}
参数映射
映射是指Map中的key如何与SQL中绑定的参数相对应。以下这几种情况
- 单个原始类型:直接映射,勿略SQL中引用名称
- Map类型:基于Map key映射
- Object:基于属性名称映射,支持嵌套对象属性访问
在Object类型中,支持通过“.”方式映射属中的属性。如:user.age
参数赋值
通过TypeHandler 为PrepareStatement设置值,通常情况下一般的数据类型MyBatis都有与之相对应的TypeHandler
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
// SQL解析的参数映射关系
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
// 循环设参数,sql中的占位符参数
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) {
// 若参数为null,直接设null
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
// 若参数有相应的TypeHandler,直接设object
// 适用于一个参数,不使用@Param注解
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 {
// 根据参数占位符的位置设置参数,在BaseTypeHandler中
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
ParameterMapping的TypeHandler为UnknownTypeHandler后,会根据parameter的参数类型与jdbcType再去查找具体的TypeHandler,之后进行设置参数
// BaseTypeHandler中的方法
public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
if (parameter == null) {
if (jdbcType == null) {
throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
}
try {
// value值为null
ps.setNull(i, jdbcType.TYPE_CODE);
} catch (SQLException e) {
throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . "
+ "Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. "
+ "Cause: " + e, e);
}
} else {
try {
// 非null,抽象方法在具体的子类中
setNonNullParameter(ps, i, parameter, jdbcType);
} catch (Exception e) {
throw new TypeException("Error setting non null for parameter #" + i + " with JdbcType " + jdbcType + " . "
+ "Try setting a different JdbcType for this parameter or a different configuration property. "
+ "Cause: " + e, e);
}
}
}
// UnknownTypeHandler中的方法
public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType)
throws SQLException {
// 根据parameter的参数类型与jdbcType再去查找具体的TypeHandler
TypeHandler handler = resolveTypeHandler(parameter, jdbcType);
handler.setParameter(ps, i, parameter, jdbcType);
}
private TypeHandler<?> resolveTypeHandler(Object parameter, JdbcType jdbcType) {
TypeHandler<?> handler;
if (parameter == null) {
handler = OBJECT_TYPE_HANDLER;
} else {
handler = typeHandlerRegistrySupplier.get().getTypeHandler(parameter.getClass(), jdbcType);
// check if handler is null (issue #270)
if (handler == null || handler instanceof UnknownTypeHandler) {
handler = OBJECT_TYPE_HANDLER;
}
}
return handler;
}
setParameter(ps, i, parameter, jdbcType);
}
private TypeHandler<?> resolveTypeHandler(Object parameter, JdbcType jdbcType) {
TypeHandler<?> handler;
if (parameter == null) {
handler = OBJECT_TYPE_HANDLER;
} else {
handler = typeHandlerRegistrySupplier.get().getTypeHandler(parameter.getClass(), jdbcType);
// check if handler is null (issue #270)
if (handler == null || handler instanceof UnknownTypeHandler) {
handler = OBJECT_TYPE_HANDLER;
}
}
return handler;
}