MyBatis预编译机制详解

您所在的位置:网站首页 前端预编译是什么 MyBatis预编译机制详解

MyBatis预编译机制详解

2024-07-13 05:38| 来源: 网络整理| 查看: 265

MyBatis预编译机制详解 一. "#{}“和”${}"的区别

"#{}"是将传入的值按照字符串的形式进行处理,如下面这条语句:

select user_id,user_name from t_user where user_id = #{user_id}

MyBaits会首先对其进行预编译,将#{user_ids}替换成?占位符,然后在执行时替换成实际传入的user_id值,**并在两边加上单引号,以字符串方式处理。**下面是MyBatis执行日志:

10:27:20.247 [main] DEBUG william.mybatis.quickstart.mapper.UserMapper.selectById - ==> Preparing: select id, user_name from t_user where id = ? 10:27:20.285 [main] DEBUG william.mybatis.quickstart.mapper.UserMapper.selectById - ==> Parameters: 1(Long)

因为"#{}"会在传入的值两端加上单引号,所以可以很大程度上防止SQL注入。有关SQL注入的知识会在后文进行说明。因此在大多数情况下,建议使用"#{}"。

"${}"是做简单的字符串替换,即将传入的值直接拼接到SQL语句中,且不会自动加单引号。将上面的SQL语句改为:

select user_id,user_name from t_user where user_id = ${user_id}

再观察MyBatis的执行日志:

10:41:32.242 [main] DEBUG william.mybatis.quickstart.mapper.UserMapper.selectById - ==> Preparing: select id, user_name, real_name, sex, mobile, email, note, position_id from t_user where id = 1 10:41:32.288 [main] DEBUG william.mybatis.quickstart.mapper.UserMapper.selectById - ==> Parameters:

可以看到,参数是直接替换的,且没有单引号处理,这样就有SQL注入的风险。

但是在一些特殊情况下,使用${}是更适合的方式,如表名、orderby等。见下面这个例子:

select user_id,user_name from ${table_name} where user_id = ${user_id}

这里如果想要动态处理表名,就只能使用"${}",因为如果使用"#{}",就会在表名字段两边加上单引号,变成下面这样:

select user_id,user_name from 't_user' where user_id = ${user_id}

这样SQL语句就会报错。

二. MyBatis预编译源码分析

MyBatis对SQL语句解析的处理在XMLStatementBuilder类中,见源码:

/** * 解析mapper中的SQL语句 */ public void parseStatementNode() { //SQL语句id,对应着Mapper接口的方法 String id = context.getStringAttribute("id"); //校验databaseId是否匹配 String databaseId = context.getStringAttribute("databaseId"); if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) { return; } //SQL标签属性解析 Integer fetchSize = context.getIntAttribute("fetchSize"); Integer timeout = context.getIntAttribute("timeout"); String parameterMap = context.getStringAttribute("parameterMap"); String parameterType = context.getStringAttribute("parameterType"); Class parameterTypeClass = resolveClass(parameterType); //参数类型 String resultMap = context.getStringAttribute("resultMap"); String resultType = context.getStringAttribute("resultType"); String lang = context.getStringAttribute("lang"); LanguageDriver langDriver = getLanguageDriver(lang); Class resultTypeClass = resolveClass(resultType); //结果类型 String resultSetType = context.getStringAttribute("resultSetType"); //Statement类型,默认PreparedStatement StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString())); ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); String nodeName = context.getNode().getNodeName(); //SQL命令类型:增删改查 SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH)); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect); boolean useCache = context.getBooleanAttribute("useCache", isSelect); boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false); // Include Fragments before parsing XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); includeParser.applyIncludes(context.getNode()); // Parse selectKey after includes and remove them. processSelectKeyNodes(id, parameterTypeClass, langDriver); // Parse the SQL (pre: and were parsed and removed) //重要:解析SQL语句,封装成一个SqlSource SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); String resultSets = context.getStringAttribute("resultSets"); String keyProperty = context.getStringAttribute("keyProperty"); String keyColumn = context.getStringAttribute("keyColumn"); KeyGenerator keyGenerator; String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX; keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true); if (configuration.hasKeyGenerator(keyStatementId)) { keyGenerator = configuration.getKeyGenerator(keyStatementId); } else { keyGenerator = context.getBooleanAttribute("useGeneratedKeys", configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE; } //解析完毕,最后通过MapperBuilderAssistant创建MappedStatement对象,统一保存到Configuration的mappedStatements属性中 builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); }

前面是对SQL标签的一些处理,如id、缓存、结果集映射等。我们这次主要分析预编译机制,因此重点关注 SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass)这个方法。这该方法会通过LanguageDriver对SQL语句进行解析,生成一个SqlSource。SqlSource封装了映射文件或者注解中定义的SQL语句,它不能直接交给数据库执行,因为里面可能包含动态SQL或者占位符等元素。而MyBatis在实际执行SQL语句时,会调用SqlSource的getBoundSql()方法获取一个BoundSql对象,BoundSql是将SqlSource中的动态内容经过处理后,返回的实际可执行的SQL语句,其中包含?占位符List封装的有序的参数映射关系,此外还有一些额外信息标识每个参数的属性名称等。

LanguageDriver的默认实现类是XMLLanguageDriver,我们进入到这个方法里面看下:

//创建SqlSource @Override public SqlSource createSqlSource(Configuration configuration, XNode script, Class parameterType) { //创建XMLScriptBuilder对象 XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType); //通过XMLScriptBuilder解析SQL脚本 return builder.parseScriptNode(); }

这里通过XMLScriptBuilder对象的parseScriptNode()方法进行SQL脚本的解析,继续跟进去:

/** * 解析SQL脚本 */ public SqlSource parseScriptNode() { //解析动态标签,包括动态SQL和${}。执行后动态SQL和${}已经被解析完毕。 //此时SQL语句中的#{}还没有处理,#{}会在SQL执行时动态解析 MixedSqlNode rootSqlNode = parseDynamicTags(context); //如果是dynamic的,则创建DynamicSqlSource,否则创建RawSqlSource SqlSource sqlSource = null; if (isDynamic) { sqlSource = new DynamicSqlSource(configuration, rootSqlNode); } else { sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType); } return sqlSource; }

parseScriptNode的功能就是判断该SQL节点是否是动态的,然后根据是否动态返回DynamicSqlSource或

RawSqlSource。是否为动态SQL的判断在parseDynamicTags()方法中:

protected MixedSqlNode parseDynamicTags(XNode node) { List contents = new ArrayList(); NodeList children = node.getNode().getChildNodes(); for (int i = 0; i


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3