mybatis-flex/docs/zh/base/querywrapper.md
2023-07-06 19:11:35 +08:00

26 KiB
Raw Blame History

灵活的 QueryWrapper

增删改查询和分页 章节中,我们随时能看到 QueryWrapper 的身影QueryWrapper 是用于构造 Sql 的 强有力工具,也是 MyBatis-Flex 的亮点和特色。

::: tip 提示 QueryWrapper 可以被序列化通过 RPC 进行传输因此在微服务项目中我们可以在客户端网关、Controller 层等)构造出 QueryWrapper传给 Provider 层进行查询返回数据。 :::

QueryWrapper 的使用

以下代码是一个完整 Spring Controller 的示例:

@RestController
public class AccountController {

    @Autowired
    AccountMapper accountMapper;
    
    @GetMapping("/accounts")
    List<Account> selectList() {
        
        //构造 QueryWrapper也支持使用 QueryWrapper.create() 构造,效果相同
        QueryWrapper query = new QueryWrapper();
        query.where(ACCOUNT.ID.ge(100));

        //通过 query 查询数据列表返回
        return accountMapper.selectListByQuery(query);
    }
}

在以上的示例中,其核心代码如下所示:

//构造 QueryWrapper
QueryWrapper query = new QueryWrapper();
query.where(ACCOUNT.ID.ge(100));

//通过 query 查询数据列表
accountMapper.selectListByQuery(query);

以上代码执行的 Sql 如下:

select * from tb_account
where id >= 100

::: tip 问题:以上示例中,ACCOUNT.ID.ge(100) 中的 ACCOUNT 是怎么来的? MyBatis-Flex 使用了 APT 技术,这个 ACCOUNT 是自动生成的。 参考:《MyBatis-Flex APT 配置》章节。 :::

select *

QueryWrapper query = new QueryWrapper();
query.select(ACCOUNT.ID, ACCOUNT.USER_NAME)
    .from(ACCOUNT)

其查询生成的 Sql 如下:

SELECT id, user_name FROM tb_account

select ... as

QueryWrapper query = new QueryWrapper()
    .select(
          ACCOUNT.ID.as("accountId")
        , ACCOUNT.USER_NAME)
    .from(ACCOUNT.as("a"));

其查询生成的 Sql 如下:

SELECT a.id as accountId, a.user_name
FROM tb_account AS a

select 多张表

QueryWrapper query = new QueryWrapper()
    .select(
          ACCOUNT.ID
        , ACCOUNT.USER_NAME
        , ARTICLE.ID.as("articleId")
        , ARTICLE.TITLE)
    .from(ACCOUNT.as("a"), ARTICLE.as("b"))
    .where(ACCOUNT.ID.eq(ARTICLE.ACCOUNT_ID));

其查询生成的 Sql 如下:

SELECT a.id, a.user_name, b.id AS articleId, b.title
FROM tb_account AS a,
     tb_article AS b
WHERE a.id = b.account_id

select functionSQL 函数)

所有函数均在 QueryMethods 类中,以下示例皆为静态导入方法,省略了类名。

示例

QueryWrapper query=new QueryWrapper()
        .select(
        ACCOUNT.ID,
        ACCOUNT.USER_NAME,
        max(ACCOUNT.BIRTHDAY),
        avg(ACCOUNT.SEX).as("sex_avg")
        ).from(ACCOUNT);

其查询生成的 Sql 如下:

SELECT id, user_name, MAX(birthday), AVG(sex) AS sex_avg
FROM tb_account

在使用函数时,一些数字、字符串常量需要通过特定的方法去构造,例如:

  • number():构建数字常量
  • string():构建字符串常量
  • column():构建自定义列

示例:

select(number(1)) --> SELECT 1
select(string("str")) --> SELECT 'str'
select(column("abc")) --> SELECT abc

目前MyBatis-Flex 已支持 110+ 个常见的 SQL 函数,查看已支持的 所有函数。 若还不满足,您可以参考 QueryMethods ,然后在自己的项目里进行自定义扩展。

支持的函数 函数说明
count 查询数据总量
distinct 对指定列进行去重
sum 返回指定字段值的和
avg 返回指定列的平均值
min 返回指定列的最小值
max 返回指定列的最大值
abs 返回绝对值
ceil 返回大于或等于 x 的最小整数(向上取整)
ceiling 返回大于或等于 x 的最小整数(向上取整)
floor 返回小于或等于 x 的最大整数(向下取整)
rand 返回 0~1 的随机数
sign 返回 x 的符号x 是负数、0、正数分别返回 -1、0、1
pi 返回圆周率
truncate 返回数值 x 保留到小数点后 y 位的值
round 返回离 x 最近的整数(四舍五入)
pow 返回 x 的 y 次方
power 返回 x 的 y 次方
sqrt 返回 x 的平方根
exp 返回 e 的 x 次方
mod 返回 x 除以 y 以后的余数
log 返回自然对数(以 e 为底的对数)
log10 返回以 10 为底的对数
radians 将角度转换为弧度
degrees 将弧度转换为角度
sin 求正弦值
asin 求反正弦值
cos 求余弦值
acos 求反余弦值
tan 求正切值
atan 求反正切值
cot 求余切值
charLength 返回字符串 s 的字符数
length 返回字符串 s 的长度
concat 将字符串 s1s2 等多个字符串合并为一个字符串
concatWs 同 CONCAT(s1, s2, ...),但是每个字符串之间要加上 x
insert 将字符串 s2 替换 s1 的 x 位置开始长度为 len 的字符串
upper 将字符串 s 的所有字符都变成大写字母
lower 将字符串 s 的所有字符都变成小写字母
left 返回字符串 s 的前 n 个字符
right 返回字符串 s 的后 n 个字符
lpad 字符串 s2 来填充 s1 的开始处,使字符串长度达到 len
rpad 字符串 s2 来填充 s1 的结尾处,使字符串长度达到 len
trim 去掉字符串 s 开始处和结尾处的空格
ltrim 去掉字符串 s 开始处的空格
rtrim 去掉字符串 s 结尾处的空格
repeat 将字符串 s 重复 n 次
space 返回 n 个空格
replace 用字符串 s2 代替字符串 s 中的字符串 s1
strcmp 比较字符串 s1 和 s2
substring 获取从字符串 s 中的第 n 个位置开始长度为 len 的字符串
instr 从字符串 s 中获取 s1 的开始位置
reverse 将字符串 s 的顺序反过来
elt 返回第 n 个字符串
field 返回第一个与字符串 s 匹配的字符串的位置
findInSet 返回在字符串 s2 中与 s1 匹配的字符串的位置
curDate 返回当前日期
currentDate 返回当前日期
curTime 返回当前时间
currentTime 返回当前时间
now 返回当前日期和时间
currentTimestamp 返回当前日期和时间
localTime 返回当前日期和时间
sysDate 返回当前日期和时间
localTimestamp 返回当前日期和时间
unixTimestamp 以 UNIX 时间戳的形式返回当前时间
fromUnixTime 把 UNIX 时间戳的时间转换为普通格式的时间
utcDate 返回 UTC国际协调时间日期
utcTime 返回 UTC 时间
month 返回日期 d 中的月份值,范围是 1~12
monthName 返回日期 d 中的月份名称,如 january
dayName 返回日期 d 是星期几,如 Monday
dayOfWeek 返回日期 d 是星期几1 表示星期日2 表示星期二
weekday 返回日期 d 是星期几0 表示星期一1 表示星期二
week 计算日期 d 是本年的第几个星期,范围是 0-53
weekOfYear 计算日期 d 是本年的第几个星期,范围是 1-53
dayOfYear 计算日期 d 是本年的第几天
dayOfMonth 计算日期 d 是本月的第几天
year 返回日期 d 中的年份值
day 返回日期 d 中的天数值
quarter 返回日期 d 是第几季度,范围 1-4
hour 返回时间 t 中的小时值
minute 返回时间 t 中的分钟值
second 返回时间 t 中的秒钟值
timeToSec 将时间 t 转换为秒
secToTime 将以秒为单位的时间 s 转换为时分秒的格式
toDays 计算日期 d 到 0000 年 1 月 1 日的天数
fromDays 计算从 0000 年 1 月 1 日开始 n 天后的日期
dateDiff 计算日期 d1 到 d2 之间相隔的天数
addDate 计算开始日期 d 加上 n 天的日期
subDate 计算起始日期 d 减去 n 天的日期
addTime 计算起始时间 t 加上 n 秒的时间
subTime 计算起始时间 t 加上 n 秒的时间
dateFormat 按照表达式 f 的要求显示日期 d
timeFormat 按照表达式 f 的要求显示时间 t
getFormat 根据字符串 s 获取 type 类型数据的显示格式
version 返回数据库的版本号
connectionId 返回服务器的连接数
database 返回当前数据库名
schema 返回当前数据库 schema
user 返回当前用户的名称
charset 返回字符串 str 的字符集
collation 返回字符串 str 的字符排列方式
lastInsertId 返回最后生成的 auto_increment 值
password 对字符串 str 进行加密
md5 对字符串 str 进行 md5 加密
encode 使用字符串 pswd_str 来加密字符串 str加密结果是一个二进制数必须使用 BLOB 类型来保持它
decode 解密函数,使用字符串 pswd_str 来为 crypt_str 解密
format 格式化函数,可以将数字 x 进行格式化,将 x 保留到小数点后 n 位,这个过程需要进行四舍五入
ascii 返回字符串 s 的第一个字符的 ASSCII 码
bin 返回 x 的二进制编码
hex 返回 x 的十六进制编码
oct 返回 x 的八进制编
conv 将 x 从 f1 进制数变成 f2 进制数
inetAton 将 IP 地址转换为数字表示IP 值需要加上引号
inetNtoa 将数字 n 转换成 IP 的形式

select 列计算

示例 1

QueryWrapper query = new QueryWrapper()
    .select(ACCOUNT.ID.add(100).as("x100"))
    .from(ACCOUNT);

String sql = query.toSQL();

列计算的 加减乘除 对应的方法分别为add / subtract / multiply / divide

其查询生成的 Sql 如下:

SELECT (`id` + 100) AS `x100` FROM `tb_account`

示例 2

QueryWrapper query = new QueryWrapper()
    .select(sum(ACCOUNT.ID.multiply(ACCOUNT.AGE)).as("total_x"))
    .from(ACCOUNT);

其查询生成的 Sql 如下:

SELECT SUM(`id` * `age`) AS `total_x` FROM `tb_account`

示例 3

QueryWrapper query = new QueryWrapper()
    .select(ACCOUNT.ID.add(ACCOUNT.AGE.add(100)).as("x100"))
    .from(ACCOUNT);

String sql = query.toSQL();

其查询生成的 Sql 如下:

SELECT (`id` + (`age` + 100)) AS `x100` FROM `tb_account`

select case...when

示例 1

QueryWrapper wrapper = QueryWrapper.create()
        .select(
             ACCOUNT.ID
            ,case_()
                .when(ACCOUNT.ID.ge(2)).then("x2")
                .when(ACCOUNT.ID.ge(1)).then("x1")
                .else_("x100")
                .end().as("xName")
        )

其查询生成的 Sql 如下:

 SELECT `id`, 
        (CASE WHEN `id` >=  2  THEN 'x2' 
            WHEN `id` >=  1  THEN 'x1' 
            ELSE 'x100' 
            END) AS `xName` 
 FROM `tb_account`

SQL 执行的结果如下:

|id     |xName     |
|1      |x1        |
|2      |x2        |

示例 2

QueryWrapper queryWrapper = QueryWrapper.create()
        .select(
                ACCOUNT.ALL_COLUMNS,
                case_(ACCOUNT.ID)
                    .when(100).then(100)
                    .when(200).then(200)
                    .else_(300).end().as("result")
        )
        .from(ACCOUNT)
        .where(ACCOUNT.USER_NAME.like("michael"));

其查询生成的 Sql 如下:

SELECT *, 
       (CASE `id` 
           WHEN 100 THEN 100 
           WHEN 200 THEN 200 
           ELSE 300 END) AS `result` 
FROM `tb_account` 
WHERE `user_name` LIKE  ?

::: tip 提示 在以上示例中,由于 caseelse 属于 Java 关键字,无法使用其进行方法命名,因此会添加一个下划线小尾巴 "_" 变成 case_else_,这是无奈之举。 在以后的 QueryWrapper 构建中,遇到 java 关键字也会采用类似的解决方法。 :::

with...select

示例 1

QueryWrapper query = new QueryWrapper()
        .with("CTE").asSelect(
                select().from(ARTICLE).where(ARTICLE.ID.ge(100))
        )
        .select()
        .from(ACCOUNT)
        .where(ACCOUNT.SEX.eq(1));

System.out.println(query.toSQL());

生成的 SQL 如下:

WITH CTE AS (SELECT * FROM `tb_article` WHERE `id` >= 100) 
SELECT * FROM `tb_account` WHERE `sex` = 1

示例 2

QueryWrapper query = new QueryWrapper()
    .with("xxx", "id", "name").asValues(
        Arrays.asList("a", "b"),
        union(
            select().from(ARTICLE).where(ARTICLE.ID.ge(200))
        )
    )
    .from(ACCOUNT)
    .where(ACCOUNT.SEX.eq(1));

System.out.println(query.toSQL());

生成的 SQL 如下:

WITH xxx(id, name) 
    AS (VALUES (a, b) UNION (SELECT * FROM `tb_article` WHERE `id` >= 200)) 
SELECT * FROM `tb_account` WHERE `sex` = 1

with recursive...select

示例 1

QueryWrapper query = new QueryWrapper()
    .withRecursive("CTE").asSelect(
        select().from(ARTICLE).where(ARTICLE.ID.ge(100))
    )
    .from(ACCOUNT)
    .where(ACCOUNT.SEX.eq(1));

System.out.println(query.toSQL());

生成的 SQL 如下:

WITH RECURSIVE CTE AS (SELECT * FROM `tb_article` WHERE `id` >= 100) 
SELECT * FROM `tb_account` WHERE `sex` = 1

示例 2

QueryWrapper query = new QueryWrapper()
    .withRecursive("CTE").asSelect(
        select().from(ARTICLE).where(ARTICLE.ID.ge(100))
    )
    .with("xxx", "id", "name").asValues(
        Arrays.asList("a", "b"),
        union(
            select().from(ARTICLE).where(ARTICLE.ID.ge(200))
        )
    )
    .from(ACCOUNT)
    .where(ACCOUNT.SEX.eq(1));

System.out.println(query.toSQL());

生成的 SQL 如下:

WITH RECURSIVE 
     CTE AS (SELECT * FROM `tb_article` WHERE `id` >= 100), 
     xxx(id, name) AS (VALUES (a, b)  UNION (SELECT * FROM `tb_article` WHERE `id` >= 200)) 
SELECT * FROM `tb_account` WHERE `sex` = 1

where

QueryWrapper queryWrapper=QueryWrapper.create()
    .select()
    .from(ACCOUNT)
    .where(ACCOUNT.ID.ge(100))
    .and(ACCOUNT.USER_NAME.like("michael"));

其查询生成的 Sql 如下:

SELECT * FROM tb_account
WHERE id >=  ?
AND user_name LIKE  ? 

where 动态条件 1

boolean flag = false;
QueryWrapper queryWrapper = QueryWrapper.create()
    .select().from(ACCOUNT)
    .where(flag ? ACCOUNT.ID.ge(100) : noCondition())
    .and(ACCOUNT.USER_NAME.like("michael"));

其查询生成的 Sql 如下:

SELECT * FROM tb_account
WHERE user_name LIKE  ? 

where 动态条件 2

boolean flag = false;
QueryWrapper queryWrapper = QueryWrapper.create()
    .select().from(ACCOUNT)
    .where(ACCOUNT.ID.ge(100).when(flag)) // when....
    .and(ACCOUNT.USER_NAME.like("michael"));

其查询生成的 Sql 如下:

SELECT * FROM tb_account
WHERE user_name LIKE  ? 

where 动态条件 3

String name = null;
QueryWrapper queryWrapper = QueryWrapper.create()
    .select().from(ACCOUNT)
    .where(ACCOUNT.ID.ge(100)) // when....
    .and(ACCOUNT.USER_NAME.like(name).when(StringUtil::isNotBlank));

其查询生成的 Sql 如下:

SELECT * FROM tb_account
WHERE id >= ? 

where 动态条件 4

String name = null;
QueryWrapper queryWrapper = QueryWrapper.create()
    .select().from(ACCOUNT)
    .where(ACCOUNT.ID.ge(100)) 
    .and(ACCOUNT.USER_NAME.like(name, If::hasText));

其查询生成的 Sql 如下:

SELECT * FROM tb_account
WHERE id >= ? 

where select

QueryWrapper queryWrapper = QueryWrapper.create()
    .select()
    .from(ACCOUNT)
    .where(ACCOUNT.ID.ge(
        select(ARTICLE.ACCOUNT_ID).from(ARTICLE).where(ARTICLE.ID.ge(100))
    ));

其查询生成的 Sql 如下:

SELECT * FROM tb_account
WHERE id >=
(SELECT account_id FROM tb_article WHERE id >=  ? )

where exists, not exists

QueryWrapper queryWrapper=QueryWrapper.create()
    .select()
    .from(ACCOUNT)
    .where(ACCOUNT.ID.ge(100))
    .and(
        exists(  // or notExists(...)
            selectOne().from(ARTICLE).where(ARTICLE.ID.ge(100))
        )
    );

其查询生成的 Sql 如下:

SELECT * FROM tb_account
WHERE id >=  ?
AND EXISTS (
    SELECT 1 FROM tb_article WHERE id >=  ?
)

and (...) or (...)

QueryWrapper queryWrapper=QueryWrapper.create()
    .select()
    .from(ACCOUNT)
    .where(ACCOUNT.ID.ge(100))
    .and(ACCOUNT.SEX.eq(1).or(ACCOUNT.SEX.eq(2)))
    .or(ACCOUNT.AGE.in(18,19,20).and(ACCOUNT.USER_NAME.like("michael")));

其查询生成的 Sql 如下:

SELECT * FROM tb_account
WHERE id >=  ?
AND (sex =  ? OR sex =  ? )
OR (age IN (?,?,?) AND user_name LIKE ? )

group by

QueryWrapper queryWrapper=QueryWrapper.create()
    .select()
    .from(ACCOUNT)
    .groupBy(ACCOUNT.USER_NAME);

其查询生成的 Sql 如下:

SELECT * FROM tb_account
GROUP BY user_name

having

QueryWrapper queryWrapper=QueryWrapper.create()
    .select()
    .from(ACCOUNT)
    .groupBy(ACCOUNT.USER_NAME)
    .having(ACCOUNT.AGE.between(18,25));

其查询生成的 Sql 如下:

SELECT * FROM tb_account
GROUP BY user_name
HAVING age BETWEEN  ? AND ?

orderBy

QueryWrapper queryWrapper=QueryWrapper.create()
    .select()
    .from(ACCOUNT)
    .orderBy(ACCOUNT.AGE.asc(), ACCOUNT.USER_NAME.desc().nullsLast());

其查询生成的 Sql 如下:

SELECT * FROM tb_account
ORDER BY age ASC, user_name DESC NULLS LAST

hint

Hint 是数据库厂商(比如 Oracle、MySQL、达梦等提供的一种 SQL语法它允许用户在 SQL 语句中插入相关的语法,从而影响 SQL 的执行方式。 它是一种【非常规】的直接影响优化器、指定执行计划的 SQL 优化手段。

QueryWrapper queryWrapper=QueryWrapper.create()
    .select().hint("INDEX_DESC")
    .from(ACCOUNT)
    .orderBy(ACCOUNT.AGE.asc(), ACCOUNT.USER_NAME.desc().nullsLast());

其查询生成的 Sql 如下:

SELECT /*+ INDEX_DESC */  * FROM tb_account
ORDER BY age ASC, user_name DESC NULLS LAST

joinleft joininner join...

QueryWrapper queryWrapper=QueryWrapper.create()
    .select()
    .from(ACCOUNT)
    .leftJoin(ARTICLE).on(ACCOUNT.ID.eq(ARTICLE.ACCOUNT_ID))
    .innerJoin(ARTICLE).on(ACCOUNT.ID.eq(ARTICLE.ACCOUNT_ID))
    .where(ACCOUNT.AGE.ge(10));

其查询生成的 Sql 如下:

SELECT * FROM tb_account
LEFT JOIN tb_article ON tb_account.id = tb_article.account_id
INNER JOIN tb_article ON tb_account.id = tb_article.account_id
WHERE tb_account.age >=  ?

join on 多个条件

QueryWrapper queryWrapper = QueryWrapper.create()
    .select()
    .from(ACCOUNT)
    .leftJoin(ARTICLE).on(
        ACCOUNT.ID.eq(ARTICLE.ACCOUNT_ID).and(ACCOUNT.AGE.eq(18))
    )
    .where(ACCOUNT.AGE.ge(10));

其查询生成的 Sql 如下:

SELECT * FROM tb_account LEFT JOIN tb_article 
ON tb_account.id = tb_article.account_id AND tb_account.age =  ?  
WHERE tb_account.age >=  ? 

join select

QueryWrapper queryWrapper = QueryWrapper.create()
    .select()
    .from(ACCOUNT)
    .leftJoin(
        select().from(ARTICLE).where(ARTICLE.ID.ge(100))
    ).as("a").on(
        ACCOUNT.ID.eq(raw("a.id"))
    )
    .where(ACCOUNT.AGE.ge(10));

其查询生成的 Sql 如下:

SELECT * FROM tb_account 
LEFT JOIN (SELECT * FROM tb_article WHERE id >=  ? ) AS a
ON tb_account.id = a.id 
WHERE tb_account.age >=  ? 

union, union all

QueryWrapper query = new QueryWrapper()
    .select(ACCOUNT.ID)
    .from(ACCOUNT)
    .orderBy(ACCOUNT.ID.desc())
    .union(select(ARTICLE.ID).from(ARTICLE))
    .unionAll(select(ARTICLE.ID).from(ARTICLE));

其查询生成的 Sql 如下:

(SELECT id FROM tb_account ORDER BY id DESC) 
UNION (SELECT id FROM tb_article) 
UNION ALL (SELECT id FROM tb_article)

limit... offset

::: tip 提示 在 "limit... offset" 的示例中MyBatis-Flex 能够自动识别当前数据库👍,并根据数据库的类型生成不同的 SQL用户也可以很轻易的通过 DialectFactory 注册(新增或改写)自己的实现方言。 :::

QueryWrapper queryWrapper = QueryWrapper.create()
    .select()
    .from(ACCOUNT)
    .orderBy(ACCOUNT.ID.desc())
    .limit(10)
    .offset(20);

MySQL 下执行的代码如下:

SELECT * FROM `tb_account` ORDER BY `id` DESC LIMIT 20, 10

PostgreSQL 下执行的代码如下:

SELECT * FROM "tb_account" ORDER BY "id" DESC LIMIT 20 OFFSET 10

Informix 下执行的代码如下:

SELECT SKIP 20 FIRST 10 * FROM "tb_account" ORDER BY "id" DESC

Oracle 下执行的代码如下:

SELECT * FROM (SELECT TEMP_DATAS.*,
    ROWNUM RN FROM (
        SELECT * FROM "tb_account" ORDER BY "id" DESC)
    TEMP_DATAS WHERE  ROWNUM <=30)
WHERE RN >20

Db2 下执行的代码如下:

SELECT * FROM "tb_account" ORDER BY "id" DESC
OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY

Sybase 下执行的代码如下:

SELECT TOP 10 START AT 21 * FROM "tb_account" ORDER BY "id" DESC

Firebird 下执行的代码如下:

SELECT * FROM "tb_account" ORDER BY "id" DESC ROWS 20 TO 30

Lambda 扩展

虽然 MyBaits-Flex 也支持 lambda 方式,但是并不推荐使用,建议在一些简单的场景下使用,以下是示例:

简单示例:

QueryWrapper query = QueryWrapper.create();
query.where(Account::getId).ge(100)
        .and(Account::getUserName).like("michael")
        .or(Account::getUserName).like(" ", If::hasText);
System.out.println(query.toSQL());

SQL 输入内容如下:

SELECT * FROM  WHERE `id` >=  100  AND `user_name` LIKE  '%michael%'

稍微复杂点的示例:

QueryWrapper query = QueryWrapper.create()
    .from(Article.class)
    .leftJoin(Account.class).as("a").on(
        wrapper -> wrapper.where(Account::getId).eq(Article::getAccountId)
    )
    .where(Account::getId).ge(100, If::notEmpty)
    .and(wrapper -> {
        wrapper.where(Account::getId).ge(100)
                .or(Account::getAge).gt(200)
                .and(Article::getAccountId).eq(200)
                .or(wrapper1 -> {
                    wrapper1.where(Account::getId).like("a", If::notEmpty);
                })
        ;
    });
System.out.println(query.toSQL());

SQL 输入内容如下:

SELECT * FROM `tb_article` 
    LEFT JOIN `tb_account` AS `a` ON `a`.`id` = `tb_article`.`account_id` 
WHERE `a`.`id` >=  100  AND 
      (`a`.`id` >=  100  
           OR `a`.`age` >  200  
           AND `tb_article`.`account_id` =  200  
           OR (`a`.`id` LIKE  '%a%' )
      )

QueryWrapper 序列化

QueryWrapper 中,由于其定义了 循环引用 的一些数据结构,同时,其很多属性都是 private 或者 protected 修饰且没有 getter setter 方法, 这会导致使用一些 json 库在序列化的过程中,出现问题;但这些问题并非 QueryWrapper 的问题,而是序列化框架的问题。

因此,我们在使用序列化框架时,需要注意其是否支持这些特征,比如在使用 FastJson2 序列化时,需要添加以下配置:

序列化:

String json = JSON.toJSONString(queryWrapper
        , JSONWriter.Feature.FieldBased // 基于 field而非 getter 方法
        , JSONWriter.Feature.ReferenceDetection);

反序列化:

QueryWrapper query  = JSON.parseObject(json, QueryWrapper.class
, JSONReader.Feature.FieldBased);

GsonJackson 等其他框架需要自行参考其文档添加相关配置;另外,我们更加建议使用专业的序列化工具去进行序列化,而非使用 json比如使用 JDK Serial 或者 fst 等。 以下是相关的序列化示例代码:

JDK Serial 序列化:

QueryWrapper queryWrapper = ...
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(queryWrapper);

JDK Serial 反序列化:

byte[] bytes = ....
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bis);
QueryWrapper queryWrapper = (QueryWrapper) ois.readObject();

fst 序列化 和 反序列化:

FSTConfiguration fst = FSTConfiguration.createDefaultConfiguration();

//序列化得到 bytes 进行存储或者传输
byte[] bytes = fst.asByteArray(wrapper);

//反序列化得到 QueryWrapper
QueryWrapper newWrapper = (QueryWrapper) fst.asObject(bytes);

特别注意事项!!!

在 QueryWrapper 的条件构建中,如果传入 null 值,则自动忽略该条件,这有许多的好处,不需要额外的通过 when() 方法判断。但是也带来一些额外的知识记忆点, 因此,正对这一点需要特别注意一下。

例如:

String userName = null;
Integer id = null;
QueryWrapper query1 = QueryWrapper.create()
    .where(ACCOUNT.AGE.ge(18))
    .and(ACCOUNT.USER_NAME.like(userName))
    .and(ACCOUNT.ID.ge(id));

QueryWrapper query2 = QueryWrapper.create()
    .where(ACCOUNT.AGE.ge(18));

在以上的 query1 中,由于 userNameid 都为 nullMyBatis-Flex 会自动忽略 null 值的条件,因此,它们构建出来的 SQL 条件是和 query2 完全一致的 。

存在疑问?

疑问1示例代码中的 QueryWrapper 所需要的 "ACCOUNT" 从哪里来的?

MyBatis-Flex 使用了 APTAnnotation Processing Tool在项目编译的时候 会自动根据 Entity 类定义的字段生成 "ACCOUNT" 类以及 Entity 对应的 Mapper 类, 通过开发工具构建项目(如下图), 或者执行 maven 编译命令: mvn clean package 都可以自动生成。这个原理和 lombok 一致。

更多关于 APT 的配置,请进入 APT 配置章节 了解。