小计01:MyBatis的OGNL
作为程序员而言,一种理想的状态是我想到什么时候而且想写的时候就在写,不受地域和时间的限制,其实和作家写文章没有什么分别,apollo源码分析(七)不想写的时候就不用写。有些人可能不知道MyBatis中使用了OGNL,有些人知道用到了OGNL却不知道在MyBatis中如何使用,本文就是讲如何在MyBatis中使用OGNL。
如果我们搜索OGNL相关的内容,通常的结果都是和Struts有关的,你肯定搜不到和MyBatis有关的,虽然和Struts中的用法类似但是换种方式理解起来就有难度。
MyBatis常用OGNL表达式e1 or e2
e1 and e2
e1 == e2,e1 eq e2
e1 != e2,e1 neq e2
e1 lt e2:小于
e1 lte e2:小于等于,其他gt(大于),gte(大于等于)
e1 in e2
e1 not in e2
e1 + e2,e1 * e2,e1/e2,e1 - e2,e1%e2
!e,not e:非,求反
e.method(args)调用对象方法
e.property对象属性值
e1[ e2 ]按索引取值,List,数组和Map
@class@method(args)调用类的静态方法
@class@field调用类的静态字段值
上述内容只是合适在MyBatis中使用的OGNL表达式,完整的表达式点击这里。
MyBatis中什么地方可以使用OGNL?如果你看过深入了解MyBatis参数,也许会有印象,因为这篇博客中提到了OGNL和一些特殊用法。
如果没看过,建议找时间看看,上面这篇博客不是很容易理解,但是理解后会很有用。
MyBatis中可以使用OGNL的地方有两处:
动态SQL表达式中
${ param}参数中
上面这两处地方在MyBatis中处理的时候都是使用OGNL处理的。
(1)动态SQL表达式中
test的值会使用OGNL计算结果
<select id="xxx" ...>select id,name,... from country<where><if test="name != null and name != ''">name like concat('%', #{ name}, '%')</if></where></select><bind>的value值会使用OGNL计算
<select id="xxx" ...>select id,name,... from country<bind name="nameLike" value="'%' + name + '%'"/><where><if test="name != null and name != ''">name like #{ nameLike}</if></where></select>注意:对<bind参数的调用可以通过#{ }或 ${ } 方式获取,#{ }可以防止注入。
在通用Mapper中支持一种UUID的主键,在通用Mapper中的实现就是使用了<bind>标签,这个标签调用了一个静态方法,exe源码提取方法大概方法如下:
<bind name="username_bind" value='@java.util.UUID@randomUUID().toString().replace("-", "")' />这种方式虽然能自动调用静态方法,但是没法回写对应的属性值,因此使用时需要注意。
(2)${ param}参数中
<select id="xxx" ...>select id,name,... from country<where><if test="name != null and name != ''">name like '${ '%' + name + '%'}'</if></where></select>${ '%' + name + '%'},而不是而不是%${ name}%,这两种方式的结果一样,但是处理过程不一样
在MyBatis中处理${ }的时候,只是使用OGNL计算这个结果值,然后替换SQL中对应的${ xxx},OGNL处理的只是${ 这里的表达式}。 这里表达式可以是OGNL支持的所有表达式,可以写的很复杂,可以调用静态方法返回值,也可以调用静态的属性值。
使用OGNL实现单表的分表功能上面说的是OGNL简单的使用方法。这里举个OGNL实现数据库分表的例子。
分表这个功能是通用Mapper中的新功能,允许在运行的时候指定一个表名,通过指定的表名对表进行操作。这个功能实现就是使用了OGNL。
首先并不是所有的表都需要该功能,因此定义了一个接口,当参数(接口方法只有实体类一个参数)对象继承该接口的时候,就允许使用动态表名。
public interface IDynamicTableName { /** * 获取动态表名 - 只要有返回值,不是null和'',就会用返回值作为表名 * * @return */String getDynamicTableName();}<if test="@tk.mybatis.mapper.util.OGNL@isDynamicParameter(_parameter) and dynamicTableName != null and dynamicTableName != ''">${ dynamicTableName}</if><if test="@tk.mybatis.mapper.util.OGNL@isNotDynamicParameter(_parameter) or dynamicTableName == null or dynamicTableName == ''">defaultTableName</if>由于我需要判断_parameter是否继承了IDynamicTableName接口,简单的写法已经无法实现,所以使用了静态方法,这两个方法如下:
/** * 判断参数是否支持动态表名 * * @param parameter * @return true支持,false不支持 */public static boolean isDynamicParameter(Object parameter) { if (parameter != null && parameter instanceof IDynamicTableName) { return true;}return false;}/** * 判断参数是否不支持动态表名 * * @param parameter * @return true不支持,false支持 */public static boolean isNotDynamicParameter(Object parameter) { return !isDynamicParameter(parameter);}根据<if>判断的php git源码分享结果来选择使用那个表名。
另外注意XML判断中有一个dynamicTableName,这个参数是根据getDynamicTableName方法得到的,MyBatis使用属性对应的getter方法来获取值,不是根据field来获取值。
原文:/post/MyBatis动态SQL标签的用法
在 MyBatis 中,动态 SQL 为开发者提供了一种灵活的方式来构建 SQL 语句,以适应不同场景的需要。动态 SQL 主要通过 OGNL 表达式实现,简化了在 SQL 语句中进行逻辑判断的过程。
实现动态 SQL 的关键元素有 if、choose、where、set 和 foreach 等。它们的用法与 JSP 中的 JSTL 语法类似,接下来分别介绍这些元素的主要功能。
动态 SQL 中的 if 语句允许开发者在 SQL 语句中添加条件判断,使得 SQL 语句的构建更加灵活。
choose 语句则为 SQL 条件提供了多选项的逻辑,使得条件判断更加丰富。
where 语句在动态 SQL 中主要用于简化 SQL 语句中 where 子句中的条件判断,帮助开发者方便地添加和管理条件。
注意:在使用 where 元素时,如果输出后的条件字符串以 "and" 开头,MyBatis 会自动忽略第一个 "and";对于以 "or" 开头的情况同样处理。在 where 元素内部,无需担心空格问题,MyBatis 会自动处理。
set 元素主要应用于更新操作,其功能与 where 元素相似,用于动态添加更新条件。
foreach 语句则提供了遍历列表元素的趋势黑马图源码功能,适用于在 SQL 语句中进行多条件操作。
动态 SQL 中 trim 元素具备在自身内容前添加前缀、后添加后缀的功能,通过 prefix、suffix、prefixOverrides 和 suffixOverrides 属性控制,有助于实现更加灵活的 SQL 语句构建。
在实际的 Java 代码开发中,通过结合 MyBatis 的动态 SQL 语句,开发者可以构建出高度灵活且适应性强的 SQL 语句,有效提升开发效率和代码可维护性。
很开心,在使用mybatis的过程中我踩到一个坑。
在实际开发过程中,我遇到了mybatis的一个问题,觉得很有必要记录下来并分享给大家。
这个坑的具体情况是这样的:在mybatis中,OgnlOps.equal(0,"")返回的是true,这违背了我们的常识,并且会带来一些问题。
接下来,我将按照遇到问题 -> 分析问题 -> 解决问题的思路,用追踪源码的方法,对这个问题进行剖析。
同时,我会分享一下我是如何通过逆向排查的方法,通过Debug模式找到关键源码,并解决这个问题的。
本文源码:mybatis 3.5.3版本。
背景介绍和需求分析
为了简化问题,我们假设有一个订单表,翡翠拍卖源码查询表结构如下:
为了方便说明,我们假设表里面只有两条数据:订单号为的订单状态为0(关闭),订单号为的订单状态为1(开启)。
已经开发好的功能是模糊查询订单名称,接口如下:
现在需要在已有功能上添加一个根据状态过滤订单的功能。
假设某个页面有这样的一个下拉框,可以根据订单状态过滤订单数据。
准备开发
现在明确了需求,根据订单状态进行过滤。
很简单,最主要的修改地方就是对mapper.xml的修改。
开始自测,遇到问题
为了确保功能的正确性,我进行了单元测试,分别传入状态0和1,预期的结果是各自查询出一条数据。
然而,执行结果却与预期不符,status=0时查询出2条数据,status=1时查询出1条数据。
当时我意识到这个问题可能并不简单,于是决定分析原因。
分析问题
为了找到问题的根源,我首先将sql打印出来,查看最终执行的sql。
通过分析sql,我发现当status为0时,mybatis并没有给我们拼接where关键字。
逆向排查法
为了定位问题,我通过日志找到了关键源码,并使用逆向排查的方法进行追踪。
最终,我发现问题的根源在于mybatis中的OgnlOps.equal(0,"")返回了true。
关键源码
通过分析源码,我找到了导致这个问题的关键代码,并解决了这个问题。
解决问题
为了解决这个问题,我修改了mapper.xml文件中的if标签,最终实现了预期效果。
总结
这次的经历让我深刻认识到,在开发过程中遇到问题时,要善于分析、思考和总结,才能不断提升自己的技能。
mybatis if else if 条件判断SQL片段表达式取值和拼接
在进行项目开发时,遇到复杂条件的动态查询是常有的事。虽然 MyBatis 不直接支持 if elseif 的判断逻辑,但可通过 choose when otherwise结构间接实现。这种结构类似于 if-else-if 条件判断,如果 choose 标签中的 when 条件不成立,则执行 otherwise 中的内容。 在先前的开发中,使用 if 标签进行条件判断,条件之间并列存在。而如今,引入 choose when otherwise 结构,仅需一个条件成立,其他条件便不再判断,若没有成立的条件,则执行 otherwise 标签中的内容。下面是一个真实的例子,旨在帮助理解这一用法,可作为参考。 例如,在筛选查询用户信息时,通过条件标签 choose when otherwise实现了 if-else-if 判断功能。choose 标签中,当条件一不成立时,会执行 otherwise 中定义的 SQL 条件。具体实现及详细使用方法,可参考相关文章了解更多。 在编写 SQL 语句时,常常会遇到一些重复的 SQL 语句片段,为避免重复编写,可以使用 SQL 片段方法。MyBatis 提供了 元素,用于定义 SQL 片段。每个片段都有唯一的 id,用于在其他地方引用。在 元素中,可以使用 、、、等标签定义复杂的 SQL 语句。下面是一个简单的示例,展示了如何定义和引用 SQL 片段。 定义 SQL 片段后,可以使用 元素引入,并在需要的地方使用。例如,查询用户信息时,可以通过片段定义 SELECT 和 WHERE 语句部分,实现代码的复用和维护。 在传递参数时,使用 #{ params} 方式获取参数值,其中的 #{ xx} 实际上是 OGNL(Object-Graph Navigation Language)表达式。MyBatis 解析 XML 映射文件时,会将 #{ xx} 转换成预处理语句参数,由 JDBC 生成 "? 符号",并在底层设置参数。 OGNL 是一种对象-图行导航语言,用于对象和视图之间的数据交互,能够访问对象属性并调用方法。MyBatis 使用 OGNL 表达式进行参数处理,生成带占位符的 SQL 语句,并设置参数。完整的 OGNL 表达式文档可以在线查看。 MyBatis 中使用 OGNL 的场景包括:MySQL 类型的 like 查询
通用 like 查询
例如,查询时使用 OGNL 计算条件值,可以灵活处理字符串的前后缀匹配。 在通用 Mapper 中,对于 UUID 主键的处理通过 元素调用静态方法实现,这种方式可以自动调用静态方法,但需要注意属性值的回写。在 like 查询中,使用正确的 OGNL 表达式 ${ '%' + name + '%'} 可确保查询结果的正确性。 处理 ${ } 表达式时,MyBatis 使用 OGNL 计算表达式的值,然后替换 SQL 中对应的 ${ xxx}。这允许在表达式中使用 OGNL 支持的所有功能,包括调用静态方法和属性值。 例如,判断入参属性值是否包含子字符串,可以使用 contains方法进行判断,以确保查询语句的正确性和效率。Mybatis OGNL导致的并发安全问题
Mybatis是一个轻量级半自动化ORM框架,通过xml描述符或注解将对象与SQL语句结合,实现面向对象与数据库映射的简化。其最大优势在于将应用程序与Sql语句解耦,Sql语句在xml文件中定义。
OGNL(Object-Graph Navigation Language)是Mybatis中广泛使用的表达式语言,用于设置和获取Java对象属性,执行列表投影和lambda表达式。灵活的OGNL表达式在Struts2等框架中应用时,也会引入可执行漏洞风险。
某公司使用Mybatis 3.2.3版本作为数据访问层。在线业务系统运行期间,出现并发安全问题。异常表现为随机出现,构造特定OGNL表达式时不会重现,具体异常堆栈信息显示List的size()方法不可访问。此问题在测试环境未重现,占总调用次数的0.%。
编写模拟多线程并发环境下的测试代码以验证问题。并发测试代码执行后,异常在预期的并发环境下重现。异常堆栈信息指向OgnlRuntime类无法访问java.util.Collections私有成员SingletonList。
问题关键在于method作为共享变量,即java.util.Collections$SingletonList.size()方法。在第一个线程允许调用method方法,第二个线程将其设为不可访问后,第一个线程再次调用时引发MethodFailedException异常。这是典型的并发同步问题。
OGNL 2.7版本已修复此问题,Mybatis在3.3.0版本中进行了修复升级。源码已直接嵌入mybatis包中,解决了并发访问共享资源导致的异常问题。
为何Mybatis将Integer为0的属性解析成空串?
在一次代码审查中,同事分享了一个有趣的问题:在Mybatis中,Integer类型的age为0时,为什么会解析成空串,导致SQL语句的条件判断失效?
为了解答疑问,作者查阅了Mybatis的源码。首先,从GitHub上的最新版本下载代码,构建测试用例。在SqlSessionFactoryBuilder的构建流程中,经过XMLConfigBuilder解析配置,构建Configuration类,进而生成SqlSessionFactory和SqlSession。执行过程中,mybatis使用SimpleExecutor或CachingExecutor,后者涉及动态代理和拦截器的执行,关键在于DynamicSqlSource和IfSqlNode类。
在IfSqlNode的evaluator.evaluateBoolean方法中,使用了OGNLCache来获取值,而问题出在OGNL表达式对空字符串的处理上。在ASTNotEq类的compareWithConversion方法中,当字符串长度为0时,会被解析为0.0,这不仅影响Integer,也影响Float和Double类型。因此,问题的根源在于OGNL表达式对空字符串的解析规则。
2024-12-29 00:38
2024-12-28 23:58
2024-12-28 22:30
2024-12-28 22:17
2024-12-28 22:10