Mybatis | 动态 SQL 基石 —— Ognl

凛冽严凝雾气昏。空中瑞雪降纷纷。须臾四野难分别,顷刻山河不见痕。
银世界,玉乾坤。望中隐隐接昆仑。若还下到三更后,直要填平玉帝门。
                                                       —— 长沙今天雪下的很大,2021/12/26 晚

本文源码使用到 Jar 包版本约束如下:

  • Spring Boot,2.4.12;
  • mybatis-spring-boot-starter,1.3.2;

一、概述

动态 SQL 是 MyBatis 的强大特性之一。如果使用过 JDBC 或其它类似的框架,应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。

例如,如下 SQL :

<select id="findActiveBlogWithTitleLike"
     resultType="Blog">
  SELECT * FROM BLOG
  WHERE state = ‘ACTIVE’
  <if test="title != null">
    AND title like #{title}
  </if>
</select>

这条语句提供了可选的查找文本功能。如果不传入 “title”,那么所有处于 “ACTIVE” 状态的 BLOG 都会返回;如果传入了 “title” 参数,那么就会对 “title” 一列进行模糊查找并返回对应的 BLOG 结果。
除了 if, Mybatis 还支持 choosewhenotherwisetrimwhere 等动态 SQL 元素,值得注意的是,这些元素中有些属性是需要通过表达式计算的,比如 if 元素的 test 属性,在 test 属性计算结果为 true 的情况下才会把元素内容拼接到 SQL 。
那这种表达式语法是什么呢?怎么书写呢?

二、OgnlCache

常用的表达式语言有 Spring Framework 的 SpEL,Ognl(Object-Graph Navigation Language),JEXL等,Mybatis 动态 SQL 基于 Ognl 的表达式语言。
目前,最新 Ognl Jar包版本为 ognl.3.3.0.jar,主要 API 集中在 ognl.Ognl 类中。

Ognl 有如下几个核心概念:

  • 表达式,表示要执行的操作,如上例 if 标签的 test 属性;
  • OgnlContext,执行上下文,包含一个 Object 类型的 rootMap 的非根对象集合。注意,根对象只能有一个,而非根对象可以有多个, 非根对象要通过 "#key" 访问,根对象可以省略 "#key" 直接访问其属性;

Mybatis 没有直接使用 ognl.Ognl,而是对 ognl.Ognl 进行了封装 —— OgnlCache

public final class OgnlCache {

	private static final Map<String, Object> expressionCache = new ConcurrentHashMap<String, Object>();
	
	private OgnlCache() {
		// Prevent Instantiation of Static Class
	}
	
	public static Object getValue(String expression, Object root) {
	try {
		Map<Object, OgnlClassResolver> context = Ognl.createDefaultContext(root, new OgnlClassResolver());
			return Ognl.getValue(parseExpression(expression), context, root);
		} catch (OgnlException e) {
			throw new BuilderException("Error evaluating expression '" + expression + "'. Cause: " + e, e);
		}
	}
	
	private static Object parseExpression(String expression) throws OgnlException {
		Object node = expressionCache.get(expression);
		if (node == null) {
			node = Ognl.parseExpression(expression);
			expressionCache.put(expression, node);
		}
		return node;
	}
}

顾名思义,OgnlCache 是在 ognl.Ognl 上增加了缓存机制,提高 Ognl 表达式计算效率,缓存主要体现在如下两个方面,

  • Ognl 表达式需要解析,为了避免重复解析,Mybatis 缓存 Ognl 表达式解析结果缓存。
  • Mybatis 使用自定义类解析器 OgnlClassResolver,该类解析器会将已经解析的 Class<?> 缓存;

OgnlCache 对外值提供 getValue(String expression, Object root) ,该方法在跟对象为 root 执行上下文中计算 expression 的值。

二、Ognl 语法

那我们如何在动态 SQL 中书写 Ognl 表达式,Ognl 表达式语言语法又是怎么样呢?

1. 常量

Ognl 包含如下几种形式常量:

  • 字符串常量,单引号 '' 或者双引号 "" 引起的若干个字符;
  • 字符常量,单引号 '' 引起的若单个字符;
  • Ognl 除了支持 Java 中 int(默认整数类型),long(以 l 作为后缀),float(以 f 作为后缀),double(默认小数类型)外,还支持 BigDecimal(以 bB 作为后缀)和 BigInteger(以 hH 作为后缀);
  • Boolean 常量,truefalse
  • null
public static void main(String[] args) throws Exception {

	printValueAndClass(OgnlCache.getValue("'foo'", new Object())); // foo, java.lang.String
	
	printValueAndClass(OgnlCache.getValue("\"foo\"", new Object())); // foo, java.lang.String
	
	printValueAndClass(OgnlCache.getValue("'b'", new Object())); // b, java.lang.Character
	
	printValueAndClass(OgnlCache.getValue("7", new Object())); // 7, java.lang.Integer
	
	printValueAndClass(OgnlCache.getValue("7l", new Object())); // 7, java.lang.Long
	
	printValueAndClass(OgnlCache.getValue("7f", new Object())); // 7.0, java.lang.Float
	
	printValueAndClass(OgnlCache.getValue("7.0", new Object())); // 7.0, java.lang.Double
	
	printValueAndClass(OgnlCache.getValue("7b", new Object())); // 7, java.math.BigDecimal
	
	printValueAndClass(OgnlCache.getValue("7h", new Object())); // 7, java.math.BigInteger
	
	printValueAndClass(OgnlCache.getValue("true", new Object())); // true, java.lang.Boolean
	
	printValueAndClass(OgnlCache.getValue("null", new Object())); // null

}

private static void printValueAndClass(Object object) {

	System.err.println(object);
	if(object != null) {
		System.err.println(object.getClass());
	}
	System.err.println();
}

2. 属性引用

Ognl 对于不同类型对象处理属性引用方式不同,

  • Map,以 key 作为属性名引用属性值 value
  • List 或者数组,对于数字类型属性,作为下标索引 List 或者数组中的元素,对于字符串类型属性,处理方式和简单对象处理属性引用方式相同;
  • 简单对象,只能处理字符串类型属性,如果该属性存在 get/set 方法,则使用该属性 get/set 方法访问该属性,否则访问和给定属性名相同的字段;

public static void main(String[] args) throws Exception {

	Map<Object, Object> map = Maps.newHashMap();
	map.put("foo", "bar");
	map.put("Aircraft", Lists.newArrayList("Airplane", "Helicopter"));
	map.put("Color", new Object[] { "Red", "Green" });
	
	// Map 直接访问 key
	printValueAndClass(OgnlCache.getValue("foo", map)); // bar, java.lang.String
	
	// List 属性为数字, 作为下标索引
	printValueAndClass(OgnlCache.getValue("Aircraft[0]", map)); // Airplane, java.lang.String
	
	// List 属性为字符串, 访问其相应字段
	printValueAndClass(OgnlCache.getValue("Aircraft.size", map)); // 2, class java.lang.Integer
	
	// 数组属性为数字, 作为下标索引
	printValueAndClass(OgnlCache.getValue("Color[0]", map)); // Red, java.lang.String
	
	// 数组属性为字符串, 访问其相应字段
	printValueAndClass(OgnlCache.getValue("Color.length", map)); // 2, java.lang.Integer
	
	// 访问简单对象属性
	printValueAndClass(OgnlCache.getValue("name", new Person("foo"))); // foo, java.lang.String

}

3. 检索

Ognl 内部处理 array.length 表达式与表达式 array["length"] 完全相同,并且表达式 array["len" + "gth"] 和前面两个表达式执行结果相同。

publicstatic void main(String[] args) throws Exception {

	Map<Object, Object> map = Maps.newHashMap();
	map.put("array", new Object[] {});
	
	printValueAndClass(OgnlCache.getValue("array.length", map)); // 0, java.lang.Integer
	printValueAndClass(OgnlCache.getValue("array[\"length\"]", map)); // 0, java.lang.Integer
	printValueAndClass(OgnlCache.getValue("array[\"len\" + \"gth\"]", map)); // 0, java.lang.Integer

}

对于 Java 数组和 List 索引比较简单,只需要给出下标即可检索出相应元素,下标超出数组或 List 的边界时,抛 IndexOutOfBoundsException 异常。

public static void main(String[] args) throws Exception {

	Map<Object, Object> map = Maps.newHashMap();
	map.put("array", new Object[] {});
	// 抛「ArrayIndexOutOfBoundsException」异常, IndexOutOfBoundsException 子类 
	printValueAndClass(OgnlCache.getValue("array[0]", map));

}

JavaBeans 支持索引属性的概念,具体来说,这意味着一个对象具有一组遵循以下模式的方法:

  • public PropertyType[] getPropertyName()
  • public void setPropertyName(PropertyType[] anArray)
  • public PropertyType getPropertyName(int index)
  • public void setPropertyName(int index, PropertyType value)

Ognl 可以解释这一点,并通过索引下标提供对属性的无缝访问。
例如,定义 Aircraft 类如下:

public class Aircraft {

    private String[] colors = new String[1024];

    public Aircraft(String[] colors) {
        super();
        this.colors = colors;
    }

    public String[] getColor() {
        return colors;
    }

    public void setColor(String[] colors) {
        this.colors = colors;
    }

    public String getColor(int index) {
        return colors[index];
    }

    public void setColor(int index, String color) {
        colors[index] = color;
    }

    public String[] getColors() {
        return colors;
    }

    public void setColors(String[] colors) {
        this.colors = colors;
    }

}

该类包含 String[] getColor()setColor(String[] colors)getColor(int index)setColor(int index, String color) 方法,所以该类具有索引属性 color,可以通过索引下标访问:

publicstatic void main(String[] args) throws Exception {
	// Red, java.lang.String
	printValueAndClass(OgnlCache.getValue("color[0]", new Aircraft(new String[] { "Red", "Green" })));

}

Ognl 扩展了索引属性的概念可以包括使用任意对象索引,而不仅仅是像 JavaBeans 索引属性那样只可以使用整数进行索引。 在以对象进行索引时,Ognl 查找具有以下签名的方法:

  • public PropertyType getPropertyName(IndexType index)
  • public void setPropertyName(IndexType index, PropertyType value)

PropertyTypeIndexType 必须在相应的 set 和 get 方法中匹配,例如,Aircraft 如下:

public class Aircraft {

private Map<String, String> map = Maps.newHashMap();

	public String getAttribute(String name) {
		return map.get(name);
	
	}
	
	public void setAttribute(String name, String value) {
		map.put(name, value);
	}

}

Aircraft 提供了 public String getAttribute(String name)public void setAttribute(String name, String value) 方法,可以使用对象索引:

publicstatic void main(String[] args) throws Exception {

	Aircraft aircraft = new Aircraft();
	
	// 对象索引赋值
	printValueAndClass(OgnlCache.getValue("attribute[\"foo\"] = 'bar'", aircraft)); // bar, java.lang.String
	// 对象索引获取值
	printValueAndClass(OgnlCache.getValue("attribute[\"foo\"]", aircraft)); // bar, java.lang.String

}

从上例可以看出,对于对象索引,可以使用相同 Ognl 表达式赋值和获取值。

4. 方法调用

Ognl 调用方法的方式与 Java 略有不同,因为 Ognl 需要在除了提供的实际参数之外没有额外类型信息情况下,运行时选择正确方法。 Ognl 总是选择能与提供的实际参数匹配的最具体的方法执行,如果有多个方法能与提供的实际参数匹配且具体程度相同,那么选择其中任意一个执行。

特别地,参数「 null 」 能够匹配所有非原始类型,因此其最有可能导致意想不到的方法调用。

public class Aircraft {

    public void print(String value) {
        System.err.println("Argument[ String ] method invoked");
    }

    public void print(Object obj) {
        System.err.println("Argument[ Object ] method invoked");
    }
}

Aircraft 包含两个单参数方法 print() ,看下传 null 时,直接使用Aircraft 调用 和 Ognl 调用分别会调用哪个方法:

public static void main(String[] args) throws Exception {

	Aircraft aircraft = new Aircraft();
	
	aircraft.print(null); // Argument[ String ] method invoked
	printValueAndClass(OgnlCache.getValue("print(null)", aircraft)); // Argument[ Object ] method invoked
}

5. 变量

Ognl 变量语法比较简单,变量可以用来存储运算中间结果以便再次使用,或者用来提高表达式可读性。在 Ognl 中,所有变量在整个表达式中都是全局的,引用变量只需在变量名称前面加井号 「 "#" 」,例如,#var

public static void main(String[] args) throws Exception {

	printValueAndClass(OgnlCache.getValue("#var = 'name', #var.equals('name')", new Object())); // true, java.lang.Boolean

}

Ognl 还在表达式计算的每个点将当前对象存储在变量 「 this 」中,可以像引用其他变量一样引用该变量。
例如,如下 Ognl 表达式对数组 array 的长度进行操作 —— 如果长度大于 7,则返回长度的 2 倍,否则返回长度加 1

public static void main(String[] args) throws Exception {


	Map<Object, Object> map = Maps.newHashMap();
	map.put("array", new Object[] {new Object(), new Object()});
	
	// 3, java.lang.Integer
	printValueAndClass(OgnlCache.getValue("array[\"length\"].(#this > 7 ? 2*#this : 1 + #this)", map));

}

变量显示赋值语法,和 Java 或者其他语言一样,例如 #var = 99

6. 括号表达式

用括号 —— "()" 括起来的表达式将和周围运算符分开,作为一个整体单元运算,这可以强制改变 Ognl 运算符默认优先级,这也是在方法参数中使用逗号 —— “,” 运算符的唯一方式。

public static voidmain(String[] args) throws Exception {

	// true, java.lang.Boolean
	printValueAndClass(OgnlCache.getValue("'foo'.equals(('bar', 'foo'))", new Object()));

}

7. 链式子表达式

如果在点 —— "." 后使用括号表达式,那么 "." 前计算结果对象将被做括号表达式的当前对象。
例如,如下 Ognl 表达式在 "." 前构造了一个 Map,该 Map 将作为 "." 后括号表达式 —— (foo) 的当前对象。

public static void main(String[] args) throws Exception {

	// foo value, java.lang.String
	printValueAndClass(OgnlCache.getValue("#{ 'foo' : 'foo value', 'bar' : 'bar value'}.(foo)", new Object()));

}

8. 集合构造

构造 List ,需要把集合中元素放在大括号 —— "{}" 中,和方法参数一样,集合中元素表达式不能包含逗号,除非放在括号 —— "()" 中。
例如:

public static void main(String[] args) throws Exception {

	// [null, Untitled], java.util.ArrayList
	printValueAndClass(OgnlCache.getValue("{ null, 'Untitled'}", new Object()));
	
	// [null, bar], java.util.ArrayList
	printValueAndClass(OgnlCache.getValue("{ null, ('foo', 'bar')}", new Object()));

}

如下 Ognl 表达式检查属性 name 是否为 null 或者 "Untitled"

public static void main(String[] args) throws Exception {

    Map<Object, Object> map = Maps.newHashMap();
    map.put("name", "Untitled");

    printValueAndClass(OgnlCache.getValue("name in { null,\"Untitled\" }", map)); // true, java.lang.Boolean

}

Ognl 创建数组方式和 Java 大同小异 —— Ognl 支持类似调用构造函数方式创建,并且允许创建时初始化,或者给定数组大小。如果使用数组大小构造,则数组中所有元素都为 null 或者 0

public static void main(String[] args) throws Exception {

	printValueAndClass(OgnlCache.getValue("new int[] { 1, 2, 3 }", new Object())); // [I@3fb6a447, class [I
	
	printValueAndClass(OgnlCache.getValue("(new int[5])[0]", new Object())); // 0, java.lang.Integer

}

Ognl 使用一种特殊语法构造 Map

public static void main(String[] args) throws Exception {

	// {foo=foo value, bar=bar value}, class java.util.LinkedHashMap
	printValueAndClass(OgnlCache.getValue("#{ 'foo' : 'foo value', 'bar' : 'bar value' }", new Object()));

}

以上 Ognl 表达式创建了一个 Map,并初始化了键 "foo""bar"
如果要指定特定 Map,可以在左大括号 —— "{" 前指定该 Map 实现类全类名,

public static void main(String[] args) throws Exception {

	// {bar=bar value, foo=foo value}, java.util.HashMap
	printValueAndClass(OgnlCache.getValue("#@java.util.HashMap@{ \"foo\" : \"foo value\", \"bar\" : \"bar value\" }", new Object()));

}

9. 集合元素选择

Ognl 提供了一种简单语法使用表达式从集合中选择某些元素构成新集合,好比从数据库表所有行中查询子集。
例如,如下表达式选择集合中所有 String 类型元素:

public static void main(String[] args) throws Exception {

	// [foo, bar], java.util.ArrayList
	printValueAndClass(OgnlCache.getValue("{'foo', 1, 'bar'}.{? #this instanceof String}", new Object()));

}

如果要选择第一个匹配元素,可以使用索引,比如 "{'foo', 1, 'bar'}.{? #this instanceof String}[0]",但是如果没有匹配元素,使用索引访问就会报 ArrayIndexOutOfBoundsException 异常。
这种语法适用于选择第一个匹配元素,并作为 List 返回,只需要将 「 ? 」改成 「 ^ 」,和正则表达式类似 。
如果没有匹配任何元素,则返回空 List

public static void main(String[] args) throws Exception {

	// [foo], java.util.ArrayList
	printValueAndClass(OgnlCache.getValue("{'foo', 1, 'bar'}.{^ #this instanceof String}", new Object()));

}

同理,选择匹配元素的最后一个元素,只需要将 「 ? 」改成 「 $ 」:

public static void main(String[] args) throws Exception {

	// [bar], java.util.ArrayList
	printValueAndClass(OgnlCache.getValue("{'foo', 1, 'bar'}.{$ #this instanceof String}", new Object()));

}

10. 调用构造方法

在 Ognl 表达式中可以像在 Java 一样使用 「 new 」运算符创建对象,不一样的是,除了 java.lang 包中的类以外,其他类需要使用全限定类名。

public static void main(String[] args) throws Exception { 

	// java.lang.Object@380fb434, java.lang.Object
	printValueAndClass(OgnlCache.getValue("new Object()", new Object()));
	
	// Wed Dec 29 19:21:18 CST 2021, java.util.Date
	printValueAndClass(OgnlCache.getValue("new java.util.Date()", new Object()));

}

11. 调用静态方法

在 Ognl 表达式中可以使用这种语法调用静态方法 「 @class@method(args) 」,如果不写「 class 」,默认「 java.lang.Math 」,以便更方便地使用 minmax 方法, 同样「 class 」需要为全类名。

public static void main(String[] args) throws Exception {

	// 1640766639837, java.lang.Long
	printValueAndClass(OgnlCache.getValue("@java.lang.System@currentTimeMillis()", new Object()));
	
	printValueAndClass(OgnlCache.getValue("@@max(7, 9)", new Object())); // 9, java.lang.Integer

}

如果已经有静态方法所属类对象,可以就像调用实例方法一样调用该实例类的静态方法,

public class OgnlTest {

	public static void print() {
	
		System.err.println("foo");
	}
	
	public static void main(String[] args) throws Exception {
	
		// null
		printValueAndClass(OgnlCache.getValue("print()", new OgnlTest()));
	
	}
}

12. 访问静态字段

在 Ognl 表达式中可以使用这种语法调用静态方法 「 @class@field 」,同样「 class 」需要为全类名。

13. 表达式求值

如果在 Ognl 表达式后面跟着一个括号 —— "()",但是括号前没有点 —— ".",那么 Ognl 就会把括号前 Ognl 表达式计算结果作为新的 Ognl 表达式,并且以括号中 Ognl 表达式计算结果作为根对象再次计算。
例如,

public class OgnlTest {

	public static String print() {
	
		return "foo";
	}
	
	public static void main(String[] args) throws Exception {
	
		// foo value, java.lang.String
		printValueAndClass(OgnlCache.getValue("@test.OgnlTest@print()(#{ 'foo' : 'foo value', 'bar' : 'bar value' })", new OgnlTest()));
	
	}
}

上例先使用表达式 「@test.OgnlTest@print() 」调用静态方法 print(),得到结果 "foo",再使用该字符串作为新表达式,括号中 —— #{ 'foo' : 'foo value', 'bar' : 'bar value' } 创建的 Map 作为根对象再次计算。

14. 和 Java 运算符异同

Ognl 大部分运算符借鉴至 Java,所以使用方法也类似,这里罗列出一些和 Java 不同的一些运算符。

  • 逗号运算符 「 ,」,这个运算符借鉴至 C,逗号运算符用于分割两个独立的表达式,并且逗号运算符计算结果为第二个表达式计算结果。例如,「 ensureLoaded(), name」, 在执行该 Ognl 表达式时,先调用方法 ensureLoaded(),然后再检索 name
  • 花括号 「 {}」构造列表,可以将列表初始值放在花括号中创建列表,例如,「 { null, true, false }」;
  • in」运算符,可以还是用该运算符判断值是否在集合中,例如,「 name in {null,"Untitled"} || name」;

15. 类型转换

Ognl 可以将对象转换成其他类型,比如布尔类型,数字类型,集合类型等,看下转换规则。
在必要时,对象可以转换成布尔类型,

  • 如果对象本来就是「 Boolean 」类型,直接取值即可;
  • 如果对象为数字类型,规则和 C 类似 —— 取其双精度浮点值和零比较,非零即为 true,零为 false
  • 如果对象为「 Character 」类型,其 char 值非零时对应 true,否则为 false
  • 其他对象, 非 nulltruenullfalse
public class OgnlTest {

	public static Boolean print(boolean b) {
	
		return b;
	}
	
	public static void main(String[] args) throws Exception {
	
		// false, java.lang.Boolean
		printValueAndClass(OgnlCache.getValue("@test.OgnlTest@print(0.0)", new OgnlTest()));
		
		// true, java.lang.Boolean
		printValueAndClass(OgnlCache.getValue("@test.OgnlTest@print(1)", new OgnlTest()));
		
		// false, java.lang.Boolean
		printValueAndClass(OgnlCache.getValue("@test.OgnlTest@print('\0')", new OgnlTest()));
		
		// true, java.lang.Boolean
		printValueAndClass(OgnlCache.getValue("@test.OgnlTest@print('f')", new OgnlTest()));
		
		// true, java.lang.Boolean
		printValueAndClass(OgnlCache.getValue("@test.OgnlTest@print(new Object())", new OgnlTest()));
		
		// false, java.lang.Boolean
		printValueAndClass(OgnlCache.getValue("@test.OgnlTest@print(null)", new OgnlTest()));
		
	}
}

对象向其他类型转换规则参考 commons-ognl language-guide

16. 运算符

Ognl 借鉴了 Java 的大部分运算符,并添加了一些新运算符。 在大多数情况下,Ognl 对给定运算符的处理与 Java 相同。
值得注意的是,是 Ognl 本质上是一种无类型语言, 这意味着 Ognl 中的每个值都是一个 Java Object,并且 Ognl 试图在特定语境中将对象进行转换。
下表列出了 Ognl 常用运算符,优先级数字越小,优先级越低,相同优先级从左到右依次计算。

优先级运算符Ognl.getValue()
1e1, e2逗号运算符,e1, e2 在相同源对象中计算,并返回 e2 计算结果
2e1 = e2赋值运算符
3e1 ? e2 : e3
4e1 || e2, e1 or e2
5e1 && e2, e1 and e2
6e1 | e2, e1 bor e2
7e1 ^ e2, e1 xor e2
8e1 & e2, e1 band e2
9e1 == e2, e1 eq e2
9e1 != e2, e1 neq e2
10e1 < e2, e1 lt e2
10e1 <= e2, e1 lte e2
10e1 > e2, e1 gt e2
10e1 >= e2, e1 gte e2
10e1 in e2
10e1 not in e2
11e1 << e2, e1 shl e2
11e1 >> e2, e1 shr e2
11e1 >>> e2, e1 ushr e2
12e1 + e2
12e1 - e2
13e1* e2
13e1 / e2
13e1 % e2
14+ e
14- e
14! e, not e
14~ e
14e instanceof class
15e.method(args)
15e.property
15e1[ e2 ]
15e1.{ e2 }
15e1.{? e2 }
15e1.(e2)
15e1(e2)
16constant
16( e )
16method(args)
16property
16[ e ]
16{ e, … }
16#variable
16@class@method(args)
16e1 in e2@class@field
16new class(args)
16new array-component-class[] { e, … }
16#{ e1 : e2, … }
16#@classname@{ e1 : e2, … }
16:[ e ]
  • 5
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
MyBatis是一个流行的Java持久层框架,它允许开发者在运行时构建动态SQL查询,从而提高了代码的灵活性和适应性。MyBatis通过结合XML映射文件和注解,实现了动态SQL的执行。以下是MyBatis动态SQL的主要实现方式: 1. XML映射文件(Mapper XML):在MyBatis中,`<select>`, `<update>`, `<delete>`等标签可以包含参数占位符,如`#{id}`, `#{name}`,这些占位符会在运行时被实际的值替换,形成动态SQL语句。 ```xml <select id="getUser" parameterType="int" resultType="User"> SELECT * FROM users WHERE id = #{id} </select> ``` 2. 参数化查询(Parametrized queries):MyBatis支持使用预编译语句,将参数与SQL语句分离,这样可以防止SQL注入攻击。 3. 显式对象(Parameter Objects):如果动态SQL非常复杂,可以创建一个Java对象作为参数传递给查询,其中包含了多个属性,MyBatis会自动将对象的属性转换为SQL中的列名。 ```java Map<String, Object> params = new HashMap<>(); params.put("startDate", startDate); params.put("endDate", endDate); List<User> users = sqlSession.selectList("getUsers", params); ``` 4. 动态SQL标签:MyBatis提供了`<if>`, `<choose>`, `<when>`, `<otherwise>`等标签,用于根据条件动态生成SQL,实现基于条件的分支查询。 ```xml <select id="getUser" parameterType="map" resultType="User"> <if test="id != null"> SELECT * FROM users WHERE id = #{id} </if> <if test="name != null"> AND name LIKE '%' + #{name} + '%' </if> </select> ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值