想必大家都写过mysql的动态sql标签(xml标签)吧,常用的有
1.概述
当我们写mapper.xml时,当mybatis启动会把我们写的每一个标签转化为一个sqlNode的内存结构,前端程序进行接口调用的时,会把参数通过controller经过service到达我们的mapper.然后我们的sqlNode根据传入的参数,进行动态sql的拼接,再次整合我们的参数结构,然后根据sql语句与二次整合的参数结构进行preparedStatement的set操作,之后执行sql语句.
以下面简单sql为例,我们简单看下sqlNode与mapper参数的数据结构.
-- sql语句
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`role_id` int(11) DEFAULT NULL COMMENT '角色id',
`name` varchar(40) DEFAULT NULL COMMENT '用户名称',
`alisa` varchar(40) DEFAULT NULL COMMENT '别名',
`tag` tinyint(4) DEFAULT NULL COMMENT '1、使用name,2、使用alisa',
`sex` tinyint(3) DEFAULT NULL COMMENT '性别:1、男,2、女',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
1.1.sqlNode内存结构
让我们看下形成的sqlNode与参数的内存结构吧:
sqlNode内存结构图
从上图我们就能看到以下几条信息
- 整个select语句就是一个大的MixedSqlNode.
- 所有的标签node(if,where)等都有自己的sqlNode实现,而且其内部也会指向也是一个MixSqlNode.
- 所有的叶子节点都是StaticTextSqlNode,也就是我们需要动态拼接的sql.
1.2.参数结构
@Test
public void getUserListByIf() {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.getUserListByIf(1);
}
User getUserListByIf(@Param("id") Integer id);
参数结构图
从上图可以看出,我们传的参数1,会被存放到一个Map中,会作为键值对的形式进行缓存,那么为什么还有一个param1参数呢?这个我们下期再讲.
1.3.小结
综上不难看出,sqlNode其实是一个树状结构,当我们进行动态sql拼接时,需要根据一定的条件,将叶子节点的StaticTextSqlNode中的text字符串拼接起来,形成一个临时的sql字符串(将临时的#{xx}替换成{?}才是最终的sql).而前端传的参数是一个map结构.
2.常用sqlNode
mybatis中常用的sql标签有以下几种:
sqlNode继承结构图
结构我们看到了,接下来我们一一来剖析各个标签的细节.
2.1.if
参见sqlNode继承图我们知道
public class IfSqlNode implements SqlNode {
private final ExpressionEvaluator evaluator;
// 如第一节所述,这个test就是我们if标签中的test内容,即:id != null
private final String test;
// 这个就是ifSqlNode下挂的子节点,如第一节内存结构图,这是一个MixSqlNode,
// 其中有一个List成员变量,
// 用于存放其他类型的SqlNode,这也说明标签内可以嵌套其他类型的标签
private final SqlNode contents;
public IfSqlNode(SqlNode contents, String test) {
this.test = test;
this.contents = contents;
this.evaluator = new ExpressionEvaluator();
}
@Override
public boolean apply(DynamicContext context) {
// 这里是通过ognl表达式判断我们写的test的条件是否满足
// 其中test = id != null
// context.getBindings,返回的是一个Map,其中有{id:1,param1:1}
if (evaluator.evaluateBoolean(test, context.getBindings())) {
// 如果满足了,执行子标签的sql拼接动作.
contents.apply(context);
return true;
}
return false;
}
}
MixSqlNode是一个核心的结构,几乎大部分sql的拼接都少不了他的存在,他起到一个承上启下的作用.
public class MixedSqlNode implements SqlNode {
// 包含的下层的sqlNode集合
private final List contents;
public MixedSqlNode(List contents) {
this.contents = contents;
}
@Override
public boolean apply(DynamicContext context) {
// 从这里不能看出,是遍历MixSqlNode下的所有子节点,进行一个字符串的拼接操作,
// 当然各个sqlNode有各自不同的apply方法,也就有着不同的sql拼接动作.
for (SqlNode node : contents) {
node.apply(context);
}
return true;
}
}
StaticTextSqlNode是一个最终形成的需要拼接的sql片段,大部分的sql拼接都是由该sqlNode完成的.
public class StaticTextSqlNode implements SqlNode {
// 如第一节中例子中的IfSqlNode的话,那么这里就是 where id = #{id}
private final String text;
public StaticTextSqlNode(String text) {
this.text = text;
}
@Override
public boolean apply(DynamicContext context) {
// 看这里就是进行的sql拼接,这里context.appendSql
// 就是调用了context中的StringJoiner
// 进行的一个append操作
context.appendSql(text);
return true;
}
}
由第一节的内存结构图可以得知整个sql拼接的过程,这里在放一下内存结构图.
IfSqlNode
好了,我们来看下
- 执行rootSqlNode的apply动作,此时1号节点MixedSqlNode的apply动作为遍历contents,分别执行相应sqlNode的apply操作.
- 发现第一个元素为2号节点,类型为StaticTextSqlNode,然后执行3号节点StaticTextSqlNode的apply操作,这时总的sql语句为select id,name,alisa,tag from user.
- 发现第二个元素为3号节点IfSqlNode,然后执行IfSqlNode的apply操作,根据ognl表达式判断id = 1不为null满足,所以执行3号节点IfSqlNode中的成员变量contents的apply操作,此时contents为MixSqlNode
- 执行4号节点MixSqlNode的apply操作时,遍历其中的contents集合,发起其中只有一个5号节点StaticTextSqlNode,随即进行sql的拼接,此时拼接好的sql语句为select id,name,alisa,tag from user where id = #{id}
- 5号节点StaticTextSqlNode拼接完成后出栈,回到上层4号节点MixedSqlNode再出栈,回到3号节点IfSqlNode,再出栈,回到1号节点rootSqlNode,此时发现其contents中还有一个6号节点StaticTextSqlNode需要执行apply方法,随即在sql后面将\n拼接到了后面,此时得到最终的sql语句为select id,name,alisa,tag from user where id = #{id}\n
2.2.bind
bind标签与其他标签不一样,因为该标签不涉及sql的拼接,该标签的作用就是将参数经过该标签规则的转换,形成另外的参数,然后在放回到原参数集合中,其实这个动作也可以完全在我们的service层处理掉.
public class VarDeclSqlNode implements SqlNode {
// bind标签上写的name
private final String name;
// bind标签上写的value,可见bind标签上的value可以写表达式
private final String expression;
public VarDeclSqlNode(String var, String exp) {
// bind标签上写的name
name = var;
// bind标签上写的value
expression = exp;
}
@Override
public boolean apply(DynamicContext context) {
// 这里可以看出bind标签的value可以写ognl表达式,而且不用写#{}
// 获取接口传的参数,因为context对应的参数,都是存放在map中的,
// 这里不需要进行#{}动态替换,参见parameterMapping
final Object value = OgnlCache.getValue(expression, context.getBindings());
// 当调用bind标签对应的SqlNode的拼接方法(apply)时,
// 这里会将接口的参数进行expression转换 并将结果绑定
// 到bind标签的name上,并放回到接口的参数结构中,
// 形成了新的参数名和处理后的参数值在后续#{}替换时,
// 该新的参数名和参数值就可以直接使用了.
context.bind(name, value);
return true;
}
}
public void getByName() {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List users = userMapper.getByName("名字");
System.out.println(123);
}
List getByName(String name);
DEBUG [main] - ==> Preparing: select * from user where name like ?
DEBUG [main] - ==> Parameters: %名字%(String)
DEBUG [main] - <== Total: 2
2.3.foreach
该标签的sql拼接与参数的多少有很大关系,首先我们看下
public class ForEachSqlNode implements SqlNode {
// 临时替换的item的前缀字符
public static final String ITEM_PREFIX = "__frch_";
private final ExpressionEvaluator evaluator;
// 该集合放到传入参数中的key
private final String collectionExpression;
// 这个contents是改sql节点下包含的子节点,
// 如这是个foreach节点,下面可能包含if节点等.
// 如果是MixedSqlNode,则需要继续解析,
// 如果是StaticSqlNode只需要将对应的文字信息拼接上即可
private final SqlNode contents;
// foreach标签的open属性,在标签内容的开始添加,
// 只加一次,不是循环几次加几次
private final String open;
// foreach标签的close属性,在标签内容的结束添加,
// 只加一次,不是循环几次加几次
private final String close;
// foreach标签的separator属性,循环几次加几次,
// 每次都加在该行的头部,第一次循环不加
private final String separator;
// foreach的item,这个字段随便定义,
// 这个会与上面的__frch_进行组装,形成每一个item的名字
private final String item;
// foreach的index,这个字段也随便定义,
// 该字段与__frch_item_index会组成定位每一个item的map的key
private final String index;
private final Configuration configuration;
}
接下来我们看看ForeachSqlNode的apply方法
public boolean apply(DynamicContext context) {
Map bindings = context.getBindings();
final Iterable> iterable = evaluator.evaluateIterable(collectionExpression, bindings);
// 可以是Set,ArrayList,如果list长度为0,那么这里返回
if (!iterable.iterator().hasNext()) {
return true;
}
boolean first = true;
// 这里是添加我们的写的open属性,比如常用的"(",
// 从这也能看出,open属性只是在开始的时候拼接,而不在每次
// 循环的时候拼接
applyOpen(context);
int i = 0;
// 这里是List集合中的内容,集合中有几个,就会执行几次
for (Object o : iterable) {
DynamicContext oldContext = context;
// 每一个List中的项都被封装为一个DynamicContext
// 如果是第一个元素或者没有separator属性,会在sql组装时添加"",相当于什么都没加
// 第二个及以后的元素需要添加我们写的分隔符,比如:,
if (first || separator == null) {
// 这里的PrefixedContext为DynamicContext的包装类,
// 每次都要为context创建一个包装类,进行sql拼接
// 当每一行的sql处理完的时候,会将context指回原context.
context = new PrefixedContext(context, "");
} else {
context = new PrefixedContext(context, separator);
}
// 这里的context虽然是包装过的PrefixedContext,
// 但是getUniqueNumber仍然调用的是原DynamicContext的
// 生成唯一id的方法,其实这个就是为了生成我们集合数组的下标.0,1,2,3...
int uniqueNumber = context.getUniqueNumber();
// Issue #709
// 如果是Entry
if (o instanceof Map.Entry) {
// 这里如果是Map.Entry结构
Map.Entry
接下来我们看下
public void batchInsertUser() {
ArrayList users = new ArrayList<>();
User user1 = new User();
users.add(user1);
User user2 = new User();
users.add(user2);
userMapper.batchInsert(users);
}
void batchInsert(List users);
注:我们这里想要拼成的sql为:
insert into user (role_id,name,alisa,tag) values (?,?,?,?),(?,?,?,?)
insert into user (role_id,name,alisa,tag) values
// 这里如果mapper传的参数只有一个且为Collection类型,
// 那么参数map中的key为list或者collection
// 为什么这里没写open,close呢?因为这个属性只在循环的外面加(只加一次),
// 而不是每次循环结束后
(#{user.roleId},#{user.name},#{user.alisa},#{user.tag})
接下来我们看下内存结构图吧
ForeachSqlNode内存结构图
我们总结下
- 执行rootSqlNode的apply动作,此时1号节点MixedSqlNode的apply动作为遍历contents,分别执行相应sqlNode的apply操作
- 发现第一个元素为2号节点,类型为StaticTextSqlNode,然后执行3号节点StaticTextSqlNode的apply操作,这时总的sql语句为insert into user (role_id,name,alisa,tag) values.
- 发现第二个元素为3号节点ForEachSqlNode,然后执行ForEachSqlNode的apply操作,发现没有设置open属性所以不设置open
- 然后遍历我们的users,发现其中有两个user对象.然后循环遍历这两个user,此时根据item前缀__frch_,以及item属性user,以及我们集合的下标(从0开始),生成唯一的对象key:__frch_user_0,对应的value就是我们list中的0号user.
- 然后执行sql拼接操作,进入到4号节点,遍历4号节点下的5号节点StaticTextSqlNode,然后进行sql替换,即(#{user.roleId},#{user.name},#{user.alisa},#{user.tag}),替换成(#{__frch_user_0.roleId},#{__frch_user_0.name},#{__frch_user_0.alisa},#{__frch_user_0.tag}),然后进行sql拼接,此时sql为insert into user (role_id,name,alisa,tag) values (#{__frch_user_0.roleId},#{__frch_user_0.name},#{__frch_user_0.alisa},#{__frch_user_0.tag}).,执行完之后返回到4号节点,再返回到3号节点.
- 然后再循环遍历第二个元素,生成对应的sql为,(#{__frch_user_1.roleId},#{__frch_user_1.name},#{__frch_user_01.alisa},#{__frch_user_1.tag}).然后在进行sql拼接insert into user (role_id,name,alisa,tag) values (#{__frch_user_0.roleId},#{__frch_user_0.name},#{__frch_user_0.alisa},#{__frch_user_0.tag}),(#{__frch_user_1.roleId},#{__frch_user_1.name},#{__frch_user_01.alisa},#{__frch_user_1.tag}).
整个sql拼接完成了,在第3步的时候,生成了相应的参数映射__frch_user_0 --> user[0].后面就是参数替换的问题了,下节我们在分享.上面这个例子是insert的batch操作,我们平时还有一种场景,就是使用in查询,需要将相应的id拼接到括号后面.
// 首先我们看下我们最终想要的sql是:select * from user where id in (1,2,3,4).
// 那么这里为什么要加open和close属性呢?我们只想在
// user列表前面加上"("和user列表结束之后加上")",
// 而不是在除第一次之外每次循环前都加上separator,
2.4.choose..when..otherwise
前面我们已经讲过
public class ChooseSqlNode implements SqlNode {
// 这是otherwise标签下的sqlNode,这个SqlNode的类型为MixSqlNode
private final SqlNode defaultSqlNode;
// 这是when标签下的所有sqlNode,这个列表中SqlNode的类型为IfSqlNode
private final List ifSqlNodes;
public ChooseSqlNode(List ifSqlNodes, SqlNode defaultSqlNode) {
this.ifSqlNodes = ifSqlNodes;
this.defaultSqlNode = defaultSqlNode;
}
@Override
public boolean apply(DynamicContext context) {
// 看到了吧,这里就是when标签对应的sqlNode,
// 只要有一个when满足的话,这里就会reture了
for (SqlNode sqlNode : ifSqlNodes) {
// 这里是IfSqlNode的拼接,可以参见2.1.
if (sqlNode.apply(context)) {
return true;
}
}
// 如果遍历完所有的when还没有找到对应的sqlNode,那么这里就会走otherwise了.
// 看看是不是跟我们的java代码switch..case..default很像啊.
if (defaultSqlNode != null) {
// 这里是MixSqlNode的拼接,会遍历MixSqlNode下的所有SqlNode,
// 分别进行相应的apply操作
defaultSqlNode.apply(context);
return true;
}
return false;
}
}
那么我们来举例说明下吧
public void getUserListByChoose() {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List userList = userMapper.getUserListByChoose("male");
}
List getUserListByChoose(@Param("sex") String sex);
我们看下ChooseSqlNode的内存结构
ChooseSqlNode内存结构
由于choose标签基本使用的是if标签的内容,这里就不做果断阐述,看下sql拼接的流程:
- 执行rootSqlNode的apply动作,此时1号节点MixedSqlNode的apply动作为遍历contents,分别执行相应sqlNode的apply操作
- 然后进入2号节点,发现是个StaticTextSqlNode,这时会把该sql直接拼接上,此时sql为select id,name,alisa,tag from user where.
- 然后进入3号节点发现是个ChooseSqlNode,这里会优先遍历ChooseSqlNode中的IfSqlNode,也就是
标签. - 此时进入第一个
标签对应的IfSqlNode,也就是进入到6号节点,判断sex == 'male',发现是true.此时会进入到该IfSqlNode的MixSqlNode的apply操作 - 所以此时进入到7号节点,然后进入到8号节点.发现是StaticTextSqlNode.将字符串拼接到sql上,此时的sql为select id,name,alisa,tag from user where sex = 1.
- 此时依次将8、7、6出栈,回到3节点,3节点就是我们的ChooseSqlNode的apply操作.遍历IfSqlNode时成功了会return,此时3号节点也出栈
- 然后回到12节点将\n拼接到sql上,此时sql为select id,name,alisa,tag from user where sex = 1.
2.5.trim
首先我们看下
public class TrimSqlNode implements SqlNode {
// 这里的contents为MixedSqlNode
private final SqlNode contents;
// 需要在该标签组成的语句最前面添加的字符,比如where,set
private final String prefix;
// 需要在该标签组成的语句最前面添加的字符
private final String suffix;
// 如果该标签下的第一个字符在这个list里面,需要去掉,注意只能剔除一次
private final List prefixesToOverride;
// 如果该标签下的最后一个字符在这个list里面,需要去掉,注意只能剔除一次
private final List suffixesToOverride;
private final Configuration configuration;
public TrimSqlNode(Configuration configuration, SqlNode contents,String prefix,
String prefixesToOverride, String suffix, String suffixesToOverride) {
this(configuration, contents, prefix, parseOverrides(prefixesToOverride),
suffix, parseOverrides(suffixesToOverride));
}
protected TrimSqlNode(Configuration configuration, SqlNode contents,
String prefix, List prefixesToOverride,
String suffix, List suffixesToOverride) {
// 这里是解析xml文件时生成的,不是执行sql语句是生成的
this.contents = contents;
this.prefix = prefix;
this.prefixesToOverride = prefixesToOverride;
this.suffix = suffix;
this.suffixesToOverride = suffixesToOverride;
this.configuration = configuration;
}
@Override
public boolean apply(DynamicContext context) {
FilteredDynamicContext filteredDynamicContext
= new FilteredDynamicContext(context);
// 这里是调用MixedSqlNode的apply方法,
// 就是遍历MixedSqlNode中的List,
// 并调用SqlNode的apply方法
// 这里应该是将and添加进来了
boolean result = contents.apply(filteredDynamicContext);
filteredDynamicContext.applyAll();
return result;
}
// 这里是处理的override属性的
private static List parseOverrides(String overrides) {
if (overrides != null) {
// 这里明显能看出,prefixesToOverride、suffixesToOverride
// 可以写多个想去除前后缀的方法
// 多个的话需要用"|"拼接
final StringTokenizer parser = new StringTokenizer(overrides, "|", false);
final List list = new ArrayList<>(parser.countTokens());
while (parser.hasMoreTokens()) {
list.add(parser.nextToken().toUpperCase(Locale.ENGLISH));
}
return list;
}
return Collections.emptyList();
}
private class FilteredDynamicContext extends DynamicContext {
// 处理sql
public void applyAll() {
sqlBuffer = new StringBuilder(sqlBuffer.toString().trim());
String trimmedUppercaseSql = sqlBuffer.toString().toUpperCase(Locale.ENGLISH);
if (trimmedUppercaseSql.length() > 0) {
// 处理头
applyPrefix(sqlBuffer, trimmedUppercaseSql);
// 处理尾
applySuffix(sqlBuffer, trimmedUppercaseSql);
}
// 然后将处理完的sql拼接上
delegate.appendSql(sqlBuffer.toString());
}
// 处理前缀
private void applyPrefix(StringBuilder sql, String trimmedUppercaseSql) {
// 处理头
// 这是where条件的第一次进来,第一个where跟的条件是不允许加and,or等前缀的
if (!prefixApplied) {
// 当第一次进来之后,就会把这个prefixApplied设为true,
// 保证where、set标签有多个and或者or的时候,不会全部剔除.只剔除第一个
prefixApplied = true;
if (prefixesToOverride != null) {
//
for (String toRemove : prefixesToOverride) {
// 这里是剔除sql片段中的前缀,从break也能看出如果有一个命中的话就会break.
if (trimmedUppercaseSql.startsWith(toRemove)) {
sql.delete(0, toRemove.trim().length());
break;
}
}
}
// 这边也是第一次进来,如果没有空格会添加上空格,
// 保证sql语句的正确性,这也是保证where、set标签
// 有多个and或者or的时候只有第一次进来时才会添加where
if (prefix != null) {
sql.insert(0, " ");
// 这里是加入where
sql.insert(0, prefix);
}
}
}
// 处理后缀
private void applySuffix(StringBuilder sql, String trimmedUppercaseSql) {
// 处理尾
if (!suffixApplied) {
// 第二次进来就不会处理尾了
suffixApplied = true;
if (suffixesToOverride != null) {
// 这里也是,如果sql的尾部是以我们配置的suffixesToOverride结尾,会剔除
// 也是只能剔除一次
for (String toRemove : suffixesToOverride) {
if (trimmedUppercaseSql.endsWith(toRemove)
|| trimmedUppercaseSql.endsWith(toRemove.trim())) {
int start = sql.length() - toRemove.trim().length();
int end = sql.length();
sql.delete(start, end);
break;
}
}
}
// 这里也是添加空格和尾部
if (suffix != null) {
sql.append(" ");
sql.append(suffix);
}
}
}
}
}
平常我们用
update user set alisa = '好学生' where id in
#{user.id}
但是如果我想更新根据不同的学生去更新不同的tag怎么办呢?就是当id=1时tag='好学生',id=2时tag='坏学生',这种sql语句就不太够用了.那么一般会用到sql的case when语句来处理这种情况.想要拼成的最终sql语句为
update user set
alisa = case id
when 1 then '好学生'
when 2 then '坏学生' end
where id in (1,2);
这种sql语句用之前的标签都无法完成,只有使用
public void batchUpdate() {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
ArrayList users = new ArrayList<>();
User user1 = new User();
users.add(user1);
user1.setId(1);
user1.setAlisa("别名1");
User user2 = new User();
users.add(user2);
user2.setId(2);
user2.setAlisa("别名2");
userMapper.batchUpdate(users);
sqlSession.commit();
}
update user set
when id = #{user.id} then #{user.alisa}
where id in
#{user.id}
这个xml的内存结构比较复杂,嵌套的参数比较多,图太大无法放下,大家可以自行尝试画一下,具体的内存结构可以在DynamicSqlSource类中看到,具体位置参见如下代码
public class DynamicSqlSource implements SqlSource {
private final Configuration configuration;
// 这里的rootSqlNode就是我们sqlNode的根节点
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);
// debug到这里能看到rootSqlNode的结构
rootSqlNode.apply(context);
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class> parameterType = parameterObject == null ?
Object.class : parameterObject.getClass();
// 如果是PreparedStatementHandler的话,这里将特殊符号转换成?
// 2.将初步解析的动态sqlNode,进行"?"替换,并定位不同"?"对应的不同参数解析
SqlSource sqlSource = sqlSourceParser.parse(
context.getSql(), parameterType, context.getBindings());
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
// 这是把context中的参数定位的map放入boundSql中
context.getBindings().forEach(boundSql::setAdditionalParameter);
return boundSql;
}
}
这里总结出几个重点
- suffixOverrides作用是在trim标签内拼接的sql会把以包含suffixOverrides结尾的字符剔除,只剔除一次.比如:该trim片段拼接到最后为set name = '张三',这时会把最后一个【,】剔除得到最终sql为set name = '张三'.这个一般用在<set>标签上.保证最后一个set结尾不带【,】
- prefixOverrides作用是在trim标签内拼接的sql会把以包含prefixOverrides开头的字符剔除,只剔除一次.比如:该trim片段拼接到最后为and name = '张三',这时会把第一个【and|or】剔除得到最终sql为name = '张三'.这个一般用在
标签上.保证第一个where条件不带and或者or. - prefix作用是在该标签的头部添加prefix内容,一般也是用在where和set语句上,保证where字符的添加
- suffix作用是在该标签的尾部添加suffix内容.目前暂时没有任何标签用到它,可自行使用.
- 这里还有一点,这里执行的顺序是2、3、1、4.先剔除头部、在添加头部、在剔除尾部、在添加尾部.可以从源码TrimSqlNode.FilteredDynamicContext的apply方法可以看到.
2.6.where
我们看下
public class WhereSqlNode extends TrimSqlNode {
// 从这可以看出WhereSqlNode其实就是在TrimSqlNode初始化了prefixOverrides,即
// 需要剔除的首部字符,以及需要添加的首部字符WHERE
private static List prefixList =
Arrays.asList("AND ","OR ","AND\n", "OR\n", "AND\r", "OR\r", "AND\t", "OR\t");
public WhereSqlNode(Configuration configuration, SqlNode contents) {
super(configuration, contents, "WHERE", prefixList, null, null);
}
}
其实这个完全根据TrimSqlNode来处理,然后我们举个例子看下吧
public void getUserListByWhere() {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List userList = userMapper.getUserListByWhere(1);
}
List getUserListByWhere(@Param("id") Integer id);
下面我们看下TrimSqlNode对应的内存数据结构
WhereSqlNode内存结构
执行顺序大家可以参照if自行补充一下.这里只补充一下:其实
- suffixOverrides = null
- prefixOverrides = {"AND ","OR ","AND\n", "OR\n", "AND\r", "OR\r", "AND\t", "OR\t"}.
- prefix = WHERE
- suffix = null
2.7.set
我们看下<set>标签的结构
public class SetSqlNode extends TrimSqlNode {
private static final List COMMA = Collections.singletonList(",");
public SetSqlNode(Configuration configuration,SqlNode contents) {
super(configuration, contents, "SET", COMMA, null, COMMA);
}
}
其实这个完全根据TrimSqlNode来处理,是不是发现跟WhereSqlNode很像啊,对的.这个就不做介绍了,他也是不支持这4个参数的修改,这4个参数的内容如下:
- suffixOverrides = {","}
- prefixOverrides = {"AND ","OR ","AND\n", "OR\n", "AND\r", "OR\r", "AND\t", "OR\t"}.
- prefix = SET
- suffix = null