1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > Mybatis源码解析:sql参数处理(3)

Mybatis源码解析:sql参数处理(3)

时间:2019-09-05 10:48:42

相关推荐

Mybatis源码解析:sql参数处理(3)

入参#{}的解析

那么如果是#{}该怎么处理呢?

<select id="get" resultType="com.entity.User">select * from user where id = #{id}</select>List<User> get(Integer id);

由上文得知,由于没有${},那么SqlSource就会变成RawSqlSource。在创建RawSqlSource的时候,在构造方法中就会对#{}解析。

RawSqlSource的构造方法。

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<>());}

SqlSourceBuilder.parse

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 = parser.parse(originalSql);return new StaticSqlSource(configuration, sql, handler.getParameterMappings());}

这里用的hander是ParameterMappingTokenHandler,它的作用是将#{XXX}替换成

ParameterMappingTokenHandler.handleToken

public String handleToken(String content) {parameterMappings.add(buildParameterMapping(content));return "?";}

这时sql就变成了select * from user where id = ?,到这里还只是解析配置文件。在具体执行方法时也要调用getBoundSql方法将参数进行赋值

//RawSqlSource.getBoundSqlpublic BoundSql getBoundSql(Object parameterObject) {return sqlSource.getBoundSql(parameterObject);}

StaticSqlSource.getBoundSql,最后调用BoundSql的构造方法,将sql语句,入参等传入

public BoundSql getBoundSql(Object parameterObject) {return new BoundSql(configuration, sql, parameterMappings, parameterObject);}

之后就要创建数据库连接,进行查询了。回到这个方法SimpleExecutor.prepareStatement。回顾一下,这是创建StatementHandler后做的一些连接数据库的准备操作。

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {Statement stmt;//获取jdbc数据库连接Connection connection = getConnection(statementLog);//一些准备工作,初始化Statement连接stmt = handler.prepare(connection, transaction.getTimeout());//使用ParameterHandler处理入参handler.parameterize(stmt);return stmt;}

我们先进入这个方法PreparedStatementHandler.parameterize。

为什么是PreparedStatementHandler之前也说过,因为语句的默认类型是PREPARED, 还有其他的类型如果是CALLABLE,对应CallableStatementHandler,STATEMENT对应SimpleStatementHandler。可以用参数statementType进行设置。

@Overridepublic void parameterize(Statement statement) throws SQLException {parameterHandler.setParameters((PreparedStatement) statement);}

DefaultParameterHandler.setParameters.

@Overridepublic void setParameters(PreparedStatement ps) {ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());//boundSql用来解析我们的sql语句,parameterMappings是我们传入的参数List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();if (parameterMappings != null) {for (int i = 0; i < parameterMappings.size(); i++) {//这里第一个参数就是idParameterMapping parameterMapping = parameterMappings.get(i);//mode属性允许能指定IN,OUT或INOUT参数。如果参数的 mode 为 OUT 或 INOUT,将会修改参数对象的属性值,以便作为输出参数返回。//#{id}默认mode为OUTif (parameterMapping.getMode() != ParameterMode.OUT) {Object value;String propertyName = parameterMapping.getProperty();//这里是boundsql中的额外参数,可以使用拦截器添加,例子放在下文if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional paramsvalue = boundSql.getAdditionalParameter(propertyName);} else if (parameterObject == null) {value = null;//如果类型处理器中有这个类型,那么直接赋值就行了,例如这里是Integer类型,类型处理器是有的//那么直接赋值} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {value = parameterObject;} else {//如果不是的会转化为元数据进行处理,metaObject元数据可以理解为用来反射的工具类,可以处理参数的get,setMetaObject 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中的PreparedStatement设置参数typeHandler.setParameter(ps, i + 1, value, jdbcType);} catch (TypeException | SQLException e) {throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);}}}}}

value如果是空的那么就直接设置为jdbc的空类型,不为空调用具体的类型处理器。

BaseTypeHandler.setParameter。该类是所有typeHandler的父类.如果不为空调用setNonNullParameter,该方法时抽象的,由具体的子类实现。这里使用的是一个相当于路由的的子类UnknownTypeHandler,这个子类可以根据传入的类型,再去找到具体的类型处理器,例如IntegerTypeHander.

@Overridepublic 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);}}}

UnknownTypeHandler.setNonNullParameter

public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType)throws SQLException {TypeHandler handler = resolveTypeHandler(parameter, jdbcType);handler.setParameter(ps, i, parameter, jdbcType);}

UnknownTypeHandler.resolveTypeHandler这个方法根据传入的参数类型,找到具体的TypeHandler

private TypeHandler<?> resolveTypeHandler(Object parameter, JdbcType jdbcType) {TypeHandler<?> handler;if (parameter == null) {handler = OBJECT_TYPE_HANDLER;} else {handler = typeHandlerRegistry.getTypeHandler(parameter.getClass(), jdbcType);// check if handler is null (issue #270)if (handler == null || handler instanceof UnknownTypeHandler) {handler = OBJECT_TYPE_HANDLER;}}return handler;}

例如如果这个参数是id,Integer类型,那么就会找到IntegerTypeHandler

//IntegerTypeHandlerpublic void setNonNullParameter(PreparedStatement ps, int i, Integer parameter, JdbcType jdbcType)throws SQLException {ps.setInt(i, parameter);}

最后还是使用jdbc的PreparedStatement处理参数。

附:自定义的拦截器用来加入参数。

@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class,Integer.class})})public class MyInterceptor implements Interceptor {@Overridepublic Object intercept(Invocation invocation) throws Throwable {StatementHandler bs = (StatementHandler) invocation.getTarget();BoundSql boundSql = bs.getBoundSql();boundSql.setAdditionalParameter("id","1");return invocation.proceed();}}

例4 ${}和#{}都存在的情况

如果是都存在的情况呢?

<select id="findUserByIdAndName" resultType="com.entity.User">select * from user where id = ${id} AND name = #{name}</select>List<User> findUserByIdAndName(@Param("id") Integer id, @Param("name") String name);

结合上文的分析,由于存在${},所以选择的DynamicSqlSource。

DynamicSqlSource.getBoundSql。这个方法上文分析到了rootSqlNode.apply(context);会将${}替换成具体参数。我们接着分析。

public BoundSql getBoundSql(Object parameterObject) {//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;}

解析#{},并将其替换成?

//RawSqlSource.parsepublic SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);String sql = parser.parse(originalSql);return new StaticSqlSource(configuration, sql, handler.getParameterMappings());}

这样我们的语句就变成了select * from user where id = 1 AND name = ?,然后调用sqlSource.getBoundSql

//StaticSqlSource.getBoundSqlpublic BoundSql getBoundSql(Object parameterObject) {return new BoundSql(configuration, sql, parameterMappings, parameterObject);}

最后的处理方式与例3相同,使用jdbc自带的PreparedStatement进行参数处理。

小结

当我们在解析mapper.xml文件时,就会将sql进行第一遍的解析,将其中的全局变量替换成具体的值。

接着进行第二遍的解析,选择不同的SqlSource。这一边的解析不改变语句中的sql内容。

如果语句中包含${},就选择DynamicSqlSource,等待具体执行sql的时候再做处理.如果仅包含#{}类型的,就选择RawSqlSource。RawSqlSource在创建的时候就会有进行一轮的解析,将语句中的#{XXX}替换为 ?(问号)

之后在执行具体的语句才动态的替换,如果之前选择的是DynamicSqlSource,那么进行两次的解析,第一次将${}替换成具体值,第二次解析#{},使用jdbc的PreparedStatement处理。如果选择的是RawSqlSource,那么这条语句就只有#{},直接用PreparedStatement处理。

可以发现,无论什么类型的sql都会被解析了4次。

需要更多教程,微信扫码即可

👆👆👆

别忘了扫码领资料哦【高清Java学习路线图】

和【全套学习视频及配套资料】

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。