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);
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() {
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);
Class<?> clazz = parameterType == null ? Object.class : 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
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);
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文件中的#{},使用 ? 替换
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;
if (configuration.isShrinkWhitespacesInSql()) {
sql = parser.parse(removeExtraWhitespaces(originalSql));
} else {
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) {
parameterMappings.add(buildParameterMapping(content));
return "?";
}
private ParameterMapping buildParameterMapping(String content) {
Map<String, String> propertiesMap = parseParameterMapping(content);
String property = propertiesMap.get("property");
Class<?> propertyType;
if (metaParameters.hasGetter(property)) {
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 {
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 = 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)) {
typeHandlerAlias = value;
} else if ("jdbcTypeName".equals(name)) {
builder.jdbcTypeName(value);
} else if ("property".equals(name)) {
} 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) {
builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));
}
return builder.build();
}
private void resolveTypeHandler() {
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();
final Annotation[][] paramAnnotations = method.getParameterAnnotations();
final SortedMap<Integer, String> map = new TreeMap<>();
int paramCount = paramAnnotations.length;
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
if (isSpecialParameter(paramTypes[paramIndex])) {
continue;
}
String name = null;
for (Annotation annotation : paramAnnotations[paramIndex]) {
if (annotation instanceof Param) {
hasParamAnnotation = true;
name = ((Param) annotation).value();
break;
}
}
if (name == null) {
if (useActualParamName) {
name = getActualParamName(method, paramIndex);
}
if (name == null) {
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) {
Object value = args[names.firstKey()];
return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
} else {
final Map<String, Object> param = new ParamMap<>();
int i = 0;
for (Map.Entry<Integer, String> entry : names.entrySet()) {
param.put(entry.getValue(), args[entry.getKey()]);
final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
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());
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)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
ParameterMapping的TypeHandler为UnknownTypeHandler后,会根据parameter的参数类型与jdbcType再去查找具体的TypeHandler,之后进行设置参数
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 {
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 {
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);
}
}
}
public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType)
throws SQLException {
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);
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; }
|