mirror of
https://gitee.com/mybatis-flex/mybatis-flex.git
synced 2025-12-06 16:48:24 +08:00
Merge branch 'main' of gitee.com:mybatis-flex/mybatis-flex into main
Signed-off-by: 王帅 <1474983351@qq.com>
This commit is contained in:
commit
bad11ec060
101
changes.txt
101
changes.txt
@ -1,3 +1,104 @@
|
||||
mybatis-flex v1.3.0 20230525:
|
||||
新增:新增 一对多、多对一 查询功能
|
||||
新增:为 SqlServer 添加独立的 LimitOffset 处理器
|
||||
新增:QueryWrapper 新增 "for update" 的 SQL 构建支持
|
||||
新增:QueryWrapper 新增 select convert(...) 的 SQL 构建支持
|
||||
新增:QueryWrapper 新增 select case when ... then 的 SQL 构建支持
|
||||
优化:APT 默认生成独立文件,之前所有 APT 生成在同一个文件修改为可配置
|
||||
修复:Table.camelToUnderline 注解在 APT 上配置不生效的问题
|
||||
修复:多租户设置 TenantId 时,在某些极端情况下出现异常的问题
|
||||
文档:新增 一对多、多对一 的相关文档
|
||||
文档:新增 select case when ... then 的 QueryWrapper 示例
|
||||
文档:添加关于 hint 的相关描述
|
||||
|
||||
|
||||
|
||||
mybatis-flex v1.2.9 20230523:
|
||||
新增:字段添加对 Byte.class 类型的支持 #I76GNW
|
||||
修复:"select * ,(select ...) from ..." 第二个 select 有参数时出错的问题
|
||||
修复:QueryCondition 在 left join 查询是出现 NPE 的问题
|
||||
修复:修改 QueryWrapper 添加 limit(1) 时,SQLServer 方言构建 SQL 出错的问题
|
||||
修复:修改 TDENGINE 方言出错的问题
|
||||
修复:baseMapper.selectOneByQueryAs() 查询出错的问题
|
||||
修复:当自定义 TypeHandler 时,通过 UpdateEntity 更新时,TypeHandler 无效的问题
|
||||
优化:重构 TableInfo.appendConditions() 代码
|
||||
优化:修改错别字 "那些" 为 "哪些"
|
||||
文档:更新 "Mybatis" 为 "MyBatis" #I72DWO
|
||||
文档:添加使用 druid-spring-boot-starter 出错到常见问题里
|
||||
|
||||
|
||||
|
||||
mybatis-flex v1.2.8 20230522:
|
||||
新增:新增 select id,(select...) from 的支持
|
||||
优化:Solon 插件增加 RowMapperInvoker 注入和 FlexGlobalConfig 可事件扩展的支持,感谢 @西东
|
||||
优化:分页的 count 查询默认去掉 left join 和 order by 等
|
||||
优化:APT 的 ALL_COLUMNS 修改 table.*
|
||||
文档:添加 hint 的相关文档
|
||||
文档:优化 mybatis-flex-solon-plugin 的使用文档
|
||||
文档:优化 queryWrapper 的相关文档
|
||||
|
||||
|
||||
|
||||
mybatis-flex v1.2.7 20230520:
|
||||
新增:添加 solon 关于 ServiceImpl 的实现
|
||||
新增:left join 等 join 查询添加 as(lambda) 的支持
|
||||
优化:优化 EnumWrapper.java 使之具有更高的性能
|
||||
优化:迁移 IService 到 core 目录
|
||||
优化:重命名 Db.updateBatchEntity 为 Db.updateEntitiesBatch
|
||||
修复:逻辑删除设置 bool 类型在 postgresql 下出错的问题
|
||||
文档:添加批量操作的相关文档说明
|
||||
文档:添加关联查询的相关文档
|
||||
|
||||
|
||||
|
||||
mybatis-flex v1.2.6 20230518:
|
||||
新增:IService 添加 updateBatch 方法,感谢 @Saoforest
|
||||
优化:findById 默认返回 isLarge 的字段 #I73SJY
|
||||
优化:WrapperUtil.getValues() 并直接读取枚举内容
|
||||
修复:ClassUtil 修复无法正确读取 JDK 动态代理超类问题,感谢 @Saoforest
|
||||
修复:批量执行每一个批次会少 1 条数据的问题,感谢 @笨小孩
|
||||
文档:添加数据权限的相关文档
|
||||
|
||||
|
||||
|
||||
mybatis-flex v1.2.5 20230517:
|
||||
新增:Db.executeBatch 方法,用于批量操作
|
||||
新增:Db 工具类添加基于 Entity 的 updateBatch 方法,感谢 @黄沐鸿
|
||||
新增:KeyGenerators.java 方便进行主键生成策略配置
|
||||
新增:APT 的 mybatis-flex.properties 文件添加使用 ClassLoader 读取,方便读取 jar 的内容,感谢 @XiaoLin
|
||||
新增:QueryWrapper 新增 hash join 的支持
|
||||
新增:QueryWrapper 新增 sql hint 的支持
|
||||
优化:添加 configuration-processor,实现 yaml 配置自动提示,感谢 @tan90
|
||||
修复:v1.2.4 新增的 paginateAs 异常问题 #I73BP6
|
||||
修复:v1.2.4 版本造成的 Db.paginate 出错的问题
|
||||
文档:优化 id 主键生成器的相关文档
|
||||
|
||||
|
||||
|
||||
mybatis-flex v1.2.4 20230515:
|
||||
新增:Db.updateBatch() 方法,用于批量修改或者插入等场景
|
||||
新增:通过雪花算法生成数据库主键,内置雪花算法,感谢 @王帅
|
||||
新增:QueryCondition 可以直接传入枚举变量,自动读取 @EnumValue
|
||||
新增:代码生成器添加 Service、ServiceImpl、Controller 的生成功能,感谢 @王帅
|
||||
新增:Db 和 BaseMapper 添加 selectObject 和 selectObjectList 等方法
|
||||
新增:BaseMapper 添加 selectOneByQueryAs 等方法,方便在 left join 等场景直接转换为 dto vo 等;
|
||||
优化:不变属性添加 final 关键字,感谢 @庄佳彬
|
||||
优化:修改拼写错误,processer->processor, 感谢 @庄佳彬
|
||||
优化:Page.of() 方法
|
||||
优化:为 IService.java 添加常用的方法
|
||||
优化:修改 SqlUtil.retBool 为 SqlUtil.toBool
|
||||
修复:orderBy 参数里传入 null 或者 空值,就会抛出异常的问题 #23
|
||||
文档:优化 mybatis-flex.com 的目录结构
|
||||
文档:新增内置主键生成器说明文档,感谢 @王帅
|
||||
文档:修正数据脱敏中的一些描述错误
|
||||
文档:IService 文档添加代码示例
|
||||
文档:优化 "部分字段更新" 的相关文档
|
||||
文档:更新代码生成器的相关文档,感谢 @王帅
|
||||
文档:添加关于 BaseMapper 进行关联查询的相关文档
|
||||
文档:添加在 Spring 的场景下使用 @Transactional 注解的注意事项
|
||||
|
||||
|
||||
|
||||
mybatis-flex v1.2.3 20230511:
|
||||
新增:MaskManager 添加 withoutMask 模板方法,感谢 @pengpeng
|
||||
新增:TenantManager 添加 withoutTenantCondition 模板方法,感谢 @pengpeng
|
||||
|
||||
41
docs/.vitepress/cache/deps/@theme_index.js
vendored
41
docs/.vitepress/cache/deps/@theme_index.js
vendored
@ -1,41 +0,0 @@
|
||||
// node_modules/vitepress/dist/client/theme-default/index.js
|
||||
import "/Users/michael/work/git/mybatis-flex/docs/node_modules/vitepress/dist/client/theme-default/styles/fonts.css";
|
||||
|
||||
// node_modules/vitepress/dist/client/theme-default/without-fonts.js
|
||||
import "/Users/michael/work/git/mybatis-flex/docs/node_modules/vitepress/dist/client/theme-default/styles/vars.css";
|
||||
import "/Users/michael/work/git/mybatis-flex/docs/node_modules/vitepress/dist/client/theme-default/styles/base.css";
|
||||
import "/Users/michael/work/git/mybatis-flex/docs/node_modules/vitepress/dist/client/theme-default/styles/utils.css";
|
||||
import "/Users/michael/work/git/mybatis-flex/docs/node_modules/vitepress/dist/client/theme-default/styles/components/custom-block.css";
|
||||
import "/Users/michael/work/git/mybatis-flex/docs/node_modules/vitepress/dist/client/theme-default/styles/components/vp-code.css";
|
||||
import "/Users/michael/work/git/mybatis-flex/docs/node_modules/vitepress/dist/client/theme-default/styles/components/vp-code-group.css";
|
||||
import "/Users/michael/work/git/mybatis-flex/docs/node_modules/vitepress/dist/client/theme-default/styles/components/vp-doc.css";
|
||||
import "/Users/michael/work/git/mybatis-flex/docs/node_modules/vitepress/dist/client/theme-default/styles/components/vp-sponsor.css";
|
||||
import VPBadge from "/Users/michael/work/git/mybatis-flex/docs/node_modules/vitepress/dist/client/theme-default/components/VPBadge.vue";
|
||||
import Layout from "/Users/michael/work/git/mybatis-flex/docs/node_modules/vitepress/dist/client/theme-default/Layout.vue";
|
||||
import { default as default2 } from "/Users/michael/work/git/mybatis-flex/docs/node_modules/vitepress/dist/client/theme-default/components/VPHomeHero.vue";
|
||||
import { default as default3 } from "/Users/michael/work/git/mybatis-flex/docs/node_modules/vitepress/dist/client/theme-default/components/VPHomeFeatures.vue";
|
||||
import { default as default4 } from "/Users/michael/work/git/mybatis-flex/docs/node_modules/vitepress/dist/client/theme-default/components/VPHomeSponsors.vue";
|
||||
import { default as default5 } from "/Users/michael/work/git/mybatis-flex/docs/node_modules/vitepress/dist/client/theme-default/components/VPDocAsideSponsors.vue";
|
||||
import { default as default6 } from "/Users/michael/work/git/mybatis-flex/docs/node_modules/vitepress/dist/client/theme-default/components/VPTeamPage.vue";
|
||||
import { default as default7 } from "/Users/michael/work/git/mybatis-flex/docs/node_modules/vitepress/dist/client/theme-default/components/VPTeamPageTitle.vue";
|
||||
import { default as default8 } from "/Users/michael/work/git/mybatis-flex/docs/node_modules/vitepress/dist/client/theme-default/components/VPTeamPageSection.vue";
|
||||
import { default as default9 } from "/Users/michael/work/git/mybatis-flex/docs/node_modules/vitepress/dist/client/theme-default/components/VPTeamMembers.vue";
|
||||
var theme = {
|
||||
Layout,
|
||||
enhanceApp: ({ app }) => {
|
||||
app.component("Badge", VPBadge);
|
||||
}
|
||||
};
|
||||
var without_fonts_default = theme;
|
||||
export {
|
||||
default5 as VPDocAsideSponsors,
|
||||
default3 as VPHomeFeatures,
|
||||
default2 as VPHomeHero,
|
||||
default4 as VPHomeSponsors,
|
||||
default9 as VPTeamMembers,
|
||||
default6 as VPTeamPage,
|
||||
default8 as VPTeamPageSection,
|
||||
default7 as VPTeamPageTitle,
|
||||
without_fonts_default as default
|
||||
};
|
||||
//# sourceMappingURL=@theme_index.js.map
|
||||
@ -1,7 +0,0 @@
|
||||
{
|
||||
"version": 3,
|
||||
"sources": ["../../../node_modules/vitepress/dist/client/theme-default/index.js", "../../../node_modules/vitepress/dist/client/theme-default/without-fonts.js"],
|
||||
"sourcesContent": ["import './styles/fonts.css';\nexport * from './without-fonts';\nexport { default as default } from './without-fonts';\n", "import './styles/vars.css';\nimport './styles/base.css';\nimport './styles/utils.css';\nimport './styles/components/custom-block.css';\nimport './styles/components/vp-code.css';\nimport './styles/components/vp-code-group.css';\nimport './styles/components/vp-doc.css';\nimport './styles/components/vp-sponsor.css';\nimport VPBadge from './components/VPBadge.vue';\nimport Layout from './Layout.vue';\n// Note: if we add more optional components here, i.e. components that are not\n// used in the theme by default unless the user imports them, make sure to update\n// the `lazyDefaultThemeComponentsRE` regex in src/node/build/bundle.ts.\nexport { default as VPHomeHero } from './components/VPHomeHero.vue';\nexport { default as VPHomeFeatures } from './components/VPHomeFeatures.vue';\nexport { default as VPHomeSponsors } from './components/VPHomeSponsors.vue';\nexport { default as VPDocAsideSponsors } from './components/VPDocAsideSponsors.vue';\nexport { default as VPTeamPage } from './components/VPTeamPage.vue';\nexport { default as VPTeamPageTitle } from './components/VPTeamPageTitle.vue';\nexport { default as VPTeamPageSection } from './components/VPTeamPageSection.vue';\nexport { default as VPTeamMembers } from './components/VPTeamMembers.vue';\nconst theme = {\n Layout,\n enhanceApp: ({ app }) => {\n app.component('Badge', VPBadge);\n }\n};\nexport default theme;\n"],
|
||||
"mappings": ";AAAA,OAAO;;;ACAP,OAAO;AACP,OAAO;AACP,OAAO;AACP,OAAO;AACP,OAAO;AACP,OAAO;AACP,OAAO;AACP,OAAO;AACP,OAAO,aAAa;AACpB,OAAO,YAAY;AAInB,SAAoB,WAAXA,gBAA6B;AACtC,SAAoB,WAAXA,gBAAiC;AAC1C,SAAoB,WAAXA,gBAAiC;AAC1C,SAAoB,WAAXA,gBAAqC;AAC9C,SAAoB,WAAXA,gBAA6B;AACtC,SAAoB,WAAXA,gBAAkC;AAC3C,SAAoB,WAAXA,gBAAoC;AAC7C,SAAoB,WAAXA,gBAAgC;AACzC,IAAM,QAAQ;AAAA,EACV;AAAA,EACA,YAAY,CAAC,EAAE,IAAI,MAAM;AACrB,QAAI,UAAU,SAAS,OAAO;AAAA,EAClC;AACJ;AACA,IAAO,wBAAQ;",
|
||||
"names": ["default"]
|
||||
}
|
||||
12
docs/.vitepress/cache/deps/_metadata.json
vendored
12
docs/.vitepress/cache/deps/_metadata.json
vendored
@ -1,17 +1,11 @@
|
||||
{
|
||||
"hash": "02c07141",
|
||||
"browserHash": "e1f90fb9",
|
||||
"hash": "6f7e7a0c",
|
||||
"browserHash": "e8cc6d21",
|
||||
"optimized": {
|
||||
"vue": {
|
||||
"src": "../../../node_modules/vue/dist/vue.runtime.esm-bundler.js",
|
||||
"file": "vue.js",
|
||||
"fileHash": "129bee0c",
|
||||
"needsInterop": false
|
||||
},
|
||||
"@theme/index": {
|
||||
"src": "../../../node_modules/vitepress/dist/client/theme-default/index.js",
|
||||
"file": "@theme_index.js",
|
||||
"fileHash": "7f1d5583",
|
||||
"fileHash": "98aba298",
|
||||
"needsInterop": false
|
||||
}
|
||||
},
|
||||
|
||||
@ -58,10 +58,13 @@ export default defineConfig({
|
||||
{
|
||||
text: '基础功能',
|
||||
items: [
|
||||
{text: '增删改', link: '/zh/base/add-delete-update'},
|
||||
{text: '增、删、改', link: '/zh/base/add-delete-update'},
|
||||
{text: '查询和分页', link: '/zh/base/query'},
|
||||
{text: 'IService', link: '/zh/base/service'},
|
||||
{text: '批量操作', link: '/zh/base/batch'},
|
||||
{text: '一对多、多对一', link: '/zh/base/field-query'},
|
||||
{text: 'QueryWrapper', link: '/zh/base/querywrapper'},
|
||||
{text: 'Db + Row', link: '/zh/base/db-row'},
|
||||
{text: 'IService', link: '/zh/base/service'},
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -70,7 +73,6 @@ export default defineConfig({
|
||||
{text: '@Table 注解', link: '/zh/core/table'},
|
||||
{text: '@Id 注解', link: '/zh/core/id'},
|
||||
{text: '@Column 注解', link: '/zh/core/column'},
|
||||
{text: 'Db + Row', link: '/zh/core/db-row'},
|
||||
{text: '逻辑删除', link: '/zh/core/logic-delete'},
|
||||
{text: '乐观锁', link: '/zh/core/version'},
|
||||
{text: '数据填充', link: '/zh/core/fill'},
|
||||
@ -79,6 +81,7 @@ export default defineConfig({
|
||||
{text: 'SQL 打印', link: '/zh/core/sql-print'},
|
||||
{text: '多数据源', link: '/zh/core/multi-datasource'},
|
||||
{text: '事务管理', link: '/zh/core/tx'},
|
||||
{text: '数据权限', link: '/zh/core/data-permission'},
|
||||
{text: '字段权限', link: '/zh/core/columns-permission'},
|
||||
{text: '字段加密', link: '/zh/core/columns-encrypt'},
|
||||
{text: '字典回写', link: '/zh/core/columns-dict'},
|
||||
|
||||
16
docs/.vitepress/theme/MyLayout.vue
Normal file
16
docs/.vitepress/theme/MyLayout.vue
Normal file
@ -0,0 +1,16 @@
|
||||
<!--.vitepress/theme/MyLayout.vue-->
|
||||
<script setup>
|
||||
import DefaultTheme from 'vitepress/theme'
|
||||
const { Layout } = DefaultTheme
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Layout>
|
||||
<!-- <template #doc-footer-before>-->
|
||||
<!-- <div class="tip custom-block" style="margin-bottom: 24px">-->
|
||||
<!-- <p class="custom-block-title">广告</p>-->
|
||||
<!-- <p>此次广告位招租。</p>-->
|
||||
<!-- </div>-->
|
||||
<!-- </template>-->
|
||||
</Layout>
|
||||
</template>
|
||||
10
docs/.vitepress/theme/index.js
Normal file
10
docs/.vitepress/theme/index.js
Normal file
@ -0,0 +1,10 @@
|
||||
// .vitepress/theme/index.js
|
||||
import DefaultTheme from 'vitepress/theme'
|
||||
import MyLayout from './MyLayout.vue'
|
||||
|
||||
export default {
|
||||
...DefaultTheme,
|
||||
// override the Layout with a wrapper component that
|
||||
// injects the slots
|
||||
Layout: MyLayout
|
||||
}
|
||||
@ -22,6 +22,6 @@ features:
|
||||
- title: 更灵活
|
||||
details: MyBatis-Flex 提供了非常灵活的 QueryWrapper,支持关联查询、多表查询、多主键、逻辑删除、乐观锁更新、数据填充、数据脱敏、等等....
|
||||
- title: 更高的性能
|
||||
details: MyBatis-Flex 通过独特的架构,没有任何 Mybatis 拦截器、在 SQL 执行的过程中,没有任何的 Sql Parse,因此会带来指数级的性能增长。
|
||||
details: MyBatis-Flex 通过独特的架构,没有任何 MyBatis 拦截器、在 SQL 执行的过程中,没有任何的 SQL Parse,因此会带来指数级的性能增长。
|
||||
---
|
||||
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
# Mybatis-Flex 的增删改功能
|
||||
# MyBatis-Flex 的增删改功能
|
||||
|
||||
Mybatis-Flex 内置了一个名为 `BaseMapper` 的接口,它实现了基本的增删改查功能以及分页查询功能。
|
||||
MyBatis-Flex 内置了一个名为 `BaseMapper` 的接口,它实现了基本的增删改查功能以及分页查询功能。
|
||||
|
||||
> Mybatis-Flex 的 **代码生成器** 生成的所有 Mapper 辅助类,都是继承 BaseMapper。
|
||||
> MyBatis-Flex 的 **代码生成器** 生成的所有 Mapper 辅助类,都是继承 BaseMapper。
|
||||
|
||||
## 新增数据
|
||||
|
||||
@ -49,7 +49,7 @@ accountMapper.deleteByCondition(ACCOUNT.ID.ge(100));
|
||||
delete from tb_account where id >= 100;
|
||||
```
|
||||
|
||||
>tip: QueryWrapper 非常灵活,也是 Mybatis-Flex 的特色之一,更多关于 QueryWrapper 的
|
||||
>tip: QueryWrapper 非常灵活,也是 MyBatis-Flex 的特色之一,更多关于 QueryWrapper 的
|
||||
> 请看 [QueryWrapper 章节](./querywrapper)。
|
||||
|
||||
## 更新数据
|
||||
@ -71,15 +71,22 @@ delete from tb_account where id >= 100;
|
||||
在很多场景下,我们希望只更新**部分字段**,而更新的字段中,一些为 null,一些非 null。此时需要用到 `UpdateEntity` 工具类,以下是示例代码:
|
||||
|
||||
```java
|
||||
Account account = UpdateEntity.of(Account.class);
|
||||
account.setId(100);
|
||||
Account account = UpdateEntity.of(Account.class, 100);
|
||||
//Account account = UpdateEntity.of(Account.class);
|
||||
//account.setId(100);
|
||||
|
||||
account.setUserName(null);
|
||||
account.setAge(10);
|
||||
|
||||
accountMapper.update(account);
|
||||
```
|
||||
|
||||
以上的示例中,会把 id (主键)为 100 这条数据中的 user_name 字段更新为 null,age 字段更新为 10,其他字段不会被更新。也就是说,通过 UpdateEntity 创建的对象,只会更新调用了 setter 方法的字段,若不调用 setter 方法,不管这个对象里的属性的值是什么,都不会更新到数据库。
|
||||
|
||||
|
||||
|
||||
以上的示例中,会把 id (主键)为 100 这条数据中的 user_name 字段更新为 null,age 字段更新为 10,其他字段不会被更新。
|
||||
|
||||
也就是说,通过 UpdateEntity 创建的对象,只会更新调用了 setter 方法的字段,若不调用 setter 方法,不管这个对象里的属性的值是什么,都不会更新到数据库。
|
||||
|
||||
其执行的 sql 内容如下:
|
||||
|
||||
@ -88,3 +95,16 @@ update tb_account
|
||||
set user_name = ?, age = ? where id = ?
|
||||
#参数: null,10,100
|
||||
```
|
||||
|
||||
注意:
|
||||
|
||||
```java
|
||||
Account account = UpdateEntity.of(Account.class, 100);
|
||||
```
|
||||
等同于:
|
||||
|
||||
```java
|
||||
Account account = UpdateEntity.of(Account.class);
|
||||
account.setId(100);
|
||||
```
|
||||
|
||||
|
||||
83
docs/zh/base/batch.md
Normal file
83
docs/zh/base/batch.md
Normal file
@ -0,0 +1,83 @@
|
||||
# 批量操作
|
||||
|
||||
在 MyBatis-Flex 中,提供了许多批量操作(批量插入、批量更新等)的方法,当有多个方法的时候,会经常导致误用的情况。
|
||||
|
||||
|
||||
|
||||
## `BaseMapper.insertBatch` 方法
|
||||
|
||||
这个方法的示例代码如下:
|
||||
|
||||
```java
|
||||
List<Account> accounts = ....
|
||||
mapper.insertBatch(accounts);
|
||||
```
|
||||
通过 `BaseMapper.insertBatch` 执行时,会通过 `accounts` 去组装一个如下的 SQL:
|
||||
|
||||
```sql
|
||||
insert into tb_account(id,nickname, .....) values
|
||||
(100,"miachel100", ....),
|
||||
(101,"miachel101", ....),
|
||||
(102,"miachel102", ....),
|
||||
(103,"miachel103", ....),
|
||||
(104,"miachel104", ....),
|
||||
(105,"miachel105", ....);
|
||||
```
|
||||
这种有一个特点:在小批量数据执行插入的时候,效率是非常高;但是当数据列表过多时,其生成的 SQL 可能会非常大, 这个大的 SQL
|
||||
在传输和执行的时候就会变得很慢了。
|
||||
|
||||
因此,`BaseMapper.insertBatch` 方法只适用于在小批量数据插入的场景,比如 100 条数据以内。
|
||||
|
||||
## `Db.executeBatch` 方法
|
||||
|
||||
`Db.executeBatch` 可以用于进行批量的插入、修改和删除,以下是使用 `Db.executeBatch` 进行批量插入的示例:
|
||||
|
||||
```java
|
||||
List<Account> accounts = ....
|
||||
Db.executeBatch(accounts.size(), 1000, AccountMapper.class, (mapper, index) -> {
|
||||
Account account = accounts.get(index);
|
||||
mapper.insert(account);
|
||||
});
|
||||
```
|
||||
`Db.executeBatch` 是通过 JDBC 的 `Statement.executeBatch()` 进行批量执行;这个在大批量数据执行的时候,效率要比 `BaseMapper.insertBatch` 高出许多;
|
||||
|
||||
::: tip 提示
|
||||
我看到有一些同学担心 `BaseMapper.insertBatch` 被误用,在 `IService` 中通过使用 `Db.executeBatch` 重写了 Service 的 `insertBatch` 方法,这也是没问题的。但还是需要明白,
|
||||
`BaseMapper.insertBatch` 和 `Db.executeBatch` 的底层实现差异,以及不同的使用场景。
|
||||
:::
|
||||
|
||||
## `Db.updateBatch` 方法
|
||||
|
||||
这个方法的示例代码如下:
|
||||
|
||||
```java
|
||||
List<Account> accounts = ....
|
||||
String sql = "insert into tb_account(user_name,age,birthday) values (?,?,?)";
|
||||
Db.updateBatch(sql, new BatchArgsSetter() {
|
||||
@Override
|
||||
public int getBatchSize() {
|
||||
return accounts.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] getSqlArgs(int index) {
|
||||
Account account = accounts = accounts.get(index);
|
||||
Object[] args = new Object[3];
|
||||
args[0] = account.getUserName;
|
||||
args[1] = account.getAge();
|
||||
args[2] = new Date();
|
||||
return args;
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
虽然这个方法叫 `updateBatch`,但一样可以执行 `insert`、`delete`、`update` 等任何 SQL; 这个方法类似 Spring 的 `jdbcTemplate.batchUpdate()` 方法。
|
||||
|
||||
|
||||
## `Db.updateEntitiesBatch` 方法
|
||||
这个方法用于批量根据 id 更新 entity,其是对 `Db.executeBatch` 的封装,使用代码如下:
|
||||
|
||||
```java
|
||||
List<Account> accounts = ....
|
||||
Db.updateEntitiesBatch(accounts, 1000);
|
||||
```
|
||||
@ -35,7 +35,7 @@ Page<Row> rowPage = Db.paginate("tb_account",3,10,query);
|
||||
|
||||
> Db 工具类还提供了更多 增、删、改、查和分页查询等方法。
|
||||
>
|
||||
> 具体参考: [Db.java](./mybatis-flex-core/src/main/java/com/mybatisflex/core/row/Db.java) 。
|
||||
> 具体参考: [Db.java](https://gitee.com/mybatis-flex/mybatis-flex/blob/main/mybatis-flex-core/src/main/java/com/mybatisflex/core/row/Db.java) 。
|
||||
|
||||
## Row.toEntity()
|
||||
|
||||
88
docs/zh/base/field-query.md
Normal file
88
docs/zh/base/field-query.md
Normal file
@ -0,0 +1,88 @@
|
||||
# 一对多、多对一
|
||||
|
||||
在很多场景下,我们可能会用到 `一对多`、`一对一`、`多对一`、`多对多`等场景的关联查询,MyBatis-Flex 内置了相关的方法,用于支持此类场景。
|
||||
|
||||
## 代码示例(多对多)
|
||||
|
||||
以下是文章的示例,一篇文章可能归属于多个分类,一个类可以有多篇文章,需要用到中间表 `article_category_mapping`。
|
||||
|
||||
```java
|
||||
public class Article {
|
||||
private Long id;
|
||||
private String title;
|
||||
private String content;
|
||||
|
||||
//文章的归属分类,可能是 1 个或者多个
|
||||
private List<Category> categories;
|
||||
|
||||
//getter setter
|
||||
}
|
||||
```
|
||||
|
||||
查询代码如下:
|
||||
|
||||
```java {10-14}
|
||||
QueryWrapper queryWrapper = QueryWrapper.create()
|
||||
.select().form(ARTICLE)
|
||||
.where(ARTICLE.id.ge(100));
|
||||
|
||||
List<Article> articles = mapper.selectListByQuery(queryWrapper
|
||||
, fieldQueryBuilder -> fieldQueryBuilder
|
||||
.field(Article::getCategories) // 或者 .field("categories")
|
||||
.type(Category.class) //非集合,自动读取 type,可以不指定 type
|
||||
.queryWrapper(article -> QueryWrapper.create()
|
||||
.select().from(CATEGORY)
|
||||
.where(CATEGORY.id.in(
|
||||
select("category_id").from("article_category_mapping")
|
||||
.where("article_id = ?", article.getId())
|
||||
)
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
通过以上代码可以看出,`Article.categories` 字段的结果,来源于 `queryWrapper()` 方法构建的 `QueryWrapper`。
|
||||
|
||||
其原理是:MyBatis-Flex 的内部逻辑是先查询出 `Article` 的数据,然后再根据 `Article` 构建出新的 SQL,查询分类,并赋值给 `Article.categories`,
|
||||
假设 `Article` 有 10 条数据,那么最终会进行 11 次数据库查询。
|
||||
|
||||
查询的 SQL 大概如下:
|
||||
|
||||
```sql
|
||||
select * from tb_article where id >= 100;
|
||||
|
||||
-- 以上 SQL 得到结果后,再执行查询分类的 SQL,如下:
|
||||
select * from tb_category where id in
|
||||
(select category_id from article_category_mapping where article_id = 100);
|
||||
|
||||
select * from tb_category where id in
|
||||
(select category_id from article_category_mapping where article_id = 101);
|
||||
|
||||
select * from tb_category where id in
|
||||
(select category_id from article_category_mapping where article_id = 102);
|
||||
|
||||
select * from tb_category where id in
|
||||
(select category_id from article_category_mapping where article_id = 103);
|
||||
|
||||
select * from tb_category where id in
|
||||
(select category_id from article_category_mapping where article_id = 104);
|
||||
|
||||
select * from tb_category where id in
|
||||
(select category_id from article_category_mapping where article_id = 105);
|
||||
|
||||
select * from tb_category where id in
|
||||
(select category_id from article_category_mapping where article_id = 106);
|
||||
|
||||
select * from tb_category where id in
|
||||
(select category_id from article_category_mapping where article_id = 107);
|
||||
|
||||
select * from tb_category where id in
|
||||
(select category_id from article_category_mapping where article_id = 108);
|
||||
|
||||
select * from tb_category where id in
|
||||
(select category_id from article_category_mapping where article_id = 109);
|
||||
```
|
||||
|
||||
## 其他场景
|
||||
|
||||
通过以上内容看出,`Article` 的任何属性,都是可以通过传入 `FieldQueryBuilder` 来构建 `QueryWrapper` 进行再次查询,
|
||||
这些不仅仅只适用于 `一对多`、`一对一`、`多对一`、`多对多`等场景。任何 `Article` 对象里的属性,需要二次查询赋值的,都是可以通过这种方式进行。
|
||||
@ -1,26 +1,148 @@
|
||||
# Mybatis-Flex 的查询和分页
|
||||
# MyBatis-Flex 的查询和分页
|
||||
|
||||
## 基础查询
|
||||
|
||||
在 Mybatis-Flex 的 BaseMapper 中,提供了如下的功能用于查询数据库的数据:
|
||||
在 MyBatis-Flex 的 `BaseMapper` 中,提供了如下的功能用于查询数据库的数据:
|
||||
|
||||
- **selectOneById(id)**:根据主键 id 查询数据
|
||||
- **selectOneByMap(map)**:根据 `map<字段名,值>` 组成的条件查询 1 条数据,若命中多条数据,则只返回第一条数据。
|
||||
- **selectOneByCondition(condition)**:根据 condition 组成的条件查询 1 条数据,若命中多条数据,则只返回第一条数据。
|
||||
- **selectOneByQuery(query)**:根据 QueryWrapper 组成的条件查询 1 条数据,若命中多条数据,则只返回第一条数据。
|
||||
- **selectOneByQueryAs(query, asType)**:和 `selectOneByQuery` 方法类似,但是在某些场景下,`query` 可能包含了 `left join` 等多表查询,返回的数据和 entity 字段不一致时,
|
||||
可以通过 `asType` 参数来指定接收的数据类型(通常是 dto、vo 等)。
|
||||
- **selectListByIds(idList)**:根据多个 id 查询,返回多条数据
|
||||
- **selectListByMap(map)**:根据 `map<字段名,值>` 组成的条件查询数据。
|
||||
- **selectListByMap(map, count)**:根据 `map<字段名,值>` 组成的条件查询数据,只取前 count 条。
|
||||
- **selectListByCondition(condition)**:根据 condition 组成的条件查询数据。
|
||||
- **selectListByCondition(condition, count)**:根据 condition 组成的条件查询数据,只取前 count 条。
|
||||
- **selectListByQuery(query)**: 根据 QueryWrapper 组成的条件查询数据。
|
||||
- **selectListByQueryAs(query, asType)**: 和 `selectListByQuery` 方法类似,但是在某些场景下,`query` 可能包含了 `left join` 等多表查询,返回的数据和 entity 字段不一致时,
|
||||
可以通过 `asType` 参数来指定接收的数据类型(通常是 dto、vo 等)。
|
||||
- **selectAll**:查询所有数据。
|
||||
- **selectObjectByQuery(query)**:查询只返回 1 列,并只有 1 条数据的场景。
|
||||
- **selectObjectListByQuery(query)**:查询只返回 1 列场景,比如 `QueryWrapper.create().select(ACCOINT.ID).from(...)`。
|
||||
- **selectObjectListByQueryAs(query, asType)**:对 `selectObjectListByQuery` 进行封装,并转换为特定的类型。
|
||||
- **selectCountByCondition**:根据 QueryWrapper 查询数据量。
|
||||
- **selectCountByQuery**:根据 QueryWrapper 查询数据量。
|
||||
|
||||
## 关联查询(或多表查询)
|
||||
|
||||
在 `BaseMapper` 中,提供了 `selectOneByQueryAs`、`selectListByQueryAs` 、`paginateAs` 等方法,用于处理关联查询的场景。
|
||||
|
||||
假设有 `tb_account` 用户表和 `tb_article` 文章表,他们的字段分别如下:
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS `tb_account`
|
||||
(
|
||||
`id` INTEGER PRIMARY KEY auto_increment,
|
||||
`user_name` VARCHAR(100),
|
||||
`age` Integer,
|
||||
`birthday` DATETIME
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `tb_article`
|
||||
(
|
||||
`id` INTEGER PRIMARY KEY auto_increment,
|
||||
`account_id` Integer,
|
||||
`title` VARCHAR(100),
|
||||
`content` text
|
||||
);
|
||||
```
|
||||
|
||||
当我们进行关联查询时,可以通过如下代码进行:
|
||||
|
||||
1、定义 `ArticleDTO` 类
|
||||
```java
|
||||
public class ArticleDTO {
|
||||
|
||||
private Long id;
|
||||
private Long accountId;
|
||||
private String title;
|
||||
private String content;
|
||||
|
||||
//以下用户相关字段
|
||||
private String userName;
|
||||
private int age;
|
||||
private Date birthday;
|
||||
}
|
||||
```
|
||||
|
||||
2、使用 `QueryWrapper` 构建 `left join` 查询,查询结果通过 `ArticleDTO` 类型接收。
|
||||
```java
|
||||
QueryWrapper query = QueryWrapper.create()
|
||||
.select(ARTICLE.ALL_COLUMNS)
|
||||
.select(ACCOUNT.USER_NAME,ACCOUNT.AGE,ACCOUNT.BIRTHDAY)
|
||||
.from(ARTICLE)
|
||||
.leftJoin(ACCOUNT).on(ARTICLE.ACCOUNT_ID.eq(ACCOUNT.ID))
|
||||
.where(ACCOUNT.ID.ge(0));
|
||||
|
||||
List<ArticleDTO> results = mapper.selectListByQueryAs(query, ArticleDTO.class);
|
||||
System.out.println(results);
|
||||
```
|
||||
|
||||
假设 `ArticleDTO` 定义的属性和 SQL 查询的字段不一致时,例如:
|
||||
|
||||
```java
|
||||
public class ArticleDTO {
|
||||
|
||||
private Long id;
|
||||
private Long accountId;
|
||||
private String title;
|
||||
private String content;
|
||||
|
||||
//以下用户字段 和 用户表定义的列不一致,表定义的列为 user_name
|
||||
private String authorName;
|
||||
private int authorAge;
|
||||
private Date birthday;
|
||||
}
|
||||
```
|
||||
|
||||
那么, `QueryWrapper` 需要添加 `as`,修改如下:
|
||||
|
||||
```java 3,4
|
||||
QueryWrapper query = QueryWrapper.create()
|
||||
.select(ARTICLE.ALL_COLUMNS)
|
||||
.select(ACCOUNT.USER_NAME.as(ArticleDTO::getAuthorName)
|
||||
,ACCOUNT.AGE.as(ArticleDTO::getAuthorAge)
|
||||
,ACCOUNT.BIRTHDAY
|
||||
)
|
||||
.from(ARTICLE)
|
||||
.leftJoin(ACCOUNT).on(ARTICLE.ACCOUNT_ID.eq(ACCOUNT.ID))
|
||||
.where(ACCOUNT.ID.ge(0));
|
||||
|
||||
List<ArticleDTO> results = mapper.selectListByQueryAs(query, ArticleDTO.class);
|
||||
System.out.println(results);
|
||||
```
|
||||
|
||||
|
||||
**注意事项:**
|
||||
|
||||
关联查询(`selectOneByQueryAs`、`selectListByQueryAs` 、`paginateAs` 等方法)中的 `asType` 参数类型(比如:`ArticleDTO`),
|
||||
一样支持使用 `@Column`、`@ColumnMask` 注解以及 `@Table` 的 `onInsert`、`onUpdate`、`onSet` 配置。
|
||||
|
||||
同时,要求 `asType` 中的类型,必须定义属性来映射查询到的数据集,因此,以下的使用示例是 **错误** 的:
|
||||
|
||||
```java
|
||||
QueryWrapper query = QueryWrapper.create()
|
||||
.select(ACCOUNT.id)
|
||||
.from(ACCOUNT);
|
||||
|
||||
List<Long> results = mapper.selectOneByQueryAs(query, Long.class);
|
||||
```
|
||||
在以上的示例中, **错误** 的原因是因为 `Long` 这个类型,并没有名称为 `id` 的属性,用来映射查询的结果集。
|
||||
|
||||
在以上的场景中,可以使用如下的方法(以下代码示例是正确的):
|
||||
```java
|
||||
QueryWrapper query = QueryWrapper.create()
|
||||
.select(ACCOUNT.id)
|
||||
.from(ACCOUNT);
|
||||
|
||||
List<Long> results = mapper.selectObjectListByQueryAs(query, Long.class);
|
||||
```
|
||||
|
||||
## 分页查询
|
||||
|
||||
在 Mybatis-Flex 的 BaseMapper 中,提供了如下的分页查询功能:
|
||||
在 MyBatis-Flex 的 BaseMapper 中,提供了如下的分页查询功能:
|
||||
|
||||
```java
|
||||
Page<T> paginate(int pageNumber, int pageSize, QueryWrapper queryWrapper);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# 灵活的 QueryWrapper
|
||||
在 [增删改](./add-delete-update) 和 [查询和分页](./query) 章节中,我们随时能看到 QueryWrapper 的身影,QueryWrapper 是用于构造 Sql 的
|
||||
强有力工具,也是 Mybatis-Flex 的亮点和特色。
|
||||
强有力工具,也是 MyBatis-Flex 的亮点和特色。
|
||||
|
||||
::: tip 提示
|
||||
QueryWrapper 可以被序列化通过 RPC 进行传输,因此,在微服务项目中,我们可以在客户端(网关、Controller 层等)构造出 QueryWrapper,传给
|
||||
@ -21,7 +21,7 @@ public class AccountController {
|
||||
@GetMapping("/accounts")
|
||||
List<Account> selectList() {
|
||||
|
||||
//构造 QueryWrapper
|
||||
//构造 QueryWrapper,也支持使用 QueryWrapper.create() 构造,效果相同
|
||||
QueryWrapper query = new QueryWrapper();
|
||||
query.where(ACCOUNT.ID.ge(100));
|
||||
|
||||
@ -51,7 +51,7 @@ where id >= 100
|
||||
## select *
|
||||
|
||||
```java
|
||||
QueryWrapper query=new QueryWrapper();
|
||||
QueryWrapper query = new QueryWrapper();
|
||||
query.select(ACCOUNT.ID, ACCOUNT.USER_NAME)
|
||||
.from(ACCOUNT)
|
||||
```
|
||||
@ -68,7 +68,7 @@ SELECT id, user_name FROM tb_account
|
||||
QueryWrapper query = new QueryWrapper()
|
||||
.select(
|
||||
ACCOUNT.ID.as("accountId")
|
||||
, ACCOUNT.USER_NAME
|
||||
, ACCOUNT.USER_NAME)
|
||||
.from(ACCOUNT.as("a"));
|
||||
```
|
||||
|
||||
@ -119,6 +119,68 @@ SELECT id, user_name, MAX(birthday), AVG(sex) AS sex_avg
|
||||
FROM tb_account
|
||||
```
|
||||
|
||||
## select case...when
|
||||
|
||||
**示例 1:**
|
||||
|
||||
```java
|
||||
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 如下:
|
||||
|
||||
```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:**
|
||||
|
||||
```java
|
||||
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 如下:
|
||||
|
||||
```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 提示
|
||||
在以上示例中,由于 `case` 和 `else` 属于 Java 关键字,无法使用其进行方法命名,因此会添加一个下划线小尾巴 `"_"` 变成 `case_` 和 `else_`,这是无奈之举。
|
||||
在以后的 QueryWrapper 构建中,遇到 java 关键字也会采用类型的解决方法。
|
||||
:::
|
||||
|
||||
## where
|
||||
|
||||
```java
|
||||
@ -171,6 +233,23 @@ SELECT * FROM tb_account
|
||||
WHERE user_name LIKE ?
|
||||
```
|
||||
|
||||
## where 动态条件 3
|
||||
|
||||
```java 1,5
|
||||
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 如下:
|
||||
|
||||
```sql
|
||||
SELECT * FROM tb_account
|
||||
WHERE id >= ?
|
||||
```
|
||||
|
||||
## where select
|
||||
```java
|
||||
QueryWrapper queryWrapper = QueryWrapper.create()
|
||||
@ -282,6 +361,28 @@ SELECT * FROM tb_account
|
||||
ORDER BY age ASC, user_name DESC NULLS LAST
|
||||
```
|
||||
|
||||
## hint
|
||||
|
||||
Hint 是数据库厂商(比如 Oracle、MySQL、达梦等)提供的一种 SQL语法,它允许用户在 SQL 语句中插入相关的语法,从而影响 SQL 的执行方式。
|
||||
它是一种【非常规】的直接影响优化器、指定执行计划的 SQL 优化手段。
|
||||
|
||||
|
||||
|
||||
```java
|
||||
QueryWrapper queryWrapper=QueryWrapper.create()
|
||||
.select().hint("INDEX_DESC")
|
||||
.from(ACCOUNT)
|
||||
.orderBy(ACCOUNT.AGE.asc(), ACCOUNT.USER_NAME.desc().nullsLast());
|
||||
```
|
||||
|
||||
其查询生成的 Sql 如下:
|
||||
|
||||
```sql
|
||||
SELECT /*+ INDEX_DESC */ * FROM tb_account
|
||||
ORDER BY age ASC, user_name DESC NULLS LAST
|
||||
```
|
||||
|
||||
|
||||
## join(left join,inner join...)
|
||||
|
||||
```java
|
||||
@ -370,7 +471,7 @@ UNION ALL (SELECT id FROM tb_article)
|
||||
## limit... offset
|
||||
|
||||
::: tip 提示
|
||||
在 "limit... offset" 的示例中,Mybatis-Flex 能够自动识别当前数据库👍,并根据数据库的类型生成不同的 SQL,用户也可以很轻易的通过 DialectFactory 注册(新增或改写)自己的实现方言。
|
||||
在 "limit... offset" 的示例中,MyBatis-Flex 能够自动识别当前数据库👍,并根据数据库的类型生成不同的 SQL,用户也可以很轻易的通过 DialectFactory 注册(新增或改写)自己的实现方言。
|
||||
:::
|
||||
|
||||
|
||||
@ -383,7 +484,7 @@ QueryWrapper queryWrapper = QueryWrapper.create()
|
||||
.offset(20);
|
||||
```
|
||||
|
||||
MySql 下执行的代码如下:
|
||||
MySQL 下执行的代码如下:
|
||||
```sql
|
||||
SELECT * FROM `tb_account` ORDER BY `id` DESC LIMIT 20, 10
|
||||
```
|
||||
@ -426,7 +527,7 @@ SELECT * FROM "tb_account" ORDER BY "id" DESC ROWS 20 TO 30
|
||||
|
||||
**疑问1:示例代码中的 QueryWrapper 所需要的 "ACCOUNT" 从哪里来的?**
|
||||
|
||||
答:Mybatis-Flex 使用了 APT(Annotation Processing Tool)在项目编译的时候,
|
||||
答:MyBatis-Flex 使用了 APT(Annotation Processing Tool)在项目编译的时候,
|
||||
会自动根据 Entity 类定义的字段生成 "ACCOUNT" 类以及 Entity 对应的 Mapper 类, 通过开发工具构建项目(如下图),
|
||||
或者执行 maven 编译命令: `mvn clean package` 都可以自动生成。这个原理和 lombok 一致。
|
||||
|
||||
@ -451,4 +552,4 @@ QueryWrapper query1 = QueryWrapper.create()
|
||||
QueryWrapper query2 = QueryWrapper.create()
|
||||
.where(ACCOUNT.AGE.ge(18));
|
||||
```
|
||||
在以上的 `query1` 和 `query2` 中,它们构建出来的 SQL 条件是完全一致的,因为 Mybatis-Flex 会自动忽略 null 值的条件。
|
||||
在以上的 `query1` 和 `query2` 中,它们构建出来的 SQL 条件是完全一致的,因为 MyBatis-Flex 会自动忽略 null 值的条件。
|
||||
@ -2,7 +2,34 @@
|
||||
|
||||
MyBatis-Flex 提供了一个名为 `IService` 的接口,及其默认实现类 `ServiceImpl` ,用于简化在 「Service」 层重复定义 「Mapper」 层的方法。
|
||||
|
||||
> `IService` 接口只是提供了简单且常用的 “增删改查” 方法,更多细节以及复杂的业务,还是需要使用 `BaseMapper` 进行处理。
|
||||
> `IService` 接口只是提供了简单且常用的 “增删改查” 方法,更多细节以及复杂的业务,还是需要使用 `Mapper` 进行处理。
|
||||
|
||||
|
||||
## 示例代码
|
||||
|
||||
接口:
|
||||
|
||||
```java
|
||||
public interface IAccountService extends IService{
|
||||
//你的自定义方法
|
||||
List<Account> customMethod();
|
||||
}
|
||||
```
|
||||
|
||||
实现类:
|
||||
|
||||
```java
|
||||
@Component
|
||||
public class AccountServiceImpl implements IAccountService
|
||||
extends ServiceImpl<AccountMapper, Account>{
|
||||
|
||||
@Override
|
||||
public List<Account> customMethod(){
|
||||
//返回 id >= 100 的数据
|
||||
return list(ACCOUNT.ID.ge(100));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 保存数据
|
||||
|
||||
@ -70,4 +97,4 @@ MyBatis-Flex 提供了一个名为 `IService` 的接口,及其默认实现类
|
||||
|
||||
## 其他方法
|
||||
|
||||
- **getBaseMapper()**:获取对应的 `BaseMapper` 接口。
|
||||
- **getMapper()**:获取对应的 `BaseMapper` 接口。
|
||||
@ -36,13 +36,13 @@ Mybaits-Flex 消息包含了如下内容:
|
||||
|
||||
::: tip 提示
|
||||
通过以上的消息内容可知:每个 SQL 的执行,都包含了:哪个访问用户、哪个 IP 地址访问,访问的是哪个 URL 地址,这个 SQL 的参数是什么,执行的时间是什么,执行
|
||||
花费了多少时间等等。这样,通过 Mybatis-flex 的 SQL 审计功能,我们能全盘了解到每个 SQL 的执行情况。
|
||||
花费了多少时间等等。这样,通过 MyBatis-flex 的 SQL 审计功能,我们能全盘了解到每个 SQL 的执行情况。
|
||||
:::
|
||||
|
||||
|
||||
## 自定义 SQL 审计内容
|
||||
|
||||
Mybatis-Flex 内置了一个名为 `MessageFactory` 的接口,我们只需实现该接口,并为 `AuditManager` 配置新的 `MessageFactory` 即可,如下所示:
|
||||
MyBatis-Flex 内置了一个名为 `MessageFactory` 的接口,我们只需实现该接口,并为 `AuditManager` 配置新的 `MessageFactory` 即可,如下所示:
|
||||
|
||||
```java
|
||||
public class MyMessageFactory implements MessageFactory {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# @Column 注解的使用
|
||||
|
||||
Mybatis-Flex 提供了 `@Column` 用来对字段进行更多的配置,以下是 `@Column` 的代码定义:
|
||||
MyBatis-Flex 提供了 `@Column` 用来对字段进行更多的配置,以下是 `@Column` 的代码定义:
|
||||
|
||||
```java
|
||||
public @interface Column {
|
||||
|
||||
69
docs/zh/core/data-permission.md
Normal file
69
docs/zh/core/data-permission.md
Normal file
@ -0,0 +1,69 @@
|
||||
# 数据权限
|
||||
|
||||
数据权限指的是不同的用户,通过某一个方法去查询的时候,得到的是不同的数据结果集。常见的数据权限有:
|
||||
|
||||
- 获取全部数据
|
||||
- 仅获取本人创建的数据
|
||||
- 获取当前用户的部门数据
|
||||
- 获取部门级以下部门的数据
|
||||
- 获取某个地区的数据
|
||||
- 等等
|
||||
|
||||
这一些,都是通过当前的用户的信息(部门、角色、权限等),查询时,添加特定的条件。在 MyBatis-Flex 中,我们可以通过 2 种方式来实现这一种需求。
|
||||
|
||||
## 方式1:使用自定义数据方言 `IDialect`
|
||||
|
||||
在自定义方言中,重写 `forSelectByQuery` 方法,这个方法是用于构建返回根据 `QueryWrapper` 查询的方法, 以下是示例代码:
|
||||
|
||||
```java
|
||||
public class MyPermissionDialect extends CommonsDialectImpl{
|
||||
|
||||
@Override
|
||||
public String forSelectByQuery(QueryWrapper queryWrapper) {
|
||||
|
||||
//获取当前用户信息,为 queryWrapper 添加额外的条件
|
||||
queryWrapper.and("...");
|
||||
|
||||
return supper.buildSelectSql(queryWrapper);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
在项目启动时,通过 `DialectFactory` 注册 `MyPermissionDialect`:
|
||||
|
||||
```java
|
||||
DialectFactory.registerDialect(DbType.MYSQL, new MyPermissionDialect());
|
||||
```
|
||||
|
||||
|
||||
**常见问题1:通过重写 `IDialect` 后,所有的查询都添加了条件,但是有些表不需要条件如何做?**
|
||||
|
||||
>答:可以通过 CPI 获取 QueryWrapper 查询了哪些表,然后进行动态处理。例如 `List<QueryTable> tables = CPI.getQueryTables(queryWrapper)`,然后进一步对
|
||||
> `tables` 进行验证是否需要添加数据权限。
|
||||
|
||||
## 方式2:重写 IService 的查询方法
|
||||
|
||||
在一般的应用中,查询是通过 Service 进行的,MyBatis-Flex 提供了 `IService` 接口及其默认的 `ServiceImpl` 实现类。
|
||||
|
||||
我们可以通过构建自己的 `IServiceImpl` 来实现这一种需求,例如:
|
||||
|
||||
```java
|
||||
public class MyServiceImpl<M extends BaseMapper<T>, T> implements IService<T> {
|
||||
|
||||
@Autowired
|
||||
protected M mapper;
|
||||
|
||||
@Override
|
||||
public BaseMapper<T> getMapper() {
|
||||
return mapper;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<T> list(QueryWrapper query) {
|
||||
//获取当前用户信息,为 queryWrapper 添加额外的条件
|
||||
return IService.super.list(query);
|
||||
}
|
||||
}
|
||||
```
|
||||
当然,在 `IService` 中,除了 `list` 方法以外,还有其他的查询方法,可能也需要复写一下。
|
||||
@ -12,13 +12,13 @@ public class Account{
|
||||
private TypeEnum typeEnum;
|
||||
}
|
||||
```
|
||||
在默认情况下,Mybatis 内置了一个名为:`EnumTypeHandler` 的处理器,用于处理这种场景。通过 `EnumTypeHandler` 处理后,数据库保存的是 `TypeEnum` 对应的属性名称,
|
||||
在默认情况下,MyBatis 内置了一个名为:`EnumTypeHandler` 的处理器,用于处理这种场景。通过 `EnumTypeHandler` 处理后,数据库保存的是 `TypeEnum` 对应的属性名称,
|
||||
是一个 String 类型。例如 `TypeEnum.TYPE1` 保存到数据库的内容为 `TYPE1` 这个字符串。
|
||||
|
||||
## @EnumValue 注解
|
||||
|
||||
|
||||
但很多时候,我们希望保存到数据库的,是 `TypeEnum` 枚举的某个属性值,而非 `TYPE1` 字符串,那么,我就需要用到 Mybatis-Flex 提供的注解 `@EnumValue`,以下是示例代码:
|
||||
但很多时候,我们希望保存到数据库的,是 `TypeEnum` 枚举的某个属性值,而非 `TYPE1` 字符串,那么,我就需要用到 MyBatis-Flex 提供的注解 `@EnumValue`,以下是示例代码:
|
||||
|
||||
```java 8,9
|
||||
public enum TypeEnum {
|
||||
@ -42,7 +42,7 @@ public enum TypeEnum {
|
||||
}
|
||||
```
|
||||
|
||||
通过注解 `@EnumValue` 为 `code` 属性标注后,当我们保存 Account 内容到数据库时,Mybatis-Flex 会自动使用 `code` 属性值进行保存,同时在读取数据库内容的时候,Mybatis-Flex 自动把数据库的值转换为
|
||||
通过注解 `@EnumValue` 为 `code` 属性标注后,当我们保存 Account 内容到数据库时,MyBatis-Flex 会自动使用 `code` 属性值进行保存,同时在读取数据库内容的时候,MyBatis-Flex 自动把数据库的值转换为
|
||||
`TypeEnum` 枚举。
|
||||
|
||||
::: tip 注意事项
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
数据填充指的是,当 Entity 数据被插入 或者 更新的时候,会为字段进行一些默认的数据设置。这个非常有用,比如说当某个 entity 被插入时候
|
||||
会设置一些数据插入的时间、数据插入的用户 id,多租户的场景下设置当前租户信息等等。
|
||||
|
||||
Mybatis-Flex 提供了两种方式,帮助开发者进行数据填充。
|
||||
MyBatis-Flex 提供了两种方式,帮助开发者进行数据填充。
|
||||
|
||||
- 1、通过 `@Table` 注解的 `onInsert` 和 `onUpdate` 配置进行操作。这部分可以参考 [@Table 注解章节](./table) 。
|
||||
- 2、通过 `@Column` 注解的 `onInsertValue` 和 `onUpdateValue` 配置进行操作。这部分可以参考 [@Column 注解章节](./column)。
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# @Id 主键的使用
|
||||
|
||||
在 Entity 类中,Mybatis-Flex 是使用 `@Id` 注解来标识主键的,如下代码所示:
|
||||
在 Entity 类中,MyBatis-Flex 是使用 `@Id` 注解来标识主键的,如下代码所示:
|
||||
|
||||
```java 5
|
||||
@Table("tb_account")
|
||||
@ -77,16 +77,16 @@ public enum KeyType {
|
||||
|
||||
## 多主键、复合主键
|
||||
|
||||
Mybatis-Flex 多主键就是在 Entity 类里有多个 `@Id` 注解标识而已,比如:
|
||||
MyBatis-Flex 多主键就是在 Entity 类里有多个 `@Id` 注解标识而已,比如:
|
||||
|
||||
```java
|
||||
@Table("tb_account")
|
||||
public class Account {
|
||||
|
||||
@Id(keyType=KeyType.Auto)
|
||||
@Id(keyType = KeyType.Auto)
|
||||
private Long id;
|
||||
|
||||
@Id(keyType=KeyType.Generator, value="uuid")
|
||||
|
||||
@Id(keyType = KeyType.Generator, value = KeyGenerators.uuid)
|
||||
private String otherId;
|
||||
|
||||
//getter setter
|
||||
@ -96,19 +96,19 @@ public class Account {
|
||||
|
||||
## 内置主键生成器
|
||||
|
||||
MyBatis-Flex 内置了三种主键生成器:
|
||||
MyBatis-Flex 内置了三种主键生成器,他们的名称都定义在 `KeyGenerators` 类里:
|
||||
|
||||
- **UUIDKeyGenerator**:生成 UUID 作为数据库主键。
|
||||
- **FlexIDKeyGenerator**:独创的 FlexID 算法生成数据库主键(了解更多信息请参阅[源码](https://gitee.com/mybatis-flex/mybatis-flex/blob/main/mybatis-flex-core/src/main/java/com/mybatisflex/core/keygen/impl/FlexIDKeyGenerator.java));
|
||||
- **SnowFlakeIDKeyGenerator**:通过雪花算法生成数据库主键。
|
||||
- **uuid**:通过 `UUIDKeyGenerator` 生成 UUID 作为数据库主键。
|
||||
- **flexId**:独创的 FlexID 算法生成数据库主键(了解更多信息请参阅[源码](https://gitee.com/mybatis-flex/mybatis-flex/blob/main/mybatis-flex-core/src/main/java/com/mybatisflex/core/keygen/impl/FlexIDKeyGenerator.java))。
|
||||
- **snowFlakeId**:通过雪花算法(`SnowFlakeIDKeyGenerator`)生成数据库主键。
|
||||
|
||||
这些主键生成器无需注册,直接使用即可:
|
||||
这些主键生成器为 MyBatis-Flex 内置的,可直接使用:
|
||||
|
||||
```java
|
||||
```java 4
|
||||
@Table("tb_account")
|
||||
public class Account {
|
||||
|
||||
@Id(keyType=KeyType.Generator, value="flexId")
|
||||
@Id(keyType=KeyType.Generator, value=KeyGenerators.flexId)
|
||||
private Long id;
|
||||
|
||||
//getter setter
|
||||
@ -166,7 +166,7 @@ public class Account {
|
||||
一般的项目中,通常是许多的 Entity 使用同一个数据库,同时使用一种主键生成方式,比如说都使用 自增,
|
||||
或者都使用通过序列(Sequence)生成,此时,我们是没有必要为每个 Entity 单独配置一样内容的。
|
||||
|
||||
Mybatis-Flex 提供了一种全局配置的方式,代码如下:
|
||||
MyBatis-Flex 提供了一种全局配置的方式,代码如下:
|
||||
|
||||
```java
|
||||
FlexGlobalConfig.KeyConfig keyConfig = new FlexGlobalConfig.KeyConfig();
|
||||
@ -183,7 +183,7 @@ FlexGlobalConfig.getDefaultConfig().setKeyConfig(keyConfig);
|
||||
@Table("tb_account")
|
||||
public class Account {
|
||||
|
||||
@Id()
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
我们可以进行表的字段设计时,用一个列标识该数据的 "删除状态",在 mybatis-flex 中,正常状态的值为 0, 已删除
|
||||
的值为 1(可以通过设置 FlexGlobalConfig 来修改这个值)。
|
||||
|
||||
## Mybatis-Flex 逻辑删除示例
|
||||
## MyBatis-Flex 逻辑删除示例
|
||||
|
||||
假设在 tb_account 表中,存在一个为 is_deleted 的字段,用来标识该数据的逻辑删除,那么 tb_account 表
|
||||
对应的 "Account.java" 实体类应该配置如下:
|
||||
@ -30,17 +30,17 @@ public class Account {
|
||||
```java
|
||||
accountMapper.deleteById(1);
|
||||
```
|
||||
Mybatis 执行的 SQL 如下:
|
||||
MyBatis 执行的 SQL 如下:
|
||||
|
||||
```sql
|
||||
UPDATE `tb_account` SET `is_delete` = 1
|
||||
WHERE `id` = ? AND `is_delete` = 0
|
||||
```
|
||||
可以看出,当执行 deleteById 时,Mybatis 只是进行了 update 操作,而非 delete 操作。
|
||||
可以看出,当执行 deleteById 时,MyBatis 只是进行了 update 操作,而非 delete 操作。
|
||||
|
||||
## 注意事项
|
||||
|
||||
当 "tb_account" 的数据被删除时( is_delete = 1 时),我们通过 Mybatis-Flex 的 selectOneById 去查找数据时,会查询不到数据。
|
||||
当 "tb_account" 的数据被删除时( is_delete = 1 时),我们通过 MyBatis-Flex 的 selectOneById 去查找数据时,会查询不到数据。
|
||||
原因是 `selectOneById` 会自动添加上 `is_delete = 0` 条件,执行的 sql 如下:
|
||||
|
||||
```java
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
|
||||
## @ColumnMask
|
||||
|
||||
Mybatis-Flex 提供了 `@ColumnMask()` 注解,以及内置的 9 种脱敏规则,帮助开发者方便的进行数据脱敏。例如:
|
||||
MyBatis-Flex 提供了 `@ColumnMask()` 注解,以及内置的 9 种脱敏规则,帮助开发者方便的进行数据脱敏。例如:
|
||||
|
||||
```java
|
||||
@Table("tb_account")
|
||||
@ -24,7 +24,7 @@ public class Account {
|
||||
|
||||
以上的示例中,使用了 `CHINESE_NAME` 的脱敏规则,其主要用于处理 "中文名字" 的场景。当我们查询到 userName 为 `张三丰` 的时候,其内容自动被处理成 `张**`。
|
||||
|
||||
除此之外,Mybatis-Flex 还提供了如下的 8 中脱敏规则(共9种),方便开发者直接使用:
|
||||
除此之外,MyBatis-Flex 还提供了如下的 8 中脱敏规则(共9种),方便开发者直接使用:
|
||||
|
||||
- 手机号脱敏
|
||||
- 固定电话脱敏
|
||||
|
||||
@ -30,7 +30,7 @@ dataSource2.setJdbcUrl(....)
|
||||
DataSource dataSource3 = new HikariDataSource();
|
||||
dataSource3.setJdbcUrl(....)
|
||||
|
||||
MybatisFlexBootstrap.getInstance()
|
||||
MyBatisFlexBootstrap.getInstance()
|
||||
.setDataSource("ds1", dataSource1)
|
||||
.addDataSource("ds2", dataSource2)
|
||||
.addDataSource("ds3", dataSource3)
|
||||
|
||||
@ -4,11 +4,11 @@
|
||||
|
||||
多租户简单来说是指一个单独的实例可以为多个用户(或组织)服务。多租户技术要求所有用户共用同一个数据中心,但能提供多个客户端相同甚至可定制化的服务,并且仍然可以保障客户的数据隔离。
|
||||
|
||||
多租户的数据隔离有许多种方案,但最为常见的是以列进行隔离的方式。Mybatis-Flex 内置的正是通过指定的列(租户ID `tenant_id`)进行隔离的方案。
|
||||
多租户的数据隔离有许多种方案,但最为常见的是以列进行隔离的方式。MyBatis-Flex 内置的正是通过指定的列(租户ID `tenant_id`)进行隔离的方案。
|
||||
|
||||
## 开始使用
|
||||
|
||||
Mybatis-Flex 使用多租户需要 2 个步骤:
|
||||
MyBatis-Flex 使用多租户需要 2 个步骤:
|
||||
|
||||
- step 1:通过 `@Column(tenantId = true)` 标识租户列。
|
||||
- step 2:为 `TenantManager` 配置 `TenantFactory`。
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# @Table 注解的使用
|
||||
|
||||
在 Mybatis-Flex 中,`@Table` 主要是用于给 Entity 实体类添加标识,用于描述 实体类 和 数据库表 的关系,以及对实体类进行的一些
|
||||
在 MyBatis-Flex 中,`@Table` 主要是用于给 Entity 实体类添加标识,用于描述 实体类 和 数据库表 的关系,以及对实体类进行的一些
|
||||
功能辅助。
|
||||
|
||||
|
||||
|
||||
@ -73,10 +73,12 @@ Db.tx(() -> {
|
||||
|
||||
## @Transactional
|
||||
|
||||
Mybatis-Flex 已支持 Spring 框架的 `@Transactional`,在使用 Spring 的情况下,可以使用 `@Transactional` 进行事务管理。
|
||||
|
||||
MyBatis-Flex 已支持 Spring 框架的 `@Transactional`,在使用 SpringBoot 的情况下,可以直接使用 `@Transactional` 进行事务管理。
|
||||
同理,使用 Spring 的 `TransactionTemplate` 进行事务管理也是没问题的。
|
||||
|
||||
> 注意:若项目未使用 SpringBoot,只用到了 Spring,需要参考 MyBatis-Flex 的 [FlexTransactionAutoConfiguration](https://gitee.com/mybatis-flex/mybatis-flex/blob/main/mybatis-flex-spring-boot-starter/src/main/java/com/mybatisflex/spring/boot/FlexTransactionAutoConfiguration.java)
|
||||
> 进行事务配置,才能正常使用 `@Transactional` 注解。
|
||||
|
||||
## 特征
|
||||
|
||||
- 1、支持嵌套事务
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Mybatis-Flex 乐观锁
|
||||
# MyBatis-Flex 乐观锁
|
||||
|
||||
## 使用场景
|
||||
|
||||
@ -35,4 +35,4 @@ public class Account {
|
||||
需要注意的是:
|
||||
|
||||
- 1、在同一张表中,只能有一个被 `@Column(version = true)` 修饰的字段。
|
||||
- 2、Account 在插入数据时,若 version 未设置值,那么会自动被 Mybatis-Flex 设置为 0。
|
||||
- 2、Account 在插入数据时,若 version 未设置值,那么会自动被 MyBatis-Flex 设置为 0。
|
||||
@ -2,15 +2,14 @@
|
||||
|
||||
## 示例中的 AccountMapper 和 "ACCOUNT" 在哪里,报错了。
|
||||
|
||||
Mybatis-Flex 使用了 APT 技术,这两个类是自动生成的。
|
||||
参考:[Mybatis-Flex APT 配置 - Mybatis-Flex 官方网站](./others/apt.md)
|
||||
MyBatis-Flex 使用了 APT 技术,这两个类是自动生成的。
|
||||
参考:[MyBatis-Flex APT 配置 - MyBatis-Flex 官方网站](./others/apt.md)
|
||||
|
||||
|
||||
|
||||
## SpringBoot 项目,启动报错 Property 'sqlSessionFactory' or 'sqlSessionTempalte' are required
|
||||
|
||||
如果当前依赖没有连接池相关依赖,则建议添加 HikariCP 依赖。
|
||||
如果已经添加了如 druid 依赖,则配置数据源类型 `spring.datasource.type=com.alibaba.druid.pool.DruidDataSource`。
|
||||
|
||||
SpringBoot v2.x 添加 hikariCP 的内容如下:
|
||||
|
||||
@ -32,6 +31,9 @@ SpringBoot v3.x 添加 hikariCP 的内容如下:
|
||||
</dependency>
|
||||
```
|
||||
|
||||
> 如果使用的是 druid 数据库连接池,则需要添加数据源类型的配置 `spring.datasource.type=com.alibaba.druid.pool.DruidDataSource`。
|
||||
|
||||
|
||||
## 整合 Springboot 3 出现 ClassNotFoundException: NestedIOException 的错误
|
||||
|
||||
需要排除 flex 中的 mybatis-spring 的依赖,主动添加最新版本的 mybatis-spring 依赖。
|
||||
@ -64,7 +66,7 @@ SpringBoot v3.x 添加 hikariCP 的内容如下:
|
||||
|
||||
原因是在数据源的配置中,未添加 `type` 字段的配置:
|
||||
|
||||
```yaml 3
|
||||
```yaml:line-numbers 3
|
||||
spring:
|
||||
datasource:
|
||||
type: com.alibaba.druid.pool.DruidDataSource
|
||||
@ -75,10 +77,35 @@ spring:
|
||||
第 3 行中的 `type` 字段不能为空,这个并非是 MyBaits-Flex 的问题,而是 Spring 没有内置对 Druid 数据源类型
|
||||
的主动发现机制。若使用 `hikariCP` 数据源,则可以不配置 `type` 内容。
|
||||
|
||||
> 若使用多数据源,或者把数据源配置到 `mybatis-flex.datasource` 下,使用 mybatis-flex 的数据源发现机制,
|
||||
> 使用 druid 也可以不用配置 type,更多文档参考:[多数据源章节](./core/multi-datasource.md)。
|
||||
> 若把数据源配置到 `mybatis-flex.datasource` 下,使用 mybatis-flex 的数据源发现机制,
|
||||
> 使用 druid 则可以不用配置 type,更多文档参考:[多数据源章节](./core/multi-datasource.md)。
|
||||
|
||||
## 多数据源下,使用 Druid 无法启动,出现 "Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured." 错误。
|
||||
|
||||
在多数据源的场景下,不能使用 "druid-spring-boot-starter" 依赖,只能使用 "druid" 。
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>druid-spring-boot-starter</artifactId>
|
||||
<version>${druid.version}</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
需要把以上的依赖,修改如下:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>druid</artifactId>
|
||||
<version>${druid.version}</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
>错误原因是:druid-spring-boot-starter 内的 DruidDataSourceAutoConfigure 会去自动加载 spring.datasource 下的配置,当使用 MyBatis-Flex 的多数据源时,
|
||||
> 这个配置已经不存在了。
|
||||
|
||||
## 与 PageHelper 集成出现错误
|
||||
|
||||
在社区中,一些老的项目在使用到了开源项目 PageHelper,用于解决 xml 的分页问题,在和 Mybatis-flex 整合使用中,出现了一些错误,
|
||||
在社区中,一些老的项目在使用到了开源项目 PageHelper,用于解决 xml 的分页问题,在和 MyBatis-flex 整合使用中,出现了一些错误,
|
||||
这是许多热心的同学给出的解决方案:https://gitee.com/mybatis-flex/mybatis-flex/issues/I71AUE
|
||||
@ -1,6 +1,6 @@
|
||||
# Mybatis-Flex 和同类框架「性能」对比
|
||||
# MyBatis-Flex 和同类框架「性能」对比
|
||||
|
||||
本文主要是展示了 Mybatis-Flex 和 Mybaits-Plus 的「性能」对比。Mybaits-Plus 是一个非常优秀 Mybaits 增强框架,
|
||||
本文主要是展示了 MyBatis-Flex 和 Mybaits-Plus 的「性能」对比。Mybaits-Plus 是一个非常优秀 Mybaits 增强框架,
|
||||
其开源于 2016 年,有很多的成功案例。
|
||||
|
||||
本文只阐述了「性能」方面的对比,「功能」对比请参考 [这里](./comparison.md)。
|
||||
@ -19,7 +19,7 @@
|
||||
|
||||
## 测试单条数据查询
|
||||
|
||||
Mybatis-Flex 的代码如下:
|
||||
MyBatis-Flex 的代码如下:
|
||||
|
||||
```java
|
||||
QueryWrapper queryWrapper = new QueryWrapper();
|
||||
@ -28,7 +28,7 @@ queryWrapper.where(FLEX_ACCOUNT.ID.ge(100)
|
||||
mapper.selectOneByQuery(queryWrapper);
|
||||
```
|
||||
|
||||
Mybatis-Plus 的代码如下:
|
||||
MyBatis-Plus 的代码如下:
|
||||
|
||||
```java
|
||||
QueryWrapper queryWrapper = new QueryWrapper();
|
||||
@ -85,14 +85,14 @@ mapper.selectOne(queryWrapper);
|
||||
```
|
||||
|
||||
::: tip 测试结论
|
||||
> Mybatis-Flex 的查询单条数据的速度,大概是 Mybatis-Plus 的 5 ~ 10+ 倍。
|
||||
> MyBatis-Flex 的查询单条数据的速度,大概是 MyBatis-Plus 的 5 ~ 10+ 倍。
|
||||
:::
|
||||
|
||||
## 测试列表(List)数据查询
|
||||
|
||||
要求返回的数据为 10 条数据。
|
||||
|
||||
Mybatis-Flex 的代码如下:
|
||||
MyBatis-Flex 的代码如下:
|
||||
|
||||
```java
|
||||
QueryWrapper queryWrapper = new QueryWrapper();
|
||||
@ -102,7 +102,7 @@ queryWrapper.where(FLEX_ACCOUNT.ID.ge(100).or(FLEX_ACCOUNT.USER_NAME
|
||||
mapper.selectListByQuery(queryWrapper);
|
||||
```
|
||||
|
||||
Mybatis-Plus 的代码如下:
|
||||
MyBatis-Plus 的代码如下:
|
||||
|
||||
```java
|
||||
QueryWrapper queryWrapper = new QueryWrapper();
|
||||
@ -159,13 +159,13 @@ mapper.selectList(queryWrapper);
|
||||
```
|
||||
|
||||
::: tip 测试结论
|
||||
> Mybatis-Flex 的查询 10 条数据的速度,大概是 Mybatis-Plus 的 5~10 倍左右。
|
||||
> MyBatis-Flex 的查询 10 条数据的速度,大概是 MyBatis-Plus 的 5~10 倍左右。
|
||||
:::
|
||||
|
||||
## 分页查询
|
||||
|
||||
|
||||
Mybatis-Flex 的代码如下:
|
||||
MyBatis-Flex 的代码如下:
|
||||
|
||||
```java
|
||||
QueryWrapper queryWrapper = new QueryWrapper()
|
||||
@ -173,7 +173,7 @@ QueryWrapper queryWrapper = new QueryWrapper()
|
||||
mapper.paginate(page, pageSize, 20000, queryWrapper);
|
||||
```
|
||||
|
||||
Mybatis-Plus 的代码如下:
|
||||
MyBatis-Plus 的代码如下:
|
||||
|
||||
```java
|
||||
LambdaQueryWrapper<PlusAccount> queryWrapper = new LambdaQueryWrapper<>();
|
||||
|
||||
@ -1,17 +1,17 @@
|
||||
# Mybatis-Flex 和同类框架「功能」对比
|
||||
# MyBatis-Flex 和同类框架「功能」对比
|
||||
|
||||
MyBatis-Flex 主要是和 `MyBatis-Plus` 与 `Fluent-Mybatis` 对比,内容来源其官网、git 或者 网络文章,若有错误欢迎纠正。
|
||||
MyBatis-Flex 主要是和 `MyBatis-Plus` 与 `Fluent-MyBatis` 对比,内容来源其官网、git 或者 网络文章,若有错误欢迎纠正。
|
||||
|
||||
- MyBatis-Plus:老牌的 MyBatis 增强框架,开源于 2016 年。
|
||||
- Fluent-Mybatis:阿里云开发的 Mybatis 增强框架(来至于阿里云·云效产品团队)
|
||||
- Fluent-MyBatis:阿里云开发的 MyBatis 增强框架(来自于阿里云·云效产品团队)
|
||||
|
||||
> 本文只阐述了「功能」方面的对比,**「性能」** 对比请参考 [这里](./benchmark.md)。
|
||||
> 若发现对比中有错误,请加入 Mybatis-Flex QQ 交流群:532992631,然后联系群主纠正。
|
||||
> 若发现对比中有错误,请加入 MyBatis-Flex QQ 交流群:532992631,然后联系群主纠正。
|
||||
|
||||
## 功能对比
|
||||
|
||||
|
||||
| 功能或特点 | MyBatis-Flex | MyBatis-Plus | Fluent-Mybatis |
|
||||
| 功能或特点 | MyBatis-Flex | MyBatis-Plus | Fluent-MyBatis |
|
||||
| -------- | -------- | -------- | -------- |
|
||||
| 对 entity 的基本增删改查 | ✅ | ✅ | ✅ |
|
||||
| 分页查询 | ✅ | ✅ | ✅ |
|
||||
@ -24,7 +24,7 @@ MyBatis-Flex 主要是和 `MyBatis-Plus` 与 `Fluent-Mybatis` 对比,内容来
|
||||
| 多种 id 生成策略 | ✅ | ✅ | ✅ |
|
||||
| 支持多主键、复合主键 | ✅ | ❌ | ❌ |
|
||||
| 字段的 typeHandler 配置 | ✅ | ✅ | ✅ |
|
||||
| 除了 Mybatis,无其他第三方依赖(更轻量) | ✅ | ❌ | ❌ |
|
||||
| 除了 MyBatis,无其他第三方依赖(更轻量) | ✅ | ❌ | ❌ |
|
||||
| QueryWrapper 是否支持在微服务项目下进行 RPC 传输 | ✅ | ❌ | 未知 |
|
||||
| 逻辑删除 | ✅ | ✅ | ✅ |
|
||||
| 乐观锁 | ✅ | ✅ | ✅ |
|
||||
@ -50,30 +50,29 @@ MyBatis-Flex 主要是和 `MyBatis-Plus` 与 `Fluent-Mybatis` 对比,内容来
|
||||
**MyBatis-Flex:**
|
||||
|
||||
````java
|
||||
QueryCondition condition = EMPLOYEE.LAST_NAME.like("B")
|
||||
QueryWrapper query = QueryWrapper.create()
|
||||
.where(EMPLOYEE.LAST_NAME.like(searchWord)) //条件为null时自动忽略
|
||||
.and(EMPLOYEE.GENDER.eq(1))
|
||||
.and(EMPLOYEE.AGE.gt(24));
|
||||
List<Employee> employees = employeeMapper.selectListByCondition(condition);
|
||||
List<Employee> employees = employeeMapper.selectListByQuery(query);
|
||||
````
|
||||
|
||||
**MyBatis-Plus:**
|
||||
|
||||
````java
|
||||
QueryWrapper<Employee> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper
|
||||
.like("last_name","B")
|
||||
.eq("gender",1)
|
||||
.gt("age",24);
|
||||
QueryWrapper<Employee> queryWrapper = Wrappers.query()
|
||||
.like(searchWord != null, "last_name", searchWord)
|
||||
.eq("gender", 1)
|
||||
.gt("age", 24);
|
||||
List<Employee> employees = employeeMapper.selectList(queryWrapper);
|
||||
````
|
||||
或者 MyBatis-Plus 的另一种写法:
|
||||
或者 MyBatis-Plus 的 lambda 写法:
|
||||
|
||||
```java
|
||||
LambdaQueryWrapper<Employee> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper
|
||||
.like(Employee::getUserName,"B")
|
||||
.eq(Employee::getGender,1)
|
||||
.gt(Employee::getAge,24);
|
||||
LambdaQueryWrapper<Employee> queryWrapper = Wrappers.<Employee>lambdaQuery()
|
||||
.like(StringUtils.isNotEmpty(searchWord), Employee::getUserName,"B")
|
||||
.eq(Employee::getGender, 1)
|
||||
.gt(Employee::getAge, 24);
|
||||
List<Employee> employees = employeeMapper.selectList(queryWrapper);
|
||||
```
|
||||
|
||||
@ -82,7 +81,7 @@ List<Employee> employees = employeeMapper.selectList(queryWrapper);
|
||||
|
||||
````java
|
||||
EmployeeQuery query = new EmployeeQuery()
|
||||
.where.lastName().like("B")
|
||||
.where.lastName().like(searchWord, If::notNull)
|
||||
.and.gender().eq(1)
|
||||
.and.age().gt(24)
|
||||
.end();
|
||||
@ -96,7 +95,7 @@ List<Employee> employees = employeeMapper.listEntity(query);
|
||||
**MyBatis-Flex:**
|
||||
|
||||
````java
|
||||
QueryWrapper query = new QueryWrapper()
|
||||
QueryWrapper query = QueryWrapper.create()
|
||||
.select(
|
||||
ACCOUNT.ID,
|
||||
ACCOUNT.USER_NAME,
|
||||
@ -108,17 +107,16 @@ List<Employee> employees = employeeMapper.selectListByQuery(query);
|
||||
**MyBatis-Plus:**
|
||||
|
||||
````java
|
||||
QueryWrapper<Employee> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper
|
||||
QueryWrapper<Employee> queryWrapper = Wrappers.query()
|
||||
.select(
|
||||
"id"
|
||||
,"user_name"
|
||||
,"max(birthday)"
|
||||
,"avg(birthday) as sex_avg"
|
||||
"id",
|
||||
"user_name",
|
||||
"max(birthday)",
|
||||
"avg(birthday) as sex_avg"
|
||||
);
|
||||
List<Employee> employees = employeeMapper.selectList(queryWrapper);
|
||||
````
|
||||
> 缺点:字段硬编码,容易错处。无法使用 ide 的字段进行重构,无法使用 IDE 自动提示,发生错误不能及时发现。
|
||||
> 缺点:字段硬编码,容易拼错。无法使用 ide 的字段进行重构,无法使用 IDE 自动提示,发生错误不能及时发现。
|
||||
|
||||
|
||||
**Fluent-MyBatis:**
|
||||
@ -153,19 +151,24 @@ OR (age IN (18,19,20) AND user_name LIKE "%michael%" )
|
||||
QueryWrapper query = QueryWrapper.create()
|
||||
.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")));
|
||||
.or(ACCOUNT.AGE.in(18, 19, 20).and(ACCOUNT.USER_NAME.like("michael")));
|
||||
```
|
||||
|
||||
**MyBatis-Plus:**
|
||||
|
||||
```java
|
||||
QueryWrapper<Employee> query = new QueryWrapper<>();
|
||||
queryWrapper.ge("id",100)
|
||||
.and(i->i.eq("sex",1).or(x->x.eq("sex",2)))
|
||||
.or(i->i.in("age",{18,19,20}).like("user_name","michael"));
|
||||
QueryWrapper<Employee> query = Wrappers.query()
|
||||
.ge("id", 100)
|
||||
.and(i -> i.eq("sex", 1).or(x -> x.eq("sex", 2)))
|
||||
.or(i -> i.in("age", 18, 19, 20).like("user_name", "michael"));
|
||||
// or lambda
|
||||
LambdaQueryWrapper<Employee> query = Wrappers.<Employee>lambdaQuery()
|
||||
.ge(Employee::getId, 100)
|
||||
.and(i -> i.eq(Employee::getSex, 1).or(x -> x.eq(Employee::getSex, 2)))
|
||||
.or(i -> i.in(Employee::getAge, 18, 19, 20).like(Employee::getUserName, "michael"));
|
||||
```
|
||||
|
||||
**Fluent-Mybatis:**
|
||||
**Fluent-MyBatis:**
|
||||
|
||||
```java
|
||||
AccountQuery query = new AccountQuery()
|
||||
@ -269,7 +272,7 @@ set user_name = "michael", age = 18, birthday = null
|
||||
where id = 100
|
||||
```
|
||||
|
||||
**Mybatis-Flex** 代码如下:
|
||||
**MyBatis-Flex** 代码如下:
|
||||
|
||||
```java
|
||||
Account account = UpdateEntity.of(Account.class);
|
||||
@ -281,7 +284,7 @@ account.setBirthday(null);
|
||||
accountMapper.update(account);
|
||||
```
|
||||
|
||||
**Mybatis-Plus** 代码如下(或可使用 Mybatis-Plus 的 `LambdaUpdateWrapper`,但性能没有 `UpdateWrapper` 好):
|
||||
**MyBatis-Plus** 代码如下(或可使用 MyBatis-Plus 的 `LambdaUpdateWrapper`,但性能没有 `UpdateWrapper` 好):
|
||||
|
||||
```java
|
||||
UpdateWrapper<Account> updateWrapper = new UpdateWrapper<>();
|
||||
@ -293,7 +296,7 @@ updateWrapper.set("birthday", null);
|
||||
accountMapper.update(null, updateWrapper);
|
||||
```
|
||||
|
||||
**Fluent-Mybatis** 代码如下:
|
||||
**Fluent-MyBatis** 代码如下:
|
||||
|
||||
```java
|
||||
AccountUpdate update = new AccountUpdate()
|
||||
|
||||
@ -27,12 +27,12 @@ Maven示例:
|
||||
<dependency>
|
||||
<groupId>com.mybatis-flex</groupId>
|
||||
<artifactId>mybatis-flex-core</artifactId>
|
||||
<version>1.2.3</version>
|
||||
<version>1.3.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.mybatis-flex</groupId>
|
||||
<artifactId>mybatis-flex-processor</artifactId>
|
||||
<version>1.2.3</version>
|
||||
<version>1.3.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
```
|
||||
@ -46,19 +46,18 @@ Gradle示例:
|
||||
```groovy
|
||||
// file: build.gradle
|
||||
ext {
|
||||
mybatis_flex_version = '1.2.3'
|
||||
mybatis_flex_version = '1.3.0'
|
||||
}
|
||||
dependencies {
|
||||
implementation("com.mybatis-flex:mybatis-flex-core:${mybatis_flex_version}")
|
||||
|
||||
// 启用APT
|
||||
annotationProcessor("com.mybatis-flex:mybatis-flex-annotation:${mybatis_flex_version}")
|
||||
annotationProcessor("com.mybatis-flex:mybatis-flex-processor:${mybatis_flex_version}")
|
||||
}
|
||||
```
|
||||
|
||||
**第 3 步:编写实体类和 Mapper**
|
||||
|
||||
> 这部分可以使用 Mybatis-Flex 的代码生成器来生成实体类哟,功能非常强大的。详情进入:[代码生成器章节](../others/codegen.md) 了解。
|
||||
|
||||
```java
|
||||
@Table("tb_account")
|
||||
public class Account {
|
||||
@ -82,20 +81,22 @@ public interface AccountMapper extends BaseMapper<Account> {
|
||||
}
|
||||
```
|
||||
|
||||
**第4步:编译项目自动生成辅助类**
|
||||
> 这部分也可以使用 MyBatis-Flex 的代码生成器来生,功能非常强大的。详情进入:[代码生成器章节](../others/codegen.md) 了解。
|
||||
|
||||
Mybatis-Flex 使用了 APT(Annotation Processing Tool)技术,在项目编译的时,会自动生成辅助操作类。
|
||||
|
||||
**第4步:编译项目,自动生成查询辅助类**
|
||||
|
||||
MyBatis-Flex 使用了 APT(Annotation Processing Tool)技术,在项目编译的时,会自动生成辅助操作类。
|
||||
|
||||
- Maven 编译: `mvn clean package`
|
||||
- Gradle 编译: `gradlew classes`
|
||||
|
||||
|
||||
|
||||
更多信息请参考:[APT 设置章节](../others/apt.md)
|
||||
|
||||
|
||||
|
||||
**第 5 步:通过 main 方法开始使用 Mybatis-Flex(无 Spring 的场景)**
|
||||
**第 5 步:通过 main 方法开始使用 MyBatis-Flex(无 Spring 的场景)**
|
||||
|
||||
```java
|
||||
public class HelloWorld {
|
||||
@ -109,7 +110,7 @@ public class HelloWorld {
|
||||
|
||||
//配置数据源
|
||||
MybatisFlexBootstrap.getInstance()
|
||||
.setDatasource(dataSource)
|
||||
.setDataSource(dataSource)
|
||||
.addMapper(AccountMapper.class)
|
||||
.start();
|
||||
|
||||
@ -134,12 +135,15 @@ public class HelloWorld {
|
||||
}
|
||||
```
|
||||
|
||||
> 以上的示例中, `ACCOUNT` 为 Mybatis-Flex 通过 APT 自动生成,无需手动编码。更多查看 [APT 文档](../others/apt.md)。
|
||||
> 以上的示例中, `ACCOUNT` 为 MyBatis-Flex 通过 APT 自动生成,无需手动编码。更多查看 [APT 文档](../others/apt.md)。
|
||||
>
|
||||
>若觉得 APT 使用不习惯,
|
||||
> 也可以使用代码生成器来生成。点击 [代码生成器文档](../others/codegen.md) 了解。
|
||||
|
||||
|
||||
## 更多示例
|
||||
|
||||
- 示例 1:[Mybatis-Flex 原生(非 Spring)](https://gitee.com/mybatis-flex/mybatis-flex/tree/main/mybatis-flex-test/mybatis-flex-native-test)
|
||||
- 示例 2:[Mybatis-Flex with Spring](https://gitee.com/mybatis-flex/mybatis-flex/tree/main/mybatis-flex-test/mybatis-flex-spring-test)
|
||||
- 示例 3:[Mybatis-Flex with Spring boot](https://gitee.com/mybatis-flex/mybatis-flex/tree/main/mybatis-flex-test/mybatis-flex-spring-boot-test)
|
||||
- 示例 1:[MyBatis-Flex 原生(非 Spring)](https://gitee.com/mybatis-flex/mybatis-flex-samples)
|
||||
- 示例 2:[MyBatis-Flex with Spring](https://gitee.com/mybatis-flex/mybatis-flex-samples)
|
||||
- 示例 3:[MyBatis-Flex with Spring boot](https://gitee.com/mybatis-flex/mybatis-flex-samples)
|
||||
- 示例 4:[Db + Row](https://gitee.com/mybatis-flex/mybatis-flex/blob/main/mybatis-flex-test/mybatis-flex-native-test/src/main/java/com/mybatisflex/test/DbTestStarter.java)
|
||||
@ -1,23 +1,23 @@
|
||||
# Maven 依赖
|
||||
|
||||
> 以下的 xml maven 依赖示例中,可能并非最新的 Mybatis-Flex 版本,请自行查看最新版本,并修改版本号。
|
||||
> 以下的 xml maven 依赖示例中,可能并非最新的 MyBatis-Flex 版本,请自行查看最新版本,并修改版本号。
|
||||
>
|
||||
> 建议配置 annotationProcessorPaths,那么可以省略mybatis-flex-processor的依赖
|
||||
>
|
||||
|
||||
|
||||
1、只用到了 Mybatis,没用到 Spring 的场景:
|
||||
1、只用到了 MyBatis,没用到 Spring 的场景:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.mybatis-flex</groupId>
|
||||
<artifactId>mybatis-flex-core</artifactId>
|
||||
<version>1.2.3</version>
|
||||
<version>1.3.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.mybatis-flex</groupId>
|
||||
<artifactId>mybatis-flex-processor</artifactId>
|
||||
<version>1.2.3</version>
|
||||
<version>1.3.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
```
|
||||
@ -28,12 +28,12 @@
|
||||
<dependency>
|
||||
<groupId>com.mybatis-flex</groupId>
|
||||
<artifactId>mybatis-flex-spring</artifactId>
|
||||
<version>1.2.3</version>
|
||||
<version>1.3.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.mybatis-flex</groupId>
|
||||
<artifactId>mybatis-flex-processor</artifactId>
|
||||
<version>1.2.3</version>
|
||||
<version>1.3.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
``````
|
||||
@ -44,12 +44,12 @@
|
||||
<dependency>
|
||||
<groupId>com.mybatis-flex</groupId>
|
||||
<artifactId>mybatis-flex-spring-boot-starter</artifactId>
|
||||
<version>1.2.3</version>
|
||||
<version>1.3.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.mybatis-flex</groupId>
|
||||
<artifactId>mybatis-flex-processor</artifactId>
|
||||
<version>1.2.3</version>
|
||||
<version>1.3.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
```
|
||||
@ -70,7 +70,7 @@
|
||||
<path>
|
||||
<groupId>com.mybatis-flex</groupId>
|
||||
<artifactId>mybatis-flex-processor</artifactId>
|
||||
<version>1.2.3</version>
|
||||
<version>1.3.0</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
</configuration>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Mybatis-Flex QQ 交流群
|
||||
# MyBatis-Flex QQ 交流群
|
||||
|
||||
群号: 532992631
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# Mybatis-Flex 支持的数据库类型
|
||||
# MyBatis-Flex 支持的数据库类型
|
||||
|
||||
Mybatis-Flex 支持的数据库类型,如下表格所示,我们还可以通过自定义方言的方式,持续添加更多的数据库支持。
|
||||
MyBatis-Flex 支持的数据库类型,如下表格所示,我们还可以通过自定义方言的方式,持续添加更多的数据库支持。
|
||||
|
||||
|
||||
| 数据库 | 描述 |
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
# 使用 Mybatis 原生功能
|
||||
# 使用 MyBatis 原生功能
|
||||
|
||||
我们使用 Mybatis-Flex 作为 Mybatis 的增强框架进行代码开发,并不会影响原有的 Mybatis 的任何功能。
|
||||
我们使用 MyBatis-Flex 作为 MyBatis 的增强框架进行代码开发,并不会影响原有的 MyBatis 的任何功能。
|
||||
|
||||
## 使用 `@Select` 等 Mybatis 原生注解
|
||||
## 使用 `@Select` 等 MyBatis 原生注解
|
||||
|
||||
Mybatis 提供了 `@Insert` 、`@Delete` 、`@Update` 、`@Select` 4 个注解,用于对 Mapper 的方法进行配置,用于原生编写原生 SQL 进行增删改查,
|
||||
在 Mybatis-Flex 我们一样可以使用这些注解。例如:
|
||||
MyBatis 提供了 `@Insert` 、`@Delete` 、`@Update` 、`@Select` 4 个注解,用于对 Mapper 的方法进行配置,用于原生编写原生 SQL 进行增删改查,
|
||||
在 MyBatis-Flex 我们一样可以使用这些注解。例如:
|
||||
|
||||
```java
|
||||
public interface MyAccountMapper extends BaseMapper<Account> {
|
||||
@ -15,9 +15,9 @@ public interface MyAccountMapper extends BaseMapper<Account> {
|
||||
}
|
||||
```
|
||||
|
||||
`@Insert` 、`@Delete` 、`@Update` 等注解也是一样的,也就是说,原有的 Mybatis 功能如何使用,在 Mybatis-Flex 就如何使用。
|
||||
`@Insert` 、`@Delete` 、`@Update` 等注解也是一样的,也就是说,原有的 MyBatis 功能如何使用,在 MyBatis-Flex 就如何使用。
|
||||
|
||||
`@InsertProvider`、`@DeleteProvider`、`@UpdateProvider`、`@SelectProvider` 等还是和原生 Mybatis 一样的用法。
|
||||
`@InsertProvider`、`@DeleteProvider`、`@UpdateProvider`、`@SelectProvider` 等还是和原生 MyBatis 一样的用法。
|
||||
|
||||
|
||||
## 使用 xml 的方式
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
# Mybatis-Flex 是什么
|
||||
# MyBatis-Flex 是什么
|
||||
|
||||
Mybatis-Flex 是一个优雅的 Mybatis 增强框架,它非常轻量、同时拥有极高的性能与灵活性。我们可以轻松的使用 Mybaits-Flex 链接任何数据库,其内置的
|
||||
MyBatis-Flex 是一个优雅的 MyBatis 增强框架,它非常轻量、同时拥有极高的性能与灵活性。我们可以轻松的使用 Mybaits-Flex 链接任何数据库,其内置的
|
||||
QueryWrapper<Badge type="tip" text="^亮点" /> 帮助我们极大的减少了 SQL 编写的工作的同时,减少出错的可能性。
|
||||
|
||||
总而言之,Mybatis-Flex 能够极大地提高我们的开发效率和开发体验,让我们有更多的时间专注于自己的事情。
|
||||
总而言之,MyBatis-Flex 能够极大地提高我们的开发效率和开发体验,让我们有更多的时间专注于自己的事情。
|
||||
|
||||
|
||||
## 特征
|
||||
@ -12,8 +12,8 @@ QueryWrapper<Badge type="tip" text="^亮点" /> 帮助我们极大的减少了 S
|
||||
这带来了几个好处:1、极高的性能;2、极易对代码进行跟踪和调试; 3、把控性更高。
|
||||
|
||||
|
||||
**2、灵活**:支持 Entity 的增删改查、以及分页查询的同时,Mybatis-Flex 提供了 Db + Row<Badge type="tip" text="^灵活" /> 工具,可以无需实体类对数据库进行增删改查以及分页查询。
|
||||
与此同时,Mybatis-Flex 内置的 QueryWrapper<Badge type="tip" text="^灵活" /> 可以轻易的帮助我们实现 **多表查询**、**链接查询**、**子查询** 等等常见的 SQL 场景。
|
||||
**2、灵活**:支持 Entity 的增删改查、以及分页查询的同时,MyBatis-Flex 提供了 Db + Row<Badge type="tip" text="^灵活" /> 工具,可以无需实体类对数据库进行增删改查以及分页查询。
|
||||
与此同时,MyBatis-Flex 内置的 QueryWrapper<Badge type="tip" text="^灵活" /> 可以轻易的帮助我们实现 **多表查询**、**链接查询**、**子查询** 等等常见的 SQL 场景。
|
||||
|
||||
|
||||
**3、强大**:支持任意关系型数据库,还可以通过方言持续扩展,同时支持 **多(复合)主键**、**逻辑删除**、**乐观锁配置**、**数据脱敏**、**数据审计**、
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# Mybatis-Flex APT 配置
|
||||
# MyBatis-Flex APT 配置
|
||||
|
||||
Mybatis-Flex 使用了 APT(Annotation Processing Tool)技术,在项目编译的时候,会自动根据 Entity 类定义的字段帮你生成 "ACCOUNT" 类以及 Entity 对应的 Mapper 类,
|
||||
MyBatis-Flex 使用了 APT(Annotation Processing Tool)技术,在项目编译的时候,会自动根据 Entity 类定义的字段帮你生成 "ACCOUNT" 类以及 Entity 对应的 Mapper 类,
|
||||
通过开发工具构建项目(如下图),或者执行 maven 编译命令: `mvn clean package` 都可以自动生成。这个原理和 lombok 一致。
|
||||
|
||||

|
||||
@ -9,7 +9,7 @@ Mybatis-Flex 使用了 APT(Annotation Processing Tool)技术,在项目编
|
||||
|
||||
## 配置文件和选项
|
||||
|
||||
要对Mybatis-Flex 的APT细节选项进行配置,你需要在`resources`目录下创建名为`mybatis-flex.properties`的文件。
|
||||
要对MyBatis-Flex 的APT细节选项进行配置,你需要在`resources`目录下创建名为`mybatis-flex.properties`的文件。
|
||||
|
||||
支持的配置选项如下:
|
||||
|
||||
@ -18,6 +18,7 @@ Mybatis-Flex 使用了 APT(Annotation Processing Tool)技术,在项目编
|
||||
| processor.enable | 全局启用apt开关 | true/false | true |
|
||||
| processor.mappersGenerateEnable | 开启 Mapper 自动生成 | true/false | false |
|
||||
| processor.genPath | APT 代码生成路径 | 合法的绝对或相对路径 | target/generated-sources/annotations |
|
||||
| processor.allInTables | 是否所有的类都生成在 Tables 类里 | true/false | false |
|
||||
| processor.tablesPackage | Tables 类名 | 合法的包名 | ${entityPackage}.table |
|
||||
| processor.tablesClassName | Tables 类名 | 合法的类名 | Tables |
|
||||
| processor.baseMapperClass | 自定义 Mapper 的父类 | 全路径类名 | com.mybatisflex.core.BaseMapper |
|
||||
@ -135,7 +136,7 @@ processor.baseMapperClass=com.domain.mapper.MyBaseMapper
|
||||
## 和 Lombok、Mapstruct 整合
|
||||
|
||||
在很多项目中,用到了 Lombok 帮我们减少代码编写,同时用到 Mapstruct 进行 bean 转换。使用到 Lombok 和 Mapstruct 时,其要求我们再 pom.xml 添加 `annotationProcessorPaths` 配置,
|
||||
此时,我们也需要把 Mybatis-Flex 的 annotation 添加到 `annotationProcessorPaths` 配置里去,如下图所示:
|
||||
此时,我们也需要把 MyBatis-Flex 的 annotation 添加到 `annotationProcessorPaths` 配置里去,如下图所示:
|
||||
|
||||
```xml 24,25,26,27,28
|
||||
<plugin>
|
||||
@ -178,7 +179,7 @@ processor.baseMapperClass=com.domain.mapper.MyBaseMapper
|
||||
```
|
||||
dependencies {
|
||||
...
|
||||
annotationProcessor 'com.mybatis-flex:mybatis-flex-processor:1.2.3'
|
||||
annotationProcessor 'com.mybatis-flex:mybatis-flex-processor:1.3.0'
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Mybatis-Flex 代码生成器
|
||||
# MyBatis-Flex 代码生成器
|
||||
|
||||
在 mybatis-flex 的模块 `mybatis-flex-codegen` 中,提供了可以通过数据库表,生成 Entity 类和 Mapper 类的功能。当我们把数据库表设计完成
|
||||
后可以使用其快速生成 Entity 和 Mapper 的 java 类。
|
||||
@ -10,7 +10,7 @@
|
||||
<dependency>
|
||||
<groupId>com.mybatis-flex</groupId>
|
||||
<artifactId>mybatis-flex-codegen</artifactId>
|
||||
<version>1.2.3</version>
|
||||
<version>1.3.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
@ -445,7 +445,7 @@ public class ColumnConfig implements Serializable {
|
||||
|
||||
## 自定义属性类型
|
||||
|
||||
Mybatis-Flex 内置了一个名为:`JdbcTypeMapping` 的 java 类,我们可以用其配置映射 Jdbc 驱动的数据类型为自定义的
|
||||
MyBatis-Flex 内置了一个名为:`JdbcTypeMapping` 的 java 类,我们可以用其配置映射 Jdbc 驱动的数据类型为自定义的
|
||||
数据类型,在开始生成代码之前,可以先调用其进行配置,例如:
|
||||
|
||||
```java
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>parent</artifactId>
|
||||
<groupId>com.mybatis-flex</groupId>
|
||||
<version>1.2.3</version>
|
||||
<version>1.3.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>parent</artifactId>
|
||||
<groupId>com.mybatis-flex</groupId>
|
||||
<version>1.2.3</version>
|
||||
<version>1.3.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@ -27,7 +27,7 @@
|
||||
<dependency>
|
||||
<groupId>com.mybatis-flex</groupId>
|
||||
<artifactId>mybatis-flex-spring</artifactId>
|
||||
<version>1.2.3</version>
|
||||
<version>1.3.0</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
|
||||
@ -19,7 +19,6 @@ package com.mybatisflex.codegen.test;
|
||||
import com.mybatisflex.codegen.Generator;
|
||||
import com.mybatisflex.codegen.config.GlobalConfig;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>parent</artifactId>
|
||||
<groupId>com.mybatis-flex</groupId>
|
||||
<version>1.2.3</version>
|
||||
<version>1.3.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@ -22,13 +22,13 @@
|
||||
<dependency>
|
||||
<groupId>com.mybatis-flex</groupId>
|
||||
<artifactId>mybatis-flex-annotation</artifactId>
|
||||
<version>1.2.3</version>
|
||||
<version>1.3.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.mybatis-flex</groupId>
|
||||
<artifactId>mybatis-flex-processor</artifactId>
|
||||
<version>1.2.3</version>
|
||||
<version>1.3.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
|
||||
@ -16,22 +16,28 @@
|
||||
package com.mybatisflex.core;
|
||||
|
||||
import com.mybatisflex.core.exception.FlexExceptions;
|
||||
import com.mybatisflex.core.field.FieldQuery;
|
||||
import com.mybatisflex.core.field.FieldQueryBuilder;
|
||||
import com.mybatisflex.core.mybatis.MappedStatementTypes;
|
||||
import com.mybatisflex.core.paginate.Page;
|
||||
import com.mybatisflex.core.provider.EntitySqlProvider;
|
||||
import com.mybatisflex.core.query.QueryColumn;
|
||||
import com.mybatisflex.core.query.QueryCondition;
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import com.mybatisflex.core.query.CPI;
|
||||
import com.mybatisflex.core.query.*;
|
||||
import com.mybatisflex.core.table.TableInfo;
|
||||
import com.mybatisflex.core.table.TableInfoFactory;
|
||||
import com.mybatisflex.core.util.CollectionUtil;
|
||||
import com.mybatisflex.core.util.ConvertUtil;
|
||||
import com.mybatisflex.core.util.ObjectUtil;
|
||||
import com.mybatisflex.core.util.StringUtil;
|
||||
import org.apache.ibatis.annotations.*;
|
||||
import org.apache.ibatis.builder.annotation.ProviderContext;
|
||||
import org.apache.ibatis.reflection.MetaObject;
|
||||
import org.apache.ibatis.reflection.SystemMetaObject;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static com.mybatisflex.core.query.QueryMethods.count;
|
||||
|
||||
public interface BaseMapper<T> {
|
||||
|
||||
@ -41,8 +47,8 @@ public interface BaseMapper<T> {
|
||||
* @param entity 实体类
|
||||
* @return 返回影响的行数
|
||||
*/
|
||||
default int insert(T entity){
|
||||
return insert(entity,false);
|
||||
default int insert(T entity) {
|
||||
return insert(entity, false);
|
||||
}
|
||||
|
||||
|
||||
@ -53,12 +59,11 @@ public interface BaseMapper<T> {
|
||||
* @param entity 实体类
|
||||
* @return 返回影响的行数
|
||||
*/
|
||||
default int insertSelective(T entity){
|
||||
return insert(entity,true);
|
||||
default int insertSelective(T entity) {
|
||||
return insert(entity, true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 插入 entity 数据
|
||||
*
|
||||
@ -70,7 +75,6 @@ public interface BaseMapper<T> {
|
||||
int insert(@Param(FlexConsts.ENTITY) T entity, @Param(FlexConsts.IGNORE_NULLS) boolean ignoreNulls);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 批量插入 entity 数据,只会根据第一条数据来构建插入的字段内容
|
||||
*
|
||||
@ -144,12 +148,12 @@ public interface BaseMapper<T> {
|
||||
/**
|
||||
* 根据多个 id 批量删除数据
|
||||
*
|
||||
* @param ids ids 列表
|
||||
* @param ids ids 列表
|
||||
* @param size 切分大小
|
||||
* @return 返回影响的行数
|
||||
* @see com.mybatisflex.core.provider.EntitySqlProvider#deleteBatchByIds(Map, ProviderContext)
|
||||
*/
|
||||
default int deleteBatchByIds(@Param(FlexConsts.PRIMARY_VALUE) List<? extends Serializable> ids, int size) {
|
||||
default int deleteBatchByIds(List<? extends Serializable> ids, int size) {
|
||||
if (size <= 0) {
|
||||
size = 1000;//默认1000
|
||||
}
|
||||
@ -263,7 +267,7 @@ public interface BaseMapper<T> {
|
||||
* @return 返回影响的行数
|
||||
*/
|
||||
|
||||
default int updateByQuery(@Param(FlexConsts.ENTITY) T entity, @Param(FlexConsts.QUERY) QueryWrapper queryWrapper) {
|
||||
default int updateByQuery(T entity, QueryWrapper queryWrapper) {
|
||||
return updateByQuery(entity, true, queryWrapper);
|
||||
}
|
||||
|
||||
@ -318,11 +322,29 @@ public interface BaseMapper<T> {
|
||||
* @param queryWrapper query 条件
|
||||
* @return entity 数据
|
||||
*/
|
||||
default T selectOneByQuery(@Param(FlexConsts.QUERY) QueryWrapper queryWrapper) {
|
||||
default T selectOneByQuery(QueryWrapper queryWrapper) {
|
||||
List<T> entities = selectListByQuery(queryWrapper.limit(1));
|
||||
return (entities == null || entities.isEmpty()) ? null : entities.get(0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据 queryWrapper 构建的条件来查询 1 条数据
|
||||
*
|
||||
* @param queryWrapper query 条件
|
||||
* @param asType 接收类型
|
||||
* @return 数据内容
|
||||
*/
|
||||
default <R> R selectOneByQueryAs(QueryWrapper queryWrapper, Class<R> asType) {
|
||||
try {
|
||||
MappedStatementTypes.setCurrentType(asType);
|
||||
List<R> entities = selectListByQueryAs(queryWrapper.limit(1), asType);
|
||||
return (entities == null || entities.isEmpty()) ? null : entities.get(0);
|
||||
} finally {
|
||||
MappedStatementTypes.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据多个主键来查询多条数据
|
||||
*
|
||||
@ -390,6 +412,133 @@ public interface BaseMapper<T> {
|
||||
List<T> selectListByQuery(@Param(FlexConsts.QUERY) QueryWrapper queryWrapper);
|
||||
|
||||
|
||||
default List<T> selectListByQuery(@Param(FlexConsts.QUERY) QueryWrapper queryWrapper
|
||||
, Consumer<FieldQueryBuilder<T>>... consumers) {
|
||||
|
||||
List<T> list = selectListByQuery(queryWrapper);
|
||||
if (list == null || list.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
list.forEach(entity -> {
|
||||
for (Consumer<FieldQueryBuilder<T>> consumer : consumers) {
|
||||
FieldQueryBuilder<T> fieldQueryBuilder = new FieldQueryBuilder<>(entity);
|
||||
consumer.accept(fieldQueryBuilder);
|
||||
FieldQuery fieldQuery = fieldQueryBuilder.build();
|
||||
QueryWrapper childQuery = fieldQuery.getQueryWrapper();
|
||||
MetaObject entityMetaObject = SystemMetaObject.forObject(entity);
|
||||
Class<?> setterType = entityMetaObject.getSetterType(fieldQuery.getField());
|
||||
|
||||
Class<?> mappingType = fieldQuery.getMappingType();
|
||||
if (mappingType == null) {
|
||||
if (setterType.isAssignableFrom(Collection.class)) {
|
||||
throw new IllegalStateException("Mapping Type can not be null for query Many.");
|
||||
} else if (setterType.isArray()) {
|
||||
mappingType = setterType.getComponentType();
|
||||
} else {
|
||||
mappingType = setterType;
|
||||
}
|
||||
}
|
||||
|
||||
Object value;
|
||||
try {
|
||||
MappedStatementTypes.setCurrentType(mappingType);
|
||||
if (setterType.isAssignableFrom(List.class)) {
|
||||
value = selectListByQueryAs(childQuery, mappingType);
|
||||
} else if (setterType.isAssignableFrom(Set.class)) {
|
||||
value = selectListByQueryAs(childQuery, mappingType);
|
||||
value = new HashSet<>((Collection<?>) value);
|
||||
} else if (setterType.isArray()) {
|
||||
value = selectListByQueryAs(childQuery, mappingType);
|
||||
value = ((List<?>) value).toArray();
|
||||
} else {
|
||||
value = selectOneByQueryAs(childQuery, mappingType);
|
||||
}
|
||||
} finally {
|
||||
MappedStatementTypes.clear();
|
||||
}
|
||||
entityMetaObject.setValue(fieldQuery.getField(), value);
|
||||
}
|
||||
});
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据 query 来构建条件查询数据列表,要求返回的数据为 asType
|
||||
* 这种场景一般用在 left join 时,有多出了 entity 本身的字段内容,可以转换为 dto、vo 等场景时
|
||||
*
|
||||
* @param queryWrapper 查询条件
|
||||
* @param asType 接收数据类型
|
||||
* @return 数据列表
|
||||
*/
|
||||
@SelectProvider(type = EntitySqlProvider.class, method = "selectListByQuery")
|
||||
<R> List<R> selectListByQueryAs(@Param(FlexConsts.QUERY) QueryWrapper queryWrapper, Class<R> asType);
|
||||
|
||||
|
||||
default <R> List<R> selectListByQueryAs(@Param(FlexConsts.QUERY) QueryWrapper queryWrapper, Class<R> asType
|
||||
, Consumer<FieldQueryBuilder<R>>... consumers) {
|
||||
List<R> list;
|
||||
try {
|
||||
MappedStatementTypes.setCurrentType(asType);
|
||||
list = selectListByQueryAs(queryWrapper, asType);
|
||||
} finally {
|
||||
MappedStatementTypes.clear();
|
||||
}
|
||||
|
||||
if (list == null || list.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
list.forEach(entity -> {
|
||||
for (Consumer<FieldQueryBuilder<R>> consumer : consumers) {
|
||||
FieldQueryBuilder<R> fieldQueryBuilder = new FieldQueryBuilder<>(entity);
|
||||
consumer.accept(fieldQueryBuilder);
|
||||
FieldQuery fieldQuery = fieldQueryBuilder.build();
|
||||
QueryWrapper childQuery = fieldQuery.getQueryWrapper();
|
||||
|
||||
MetaObject entityMetaObject = SystemMetaObject.forObject(entity);
|
||||
Class<?> setterType = entityMetaObject.getSetterType(fieldQuery.getField());
|
||||
|
||||
Class<?> mappingType = fieldQuery.getMappingType();
|
||||
if (mappingType == null) {
|
||||
if (setterType.isAssignableFrom(Collection.class)) {
|
||||
throw new IllegalStateException("Mapping Type can not be null for query Many.");
|
||||
} else if (setterType.isArray()) {
|
||||
mappingType = setterType.getComponentType();
|
||||
} else {
|
||||
mappingType = setterType;
|
||||
}
|
||||
}
|
||||
|
||||
Object value;
|
||||
try {
|
||||
MappedStatementTypes.setCurrentType(mappingType);
|
||||
if (setterType.isAssignableFrom(List.class)) {
|
||||
value = selectListByQueryAs(childQuery, mappingType);
|
||||
} else if (setterType.isAssignableFrom(Set.class)) {
|
||||
value = selectListByQueryAs(childQuery, mappingType);
|
||||
value = new HashSet<>((Collection<?>) value);
|
||||
} else if (setterType.isArray()) {
|
||||
value = selectListByQueryAs(childQuery, mappingType);
|
||||
value = ((List<?>) value).toArray();
|
||||
} else {
|
||||
value = selectOneByQueryAs(childQuery, mappingType);
|
||||
}
|
||||
} finally {
|
||||
MappedStatementTypes.clear();
|
||||
}
|
||||
|
||||
|
||||
entityMetaObject.setValue(fieldQuery.getField(), value);
|
||||
}
|
||||
});
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 查询全部数据
|
||||
*
|
||||
@ -400,6 +549,76 @@ public interface BaseMapper<T> {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据 queryWrapper 1 条数据
|
||||
* queryWrapper 执行的结果应该只有 1 列,例如 QueryWrapper.create().select(ACCOUNT.id).where...
|
||||
*
|
||||
* @param queryWrapper 查询包装器
|
||||
* @return 数据量
|
||||
*/
|
||||
default Object selectObjectByQuery(QueryWrapper queryWrapper) {
|
||||
List<Object> objects = selectObjectListByQuery(queryWrapper.limit(1));
|
||||
return objects == null || objects.isEmpty() ? null : objects.get(0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据 queryWrapper 来查询数据列表
|
||||
* queryWrapper 执行的结果应该只有 1 列,例如 QueryWrapper.create().select(ACCOUNT.id).where...
|
||||
*
|
||||
* @param queryWrapper 查询包装器
|
||||
* @return 数据列表
|
||||
* @see EntitySqlProvider#selectObjectByQuery(Map, ProviderContext)
|
||||
*/
|
||||
@SelectProvider(type = EntitySqlProvider.class, method = "selectObjectByQuery")
|
||||
List<Object> selectObjectListByQuery(@Param(FlexConsts.QUERY) QueryWrapper queryWrapper);
|
||||
|
||||
|
||||
/**
|
||||
* 对 {@link #selectObjectListByQuery(QueryWrapper)} 进行数据转换
|
||||
* 根据 queryWrapper 来查询数据列表
|
||||
* queryWrapper 执行的结果应该只有 1 列,例如 QueryWrapper.create().select(ACCOUNT.id).where...
|
||||
*
|
||||
* @param queryWrapper 查询包装器
|
||||
* @param asType 转换成的数据类型
|
||||
* @return 数据列表
|
||||
*/
|
||||
default <R> List<R> selectObjectListByQueryAs(QueryWrapper queryWrapper, Class<R> asType) {
|
||||
List<Object> queryResults = selectObjectListByQuery(queryWrapper);
|
||||
if (queryResults == null || queryResults.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<R> results = new ArrayList<>();
|
||||
for (Object queryResult : queryResults) {
|
||||
results.add((R) ConvertUtil.convert(queryResult, asType));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 查询数据量
|
||||
*
|
||||
* @param queryWrapper 查询包装器
|
||||
* @return 数据量
|
||||
*/
|
||||
default long selectCountByQuery(QueryWrapper queryWrapper) {
|
||||
List<QueryColumn> selectColumns = CPI.getSelectColumns(queryWrapper);
|
||||
if (CollectionUtil.isEmpty(selectColumns)) {
|
||||
queryWrapper.select(count());
|
||||
}
|
||||
List<Object> objects = selectObjectListByQuery(queryWrapper);
|
||||
Object object = objects == null || objects.isEmpty() ? null : objects.get(0);
|
||||
if (object == null) {
|
||||
return 0;
|
||||
} else if (object instanceof Number) {
|
||||
return ((Number) object).longValue();
|
||||
} else {
|
||||
throw FlexExceptions.wrap("selectCountByQuery error, Can not get number value for queryWrapper: %s", queryWrapper);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据条件查询数据总量
|
||||
*
|
||||
@ -411,17 +630,6 @@ public interface BaseMapper<T> {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据 queryWrapper 来查询数据量
|
||||
*
|
||||
* @param queryWrapper 查询包装器
|
||||
* @return 数据量
|
||||
* @see EntitySqlProvider#selectCountByQuery(Map, ProviderContext)
|
||||
*/
|
||||
@SelectProvider(type = EntitySqlProvider.class, method = "selectCountByQuery")
|
||||
long selectCountByQuery(@Param(FlexConsts.QUERY) QueryWrapper queryWrapper);
|
||||
|
||||
|
||||
/**
|
||||
* 分页查询
|
||||
*
|
||||
@ -486,17 +694,63 @@ public interface BaseMapper<T> {
|
||||
* @param queryWrapper 查询条件
|
||||
* @return page 数据
|
||||
*/
|
||||
default Page<T> paginate(@Param("page") Page<T> page, @Param("query") QueryWrapper queryWrapper) {
|
||||
default Page<T> paginate(Page<T> page, QueryWrapper queryWrapper) {
|
||||
return paginateAs(page, queryWrapper, null);
|
||||
}
|
||||
|
||||
List<QueryColumn> groupByColumns = null;
|
||||
|
||||
default <R> Page<R> paginateAs(Page<R> page, QueryWrapper queryWrapper, Class<R> asType) {
|
||||
List<QueryColumn> selectColumns = CPI.getSelectColumns(queryWrapper);
|
||||
List<QueryOrderBy> orderBys = CPI.getOrderBys(queryWrapper);
|
||||
|
||||
List<Join> joins = CPI.getJoins(queryWrapper);
|
||||
boolean removedJoins = true;
|
||||
// 只有 totalRow 小于 0 的时候才会去查询总量
|
||||
// 这样方便用户做总数缓存,而非每次都要去查询总量
|
||||
// 一般的分页场景中,只有第一页的时候有必要去查询总量,第二页以后是不需要的
|
||||
if (page.getTotalRow() < 0) {
|
||||
groupByColumns = CPI.getGroupByColumns(queryWrapper);
|
||||
//清除group by 去查询数据
|
||||
CPI.setGroupByColumns(queryWrapper, null);
|
||||
|
||||
//移除 select
|
||||
CPI.setSelectColumns(queryWrapper, Collections.singletonList(count().as("total")));
|
||||
|
||||
//移除 OrderBy
|
||||
if (CollectionUtil.isNotEmpty(orderBys)) {
|
||||
CPI.setOrderBys(queryWrapper, null);
|
||||
}
|
||||
|
||||
|
||||
//移除 left join
|
||||
if (joins != null && !joins.isEmpty()) {
|
||||
for (Join join : joins) {
|
||||
if (!Join.TYPE_LEFT.equals(CPI.getJoinType(join))) {
|
||||
removedJoins = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
removedJoins = false;
|
||||
}
|
||||
|
||||
if (removedJoins) {
|
||||
List<String> joinTables = new ArrayList<>();
|
||||
joins.forEach(join -> {
|
||||
QueryTable joinQueryTable = CPI.getJoinQueryTable(join);
|
||||
if (joinQueryTable != null && StringUtil.isNotBlank(joinQueryTable.getName())) {
|
||||
joinTables.add(joinQueryTable.getName());
|
||||
}
|
||||
});
|
||||
|
||||
QueryCondition where = CPI.getWhereQueryCondition(queryWrapper);
|
||||
if (CPI.containsTable(where, CollectionUtil.toArrayString(joinTables))) {
|
||||
removedJoins = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (removedJoins) {
|
||||
CPI.setJoins(queryWrapper, null);
|
||||
}
|
||||
|
||||
|
||||
long count = selectCountByQuery(queryWrapper);
|
||||
page.setTotalRow(count);
|
||||
}
|
||||
@ -505,15 +759,36 @@ public interface BaseMapper<T> {
|
||||
return page;
|
||||
}
|
||||
|
||||
//恢复数量查询清除的 groupBy
|
||||
if (groupByColumns != null) {
|
||||
CPI.setGroupByColumns(queryWrapper, groupByColumns);
|
||||
//重置 selectColumns
|
||||
CPI.setSelectColumns(queryWrapper, selectColumns);
|
||||
|
||||
//重置 orderBys
|
||||
if (CollectionUtil.isNotEmpty(orderBys)) {
|
||||
CPI.setOrderBys(queryWrapper, orderBys);
|
||||
}
|
||||
|
||||
//重置 join
|
||||
if (removedJoins) {
|
||||
CPI.setJoins(queryWrapper, joins);
|
||||
}
|
||||
|
||||
int offset = page.getPageSize() * (page.getPageNumber() - 1);
|
||||
queryWrapper.limit(offset, page.getPageSize());
|
||||
List<T> rows = selectListByQuery(queryWrapper);
|
||||
page.setRecords(rows);
|
||||
|
||||
if (asType != null) {
|
||||
try {
|
||||
// 调用内部方法,不走代理,需要主动设置 MappedStatementType
|
||||
// fixed https://gitee.com/mybatis-flex/mybatis-flex/issues/I73BP6
|
||||
MappedStatementTypes.setCurrentType(asType);
|
||||
List<R> records = selectListByQueryAs(queryWrapper, asType);
|
||||
page.setRecords(records);
|
||||
} finally {
|
||||
MappedStatementTypes.clear();
|
||||
}
|
||||
} else {
|
||||
List<R> records = (List<R>) selectListByQuery(queryWrapper);
|
||||
page.setRecords(records);
|
||||
}
|
||||
return page;
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,7 +20,7 @@ package com.mybatisflex.core;
|
||||
*/
|
||||
public class FlexConsts {
|
||||
public static final String NAME = "MyBatis-Flex";
|
||||
public static final String VERSION = "1.2.3";
|
||||
public static final String VERSION = "1.3.0";
|
||||
|
||||
public static final String DEFAULT_PRIMARY_FIELD = "id";
|
||||
|
||||
@ -39,6 +39,7 @@ public class FlexConsts {
|
||||
public static final String IGNORE_NULLS = "$$ignoreNulls";
|
||||
|
||||
public static final String METHOD_INSERT_BATCH = "insertBatch";
|
||||
public static final String METHOD_SELECT_LIST_BY_QUERY_AS = "selectListByQueryAs";
|
||||
|
||||
/**
|
||||
* 当 entity 使用逻辑删除时,0 为 entity 的正常状态
|
||||
|
||||
@ -54,10 +54,6 @@ public enum DbType {
|
||||
* POSTGRE
|
||||
*/
|
||||
POSTGRE_SQL("postgresql", "PostgreSQL 数据库"),
|
||||
/**
|
||||
* SQLSERVER 2005
|
||||
*/
|
||||
SQLSERVER_2005("sqlserver2005", "SQLServer2005 数据库"),
|
||||
/**
|
||||
* SQLSERVER
|
||||
*/
|
||||
|
||||
@ -87,7 +87,7 @@ public class DbTypeUtil {
|
||||
* @param jdbcUrl jdbcURL
|
||||
* @return 返回数据库类型
|
||||
*/
|
||||
private static DbType parseDbType(String jdbcUrl) {
|
||||
public static DbType parseDbType(String jdbcUrl) {
|
||||
jdbcUrl = jdbcUrl.toLowerCase();
|
||||
if (jdbcUrl.contains(":mysql:") || jdbcUrl.contains(":cobar:")) {
|
||||
return DbType.MYSQL;
|
||||
@ -95,9 +95,7 @@ public class DbTypeUtil {
|
||||
return DbType.MARIADB;
|
||||
} else if (jdbcUrl.contains(":oracle:")) {
|
||||
return DbType.ORACLE;
|
||||
} else if (jdbcUrl.contains(":sqlserver:") || jdbcUrl.contains(":microsoft:")) {
|
||||
return DbType.SQLSERVER_2005;
|
||||
} else if (jdbcUrl.contains(":sqlserver2012:")) {
|
||||
} else if (jdbcUrl.contains(":sqlserver:") || jdbcUrl.contains(":microsoft:") || jdbcUrl.contains(":sqlserver2012:")) {
|
||||
return DbType.SQLSERVER;
|
||||
} else if (jdbcUrl.contains(":postgresql:")) {
|
||||
return DbType.POSTGRE_SQL;
|
||||
|
||||
@ -106,7 +106,7 @@ public class DialectFactory {
|
||||
case CSIIDB:
|
||||
return new CommonsDialectImpl(KeywordWrap.BACKQUOTE, LimitOffsetProcessor.MYSQL);
|
||||
case ORACLE:
|
||||
return new OracleDialect(LimitOffsetProcessor.ORACLE);
|
||||
return new OracleDialect();
|
||||
case DM:
|
||||
case GAUSS:
|
||||
return new CommonsDialectImpl(KeywordWrap.DOUBLE_QUOTATION, LimitOffsetProcessor.ORACLE);
|
||||
@ -121,21 +121,19 @@ public class DialectFactory {
|
||||
case VERTICA:
|
||||
case REDSHIFT:
|
||||
case OPENGAUSS:
|
||||
case TDENGINE:
|
||||
case UXDB:
|
||||
return new CommonsDialectImpl(KeywordWrap.DOUBLE_QUOTATION, LimitOffsetProcessor.POSTGRESQL);
|
||||
case TDENGINE:
|
||||
return new CommonsDialectImpl(KeywordWrap.BACKQUOTE, LimitOffsetProcessor.POSTGRESQL);
|
||||
case ORACLE_12C:
|
||||
return new OracleDialect(LimitOffsetProcessor.DERBY);
|
||||
case FIREBIRD:
|
||||
case DB2:
|
||||
return new CommonsDialectImpl(KeywordWrap.DOUBLE_QUOTATION, LimitOffsetProcessor.DERBY);
|
||||
case SQLSERVER:
|
||||
return new CommonsDialectImpl(KeywordWrap.SQUARE_BRACKETS, LimitOffsetProcessor.DERBY);
|
||||
case SQLSERVER_2005:
|
||||
return new CommonsDialectImpl(KeywordWrap.SQUARE_BRACKETS, LimitOffsetProcessor.DB2);
|
||||
return new CommonsDialectImpl(KeywordWrap.SQUARE_BRACKETS, LimitOffsetProcessor.SQLSERVER);
|
||||
case INFORMIX:
|
||||
return new CommonsDialectImpl(KeywordWrap.DOUBLE_QUOTATION, LimitOffsetProcessor.INFORMIX);
|
||||
case DB2:
|
||||
return new CommonsDialectImpl(KeywordWrap.DOUBLE_QUOTATION, LimitOffsetProcessor.DB2);
|
||||
case SYBASE:
|
||||
return new CommonsDialectImpl(KeywordWrap.DOUBLE_QUOTATION, LimitOffsetProcessor.SYBASE);
|
||||
default:
|
||||
|
||||
@ -25,6 +25,8 @@ public interface IDialect {
|
||||
|
||||
String wrap(String keyword);
|
||||
|
||||
String forHint(String hintString);
|
||||
|
||||
String forInsertRow(String tableName, Row row);
|
||||
|
||||
String forInsertBatchWithFirstRowColumns(String tableName, List<Row> rows);
|
||||
@ -43,15 +45,10 @@ public interface IDialect {
|
||||
|
||||
String forSelectOneById(String tableName, String[] primaryKeys, Object[] primaryValues);
|
||||
|
||||
String forSelectListByQuery(QueryWrapper queryWrapper);
|
||||
|
||||
String forSelectCountByQuery(QueryWrapper queryWrapper);
|
||||
|
||||
String forSelectByQuery(QueryWrapper queryWrapper);
|
||||
|
||||
String buildSelectSql(QueryWrapper queryWrapper);
|
||||
|
||||
String buildSelectCountSql(QueryWrapper queryWrapper);
|
||||
|
||||
String buildDeleteSql(QueryWrapper queryWrapper);
|
||||
|
||||
String buildWhereConditionSql(QueryWrapper queryWrapper);
|
||||
|
||||
@ -17,6 +17,10 @@ package com.mybatisflex.core.dialect;
|
||||
|
||||
import com.mybatisflex.core.util.StringUtil;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 用于对数据库的关键字包装
|
||||
*/
|
||||
@ -47,6 +51,16 @@ public class KeywordWrap {
|
||||
*/
|
||||
public final static KeywordWrap SQUARE_BRACKETS = new KeywordWrap("[", "]");
|
||||
|
||||
/**
|
||||
* 大小写敏感
|
||||
*/
|
||||
private boolean caseSensitive = false;
|
||||
|
||||
/**
|
||||
* 数据库关键字
|
||||
*/
|
||||
private Set<String> keywords = Collections.emptySet();
|
||||
|
||||
/**
|
||||
* 前缀
|
||||
*/
|
||||
@ -59,12 +73,28 @@ public class KeywordWrap {
|
||||
|
||||
|
||||
public KeywordWrap(String prefix, String suffix) {
|
||||
this(false, Collections.emptySet(), prefix, suffix);
|
||||
}
|
||||
|
||||
public KeywordWrap(Set<String> keywords, String prefix, String suffix) {
|
||||
this(false, keywords, prefix, suffix);
|
||||
}
|
||||
|
||||
public KeywordWrap(boolean caseSensitive, Set<String> keywords, String prefix, String suffix) {
|
||||
this.caseSensitive = caseSensitive;
|
||||
this.keywords = keywords;
|
||||
this.prefix = prefix;
|
||||
this.suffix = suffix;
|
||||
}
|
||||
|
||||
public String wrap(String keyword) {
|
||||
return StringUtil.isBlank(keyword) ? "" : prefix + keyword + suffix;
|
||||
if (StringUtil.isBlank(keyword) || "*".equals(keyword)) {
|
||||
return keyword;
|
||||
}
|
||||
if (caseSensitive || keywords.isEmpty() || keywords.contains(keyword.toUpperCase(Locale.ENGLISH))) {
|
||||
return prefix + keyword + suffix;
|
||||
}
|
||||
return keyword;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -15,7 +15,12 @@
|
||||
*/
|
||||
package com.mybatisflex.core.dialect;
|
||||
|
||||
import com.mybatisflex.core.query.CPI;
|
||||
import com.mybatisflex.core.query.QueryOrderBy;
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import com.mybatisflex.core.util.CollectionUtil;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* limit 和 offset 参数的处理器
|
||||
@ -57,29 +62,33 @@ public interface LimitOffsetProcessor {
|
||||
LimitOffsetProcessor DERBY = (sql, queryWrapper, limitRows, limitOffset) -> {
|
||||
if (limitRows != null && limitOffset != null) {
|
||||
// OFFSET ** ROWS FETCH NEXT ** ROWS ONLY")
|
||||
sql.append(" OFFSET ").append(limitOffset).append(" ROWS FETCH NEXT ").append(limitRows).append(" ROWS ONLY");
|
||||
sql.append(" OFFSET ").append(limitOffset).append(" ROWS FETCH NEXT ").append(limitRows).append(" ROWS ONLY");
|
||||
} else if (limitRows != null) {
|
||||
// FETCH FIRST 20 ROWS ONLY
|
||||
sql.append(" FETCH FIRST ").append(limitRows).append(" ROWS ONLY");
|
||||
sql.append(" OFFSET 0 ROWS FETCH NEXT ").append(limitRows).append(" ROWS ONLY");
|
||||
}
|
||||
return sql;
|
||||
};
|
||||
|
||||
/**
|
||||
* db2 的处理器
|
||||
* 适合 {@link DbType#DB2,DbType#SQLSERVER_2005}
|
||||
* derby 的处理器
|
||||
* 适合 {@link DbType#DERBY,DbType#ORACLE_12C,DbType#SQLSERVER ,DbType#POSTGRE_SQL}
|
||||
*/
|
||||
LimitOffsetProcessor DB2 = (sql, queryWrapper, limitRows, limitOffset) -> {
|
||||
LimitOffsetProcessor SQLSERVER = (sql, queryWrapper, limitRows, limitOffset) -> {
|
||||
if (limitRows != null && limitOffset != null) {
|
||||
// OFFSET ** ROWS FETCH NEXT ** ROWS ONLY")
|
||||
sql.append(" OFFSET ").append(limitOffset).append(" ROWS FETCH NEXT ").append(limitRows).append(" ROWS ONLY");
|
||||
sql.append(" OFFSET ").append(limitOffset).append(" ROWS FETCH NEXT ").append(limitRows).append(" ROWS ONLY");
|
||||
} else if (limitRows != null) {
|
||||
// FETCH FIRST 20 ROWS ONLY
|
||||
sql.append(" FETCH FIRST ").append(limitRows).append(" ROWS ONLY");
|
||||
List<QueryOrderBy> orderBys = CPI.getOrderBys(queryWrapper);
|
||||
if (CollectionUtil.isNotEmpty(orderBys)) {
|
||||
sql.append(" OFFSET 0 ROWS FETCH NEXT ").append(limitRows).append(" ROWS ONLY");
|
||||
} else {
|
||||
sql.insert(6, " TOP " + limitRows);
|
||||
}
|
||||
}
|
||||
return sql;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Informix 的处理器
|
||||
* 适合 {@link DbType#INFORMIX}
|
||||
|
||||
@ -56,7 +56,12 @@ public class CommonsDialectImpl implements IDialect {
|
||||
|
||||
@Override
|
||||
public String wrap(String keyword) {
|
||||
return keywordWrap.wrap(keyword);
|
||||
return "*".equals(keyword) ? keyword : keywordWrap.wrap(keyword);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String forHint(String hintString) {
|
||||
return StringUtil.isNotBlank(hintString) ? "/*+ " + hintString + " */ " : "";
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -255,17 +260,11 @@ public class CommonsDialectImpl implements IDialect {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String forSelectListByQuery(QueryWrapper queryWrapper) {
|
||||
public String forSelectByQuery(QueryWrapper queryWrapper) {
|
||||
return buildSelectSql(queryWrapper);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String forSelectCountByQuery(QueryWrapper queryWrapper) {
|
||||
return buildSelectCountSql(queryWrapper);
|
||||
}
|
||||
|
||||
|
||||
////////////build query sql///////
|
||||
@Override
|
||||
public String buildSelectSql(QueryWrapper queryWrapper) {
|
||||
@ -275,7 +274,7 @@ public class CommonsDialectImpl implements IDialect {
|
||||
|
||||
List<QueryColumn> selectColumns = CPI.getSelectColumns(queryWrapper);
|
||||
|
||||
StringBuilder sqlBuilder = buildSelectColumnSql(allTables, selectColumns);
|
||||
StringBuilder sqlBuilder = buildSelectColumnSql(allTables, selectColumns, CPI.getHint(queryWrapper));
|
||||
sqlBuilder.append(" FROM ").append(StringUtil.join(", ", queryTables, queryTable -> queryTable.toSql(this)));
|
||||
|
||||
buildJoinSql(sqlBuilder, queryWrapper, allTables);
|
||||
@ -298,11 +297,19 @@ public class CommonsDialectImpl implements IDialect {
|
||||
sqlBuilder = buildLimitOffsetSql(sqlBuilder, queryWrapper, limitRows, limitOffset);
|
||||
}
|
||||
|
||||
List<String> endFragments = CPI.getEndFragments(queryWrapper);
|
||||
if (CollectionUtil.isNotEmpty(endFragments)) {
|
||||
for (String endFragment : endFragments) {
|
||||
sqlBuilder.append(" ").append(endFragment);
|
||||
}
|
||||
}
|
||||
|
||||
return sqlBuilder.toString();
|
||||
}
|
||||
|
||||
private StringBuilder buildSelectColumnSql(List<QueryTable> queryTables, List<QueryColumn> selectColumns) {
|
||||
private StringBuilder buildSelectColumnSql(List<QueryTable> queryTables, List<QueryColumn> selectColumns, String hint) {
|
||||
StringBuilder sqlBuilder = new StringBuilder("SELECT ");
|
||||
sqlBuilder.append(forHint(hint));
|
||||
if (selectColumns == null || selectColumns.isEmpty()) {
|
||||
sqlBuilder.append("*");
|
||||
} else {
|
||||
@ -320,29 +327,6 @@ public class CommonsDialectImpl implements IDialect {
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String buildSelectCountSql(QueryWrapper queryWrapper) {
|
||||
List<QueryTable> queryTables = CPI.getQueryTables(queryWrapper);
|
||||
List<QueryTable> joinTables = CPI.getJoinTables(queryWrapper);
|
||||
List<QueryTable> allTables = CollectionUtil.merge(queryTables, joinTables);
|
||||
|
||||
//ignore selectColumns
|
||||
StringBuilder sqlBuilder = new StringBuilder("SELECT COUNT(*) FROM ");
|
||||
sqlBuilder.append(StringUtil.join(", ", queryTables, queryTable -> queryTable.toSql(this)));
|
||||
|
||||
|
||||
buildJoinSql(sqlBuilder, queryWrapper, allTables);
|
||||
buildWhereSql(sqlBuilder, queryWrapper, allTables, true);
|
||||
buildGroupBySql(sqlBuilder, queryWrapper, allTables);
|
||||
buildHavingSql(sqlBuilder, queryWrapper, allTables);
|
||||
|
||||
// ignore orderBy and limit
|
||||
// buildOrderBySql(sqlBuilder, queryWrapper);
|
||||
// buildLimitSql(sqlBuilder, queryWrapper);
|
||||
|
||||
return sqlBuilder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String buildDeleteSql(QueryWrapper queryWrapper) {
|
||||
List<QueryTable> queryTables = CPI.getQueryTables(queryWrapper);
|
||||
@ -350,7 +334,7 @@ public class CommonsDialectImpl implements IDialect {
|
||||
List<QueryTable> allTables = CollectionUtil.merge(queryTables, joinTables);
|
||||
|
||||
//ignore selectColumns
|
||||
StringBuilder sqlBuilder = new StringBuilder("DELETE FROM ");
|
||||
StringBuilder sqlBuilder = new StringBuilder("DELETE " + forHint(CPI.getHint(queryWrapper)) + "FROM ");
|
||||
sqlBuilder.append(StringUtil.join(", ", queryTables, queryTable -> queryTable.toSql(this)));
|
||||
|
||||
buildJoinSql(sqlBuilder, queryWrapper, allTables);
|
||||
@ -362,6 +346,13 @@ public class CommonsDialectImpl implements IDialect {
|
||||
//buildOrderBySql(sqlBuilder, queryWrapper);
|
||||
//buildLimitSql(sqlBuilder, queryWrapper);
|
||||
|
||||
List<String> endFragments = CPI.getEndFragments(queryWrapper);
|
||||
if (CollectionUtil.isNotEmpty(endFragments)) {
|
||||
for (String endFragment : endFragments) {
|
||||
sqlBuilder.append(" ").append(endFragment);
|
||||
}
|
||||
}
|
||||
|
||||
return sqlBuilder.toString();
|
||||
}
|
||||
|
||||
@ -545,7 +536,7 @@ public class CommonsDialectImpl implements IDialect {
|
||||
List<QueryTable> allTables = CollectionUtil.merge(queryTables, joinTables);
|
||||
|
||||
//ignore selectColumns
|
||||
StringBuilder sqlBuilder = new StringBuilder("UPDATE ");
|
||||
StringBuilder sqlBuilder = new StringBuilder("UPDATE ").append(forHint(CPI.getHint(queryWrapper)));
|
||||
sqlBuilder.append(wrap(tableInfo.getTableName()));
|
||||
sqlBuilder.append(" SET ").append(wrap(logicDeleteColumn)).append(" = ").append(getLogicDeletedValue());
|
||||
|
||||
@ -635,7 +626,7 @@ public class CommonsDialectImpl implements IDialect {
|
||||
|
||||
Set<String> modifyAttrs = tableInfo.obtainUpdateColumns(entity, ignoreNulls, true);
|
||||
|
||||
sql.append("UPDATE ").append(wrap(tableInfo.getTableName())).append(" SET ");
|
||||
sql.append("UPDATE ").append(forHint(CPI.getHint(queryWrapper))).append(wrap(tableInfo.getTableName())).append(" SET ");
|
||||
|
||||
StringJoiner stringJoiner = new StringJoiner(", ");
|
||||
|
||||
@ -665,12 +656,20 @@ public class CommonsDialectImpl implements IDialect {
|
||||
}
|
||||
|
||||
sql.append(" WHERE ").append(whereConditionSql);
|
||||
|
||||
List<String> endFragments = CPI.getEndFragments(queryWrapper);
|
||||
if (CollectionUtil.isNotEmpty(endFragments)) {
|
||||
for (String endFragment : endFragments) {
|
||||
sql.append(" ").append(endFragment);
|
||||
}
|
||||
}
|
||||
|
||||
return sql.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String forSelectOneEntityById(TableInfo tableInfo) {
|
||||
StringBuilder sql = buildSelectColumnSql(null, tableInfo.getDefaultQueryColumn());
|
||||
StringBuilder sql = buildSelectColumnSql(null, null, null);
|
||||
sql.append(" FROM ").append(wrap(tableInfo.getTableName()));
|
||||
sql.append(" WHERE ");
|
||||
String[] pKeys = tableInfo.getPrimaryKeys();
|
||||
@ -699,7 +698,7 @@ public class CommonsDialectImpl implements IDialect {
|
||||
|
||||
@Override
|
||||
public String forSelectEntityListByIds(TableInfo tableInfo, Object[] primaryValues) {
|
||||
StringBuilder sql = buildSelectColumnSql(null, tableInfo.getDefaultQueryColumn());
|
||||
StringBuilder sql = buildSelectColumnSql(null, tableInfo.getDefaultQueryColumn(), null);
|
||||
sql.append(" FROM ").append(wrap(tableInfo.getTableName()));
|
||||
sql.append(" WHERE ");
|
||||
String[] primaryKeys = tableInfo.getPrimaryKeys();
|
||||
@ -845,7 +844,8 @@ public class CommonsDialectImpl implements IDialect {
|
||||
|
||||
protected Object getLogicNormalValue() {
|
||||
Object normalValueOfLogicDelete = FlexGlobalConfig.getDefaultConfig().getNormalValueOfLogicDelete();
|
||||
if (normalValueOfLogicDelete instanceof Number) {
|
||||
if (normalValueOfLogicDelete instanceof Number
|
||||
|| normalValueOfLogicDelete instanceof Boolean) {
|
||||
return normalValueOfLogicDelete;
|
||||
}
|
||||
return "\"" + normalValueOfLogicDelete.toString() + "\"";
|
||||
@ -854,7 +854,8 @@ public class CommonsDialectImpl implements IDialect {
|
||||
|
||||
protected Object getLogicDeletedValue() {
|
||||
Object deletedValueOfLogicDelete = FlexGlobalConfig.getDefaultConfig().getDeletedValueOfLogicDelete();
|
||||
if (deletedValueOfLogicDelete instanceof Number) {
|
||||
if (deletedValueOfLogicDelete instanceof Number
|
||||
|| deletedValueOfLogicDelete instanceof Boolean) {
|
||||
return deletedValueOfLogicDelete;
|
||||
}
|
||||
return "\"" + deletedValueOfLogicDelete.toString() + "\"";
|
||||
|
||||
@ -18,14 +18,12 @@ package com.mybatisflex.core.dialect.impl;
|
||||
import com.mybatisflex.core.dialect.KeywordWrap;
|
||||
import com.mybatisflex.core.dialect.LimitOffsetProcessor;
|
||||
import com.mybatisflex.core.util.CollectionUtil;
|
||||
import com.mybatisflex.core.util.StringUtil;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Set;
|
||||
|
||||
public class OracleDialect extends CommonsDialectImpl {
|
||||
|
||||
private boolean caseSensitive;
|
||||
private final Set<String> keywords = CollectionUtil.newHashSet(
|
||||
private static final Set<String> keywords = CollectionUtil.newHashSet(
|
||||
"ACCESS", "ADD", "ALL", "ALTER",
|
||||
"AND", "ANY", "ARRAYLEN", "AS",
|
||||
"ASC", "AUDIT", "BETWEEN", "BY",
|
||||
@ -55,26 +53,11 @@ public class OracleDialect extends CommonsDialectImpl {
|
||||
"VALIDATE", "VALUES", "VARCHAR", "VARCHAR2"
|
||||
);
|
||||
|
||||
public OracleDialect() {
|
||||
this(LimitOffsetProcessor.ORACLE);
|
||||
}
|
||||
public OracleDialect(LimitOffsetProcessor limitOffsetProcessor) {
|
||||
super(KeywordWrap.NONE, limitOffsetProcessor);
|
||||
super(new KeywordWrap(keywords,"\"","\""),limitOffsetProcessor);
|
||||
}
|
||||
|
||||
public boolean isCaseSensitive() {
|
||||
return caseSensitive;
|
||||
}
|
||||
|
||||
public void setCaseSensitive(boolean caseSensitive) {
|
||||
this.caseSensitive = caseSensitive;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String wrap(String keyword) {
|
||||
if (StringUtil.isBlank(keyword)) {
|
||||
return "";
|
||||
}
|
||||
if (caseSensitive || keywords.contains(keyword.toUpperCase(Locale.ENGLISH))) {
|
||||
return "\"" + keyword + "\"";
|
||||
}
|
||||
return keyword;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.mybatisflex.core.field;
|
||||
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class FieldQuery implements Serializable {
|
||||
|
||||
private String field;
|
||||
private Class<?> mappingType;
|
||||
private QueryWrapper queryWrapper;
|
||||
|
||||
public String getField() {
|
||||
return field;
|
||||
}
|
||||
|
||||
public void setField(String field) {
|
||||
this.field = field;
|
||||
}
|
||||
|
||||
public Class<?> getMappingType() {
|
||||
return mappingType;
|
||||
}
|
||||
|
||||
public void setMappingType(Class<?> mappingType) {
|
||||
this.mappingType = mappingType;
|
||||
}
|
||||
|
||||
public QueryWrapper getQueryWrapper() {
|
||||
return queryWrapper;
|
||||
}
|
||||
|
||||
public void setQueryWrapper(QueryWrapper queryWrapper) {
|
||||
this.queryWrapper = queryWrapper;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.mybatisflex.core.field;
|
||||
|
||||
import com.mybatisflex.core.util.LambdaGetter;
|
||||
import com.mybatisflex.core.util.LambdaUtil;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class FieldQueryBuilder<T> implements Serializable {
|
||||
|
||||
private T entity;
|
||||
private FieldQuery fieldQuery = new FieldQuery();
|
||||
|
||||
public FieldQueryBuilder(T entity) {
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
public FieldQueryBuilder<T> field(String field){
|
||||
fieldQuery.setField(field);
|
||||
return this;
|
||||
}
|
||||
|
||||
public FieldQueryBuilder<T> field(LambdaGetter<T> fn){
|
||||
return field(LambdaUtil.getFieldName(fn));
|
||||
}
|
||||
|
||||
public FieldQueryBuilder<T> type(Class<?> mappingType){
|
||||
fieldQuery.setMappingType(mappingType);
|
||||
return this;
|
||||
}
|
||||
|
||||
public FieldQueryBuilder<T> queryWrapper(QueryBuilder<T> fun){
|
||||
fieldQuery.setQueryWrapper(fun.build(entity));
|
||||
return this;
|
||||
}
|
||||
|
||||
public FieldQuery build() {
|
||||
return fieldQuery;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.mybatisflex.core.field;
|
||||
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
|
||||
public interface QueryBuilder<T> {
|
||||
QueryWrapper build(T entity);
|
||||
}
|
||||
@ -15,58 +15,27 @@
|
||||
*/
|
||||
package com.mybatisflex.core.handler;
|
||||
|
||||
import com.mybatisflex.annotation.EnumValue;
|
||||
import com.mybatisflex.core.exception.FlexExceptions;
|
||||
import com.mybatisflex.core.util.ClassUtil;
|
||||
import com.mybatisflex.core.util.StringUtil;
|
||||
import com.mybatisflex.core.util.EnumWrapper;
|
||||
import org.apache.ibatis.type.BaseTypeHandler;
|
||||
import org.apache.ibatis.type.JdbcType;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.sql.CallableStatement;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
|
||||
public class FlexEnumTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> {
|
||||
|
||||
private final Class<?> enumPropertyType;
|
||||
private final E[] enums;
|
||||
private Field property;
|
||||
private Method getter;
|
||||
private EnumWrapper<E> enumWrapper;
|
||||
|
||||
public FlexEnumTypeHandler(Class<E> enumClass) {
|
||||
List<Field> allFields = ClassUtil.getAllFields(enumClass, field -> field.getAnnotation(EnumValue.class) != null);
|
||||
Field field = allFields.get(0);
|
||||
|
||||
String fieldGetterName = "get" + StringUtil.firstCharToUpperCase(field.getName());
|
||||
List<Method> allMethods = ClassUtil.getAllMethods(enumClass, method -> {
|
||||
String methodName = method.getName();
|
||||
return methodName.equals(fieldGetterName);
|
||||
});
|
||||
|
||||
enumPropertyType = ClassUtil.wrap(field.getType());
|
||||
enums = enumClass.getEnumConstants();
|
||||
|
||||
if (allMethods.isEmpty()) {
|
||||
if (Modifier.isPublic(field.getModifiers())) {
|
||||
property = field;
|
||||
} else {
|
||||
throw new IllegalStateException("Can not find \"" + fieldGetterName + "()\" method in enum: " + enumClass.getName());
|
||||
}
|
||||
} else {
|
||||
getter = allMethods.get(0);
|
||||
}
|
||||
|
||||
enumWrapper = EnumWrapper.of(enumClass);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
|
||||
Object value = getValue(parameter);
|
||||
Object value = enumWrapper.getEnumValue(parameter);
|
||||
if (jdbcType == null) {
|
||||
ps.setObject(i, value);
|
||||
} else {
|
||||
@ -74,55 +43,34 @@ public class FlexEnumTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private Object getValue(E object) {
|
||||
try {
|
||||
return getter != null
|
||||
? getter.invoke(object)
|
||||
: property.get(object);
|
||||
} catch (Exception e) {
|
||||
throw FlexExceptions.wrap(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
|
||||
Object value = rs.getObject(columnName, this.enumPropertyType);
|
||||
Object value = rs.getObject(columnName, enumWrapper.getEnumPropertyType());
|
||||
if (null == value && rs.wasNull()) {
|
||||
return null;
|
||||
}
|
||||
return convertToEnum(value);
|
||||
return enumWrapper.toEnum(value);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
|
||||
Object value = rs.getObject(columnIndex, this.enumPropertyType);
|
||||
Object value = rs.getObject(columnIndex, enumWrapper.getEnumPropertyType());
|
||||
if (null == value && rs.wasNull()) {
|
||||
return null;
|
||||
}
|
||||
return convertToEnum(value);
|
||||
return enumWrapper.toEnum(value);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
|
||||
Object value = cs.getObject(columnIndex, this.enumPropertyType);
|
||||
Object value = cs.getObject(columnIndex, enumWrapper.getEnumPropertyType());
|
||||
if (null == value && cs.wasNull()) {
|
||||
return null;
|
||||
}
|
||||
return convertToEnum(value);
|
||||
return enumWrapper.toEnum(value);
|
||||
}
|
||||
|
||||
|
||||
private E convertToEnum(Object value) {
|
||||
for (E e : enums) {
|
||||
if (value.equals(getValue(e))) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -30,9 +30,9 @@ public class KeyGeneratorFactory {
|
||||
/** 内置了 uuid 的生成器,因此主键配置的时候可以直接配置为 @Id(keyType = KeyType.Generator, value = "uuid")
|
||||
* {@link com.mybatisflex.annotation.Id}
|
||||
*/
|
||||
register("uuid", new UUIDKeyGenerator());
|
||||
register("flexId", new FlexIDKeyGenerator());
|
||||
register("snowFlakeId", new SnowFlakeIDKeyGenerator());
|
||||
register(KeyGenerators.uuid, new UUIDKeyGenerator());
|
||||
register(KeyGenerators.flexId, new FlexIDKeyGenerator());
|
||||
register(KeyGenerators.snowFlakeId, new SnowFlakeIDKeyGenerator());
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.mybatisflex.core.keygen;
|
||||
|
||||
public class KeyGenerators {
|
||||
|
||||
/**
|
||||
* uuid 主键生成器
|
||||
* {@link com.mybatisflex.core.keygen.impl.UUIDKeyGenerator}
|
||||
*/
|
||||
public static final String uuid = "uuid";
|
||||
|
||||
/**
|
||||
* flexId 主键生成器
|
||||
* {@link com.mybatisflex.core.keygen.impl.FlexIDKeyGenerator}
|
||||
*/
|
||||
public static final String flexId = "flexId";
|
||||
|
||||
/**
|
||||
* 雪花算法主键生成器
|
||||
* {@link com.mybatisflex.core.keygen.impl.SnowFlakeIDKeyGenerator}
|
||||
*/
|
||||
public static final String snowFlakeId = "snowFlakeId";
|
||||
}
|
||||
@ -42,12 +42,15 @@ import org.apache.ibatis.mapping.MappedStatement;
|
||||
import org.apache.ibatis.mapping.ResultMap;
|
||||
import org.apache.ibatis.session.*;
|
||||
import org.apache.ibatis.transaction.Transaction;
|
||||
import org.apache.ibatis.util.MapUtil;
|
||||
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class FlexConfiguration extends Configuration {
|
||||
|
||||
private static Map<Class<?>, MappedStatement> dynamicMappedStatementCache = new ConcurrentHashMap<>();
|
||||
|
||||
public FlexConfiguration(Environment environment) {
|
||||
super(environment);
|
||||
@ -126,6 +129,22 @@ public class FlexConfiguration extends Configuration {
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public MappedStatement getMappedStatement(String id) {
|
||||
MappedStatement ms = super.getMappedStatement(id);
|
||||
|
||||
//动态 resultsMap
|
||||
if (id.endsWith(FlexConsts.METHOD_SELECT_LIST_BY_QUERY_AS)) {
|
||||
Class<?> asType = MappedStatementTypes.getCurrentType();
|
||||
return MapUtil.computeIfAbsent(dynamicMappedStatementCache, asType,
|
||||
aClass -> replaceResultMap(ms, TableInfoFactory.ofEntityClass(asType))
|
||||
);
|
||||
}
|
||||
|
||||
return ms;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void addMappedStatement(MappedStatement ms) {
|
||||
//替换 RowMapper.insert 的主键生成器
|
||||
@ -141,7 +160,7 @@ public class FlexConfiguration extends Configuration {
|
||||
//entity select
|
||||
else if (StringUtil.endsWithAny(ms.getId(), "selectOneById", "selectListByIds"
|
||||
, "selectListByQuery")) {
|
||||
ms = replaceResultMap(ms);
|
||||
ms = replaceResultMap(ms, getTableInfo(ms));
|
||||
}
|
||||
|
||||
super.addMappedStatement(ms);
|
||||
@ -151,9 +170,8 @@ public class FlexConfiguration extends Configuration {
|
||||
/**
|
||||
* 替换 entity 查询的 ResultMap
|
||||
*/
|
||||
private MappedStatement replaceResultMap(MappedStatement ms) {
|
||||
private MappedStatement replaceResultMap(MappedStatement ms, TableInfo tableInfo) {
|
||||
|
||||
TableInfo tableInfo = getTableInfo(ms);
|
||||
if (tableInfo == null) {
|
||||
return ms;
|
||||
}
|
||||
|
||||
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.mybatisflex.core.mybatis;
|
||||
|
||||
public class MappedStatementTypes {
|
||||
|
||||
private static ThreadLocal<Class<?>> currentTypeTL = new ThreadLocal<>();
|
||||
|
||||
public static void setCurrentType(Class<?> type){
|
||||
currentTypeTL.set(type);
|
||||
}
|
||||
|
||||
public static Class<?> getCurrentType(){
|
||||
return currentTypeTL.get();
|
||||
}
|
||||
|
||||
public static void clear(){
|
||||
currentTypeTL.remove();
|
||||
}
|
||||
|
||||
}
|
||||
@ -16,6 +16,7 @@
|
||||
package com.mybatisflex.core.mybatis;
|
||||
|
||||
import com.mybatisflex.annotation.UseDataSource;
|
||||
import com.mybatisflex.core.FlexConsts;
|
||||
import com.mybatisflex.core.FlexGlobalConfig;
|
||||
import com.mybatisflex.core.datasource.DataSourceKey;
|
||||
import com.mybatisflex.core.datasource.FlexDataSource;
|
||||
@ -45,7 +46,11 @@ public class MapperInvocationHandler implements InvocationHandler {
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
boolean clearDsKey = false;
|
||||
boolean clearDbType = false;
|
||||
boolean isSelectListByQueryAsMethod = FlexConsts.METHOD_SELECT_LIST_BY_QUERY_AS.equals(method.getName());
|
||||
try {
|
||||
if (isSelectListByQueryAsMethod){
|
||||
MappedStatementTypes.setCurrentType((Class<?>) args[1]);
|
||||
}
|
||||
//获取用户动态指定,由用户指定数据源,则应该有用户清除
|
||||
String dataSourceKey = DataSourceKey.get();
|
||||
|
||||
@ -73,6 +78,9 @@ public class MapperInvocationHandler implements InvocationHandler {
|
||||
}
|
||||
return method.invoke(mapper, args);
|
||||
} finally {
|
||||
if (isSelectListByQueryAsMethod){
|
||||
MappedStatementTypes.clear();
|
||||
}
|
||||
if (clearDbType) {
|
||||
DialectFactory.clearHintDbType();
|
||||
}
|
||||
|
||||
@ -32,12 +32,12 @@ public class Page<T> implements Serializable {
|
||||
private long totalPage = INIT_VALUE;
|
||||
private long totalRow = INIT_VALUE;
|
||||
|
||||
public static Page of(int pageNumber, int pageSize) {
|
||||
return new Page(pageNumber, pageSize);
|
||||
public static <T> Page<T> of(int pageNumber, int pageSize) {
|
||||
return new Page<>(pageNumber, pageSize);
|
||||
}
|
||||
|
||||
public static Page of(int pageNumber, int pageSize, long totalRow) {
|
||||
return new Page(pageNumber, pageSize, totalRow);
|
||||
public static <T> Page<T> of(int pageNumber, int pageSize, long totalRow) {
|
||||
return new Page<>(pageNumber, pageSize, totalRow);
|
||||
}
|
||||
|
||||
public Page() {
|
||||
@ -114,7 +114,7 @@ public class Page<T> implements Serializable {
|
||||
newPage.totalPage = totalPage;
|
||||
newPage.totalRow = totalRow;
|
||||
|
||||
if (records != null) {
|
||||
if (records != null && !records.isEmpty()) {
|
||||
List<R> newRecords = new ArrayList<>(records.size());
|
||||
for (T t : records) {
|
||||
newRecords.add(mapper.apply(t));
|
||||
|
||||
@ -18,16 +18,17 @@ package com.mybatisflex.core.provider;
|
||||
import com.mybatisflex.core.dialect.DialectFactory;
|
||||
import com.mybatisflex.core.exception.FlexExceptions;
|
||||
import com.mybatisflex.core.query.CPI;
|
||||
import com.mybatisflex.core.query.QueryTable;
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import com.mybatisflex.core.table.TableInfo;
|
||||
import com.mybatisflex.core.table.TableInfoFactory;
|
||||
import com.mybatisflex.core.util.ArrayUtil;
|
||||
import com.mybatisflex.core.util.CollectionUtil;
|
||||
import com.mybatisflex.core.util.StringUtil;
|
||||
import org.apache.ibatis.builder.annotation.ProviderContext;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
public class EntitySqlProvider {
|
||||
|
||||
@ -76,7 +77,6 @@ public class EntitySqlProvider {
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* insertBatch 的 sql 构建
|
||||
*
|
||||
@ -308,17 +308,18 @@ public class EntitySqlProvider {
|
||||
if (queryWrapper == null) {
|
||||
throw FlexExceptions.wrap("queryWrapper can not be null.");
|
||||
}
|
||||
List<TableInfo> tableInfos = getTableInfos(context, queryWrapper);
|
||||
for (TableInfo tableInfo : tableInfos) {
|
||||
tableInfo.appendConditions(null, queryWrapper);
|
||||
|
||||
TableInfo tableInfo = ProviderUtil.getTableInfo(context);
|
||||
tableInfo.appendConditions(null, queryWrapper);
|
||||
CPI.setSelectColumnsIfNecessary(queryWrapper, tableInfo.getDefaultQueryColumn());
|
||||
CPI.setFromIfNecessary(queryWrapper, tableInfo.getTableName());
|
||||
}
|
||||
|
||||
Object[] values = CPI.getValueArray(queryWrapper);
|
||||
ProviderUtil.setSqlArgs(params, values);
|
||||
|
||||
CPI.setSelectColumnsIfNecessary(queryWrapper, tableInfo.getDefaultQueryColumn());
|
||||
CPI.setFromIfNecessary(queryWrapper, tableInfo.getTableName());
|
||||
|
||||
return DialectFactory.getDialect().forSelectListByQuery(queryWrapper);
|
||||
return DialectFactory.getDialect().forSelectByQuery(queryWrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -327,22 +328,50 @@ public class EntitySqlProvider {
|
||||
* @param params
|
||||
* @param context
|
||||
* @return sql
|
||||
* @see com.mybatisflex.core.BaseMapper#selectCountByQuery(QueryWrapper)
|
||||
* @see com.mybatisflex.core.BaseMapper#selectObjectByQuery(QueryWrapper)
|
||||
*/
|
||||
public static String selectCountByQuery(Map params, ProviderContext context) {
|
||||
public static String selectObjectByQuery(Map params, ProviderContext context) {
|
||||
QueryWrapper queryWrapper = ProviderUtil.getQueryWrapper(params);
|
||||
if (queryWrapper == null) {
|
||||
throw FlexExceptions.wrap("queryWrapper can not be null.");
|
||||
}
|
||||
|
||||
TableInfo tableInfo = ProviderUtil.getTableInfo(context);
|
||||
tableInfo.appendConditions(null, queryWrapper);
|
||||
List<TableInfo> tableInfos = getTableInfos(context, queryWrapper);
|
||||
|
||||
for (TableInfo tableInfo : tableInfos) {
|
||||
tableInfo.appendConditions(null, queryWrapper);
|
||||
CPI.setFromIfNecessary(queryWrapper, tableInfo.getTableName());
|
||||
}
|
||||
|
||||
Object[] values = CPI.getValueArray(queryWrapper);
|
||||
ProviderUtil.setSqlArgs(params, values);
|
||||
|
||||
CPI.setFromIfNecessary(queryWrapper, tableInfo.getTableName());
|
||||
return DialectFactory.getDialect().forSelectCountByQuery(queryWrapper);
|
||||
return DialectFactory.getDialect().forSelectByQuery(queryWrapper);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private static List<TableInfo> getTableInfos(ProviderContext context, QueryWrapper queryWrapper) {
|
||||
List<TableInfo> tableInfos;
|
||||
List<QueryTable> queryTables = CPI.getQueryTables(queryWrapper);
|
||||
if (CollectionUtil.isNotEmpty(queryTables)) {
|
||||
tableInfos = new ArrayList<>();
|
||||
for (QueryTable queryTable : queryTables) {
|
||||
String tableName = queryTable.getName();
|
||||
if (StringUtil.isNotBlank(tableName)) {
|
||||
TableInfo tableInfo = TableInfoFactory.ofTableName(tableName);
|
||||
if (tableInfo != null) {
|
||||
tableInfos.add(tableInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tableInfos = Collections.singletonList(ProviderUtil.getTableInfo(context));
|
||||
}
|
||||
return tableInfos;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -22,6 +22,8 @@ import com.mybatisflex.core.query.QueryWrapper;
|
||||
import com.mybatisflex.core.row.Row;
|
||||
import com.mybatisflex.core.row.RowCPI;
|
||||
import com.mybatisflex.core.row.RowMapper;
|
||||
import com.mybatisflex.core.table.TableInfo;
|
||||
import com.mybatisflex.core.table.TableInfoFactory;
|
||||
import com.mybatisflex.core.util.ArrayUtil;
|
||||
import com.mybatisflex.core.util.CollectionUtil;
|
||||
|
||||
@ -213,6 +215,36 @@ public class RowSqlProvider {
|
||||
return DialectFactory.getDialect().forUpdateBatchById(tableName, rows);
|
||||
}
|
||||
|
||||
/**
|
||||
* updateEntity 的 sql 构建
|
||||
*
|
||||
* @param params
|
||||
* @return sql
|
||||
* @see RowMapper#updateEntity(Object entities)
|
||||
*/
|
||||
public static String updateEntity(Map params) {
|
||||
Object entity = ProviderUtil.getEntity(params);
|
||||
if (entity == null) {
|
||||
throw FlexExceptions.wrap("entity can not be null");
|
||||
}
|
||||
|
||||
// 该 Mapper 是通用 Mapper 无法通过 ProviderContext 获取,直接使用 TableInfoFactory
|
||||
TableInfo tableInfo = TableInfoFactory.ofEntityClass(entity.getClass());
|
||||
|
||||
// 执行 onUpdate 监听器
|
||||
tableInfo.invokeOnUpdateListener(entity);
|
||||
|
||||
Object[] updateValues = tableInfo.buildUpdateSqlArgs(entity, false, false);
|
||||
Object[] primaryValues = tableInfo.buildPkSqlArgs(entity);
|
||||
Object[] tenantIdArgs = tableInfo.buildTenantIdArgs();
|
||||
|
||||
FlexExceptions.assertAreNotNull(primaryValues, "The value of primary key must not be null, entity[%s]", entity);
|
||||
|
||||
ProviderUtil.setSqlArgs(params, ArrayUtil.concat(updateValues, primaryValues, tenantIdArgs));
|
||||
|
||||
return DialectFactory.getDialect().forUpdateEntity(tableInfo, entity, false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* selectOneById 的 sql 构建
|
||||
@ -248,7 +280,7 @@ public class RowSqlProvider {
|
||||
ProviderUtil.setSqlArgs(params, valueArray);
|
||||
|
||||
|
||||
return DialectFactory.getDialect().forSelectListByQuery(queryWrapper);
|
||||
return DialectFactory.getDialect().forSelectByQuery(queryWrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -258,7 +290,7 @@ public class RowSqlProvider {
|
||||
* @return sql
|
||||
* @see RowMapper#selectCountByQuery(String, QueryWrapper)
|
||||
*/
|
||||
public static String selectCountByQuery(Map params) {
|
||||
public static String selectObjectByQuery(Map params) {
|
||||
String tableName = ProviderUtil.getTableName(params);
|
||||
|
||||
QueryWrapper queryWrapper = ProviderUtil.getQueryWrapper(params);
|
||||
@ -267,7 +299,7 @@ public class RowSqlProvider {
|
||||
Object[] valueArray = CPI.getValueArray(queryWrapper);
|
||||
ProviderUtil.setSqlArgs(params, valueArray);
|
||||
|
||||
return DialectFactory.getDialect().forSelectCountByQuery(queryWrapper);
|
||||
return DialectFactory.getDialect().forSelectByQuery(queryWrapper);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -23,6 +23,7 @@ public class BaseQueryWrapper<T> implements Serializable {
|
||||
|
||||
protected List<QueryTable> queryTables;
|
||||
protected String dataSource;
|
||||
protected String hint;
|
||||
|
||||
protected List<QueryColumn> selectColumns;
|
||||
protected List<Join> joins;
|
||||
@ -37,6 +38,8 @@ public class BaseQueryWrapper<T> implements Serializable {
|
||||
protected Integer limitOffset;
|
||||
protected Integer limitRows;
|
||||
|
||||
protected List<String> endFragments;
|
||||
|
||||
protected Map<String, Object> context;
|
||||
|
||||
// protected boolean ignoreBlankStrings = false;
|
||||
@ -52,7 +55,7 @@ public class BaseQueryWrapper<T> implements Serializable {
|
||||
}
|
||||
|
||||
|
||||
protected T AddJoin(Join join) {
|
||||
protected T addJoin(Join join) {
|
||||
if (joins == null) {
|
||||
joins = new LinkedList<>();
|
||||
}
|
||||
@ -119,6 +122,13 @@ public class BaseQueryWrapper<T> implements Serializable {
|
||||
joinTables.add(queryTable);
|
||||
}
|
||||
|
||||
protected void addEndFragment(String fragment){
|
||||
if (endFragments == null){
|
||||
endFragments = new ArrayList<>();
|
||||
}
|
||||
endFragments.add(fragment);
|
||||
}
|
||||
|
||||
|
||||
protected List<QueryTable> getQueryTables() {
|
||||
return queryTables;
|
||||
@ -136,6 +146,14 @@ public class BaseQueryWrapper<T> implements Serializable {
|
||||
this.dataSource = dataSource;
|
||||
}
|
||||
|
||||
protected String getHint() {
|
||||
return hint;
|
||||
}
|
||||
|
||||
protected void setHint(String hint) {
|
||||
this.hint = hint;
|
||||
}
|
||||
|
||||
protected List<QueryColumn> getSelectColumns() {
|
||||
return selectColumns;
|
||||
}
|
||||
@ -212,6 +230,14 @@ public class BaseQueryWrapper<T> implements Serializable {
|
||||
this.limitRows = limitRows;
|
||||
}
|
||||
|
||||
protected List<String> getEndFragments() {
|
||||
return endFragments;
|
||||
}
|
||||
|
||||
protected void setEndFragments(List<String> endFragments) {
|
||||
this.endFragments = endFragments;
|
||||
}
|
||||
|
||||
protected Map<String, Object> getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
@ -25,11 +25,10 @@ import java.util.List;
|
||||
*/
|
||||
public class Brackets extends QueryCondition {
|
||||
|
||||
private final QueryCondition childCondition;
|
||||
|
||||
private final QueryCondition child;
|
||||
|
||||
public Brackets(QueryCondition childCondition) {
|
||||
this.childCondition = childCondition;
|
||||
this.child = childCondition;
|
||||
}
|
||||
|
||||
|
||||
@ -46,16 +45,16 @@ public class Brackets extends QueryCondition {
|
||||
}
|
||||
|
||||
protected void connectToChild(QueryCondition nextCondition, SqlConnector connector) {
|
||||
childCondition.connect(nextCondition, connector);
|
||||
child.connect(nextCondition, connector);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue() {
|
||||
return checkEffective() ? WrapperUtil.getValues(childCondition) : null;
|
||||
return checkEffective() ? WrapperUtil.getValues(child) : null;
|
||||
}
|
||||
|
||||
public QueryCondition getChildCondition() {
|
||||
return childCondition;
|
||||
public QueryCondition getChild() {
|
||||
return child;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -64,7 +63,7 @@ public class Brackets extends QueryCondition {
|
||||
if (!effective) {
|
||||
return false;
|
||||
}
|
||||
QueryCondition condition = this.childCondition;
|
||||
QueryCondition condition = this.child;
|
||||
while (condition != null) {
|
||||
if (condition.checkEffective()) {
|
||||
return true;
|
||||
@ -81,7 +80,7 @@ public class Brackets extends QueryCondition {
|
||||
|
||||
StringBuilder sql = new StringBuilder();
|
||||
if (checkEffective()) {
|
||||
String childSql = childCondition.toSql(queryTables, dialect);
|
||||
String childSql = child.toSql(queryTables, dialect);
|
||||
if (StringUtil.isNotBlank(childSql)) {
|
||||
QueryCondition effectiveBefore = getEffectiveBefore();
|
||||
if (effectiveBefore != null) {
|
||||
@ -101,10 +100,15 @@ public class Brackets extends QueryCondition {
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
boolean containsTable(String... tables) {
|
||||
return child != null && child.containsTable(tables);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Brackets{" +
|
||||
"childCondition=" + childCondition +
|
||||
"childCondition=" + child +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,6 +55,14 @@ public class CPI {
|
||||
queryWrapper.setDataSource(datasource);
|
||||
}
|
||||
|
||||
public static String getHint(QueryWrapper queryWrapper) {
|
||||
return queryWrapper.getHint();
|
||||
}
|
||||
|
||||
public static void setHint(QueryWrapper queryWrapper, String hint) {
|
||||
queryWrapper.setHint(hint);
|
||||
}
|
||||
|
||||
public static List<QueryColumn> getSelectColumns(QueryWrapper queryWrapper) {
|
||||
return queryWrapper.getSelectColumns();
|
||||
}
|
||||
@ -80,6 +88,13 @@ public class CPI {
|
||||
queryWrapper.setJoins(joins);
|
||||
}
|
||||
|
||||
public static String getJoinType(Join join) {
|
||||
return join.type;
|
||||
}
|
||||
|
||||
public static QueryTable getJoinQueryTable(Join join) {
|
||||
return join.getQueryTable();
|
||||
}
|
||||
|
||||
public static List<QueryTable> getJoinTables(QueryWrapper queryWrapper) {
|
||||
return queryWrapper.getJoinTables();
|
||||
@ -143,6 +158,15 @@ public class CPI {
|
||||
queryWrapper.setLimitRows(limitRows);
|
||||
}
|
||||
|
||||
public static List<String> getEndFragments(QueryWrapper queryWrapper) {
|
||||
return queryWrapper.getEndFragments();
|
||||
}
|
||||
|
||||
public static void setEndFragments(QueryWrapper queryWrapper,List<String> endFragments) {
|
||||
queryWrapper.setEndFragments(endFragments);
|
||||
}
|
||||
|
||||
|
||||
public static Map<String, Object> getContext(QueryWrapper queryWrapper) {
|
||||
return queryWrapper.getContext();
|
||||
}
|
||||
@ -175,4 +199,12 @@ public class CPI {
|
||||
queryWrapper.from(tableName);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean containsTable(QueryCondition condition, String... tables) {
|
||||
return condition != null && condition.containsTable(tables);
|
||||
}
|
||||
|
||||
public static QueryWrapper getQueryWrapper(SelectQueryColumn selectQueryColumn) {
|
||||
return selectQueryColumn.getQueryWrapper();
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,135 @@
|
||||
/**
|
||||
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.mybatisflex.core.query;
|
||||
|
||||
import com.mybatisflex.core.dialect.IDialect;
|
||||
import com.mybatisflex.core.util.ArrayUtil;
|
||||
import com.mybatisflex.core.util.LambdaGetter;
|
||||
import com.mybatisflex.core.util.LambdaUtil;
|
||||
import com.mybatisflex.core.util.StringUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class CaseQueryColumn extends QueryColumn implements HasParamsColumn{
|
||||
|
||||
private List<When> whens;
|
||||
private Object elseValue;
|
||||
|
||||
void addWhen(When when) {
|
||||
if (whens == null) {
|
||||
whens = new ArrayList<>();
|
||||
}
|
||||
whens.add(when);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
String toSelectSql(List<QueryTable> queryTables, IDialect dialect) {
|
||||
StringBuilder sql = new StringBuilder("CASE");
|
||||
for (When when : whens) {
|
||||
sql.append(" WHEN ").append(when.whenCondition.toSql(queryTables, dialect));
|
||||
sql.append(" THEN ").append(buildValue(when.thenValue));
|
||||
}
|
||||
if (elseValue != null) {
|
||||
sql.append(" ELSE ").append(buildValue(elseValue));
|
||||
}
|
||||
sql.append(" END");
|
||||
if (StringUtil.isNotBlank(alias)) {
|
||||
return "(" + sql + ") AS " + dialect.wrap(alias);
|
||||
}
|
||||
return sql.toString();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
String toConditionSql(List<QueryTable> queryTables, IDialect dialect) {
|
||||
StringBuilder sql = new StringBuilder("CASE");
|
||||
for (When when : whens) {
|
||||
sql.append(" WHEN ").append(when.whenCondition.toSql(queryTables, dialect));
|
||||
sql.append(" THEN ").append(buildValue(when.thenValue));
|
||||
}
|
||||
if (elseValue != null) {
|
||||
sql.append(" ELSE ").append(buildValue(elseValue));
|
||||
}
|
||||
sql.append(" END");
|
||||
return "(" + sql + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryColumn as(String alias) {
|
||||
this.alias = alias;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> QueryColumn as(LambdaGetter<T> fn) {
|
||||
return as(LambdaUtil.getFieldName(fn));
|
||||
}
|
||||
|
||||
private String buildValue(Object value) {
|
||||
if (value instanceof Number || value instanceof Boolean) {
|
||||
return String.valueOf(value);
|
||||
} else {
|
||||
return "'" + value + "'";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] getParamValues() {
|
||||
Object[] values = new Object[0];
|
||||
for (When when : whens) {
|
||||
values = ArrayUtil.concat(values, WrapperUtil.getValues(when.whenCondition));
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
|
||||
public static class When {
|
||||
private Builder builder;
|
||||
private QueryCondition whenCondition;
|
||||
private Object thenValue;
|
||||
|
||||
public When(Builder builder, QueryCondition whenCondition) {
|
||||
this.builder = builder;
|
||||
this.whenCondition = whenCondition;
|
||||
}
|
||||
|
||||
public Builder then(Object thenValue) {
|
||||
this.thenValue = thenValue;
|
||||
this.builder.caseQueryColumn.addWhen(this);
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private CaseQueryColumn caseQueryColumn = new CaseQueryColumn();
|
||||
|
||||
public When when(QueryCondition condition) {
|
||||
return new When(this, condition);
|
||||
}
|
||||
|
||||
public Builder else_(Object elseValue) {
|
||||
caseQueryColumn.elseValue = elseValue;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CaseQueryColumn end() {
|
||||
return caseQueryColumn;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,130 @@
|
||||
/**
|
||||
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.mybatisflex.core.query;
|
||||
|
||||
import com.mybatisflex.core.dialect.IDialect;
|
||||
import com.mybatisflex.core.util.LambdaGetter;
|
||||
import com.mybatisflex.core.util.LambdaUtil;
|
||||
import com.mybatisflex.core.util.StringUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class CaseSearchQueryColumn extends QueryColumn {
|
||||
|
||||
private QueryColumn queryColumn;
|
||||
private List<When> whens;
|
||||
private Object elseValue;
|
||||
|
||||
@Override
|
||||
String toSelectSql(List<QueryTable> queryTables, IDialect dialect) {
|
||||
StringBuilder sql = new StringBuilder("CASE ");
|
||||
sql.append(queryColumn.toSelectSql(queryTables, dialect));
|
||||
for (When when : whens) {
|
||||
sql.append(" WHEN ").append(buildValue(when.searchValue)).append(" THEN ").append(buildValue(when.thenValue));
|
||||
}
|
||||
if (elseValue != null) {
|
||||
sql.append(" ELSE ").append(buildValue(elseValue));
|
||||
}
|
||||
sql.append(" END");
|
||||
if (StringUtil.isNotBlank(alias)) {
|
||||
return "(" + sql + ") AS " + dialect.wrap(alias);
|
||||
}
|
||||
return sql.toString();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
String toConditionSql(List<QueryTable> queryTables, IDialect dialect) {
|
||||
StringBuilder sql = new StringBuilder("CASE ");
|
||||
sql.append(queryColumn.toSelectSql(queryTables, dialect));
|
||||
for (When when : whens) {
|
||||
sql.append(" WHEN ").append(buildValue(when.searchValue)).append(" THEN ").append(buildValue(when.thenValue));
|
||||
}
|
||||
if (elseValue != null) {
|
||||
sql.append(" ELSE ").append(buildValue(elseValue));
|
||||
}
|
||||
sql.append(" END");
|
||||
return "(" + sql + ")";
|
||||
}
|
||||
|
||||
|
||||
private String buildValue(Object value) {
|
||||
if (value instanceof Number || value instanceof Boolean) {
|
||||
return String.valueOf(value);
|
||||
} else {
|
||||
return "'" + value + "'";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void addWhen(When when) {
|
||||
if (whens == null) {
|
||||
whens = new ArrayList<>();
|
||||
}
|
||||
whens.add(when);
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryColumn as(String alias) {
|
||||
this.alias = alias;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> QueryColumn as(LambdaGetter<T> fn) {
|
||||
return as(LambdaUtil.getFieldName(fn));
|
||||
}
|
||||
|
||||
public static class When {
|
||||
private Builder builder;
|
||||
private Object searchValue;
|
||||
private Object thenValue;
|
||||
|
||||
public When(Builder builder, Object searchValue) {
|
||||
this.builder = builder;
|
||||
this.searchValue = searchValue;
|
||||
}
|
||||
|
||||
public Builder then(Object thenValue) {
|
||||
this.thenValue = thenValue;
|
||||
this.builder.caseQueryColumn.addWhen(this);
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
|
||||
private CaseSearchQueryColumn caseQueryColumn = new CaseSearchQueryColumn();
|
||||
|
||||
public Builder(QueryColumn queryColumn) {
|
||||
this.caseQueryColumn.queryColumn = queryColumn;
|
||||
}
|
||||
|
||||
public When when(Object searchValue) {
|
||||
return new When(this, searchValue);
|
||||
}
|
||||
|
||||
public Builder else_(Object elseValue) {
|
||||
caseQueryColumn.elseValue = elseValue;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CaseSearchQueryColumn end() {
|
||||
return caseQueryColumn;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
package com.mybatisflex.core.query;
|
||||
|
||||
public interface HasParamsColumn {
|
||||
|
||||
Object[] getParamValues();
|
||||
}
|
||||
@ -30,17 +30,18 @@ public class Join implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
static final String TYPE_LEFT = " LEFT JOIN ";
|
||||
static final String TYPE_RIGHT = " RIGHT JOIN ";
|
||||
static final String TYPE_INNER = " INNER JOIN ";
|
||||
static final String TYPE_FULL = " FULL JOIN ";
|
||||
static final String TYPE_CROSS = " CROSS JOIN ";
|
||||
public static final String TYPE_JOIN = " JOIN ";
|
||||
public static final String TYPE_LEFT = " LEFT JOIN ";
|
||||
public static final String TYPE_RIGHT = " RIGHT JOIN ";
|
||||
public static final String TYPE_INNER = " INNER JOIN ";
|
||||
public static final String TYPE_FULL = " FULL JOIN ";
|
||||
public static final String TYPE_CROSS = " CROSS JOIN ";
|
||||
|
||||
|
||||
private final String type;
|
||||
private final QueryTable queryTable;
|
||||
private QueryCondition on;
|
||||
private boolean effective;
|
||||
protected final String type;
|
||||
protected final QueryTable queryTable;
|
||||
protected QueryCondition on;
|
||||
protected boolean effective;
|
||||
|
||||
public Join(String type, String table, boolean when) {
|
||||
this.type = type;
|
||||
@ -55,7 +56,6 @@ public class Join implements Serializable {
|
||||
}
|
||||
|
||||
|
||||
|
||||
QueryTable getQueryTable() {
|
||||
return queryTable;
|
||||
}
|
||||
|
||||
@ -61,4 +61,9 @@ public class OperatorQueryCondition extends QueryCondition {
|
||||
public Object getValue() {
|
||||
return WrapperUtil.getValues(child);
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean containsTable(String... tables) {
|
||||
return child != null && child.containsTable(tables);
|
||||
}
|
||||
}
|
||||
|
||||
@ -63,4 +63,10 @@ public class OperatorSelectCondition extends QueryCondition {
|
||||
public Object getValue() {
|
||||
return queryWrapper.getValueArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean containsTable(String... tables) {
|
||||
QueryCondition condition = queryWrapper.getWhereQueryCondition();
|
||||
return condition != null && condition.containsTable(tables);
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,6 +18,8 @@ package com.mybatisflex.core.query;
|
||||
|
||||
import com.mybatisflex.core.dialect.IDialect;
|
||||
import com.mybatisflex.core.table.TableDef;
|
||||
import com.mybatisflex.core.util.LambdaGetter;
|
||||
import com.mybatisflex.core.util.LambdaUtil;
|
||||
import com.mybatisflex.core.util.SqlUtil;
|
||||
import com.mybatisflex.core.util.StringUtil;
|
||||
|
||||
@ -80,6 +82,10 @@ public class QueryColumn implements Serializable {
|
||||
this.alias = alias;
|
||||
}
|
||||
|
||||
public <T> QueryColumn as(LambdaGetter<T> fn) {
|
||||
return as(LambdaUtil.getFieldName(fn));
|
||||
}
|
||||
|
||||
public QueryColumn as(String alias) {
|
||||
SqlUtil.keepColumnSafely(alias);
|
||||
QueryColumn newColumn = new QueryColumn();
|
||||
|
||||
@ -120,7 +120,7 @@ public class QueryCondition implements Serializable {
|
||||
this.effective = (effective != null && effective);
|
||||
}
|
||||
|
||||
public <T> QueryCondition when(Predicate<T> fn){
|
||||
public <T> QueryCondition when(Predicate<T> fn) {
|
||||
Object val = this.value;
|
||||
if (LOGIC_LIKE.equals(logic) && val instanceof String) {
|
||||
String valStr = (String) val;
|
||||
@ -183,8 +183,12 @@ public class QueryCondition implements Serializable {
|
||||
if (effectiveBefore != null) {
|
||||
sql.append(effectiveBefore.connector);
|
||||
}
|
||||
//列
|
||||
sql.append(getColumn().toConditionSql(queryTables, dialect));
|
||||
//逻辑符号
|
||||
sql.append(" ").append(logic).append(" ");
|
||||
|
||||
//值(或者问号)
|
||||
if (value instanceof QueryColumn) {
|
||||
sql.append(((QueryColumn) value).toConditionSql(queryTables, dialect));
|
||||
}
|
||||
@ -264,6 +268,19 @@ public class QueryCondition implements Serializable {
|
||||
return paramsCount;
|
||||
}
|
||||
|
||||
|
||||
boolean containsTable(String... tables){
|
||||
if (column == null){
|
||||
return false;
|
||||
}
|
||||
for (String table : tables) {
|
||||
if (column.table != null && table.equals(column.table.name)){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "QueryCondition{" +
|
||||
|
||||
@ -65,11 +65,27 @@ public class QueryMethods {
|
||||
return new DistinctQueryColumn(columns);
|
||||
}
|
||||
|
||||
public static CaseQueryColumn.Builder case_() {
|
||||
return new CaseQueryColumn.Builder();
|
||||
}
|
||||
|
||||
public static CaseSearchQueryColumn.Builder case_(QueryColumn queryColumn) {
|
||||
return new CaseSearchQueryColumn.Builder(queryColumn);
|
||||
}
|
||||
|
||||
//CONVERT ( data_type [ ( length ) ] , expression [ , style ] )
|
||||
public static StringFunctionQueryColumn convert(String... params) {
|
||||
return new StringFunctionQueryColumn("CONVERT", params);
|
||||
}
|
||||
|
||||
public static StringQueryColumn column(String column) {
|
||||
return new StringQueryColumn(column);
|
||||
}
|
||||
|
||||
public static SelectQueryColumn column(QueryWrapper queryWrapper) {
|
||||
return new SelectQueryColumn(queryWrapper);
|
||||
}
|
||||
|
||||
public static QueryCondition exists(QueryWrapper queryWrapper) {
|
||||
return new OperatorSelectCondition(" EXISTS ", queryWrapper);
|
||||
}
|
||||
@ -82,7 +98,7 @@ public class QueryMethods {
|
||||
return new OperatorQueryCondition(" NOT ", childCondition);
|
||||
}
|
||||
|
||||
public static QueryCondition noCondition(){
|
||||
public static QueryCondition noCondition() {
|
||||
return QueryCondition.createEmpty();
|
||||
}
|
||||
|
||||
@ -95,12 +111,19 @@ public class QueryMethods {
|
||||
return newWrapper().select(queryColumns);
|
||||
}
|
||||
|
||||
|
||||
public static QueryWrapper selectOne() {
|
||||
return select(column("1"));
|
||||
}
|
||||
|
||||
public static RawValue raw(String raw){
|
||||
public static QueryWrapper selectCount() {
|
||||
return select(count());
|
||||
}
|
||||
|
||||
public static QueryWrapper selectCountOne(String countP) {
|
||||
return select(count("1"));
|
||||
}
|
||||
|
||||
public static RawValue raw(String raw) {
|
||||
return new RawValue(raw);
|
||||
}
|
||||
|
||||
|
||||
@ -155,8 +155,8 @@ public class QueryWrapper extends BaseQueryWrapper<QueryWrapper> {
|
||||
}
|
||||
|
||||
|
||||
public Joiner<QueryWrapper> leftJoinIf(String table, boolean condition) {
|
||||
return joining(Join.TYPE_LEFT, table, condition);
|
||||
public Joiner<QueryWrapper> leftJoinIf(String table, boolean when) {
|
||||
return joining(Join.TYPE_LEFT, table, when);
|
||||
}
|
||||
|
||||
public Joiner<QueryWrapper> leftJoin(TableDef table) {
|
||||
@ -164,88 +164,104 @@ public class QueryWrapper extends BaseQueryWrapper<QueryWrapper> {
|
||||
}
|
||||
|
||||
|
||||
public Joiner<QueryWrapper> leftJoinIf(TableDef table, boolean condition) {
|
||||
return joining(Join.TYPE_LEFT, table.getTableName(), condition);
|
||||
public Joiner<QueryWrapper> leftJoinIf(TableDef table, boolean when) {
|
||||
return joining(Join.TYPE_LEFT, table.getTableName(), when);
|
||||
}
|
||||
|
||||
public Joiner<QueryWrapper> leftJoin(QueryWrapper table) {
|
||||
return joining(Join.TYPE_LEFT, table, true);
|
||||
}
|
||||
|
||||
public Joiner<QueryWrapper> leftJoinIf(QueryWrapper table, boolean condition) {
|
||||
return joining(Join.TYPE_LEFT, table, condition);
|
||||
public Joiner<QueryWrapper> leftJoinIf(QueryWrapper table, boolean when) {
|
||||
return joining(Join.TYPE_LEFT, table, when);
|
||||
}
|
||||
|
||||
public Joiner<QueryWrapper> rightJoin(String table) {
|
||||
return joining(Join.TYPE_RIGHT, table, true);
|
||||
}
|
||||
|
||||
public Joiner<QueryWrapper> rightJoinIf(String table, boolean condition) {
|
||||
return joining(Join.TYPE_RIGHT, table, condition);
|
||||
public Joiner<QueryWrapper> rightJoinIf(String table, boolean when) {
|
||||
return joining(Join.TYPE_RIGHT, table, when);
|
||||
}
|
||||
|
||||
public Joiner<QueryWrapper> rightJoin(QueryWrapper table) {
|
||||
return joining(Join.TYPE_RIGHT, table, true);
|
||||
}
|
||||
|
||||
public Joiner<QueryWrapper> rightJoinIf(QueryWrapper table, boolean condition) {
|
||||
return joining(Join.TYPE_RIGHT, table, condition);
|
||||
public Joiner<QueryWrapper> rightJoinIf(QueryWrapper table, boolean when) {
|
||||
return joining(Join.TYPE_RIGHT, table, when);
|
||||
}
|
||||
|
||||
public Joiner<QueryWrapper> innerJoin(String table) {
|
||||
return joining(Join.TYPE_INNER, table, true);
|
||||
}
|
||||
|
||||
public Joiner<QueryWrapper> innerJoinIf(String table, boolean condition) {
|
||||
return joining(Join.TYPE_INNER, table, condition);
|
||||
public Joiner<QueryWrapper> innerJoinIf(String table, boolean when) {
|
||||
return joining(Join.TYPE_INNER, table, when);
|
||||
}
|
||||
|
||||
public Joiner<QueryWrapper> innerJoin(TableDef table) {
|
||||
return innerJoinIf(table, true);
|
||||
}
|
||||
|
||||
public Joiner<QueryWrapper> innerJoinIf(TableDef table, boolean condition) {
|
||||
return joining(Join.TYPE_INNER, table.getTableName(), condition);
|
||||
public Joiner<QueryWrapper> innerJoinIf(TableDef table, boolean when) {
|
||||
return joining(Join.TYPE_INNER, table.getTableName(), when);
|
||||
}
|
||||
|
||||
public Joiner<QueryWrapper> innerJoin(QueryWrapper table) {
|
||||
return joining(Join.TYPE_INNER, table, true);
|
||||
}
|
||||
|
||||
public Joiner<QueryWrapper> innerJoinIf(QueryWrapper table, boolean condition) {
|
||||
return joining(Join.TYPE_INNER, table, condition);
|
||||
public Joiner<QueryWrapper> innerJoinIf(QueryWrapper table, boolean when) {
|
||||
return joining(Join.TYPE_INNER, table, when);
|
||||
}
|
||||
|
||||
public Joiner<QueryWrapper> fullJoin(String table) {
|
||||
return joining(Join.TYPE_FULL, table, true);
|
||||
}
|
||||
|
||||
public Joiner<QueryWrapper> fullJoinIf(String table, boolean condition) {
|
||||
return joining(Join.TYPE_FULL, table, condition);
|
||||
public Joiner<QueryWrapper> fullJoinIf(String table, boolean when) {
|
||||
return joining(Join.TYPE_FULL, table, when);
|
||||
}
|
||||
|
||||
public Joiner<QueryWrapper> fullJoin(QueryWrapper table) {
|
||||
return joining(Join.TYPE_FULL, table, true);
|
||||
}
|
||||
|
||||
public Joiner<QueryWrapper> fullJoinIf(QueryWrapper table, boolean condition) {
|
||||
return joining(Join.TYPE_FULL, table, condition);
|
||||
public Joiner<QueryWrapper> fullJoinIf(QueryWrapper table, boolean when) {
|
||||
return joining(Join.TYPE_FULL, table, when);
|
||||
}
|
||||
|
||||
public Joiner<QueryWrapper> crossJoin(String table) {
|
||||
return joining(Join.TYPE_CROSS, table, true);
|
||||
}
|
||||
|
||||
public Joiner<QueryWrapper> crossJoinIf(String table, boolean condition) {
|
||||
return joining(Join.TYPE_CROSS, table, condition);
|
||||
public Joiner<QueryWrapper> crossJoinIf(String table, boolean when) {
|
||||
return joining(Join.TYPE_CROSS, table, when);
|
||||
}
|
||||
|
||||
public Joiner<QueryWrapper> crossJoin(QueryWrapper table) {
|
||||
return joining(Join.TYPE_CROSS, table, true);
|
||||
}
|
||||
|
||||
public Joiner<QueryWrapper> crossJoinIf(QueryWrapper table, boolean condition) {
|
||||
return joining(Join.TYPE_CROSS, table, condition);
|
||||
public Joiner<QueryWrapper> crossJoinIf(QueryWrapper table, boolean when) {
|
||||
return joining(Join.TYPE_CROSS, table, when);
|
||||
}
|
||||
|
||||
public Joiner<QueryWrapper> join(String table) {
|
||||
return joining(Join.TYPE_JOIN, table, true);
|
||||
}
|
||||
|
||||
public Joiner<QueryWrapper> join(String table, boolean when) {
|
||||
return joining(Join.TYPE_JOIN, table, when);
|
||||
}
|
||||
|
||||
public Joiner<QueryWrapper> join(QueryWrapper table) {
|
||||
return joining(Join.TYPE_JOIN, table, true);
|
||||
}
|
||||
|
||||
public Joiner<QueryWrapper> join(QueryWrapper table, boolean when) {
|
||||
return joining(Join.TYPE_JOIN, table, when);
|
||||
}
|
||||
|
||||
public QueryWrapper union(QueryWrapper unionQuery) {
|
||||
@ -264,16 +280,33 @@ public class QueryWrapper extends BaseQueryWrapper<QueryWrapper> {
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryWrapper forUpdate() {
|
||||
addEndFragment("FOR UPDATE");
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryWrapper forUpdateNoWait() {
|
||||
addEndFragment("FOR UPDATE NOWAIT");
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
// public QueryWrapper end(String sqlPart){
|
||||
// addEndFragment(sqlPart);
|
||||
// return this;
|
||||
// }
|
||||
|
||||
|
||||
protected Joiner<QueryWrapper> joining(String type, String table, boolean condition) {
|
||||
Join join = new Join(type, table, condition);
|
||||
addJoinTable(join.getQueryTable());
|
||||
return new Joiner<>(AddJoin(join), join);
|
||||
return new Joiner<>(addJoin(join), join);
|
||||
}
|
||||
|
||||
protected Joiner<QueryWrapper> joining(String type, QueryWrapper queryWrapper, boolean condition) {
|
||||
Join join = new Join(type, queryWrapper, condition);
|
||||
addJoinTable(join.getQueryTable());
|
||||
return new Joiner<>(AddJoin(join), join);
|
||||
return new Joiner<>(addJoin(join), join);
|
||||
}
|
||||
|
||||
|
||||
@ -315,8 +348,14 @@ public class QueryWrapper extends BaseQueryWrapper<QueryWrapper> {
|
||||
}
|
||||
|
||||
public QueryWrapper orderBy(String... orderBys) {
|
||||
if (orderBys == null || orderBys.length == 0) {
|
||||
//ignore
|
||||
return this;
|
||||
}
|
||||
for (String queryOrderBy : orderBys) {
|
||||
addOrderBy(new StringQueryOrderBy(queryOrderBy));
|
||||
if (StringUtil.isNotBlank(queryOrderBy)) {
|
||||
addOrderBy(new StringQueryOrderBy(queryOrderBy));
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@ -343,12 +382,33 @@ public class QueryWrapper extends BaseQueryWrapper<QueryWrapper> {
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryWrapper hint(String hint) {
|
||||
setHint(hint);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 queryWrapper 的参数
|
||||
* 在构建 sql 的时候,需要保证 where 在 having 的前面
|
||||
*/
|
||||
Object[] getValueArray() {
|
||||
|
||||
List<Object> columnValues = null;
|
||||
List<QueryColumn> selectColumns = getSelectColumns();
|
||||
if (CollectionUtil.isNotEmpty(selectColumns)) {
|
||||
for (QueryColumn selectColumn : selectColumns) {
|
||||
if (selectColumn instanceof HasParamsColumn) {
|
||||
Object[] paramValues = ((HasParamsColumn) selectColumn).getParamValues();
|
||||
if (ArrayUtil.isNotEmpty(paramValues)) {
|
||||
if (columnValues == null) {
|
||||
columnValues = new ArrayList<>();
|
||||
}
|
||||
columnValues.addAll(Arrays.asList(paramValues));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//select 子查询的参数:select * from (select ....)
|
||||
List<Object> tableValues = null;
|
||||
List<QueryTable> queryTables = getQueryTables();
|
||||
@ -404,7 +464,8 @@ public class QueryWrapper extends BaseQueryWrapper<QueryWrapper> {
|
||||
}
|
||||
}
|
||||
|
||||
Object[] returnValues = tableValues == null ? WrapperUtil.NULL_PARA_ARRAY : tableValues.toArray();
|
||||
Object[] returnValues = columnValues == null ? WrapperUtil.NULL_PARA_ARRAY : columnValues.toArray();
|
||||
returnValues = tableValues != null ? ArrayUtil.concat(returnValues, tableValues.toArray()) : returnValues;
|
||||
returnValues = joinValues != null ? ArrayUtil.concat(returnValues, joinValues.toArray()) : returnValues;
|
||||
returnValues = ArrayUtil.concat(returnValues, paramValues);
|
||||
|
||||
|
||||
@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.mybatisflex.core.query;
|
||||
|
||||
import com.mybatisflex.core.dialect.IDialect;
|
||||
import com.mybatisflex.core.util.SqlUtil;
|
||||
import com.mybatisflex.core.util.StringUtil;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class SelectQueryColumn extends QueryColumn implements HasParamsColumn {
|
||||
|
||||
private QueryWrapper queryWrapper;
|
||||
|
||||
public SelectQueryColumn(QueryWrapper queryWrapper) {
|
||||
this.queryWrapper = queryWrapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SelectQueryColumn as(String alias) {
|
||||
SqlUtil.keepColumnSafely(alias);
|
||||
this.alias = alias;
|
||||
return this;
|
||||
}
|
||||
|
||||
QueryWrapper getQueryWrapper() {
|
||||
return queryWrapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
String toSelectSql(List<QueryTable> queryTables, IDialect dialect) {
|
||||
String selectSql = dialect.forSelectByQuery(queryWrapper);
|
||||
if (StringUtil.isNotBlank(selectSql) && StringUtil.isNotBlank(alias)) {
|
||||
selectSql = "(" + selectSql + ") AS " + dialect.wrap(alias);
|
||||
}
|
||||
return selectSql;
|
||||
}
|
||||
|
||||
@Override
|
||||
String toConditionSql(List<QueryTable> queryTables, IDialect dialect) {
|
||||
return super.toConditionSql(queryTables, dialect);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] getParamValues() {
|
||||
return queryWrapper.getValueArray();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.mybatisflex.core.query;
|
||||
|
||||
import com.mybatisflex.core.dialect.IDialect;
|
||||
import com.mybatisflex.core.util.SqlUtil;
|
||||
import com.mybatisflex.core.util.StringUtil;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 数据库 聚合函数,例如 CONVERT(NVARCHAR(30), GETDATE(), 126) 等等
|
||||
*/
|
||||
public class StringFunctionQueryColumn extends QueryColumn {
|
||||
|
||||
protected String fnName;
|
||||
protected List<String> params;
|
||||
|
||||
public StringFunctionQueryColumn(String fnName, String ...params) {
|
||||
SqlUtil.keepColumnSafely(fnName);
|
||||
this.fnName = fnName;
|
||||
this.params = Arrays.asList(params);
|
||||
}
|
||||
|
||||
|
||||
public String getFnName() {
|
||||
return fnName;
|
||||
}
|
||||
|
||||
public void setFnName(String fnName) {
|
||||
this.fnName = fnName;
|
||||
}
|
||||
|
||||
|
||||
public List<String> getParams() {
|
||||
return params;
|
||||
}
|
||||
|
||||
public void setParams(List<String> params) {
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toSelectSql(List<QueryTable> queryTables, IDialect dialect) {
|
||||
String sql = StringUtil.join(", ",params);
|
||||
return StringUtil.isBlank(sql) ? "" : fnName + "(" + sql + ")" + WrapperUtil.buildAsAlias(alias);
|
||||
}
|
||||
|
||||
@Override
|
||||
String toConditionSql(List<QueryTable> queryTables, IDialect dialect) {
|
||||
String sql = StringUtil.join(", ",params);
|
||||
return StringUtil.isBlank(sql) ? "" : fnName + "(" + sql + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryColumn as(String alias) {
|
||||
SqlUtil.keepColumnSafely(alias);
|
||||
this.alias = alias;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "StringFunctionQueryColumn{" +
|
||||
"fnName='" + fnName + '\'' +
|
||||
", params=" + params +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@ -18,6 +18,7 @@ package com.mybatisflex.core.query;
|
||||
|
||||
import com.mybatisflex.core.util.ClassUtil;
|
||||
import com.mybatisflex.core.util.CollectionUtil;
|
||||
import com.mybatisflex.core.util.EnumWrapper;
|
||||
import com.mybatisflex.core.util.StringUtil;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
@ -40,7 +41,7 @@ class WrapperUtil {
|
||||
while (condition != null) {
|
||||
if (condition.checkEffective()) {
|
||||
if (condition instanceof Brackets) {
|
||||
List<QueryWrapper> childQueryWrapper = getChildSelect(((Brackets) condition).getChildCondition());
|
||||
List<QueryWrapper> childQueryWrapper = getChildSelect(((Brackets) condition).getChild());
|
||||
if (!childQueryWrapper.isEmpty()) {
|
||||
if (list == null) {
|
||||
list = new ArrayList<>();
|
||||
@ -82,14 +83,14 @@ class WrapperUtil {
|
||||
return NULL_PARA_ARRAY;
|
||||
}
|
||||
|
||||
List<Object> paras = new ArrayList<>();
|
||||
getValues(condition, paras);
|
||||
List<Object> params = new ArrayList<>();
|
||||
getValues(condition, params);
|
||||
|
||||
return paras.isEmpty() ? NULL_PARA_ARRAY : paras.toArray();
|
||||
return params.isEmpty() ? NULL_PARA_ARRAY : params.toArray();
|
||||
}
|
||||
|
||||
|
||||
private static void getValues(QueryCondition condition, List<Object> paras) {
|
||||
private static void getValues(QueryCondition condition, List<Object> params) {
|
||||
if (condition == null) {
|
||||
return;
|
||||
}
|
||||
@ -98,29 +99,35 @@ class WrapperUtil {
|
||||
if (value == null
|
||||
|| value instanceof QueryColumn
|
||||
|| value instanceof RawValue) {
|
||||
getValues(condition.next, paras);
|
||||
getValues(condition.next, params);
|
||||
return;
|
||||
}
|
||||
|
||||
if (value.getClass().isArray()) {
|
||||
Object[] values = (Object[]) value;
|
||||
for (Object object : values) {
|
||||
if (object != null && ClassUtil.isArray(object.getClass())) {
|
||||
for (int i = 0; i < Array.getLength(object); i++) {
|
||||
paras.add(Array.get(object, i));
|
||||
}
|
||||
} else {
|
||||
paras.add(object);
|
||||
}
|
||||
addParam(params, value);
|
||||
getValues(condition.next, params);
|
||||
}
|
||||
|
||||
private static void addParam(List<Object> paras, Object value) {
|
||||
if (value == null) {
|
||||
paras.add(null);
|
||||
} else if (ClassUtil.isArray(value.getClass())) {
|
||||
for (int i = 0; i < Array.getLength(value); i++) {
|
||||
addParam(paras, Array.get(value, i));
|
||||
}
|
||||
} else if (value instanceof QueryWrapper) {
|
||||
Object[] valueArray = ((QueryWrapper) value).getValueArray();
|
||||
paras.addAll(Arrays.asList(valueArray));
|
||||
} else if (value.getClass().isEnum()) {
|
||||
EnumWrapper enumWrapper = EnumWrapper.of(value.getClass());
|
||||
if (enumWrapper.hasEnumValueAnnotation()) {
|
||||
paras.add(enumWrapper.getEnumValue((Enum) value));
|
||||
} else {
|
||||
paras.add(value);
|
||||
}
|
||||
} else {
|
||||
paras.add(value);
|
||||
}
|
||||
|
||||
getValues(condition.next, paras);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.mybatisflex.core.row;
|
||||
|
||||
public interface BatchArgsSetter {
|
||||
|
||||
Object[] NONE_ARGS = new Object[0];
|
||||
|
||||
int getBatchSize();
|
||||
|
||||
Object[] getSqlArgs(int index);
|
||||
}
|
||||
@ -24,13 +24,16 @@ import com.mybatisflex.core.query.QueryTable;
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import com.mybatisflex.core.transaction.Propagation;
|
||||
import com.mybatisflex.core.transaction.TransactionalManager;
|
||||
import com.mybatisflex.core.util.CollectionUtil;
|
||||
import org.apache.ibatis.session.SqlSessionFactory;
|
||||
import org.apache.ibatis.util.MapUtil;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
@ -98,7 +101,11 @@ public class Db {
|
||||
* @param batchSize 每次提交的数据量
|
||||
*/
|
||||
public static int[] insertBatch(String tableName, Collection<Row> rows, int batchSize) {
|
||||
return invoker().insertBatch(tableName, rows, batchSize);
|
||||
List<Row> list = CollectionUtil.toList(rows);
|
||||
return executeBatch(rows.size(), batchSize, RowMapper.class, (mapper, index) -> {
|
||||
Row row = list.get(index);
|
||||
mapper.insert(tableName, row);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -196,6 +203,18 @@ public class Db {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param sql
|
||||
* @param batchArgsSetter
|
||||
* @return
|
||||
*/
|
||||
public static int[] updateBatch(String sql, BatchArgsSetter batchArgsSetter) {
|
||||
int batchSize = batchArgsSetter.getBatchSize();
|
||||
return executeBatch(batchSize, batchSize, RowMapper.class
|
||||
, (mapper, index) -> mapper.updateBySql(sql, batchArgsSetter.getSqlArgs(index)));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据 id 来更新数据
|
||||
*
|
||||
@ -252,6 +271,46 @@ public class Db {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据主键来批量更新数据
|
||||
*
|
||||
* @param entities 实体
|
||||
* @param batchSize 批次大小
|
||||
* @return int
|
||||
*/
|
||||
public static <T> int updateEntitiesBatch(Collection<T> entities, int batchSize) {
|
||||
List<T> list = CollectionUtil.toList(entities);
|
||||
return Arrays.stream(executeBatch(list.size(), batchSize, RowMapper.class, (mapper, index) -> {
|
||||
T entity = list.get(index);
|
||||
mapper.updateEntity(entity);
|
||||
})).sum();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据主键来批量更新数据
|
||||
*
|
||||
* @param entities 实体
|
||||
* @return int 影响行数
|
||||
*/
|
||||
public static <T> int updateEntitiesBatch(Collection<T> entities) {
|
||||
return updateEntitiesBatch(entities, RowMapper.DEFAULT_BATCH_SIZE);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 批量执行工具方法
|
||||
*
|
||||
* @param totalSize 执行总量
|
||||
* @param batchSize 每一批次的数据量
|
||||
* @param mapperClass 通过那个 Mapper 来执行
|
||||
* @param consumer 执行内容
|
||||
* @param <M> Mapper
|
||||
* @return 执行影响的行数
|
||||
*/
|
||||
public static <M> int[] executeBatch(int totalSize, int batchSize, Class<M> mapperClass, BiConsumer<M, Integer> consumer) {
|
||||
return invoker().executeBatch(totalSize, batchSize, mapperClass, consumer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 sql 来查询 1 条数据
|
||||
*
|
||||
@ -431,6 +490,29 @@ public class Db {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据 queryWrapper 查询内容,数据返回的应该只有 1 行 1 列
|
||||
*
|
||||
* @param tableName 表名
|
||||
* @param queryWrapper query 封装
|
||||
* @return 数据内容
|
||||
*/
|
||||
public static Object selectObject(String tableName, QueryWrapper queryWrapper) {
|
||||
return invoker().selectObjectByQuery(tableName, queryWrapper);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据 queryWrapper 查询内容,数据返回的应该只有 1 行 1 列
|
||||
*
|
||||
* @param queryWrapper query 封装
|
||||
* @return 数据内容
|
||||
*/
|
||||
public static Object selectObject(QueryWrapper queryWrapper) {
|
||||
return invoker().selectObjectByQuery(null, queryWrapper);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 查询某列内容,数据返回应该有 多行 1 列
|
||||
*
|
||||
@ -442,6 +524,29 @@ public class Db {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据 queryWrapper 查询内容,数据返回的应该只有 1 行 1 列
|
||||
*
|
||||
* @param tableName 表名
|
||||
* @param queryWrapper query 封装
|
||||
* @return 数据内容
|
||||
*/
|
||||
public static Object selectObjectList(String tableName, QueryWrapper queryWrapper) {
|
||||
return invoker().selectObjectListByQuery(tableName, queryWrapper);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据 queryWrapper 查询内容,数据返回的应该只有 1 行 1 列
|
||||
*
|
||||
* @param queryWrapper query 封装
|
||||
* @return 数据内容
|
||||
*/
|
||||
public static Object selectObjectList(QueryWrapper queryWrapper) {
|
||||
return invoker().selectObjectListByQuery(null, queryWrapper);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 查收 count 数据,一般用于 select count(*)...
|
||||
* 或者返回的内容是一行1列,且是数值类型的也可以用此方法
|
||||
@ -485,7 +590,7 @@ public class Db {
|
||||
public static long selectCountByQuery(QueryWrapper queryWrapper) {
|
||||
List<QueryTable> queryTables = CPI.getQueryTables(queryWrapper);
|
||||
if (queryTables == null || queryTables.isEmpty()) {
|
||||
throw FlexExceptions.wrap("table must not be null or empty in Db.selectCountByQuery");
|
||||
throw FlexExceptions.wrap("Query tables must not be null or empty in Db.selectCountByQuery");
|
||||
}
|
||||
return invoker().selectCountByQuery(null, queryWrapper);
|
||||
}
|
||||
|
||||
@ -19,20 +19,21 @@ import com.mybatisflex.core.FlexConsts;
|
||||
import com.mybatisflex.core.exception.FlexExceptions;
|
||||
import com.mybatisflex.core.paginate.Page;
|
||||
import com.mybatisflex.core.provider.RowSqlProvider;
|
||||
import com.mybatisflex.core.query.CPI;
|
||||
import com.mybatisflex.core.query.QueryColumn;
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import com.mybatisflex.core.query.*;
|
||||
import com.mybatisflex.core.util.CollectionUtil;
|
||||
import com.mybatisflex.core.util.StringUtil;
|
||||
import org.apache.ibatis.annotations.*;
|
||||
import org.apache.ibatis.exceptions.TooManyResultsException;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
import static com.mybatisflex.core.query.QueryMethods.count;
|
||||
|
||||
|
||||
public interface RowMapper {
|
||||
|
||||
int DEFAULT_BATCH_SIZE = 1000;
|
||||
|
||||
//////insert //////
|
||||
|
||||
/**
|
||||
@ -92,7 +93,7 @@ public interface RowMapper {
|
||||
* @param row id 和 值的数据,可以通过 {@link Row#ofKey(String, Object)} 来创建
|
||||
* @return 执行影响的行数
|
||||
*/
|
||||
default int deleteById(@Param(FlexConsts.TABLE_NAME) String tableName, @Param(FlexConsts.ROW) Row row) {
|
||||
default int deleteById(String tableName, Row row) {
|
||||
return deleteById(tableName, StringUtil.join(",", row.obtainsPrimaryKeyStrings()), row.obtainsPrimaryValues());
|
||||
}
|
||||
|
||||
@ -186,6 +187,17 @@ public interface RowMapper {
|
||||
@UpdateProvider(value = RowSqlProvider.class, method = "updateBatchById")
|
||||
int updateBatchById(@Param(FlexConsts.TABLE_NAME) String tableName, @Param(FlexConsts.ROWS) List<Row> rows);
|
||||
|
||||
|
||||
/**
|
||||
* 更新 entity,主要用于进行批量更新的场景
|
||||
*
|
||||
* @param entity 实体类
|
||||
* @see RowSqlProvider#updateEntity(Map)
|
||||
* @see Db#updateEntitiesBatch(Collection, int)
|
||||
*/
|
||||
@UpdateProvider(value = RowSqlProvider.class, method = "updateEntity")
|
||||
int updateEntity(@Param(FlexConsts.ENTITY) Object entity);
|
||||
|
||||
///////select /////
|
||||
|
||||
/**
|
||||
@ -278,7 +290,7 @@ public interface RowMapper {
|
||||
* @param tableName 表名
|
||||
* @return row 列表
|
||||
*/
|
||||
default List<Row> selectAll(@Param(FlexConsts.TABLE_NAME) String tableName) {
|
||||
default List<Row> selectAll(String tableName) {
|
||||
return selectListByQuery(tableName, QueryWrapper.create());
|
||||
}
|
||||
|
||||
@ -325,15 +337,58 @@ public interface RowMapper {
|
||||
|
||||
|
||||
/**
|
||||
* 根据 queryWrapper 来查询数量
|
||||
* 根据 queryWrapper 1 条数据
|
||||
* queryWrapper 执行的结果应该只有 1 列,例如 QueryWrapper.create().select(ACCOUNT.id).where...
|
||||
*
|
||||
* @param tableName 表名
|
||||
* @param queryWrapper queryWrapper
|
||||
* @return 数量
|
||||
* @see RowSqlProvider#selectCountByQuery(Map)
|
||||
* @return 数据
|
||||
*/
|
||||
@SelectProvider(value = RowSqlProvider.class, method = "selectCountByQuery")
|
||||
long selectCountByQuery(@Param(FlexConsts.TABLE_NAME) String tableName, @Param(FlexConsts.QUERY) QueryWrapper queryWrapper);
|
||||
default Object selectObjectByQuery(String tableName, QueryWrapper queryWrapper) {
|
||||
queryWrapper.limit(1);
|
||||
List<Object> objects = selectObjectListByQuery(tableName, queryWrapper);
|
||||
if (objects == null || objects.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return objects.get(0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据 queryWrapper 来查询数据列表
|
||||
* queryWrapper 执行的结果应该只有 1 列,例如 QueryWrapper.create().select(ACCOUNT.id).where...
|
||||
*
|
||||
* @param queryWrapper 查询包装器
|
||||
* @return 数据列表
|
||||
* @see RowSqlProvider#selectObjectByQuery(Map)
|
||||
*/
|
||||
@SelectProvider(type = RowSqlProvider.class, method = "selectObjectByQuery")
|
||||
List<Object> selectObjectListByQuery(@Param(FlexConsts.TABLE_NAME) String tableName, @Param(FlexConsts.QUERY) QueryWrapper queryWrapper);
|
||||
|
||||
|
||||
/**
|
||||
* 查询数据量
|
||||
*
|
||||
* @param tableName 表名
|
||||
* @param queryWrapper 查询包装器
|
||||
* @return 数据量
|
||||
*/
|
||||
default long selectCountByQuery(String tableName, QueryWrapper queryWrapper) {
|
||||
List<QueryColumn> selectColumns = CPI.getSelectColumns(queryWrapper);
|
||||
if (CollectionUtil.isEmpty(selectColumns)) {
|
||||
queryWrapper.select(count());
|
||||
}
|
||||
|
||||
List<Object> objects = selectObjectListByQuery(tableName, queryWrapper);
|
||||
Object object = objects == null || objects.isEmpty() ? null : objects.get(0);
|
||||
if (object == null) {
|
||||
return 0;
|
||||
} else if (object instanceof Number) {
|
||||
return ((Number) object).longValue();
|
||||
} else {
|
||||
throw FlexExceptions.wrap("selectCountByQuery error, Can not get number value for queryWrapper: %s", queryWrapper);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
@ -346,15 +401,59 @@ public interface RowMapper {
|
||||
*/
|
||||
default Page<Row> paginate(String tableName, Page<Row> page, QueryWrapper queryWrapper) {
|
||||
|
||||
List<QueryColumn> groupByColumns = CPI.getGroupByColumns(queryWrapper);
|
||||
CPI.setFromIfNecessary(queryWrapper, tableName);
|
||||
|
||||
List<QueryColumn> selectColumns = CPI.getSelectColumns(queryWrapper);
|
||||
|
||||
List<QueryOrderBy> orderBys = CPI.getOrderBys(queryWrapper);
|
||||
|
||||
List<Join> joins = CPI.getJoins(queryWrapper);
|
||||
boolean removedJoins = true;
|
||||
|
||||
// 只有 totalRow 小于 0 的时候才会去查询总量
|
||||
// 这样方便用户做总数缓存,而非每次都要去查询总量
|
||||
// 一般的分页场景中,只有第一页的时候有必要去查询总量,第二页以后是不需要的
|
||||
if (page.getTotalRow() < 0) {
|
||||
|
||||
//清除group by 去查询数据
|
||||
CPI.setGroupByColumns(queryWrapper, null);
|
||||
//移除 seelct
|
||||
CPI.setSelectColumns(queryWrapper, Collections.singletonList(count().as("total")));
|
||||
|
||||
//移除 OrderBy
|
||||
if (CollectionUtil.isNotEmpty(orderBys)) {
|
||||
CPI.setOrderBys(queryWrapper, null);
|
||||
}
|
||||
|
||||
//移除 left join
|
||||
if (joins != null && !joins.isEmpty()) {
|
||||
for (Join join : joins) {
|
||||
if (!Join.TYPE_LEFT.equals(CPI.getJoinType(join))) {
|
||||
removedJoins = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
removedJoins = false;
|
||||
}
|
||||
|
||||
if (removedJoins) {
|
||||
List<String> joinTables = new ArrayList<>();
|
||||
joins.forEach(join -> {
|
||||
QueryTable joinQueryTable = CPI.getJoinQueryTable(join);
|
||||
if (joinQueryTable != null && StringUtil.isNotBlank(joinQueryTable.getName())) {
|
||||
joinTables.add(joinQueryTable.getName());
|
||||
}
|
||||
});
|
||||
|
||||
QueryCondition where = CPI.getWhereQueryCondition(queryWrapper);
|
||||
if (CPI.containsTable(where, CollectionUtil.toArrayString(joinTables))) {
|
||||
removedJoins = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (removedJoins) {
|
||||
CPI.setJoins(queryWrapper, null);
|
||||
}
|
||||
|
||||
long count = selectCountByQuery(tableName, queryWrapper);
|
||||
page.setTotalRow(count);
|
||||
}
|
||||
@ -363,14 +462,26 @@ public interface RowMapper {
|
||||
return page;
|
||||
}
|
||||
|
||||
//恢复数量查询清除的 groupBy
|
||||
CPI.setGroupByColumns(queryWrapper, groupByColumns);
|
||||
//重置 selectColumns
|
||||
CPI.setSelectColumns(queryWrapper, selectColumns);
|
||||
|
||||
//重置 orderBys
|
||||
if (CollectionUtil.isNotEmpty(orderBys)) {
|
||||
CPI.setOrderBys(queryWrapper, orderBys);
|
||||
}
|
||||
|
||||
//重置 join
|
||||
if (removedJoins) {
|
||||
CPI.setJoins(queryWrapper, joins);
|
||||
}
|
||||
|
||||
int offset = page.getPageSize() * (page.getPageNumber() - 1);
|
||||
queryWrapper.limit(offset, page.getPageSize());
|
||||
|
||||
List<Row> records = selectListByQuery(tableName, queryWrapper);
|
||||
page.setRecords(records);
|
||||
return page;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -24,6 +24,7 @@ import org.apache.ibatis.session.SqlSessionFactory;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class RowMapperInvoker {
|
||||
@ -50,41 +51,6 @@ public class RowMapperInvoker {
|
||||
return execute(mapper -> mapper.insertBySql(sql, args));
|
||||
}
|
||||
|
||||
|
||||
public int[] insertBatch(String tableName, Collection<Row> rows, int batchSize) {
|
||||
int[] results = new int[rows.size()];
|
||||
try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH, true)) {
|
||||
RowMapper mapper = sqlSession.getMapper(RowMapper.class);
|
||||
int counter = 0;
|
||||
int resultsPos = 0;
|
||||
for (Row row : rows) {
|
||||
if (++counter > batchSize) {
|
||||
counter = 0;
|
||||
List<BatchResult> batchResults = sqlSession.flushStatements();
|
||||
for (BatchResult batchResult : batchResults) {
|
||||
int[] updateCounts = batchResult.getUpdateCounts();
|
||||
for (int updateCount : updateCounts) {
|
||||
results[resultsPos++] = updateCount;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
mapper.insert(tableName, row);
|
||||
}
|
||||
}
|
||||
|
||||
if (counter != 0) {
|
||||
List<BatchResult> batchResults = sqlSession.flushStatements();
|
||||
for (BatchResult batchResult : batchResults) {
|
||||
int[] updateCounts = batchResult.getUpdateCounts();
|
||||
for (int updateCount : updateCounts) {
|
||||
results[resultsPos++] = updateCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
public int insertBatchWithFirstRowColumns(String tableName, List<Row> rows) {
|
||||
return execute(mapper -> mapper.insertBatchWithFirstRowColumns(tableName, rows));
|
||||
}
|
||||
@ -114,6 +80,41 @@ public class RowMapperInvoker {
|
||||
return execute(mapper -> mapper.updateBySql(sql, args));
|
||||
}
|
||||
|
||||
|
||||
public <M> int[] executeBatch(int totalSize, int batchSize, Class<M> mapperClass, BiConsumer<M, Integer> consumer) {
|
||||
int[] results = new int[totalSize];
|
||||
try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH, true)) {
|
||||
M mapper = sqlSession.getMapper(mapperClass);
|
||||
int counter = 0;
|
||||
int resultsPos = 0;
|
||||
for (int i = 0; i < totalSize; i++) {
|
||||
consumer.accept(mapper, i);
|
||||
if (++counter == batchSize) {
|
||||
counter = 0;
|
||||
List<BatchResult> batchResults = sqlSession.flushStatements();
|
||||
for (BatchResult batchResult : batchResults) {
|
||||
int[] updateCounts = batchResult.getUpdateCounts();
|
||||
for (int updateCount : updateCounts) {
|
||||
results[resultsPos++] = updateCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (counter != 0) {
|
||||
List<BatchResult> batchResults = sqlSession.flushStatements();
|
||||
for (BatchResult batchResult : batchResults) {
|
||||
int[] updateCounts = batchResult.getUpdateCounts();
|
||||
for (int updateCount : updateCounts) {
|
||||
results[resultsPos++] = updateCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
public int updateById(String tableName, Row row) {
|
||||
return execute(mapper -> mapper.updateById(tableName, row));
|
||||
}
|
||||
@ -154,6 +155,14 @@ public class RowMapperInvoker {
|
||||
return execute(mapper -> mapper.selectAll(tableName));
|
||||
}
|
||||
|
||||
public Object selectObjectByQuery(String tableName, QueryWrapper queryWrapper) {
|
||||
return execute(mapper -> mapper.selectObjectByQuery(tableName, queryWrapper));
|
||||
}
|
||||
|
||||
public List<Object> selectObjectListByQuery(String tableName, QueryWrapper queryWrapper) {
|
||||
return execute(mapper -> mapper.selectObjectListByQuery(tableName, queryWrapper));
|
||||
}
|
||||
|
||||
public Object selectObject(String sql, Object... args) {
|
||||
return execute(mapper -> mapper.selectObject(sql, args));
|
||||
}
|
||||
|
||||
@ -13,38 +13,40 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.mybatisflex.spring.service;
|
||||
package com.mybatisflex.core.service;
|
||||
|
||||
import com.mybatisflex.core.BaseMapper;
|
||||
import com.mybatisflex.core.paginate.Page;
|
||||
import com.mybatisflex.core.query.QueryCondition;
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import com.mybatisflex.core.row.Db;
|
||||
import com.mybatisflex.core.row.RowMapper;
|
||||
import com.mybatisflex.core.util.ClassUtil;
|
||||
import com.mybatisflex.core.util.CollectionUtil;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import com.mybatisflex.core.util.SqlUtil;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
|
||||
import static com.mybatisflex.core.util.SqlUtil.retBool;
|
||||
|
||||
/**
|
||||
* 由 Mybatis-Flex 提供的顶级增强 Service 接口。
|
||||
*
|
||||
* @param <T> 实体类(Entity)类型
|
||||
* @author 王帅
|
||||
* @since 2023-05-01
|
||||
* @param <T> 实体类(Entity)类型
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public interface IService<T> {
|
||||
|
||||
// ===== 保存(增)操作 =====
|
||||
|
||||
/**
|
||||
* 获取对应实体类(Entity)的基础映射类(BaseMapper)。
|
||||
*
|
||||
* @return 基础映射类(BaseMapper)
|
||||
*/
|
||||
BaseMapper<T> getBaseMapper();
|
||||
|
||||
// ===== 保存(增)操作 =====
|
||||
BaseMapper<T> getMapper();
|
||||
|
||||
/**
|
||||
* 保存实体类对象数据。
|
||||
@ -55,7 +57,7 @@ public interface IService<T> {
|
||||
* {@code null} 字段的数据,使数据库配置的默认值生效。
|
||||
*/
|
||||
default boolean save(T entity) {
|
||||
return retBool(getBaseMapper().insertSelective(entity));
|
||||
return SqlUtil.toBool(getMapper().insertSelective(entity));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -66,7 +68,7 @@ public interface IService<T> {
|
||||
* @apiNote 如果实体类对象主键有值,则更新数据,若没有值,则保存数据。
|
||||
*/
|
||||
default boolean saveOrUpdate(T entity) {
|
||||
return retBool(getBaseMapper().insertOrUpdate(entity));
|
||||
return SqlUtil.toBool(getMapper().insertOrUpdate(entity));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -75,25 +77,23 @@ public interface IService<T> {
|
||||
* @param entities 实体类对象
|
||||
* @return {@code true} 保存成功,{@code false} 保存失败。
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
default boolean saveBatch(Collection<T> entities) {
|
||||
return retBool(getBaseMapper().insertBatch(new ArrayList<>(entities)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量保存实体类对象数据。
|
||||
*
|
||||
* @param entities 实体类对象
|
||||
* @param size 每次保存切分的数量
|
||||
* @return {@code true} 保存成功,{@code false} 保存失败。
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
default boolean saveBatch(Collection<T> entities, int size) {
|
||||
return retBool(getBaseMapper().insertBatch(new ArrayList<>(entities), size));
|
||||
return SqlUtil.toBool(getMapper().insertBatch(new ArrayList<>(entities)));
|
||||
}
|
||||
|
||||
// ===== 删除(删)操作 =====
|
||||
|
||||
/**
|
||||
* 批量保存实体类对象数据。
|
||||
*
|
||||
* @param entities 实体类对象
|
||||
* @param size 每次保存切分的数量
|
||||
* @return {@code true} 保存成功,{@code false} 保存失败。
|
||||
*/
|
||||
default boolean saveBatch(Collection<T> entities, int size) {
|
||||
return SqlUtil.toBool(getMapper().insertBatch(CollectionUtil.toList(entities), size));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据查询条件删除数据。
|
||||
*
|
||||
@ -101,17 +101,17 @@ public interface IService<T> {
|
||||
* @return {@code true} 删除成功,{@code false} 删除失败。
|
||||
*/
|
||||
default boolean remove(QueryWrapper query) {
|
||||
return retBool(getBaseMapper().deleteByQuery(query));
|
||||
return SqlUtil.toBool(getMapper().deleteByQuery(query));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据查询条件删除数据。
|
||||
*
|
||||
* @param query 查询条件
|
||||
* @param condition 查询条件
|
||||
* @return {@code true} 删除成功,{@code false} 删除失败。
|
||||
*/
|
||||
default boolean remove(QueryCondition query) {
|
||||
return retBool(getBaseMapper().deleteByCondition(query));
|
||||
default boolean remove(QueryCondition condition) {
|
||||
return SqlUtil.toBool(getMapper().deleteByCondition(condition));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -121,7 +121,7 @@ public interface IService<T> {
|
||||
* @return {@code true} 删除成功,{@code false} 删除失败。
|
||||
*/
|
||||
default boolean removeById(Serializable id) {
|
||||
return retBool(getBaseMapper().deleteById(id));
|
||||
return SqlUtil.toBool(getMapper().deleteById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -130,14 +130,15 @@ public interface IService<T> {
|
||||
* @param ids 数据主键
|
||||
* @return {@code true} 删除成功,{@code false} 删除失败。
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
default boolean removeByIds(Collection<? extends Serializable> ids) {
|
||||
if (CollectionUtil.isEmpty(ids)) {
|
||||
return false;
|
||||
}
|
||||
return retBool(getBaseMapper().deleteBatchByIds(ids));
|
||||
return SqlUtil.toBool(getMapper().deleteBatchByIds(ids));
|
||||
}
|
||||
|
||||
// ===== 更新(改)操作 =====
|
||||
|
||||
/**
|
||||
* 根据 {@link Map} 构建查询条件删除数据。
|
||||
*
|
||||
@ -145,31 +146,57 @@ public interface IService<T> {
|
||||
* @return {@code true} 删除成功,{@code false} 删除失败。
|
||||
*/
|
||||
default boolean removeByMap(Map<String, Object> query) {
|
||||
return retBool(getBaseMapper().deleteByMap(query));
|
||||
return SqlUtil.toBool(getMapper().deleteByMap(query));
|
||||
}
|
||||
|
||||
// ===== 更新(改)操作 =====
|
||||
|
||||
/**
|
||||
* 根据查询条件更新数据。
|
||||
*
|
||||
* @param entity 实体类对象
|
||||
* @param query 查询条件
|
||||
* @param query 查询条件
|
||||
* @return {@code true} 更新成功,{@code false} 更新失败。
|
||||
*/
|
||||
default boolean update(T entity, QueryWrapper query) {
|
||||
return retBool(getBaseMapper().updateByQuery(entity, query));
|
||||
return SqlUtil.toBool(getMapper().updateByQuery(entity, query));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据查询条件更新数据。
|
||||
*
|
||||
* @param entity 实体类对象
|
||||
* @param query 查询条件
|
||||
* @param entity 实体类对象
|
||||
* @param condition 查询条件
|
||||
* @return {@code true} 更新成功,{@code false} 更新失败。
|
||||
*/
|
||||
default boolean update(T entity, QueryCondition query) {
|
||||
return retBool(getBaseMapper().updateByCondition(entity, query));
|
||||
default boolean update(T entity, QueryCondition condition) {
|
||||
return SqlUtil.toBool(getMapper().updateByCondition(entity, condition));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 id 批量更新数据
|
||||
*
|
||||
* @param entities 实体类对象集合
|
||||
* @return boolean {@code true} 更新成功,{@code false} 更新失败。
|
||||
*/
|
||||
default boolean updateBatch(Collection<T> entities) {
|
||||
return updateBatch(entities, RowMapper.DEFAULT_BATCH_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 id 批量更新数据
|
||||
*
|
||||
* @param entities 实体类对象集合
|
||||
* @param batchSize 每批次更新数量
|
||||
* @return boolean {@code true} 更新成功,{@code false} 更新失败。
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
default boolean updateBatch(Collection<T> entities, int batchSize) {
|
||||
return Db.tx(() -> {
|
||||
final List<T> entityList = CollectionUtil.toList(entities);
|
||||
|
||||
// BaseMapper 是经过 Mybatis 动态代理处理过的对象,需要获取原始 BaseMapper 类型
|
||||
final Class<BaseMapper<T>> usefulClass = (Class<BaseMapper<T>>) ClassUtil.getUsefulClass(getMapper().getClass());
|
||||
return SqlUtil.toBool(Arrays.stream(Db.executeBatch(entityList.size(), batchSize, usefulClass, (mapper, index) -> mapper.update(entityList.get(index)))).sum());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -179,18 +206,18 @@ public interface IService<T> {
|
||||
* @return {@code true} 更新成功,{@code false} 更新失败。
|
||||
*/
|
||||
default boolean updateById(T entity) {
|
||||
return retBool(getBaseMapper().update(entity));
|
||||
return SqlUtil.toBool(getMapper().update(entity));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 {@link Map} 构建查询条件更新数据。
|
||||
*
|
||||
* @param entity 实体类对象
|
||||
* @param query 查询条件
|
||||
* @param query 查询条件
|
||||
* @return {@code true} 更新成功,{@code false} 更新失败。
|
||||
*/
|
||||
default boolean updateByMap(T entity, Map<String, Object> query) {
|
||||
return retBool(getBaseMapper().updateByMap(entity, query));
|
||||
return SqlUtil.toBool(getMapper().updateByMap(entity, query));
|
||||
}
|
||||
|
||||
// ===== 查询(查)操作 =====
|
||||
@ -202,7 +229,7 @@ public interface IService<T> {
|
||||
* @return 查询结果数据
|
||||
*/
|
||||
default T getById(Serializable id) {
|
||||
return getBaseMapper().selectOneById(id);
|
||||
return getMapper().selectOneById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -223,7 +250,19 @@ public interface IService<T> {
|
||||
* @return 查询结果数据
|
||||
*/
|
||||
default T getOne(QueryWrapper query) {
|
||||
return getBaseMapper().selectOneByQuery(query);
|
||||
return getMapper().selectOneByQuery(query);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据查询条件查询一条数据,并通过 asType 进行接收
|
||||
*
|
||||
* @param query 查询条件
|
||||
* @param asType 接收的数据类型
|
||||
* @return 查询结果数据
|
||||
*/
|
||||
default <R> R getOneAs(QueryWrapper query, Class<R> asType) {
|
||||
return getMapper().selectOneByQueryAs(query, asType);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -237,25 +276,38 @@ public interface IService<T> {
|
||||
return Optional.ofNullable(getOne(query));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据查询条件查询一条数据。
|
||||
*
|
||||
* @param query 查询条件
|
||||
* @param query 查询条件
|
||||
* @param asType 接收的数据类型
|
||||
* @return 查询结果数据
|
||||
* @apiNote 该方法会将查询结果封装为 {@link Optional} 类进行返回,方便链式操作。
|
||||
*/
|
||||
default T getOne(QueryCondition query) {
|
||||
return getBaseMapper().selectOneByCondition(query);
|
||||
default <R> Optional<R> getOneOptAs(QueryWrapper query, Class<R> asType) {
|
||||
return Optional.ofNullable(getOneAs(query, asType));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据查询条件查询一条数据。
|
||||
*
|
||||
* @param query 查询条件
|
||||
* @param condition 查询条件
|
||||
* @return 查询结果数据
|
||||
*/
|
||||
default T getOne(QueryCondition condition) {
|
||||
return getMapper().selectOneByCondition(condition);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据查询条件查询一条数据。
|
||||
*
|
||||
* @param condition 查询条件
|
||||
* @return 查询结果数据
|
||||
* @apiNote 该方法会将查询结果封装为 {@link Optional} 类进行返回,方便链式操作。
|
||||
*/
|
||||
default Optional<T> getOneOpt(QueryCondition query) {
|
||||
return Optional.ofNullable(getOne(query));
|
||||
default Optional<T> getOneOpt(QueryCondition condition) {
|
||||
return Optional.ofNullable(getOne(condition));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -264,9 +316,30 @@ public interface IService<T> {
|
||||
* @return 所有数据
|
||||
*/
|
||||
default List<T> list() {
|
||||
return getBaseMapper().selectAll();
|
||||
return getMapper().selectAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据查询条件查询数据集合。
|
||||
*
|
||||
* @param condition 查询条件
|
||||
* @return 数据集合
|
||||
*/
|
||||
default List<T> list(QueryCondition condition) {
|
||||
return getMapper().selectListByCondition(condition);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据查询条件查询数据集合。
|
||||
*
|
||||
* @param condition 查询条件
|
||||
* @return 数据集合
|
||||
*/
|
||||
default List<T> list(QueryCondition condition, int count) {
|
||||
return getMapper().selectListByCondition(condition, count);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据查询条件查询数据集合。
|
||||
*
|
||||
@ -274,19 +347,21 @@ public interface IService<T> {
|
||||
* @return 数据集合
|
||||
*/
|
||||
default List<T> list(QueryWrapper query) {
|
||||
return getBaseMapper().selectListByQuery(query);
|
||||
return getMapper().selectListByQuery(query);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据查询条件查询数据集合。
|
||||
* 根据查询条件查询数据集合,并通过 asType 进行接收
|
||||
*
|
||||
* @param query 查询条件
|
||||
* @param query 查询条件
|
||||
* @param asType 接收的数据类型
|
||||
* @return 数据集合
|
||||
*/
|
||||
default List<T> list(QueryCondition query) {
|
||||
return getBaseMapper().selectListByCondition(query);
|
||||
default <R> List<R> listAs(QueryWrapper query, Class<R> asType) {
|
||||
return getMapper().selectListByQueryAs(query, asType);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据数据主键查询数据集合。
|
||||
*
|
||||
@ -294,7 +369,7 @@ public interface IService<T> {
|
||||
* @return 数据集合
|
||||
*/
|
||||
default List<T> listByIds(Collection<? extends Serializable> ids) {
|
||||
return getBaseMapper().selectListByIds(ids);
|
||||
return getMapper().selectListByIds(ids);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -304,7 +379,7 @@ public interface IService<T> {
|
||||
* @return 数据集合
|
||||
*/
|
||||
default List<T> listByMap(Map<String, Object> query) {
|
||||
return getBaseMapper().selectListByMap(query);
|
||||
return getMapper().selectListByMap(query);
|
||||
}
|
||||
|
||||
// ===== 数量查询操作 =====
|
||||
@ -316,17 +391,17 @@ public interface IService<T> {
|
||||
* @return {@code true} 数据存在,{@code false} 数据不存在。
|
||||
*/
|
||||
default boolean exists(QueryWrapper query) {
|
||||
return retBool(count(query));
|
||||
return SqlUtil.toBool(count(query));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据查询条件判断数据是否存在。
|
||||
*
|
||||
* @param query 查询条件
|
||||
* @param condition 查询条件
|
||||
* @return {@code true} 数据存在,{@code false} 数据不存在。
|
||||
*/
|
||||
default boolean exists(QueryCondition query) {
|
||||
return retBool(count(query));
|
||||
default boolean exists(QueryCondition condition) {
|
||||
return SqlUtil.toBool(count(condition));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -335,7 +410,7 @@ public interface IService<T> {
|
||||
* @return 所有数据数量
|
||||
*/
|
||||
default long count() {
|
||||
return getBaseMapper().selectCountByQuery(QueryWrapper.create());
|
||||
return getMapper().selectCountByQuery(QueryWrapper.create());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -345,17 +420,17 @@ public interface IService<T> {
|
||||
* @return 数据数量
|
||||
*/
|
||||
default long count(QueryWrapper query) {
|
||||
return getBaseMapper().selectCountByQuery(query);
|
||||
return getMapper().selectCountByQuery(query);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据查询条件查询数据数量。
|
||||
*
|
||||
* @param query 查询条件
|
||||
* @param condition 查询条件
|
||||
* @return 数据数量
|
||||
*/
|
||||
default long count(QueryCondition query) {
|
||||
return getBaseMapper().selectCountByCondition(query);
|
||||
default long count(QueryCondition condition) {
|
||||
return getMapper().selectCountByCondition(condition);
|
||||
}
|
||||
|
||||
// ===== 分页查询操作 =====
|
||||
@ -367,29 +442,29 @@ public interface IService<T> {
|
||||
* @return 分页对象
|
||||
*/
|
||||
default Page<T> page(Page<T> page) {
|
||||
return getBaseMapper().paginate(page, QueryWrapper.create());
|
||||
return getMapper().paginate(page, QueryWrapper.create());
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据查询条件分页查询数据。
|
||||
*
|
||||
* @param page 分页对象
|
||||
* @param page 分页对象
|
||||
* @param query 查询条件
|
||||
* @return 分页对象
|
||||
*/
|
||||
default Page<T> page(Page<T> page, QueryWrapper query) {
|
||||
return getBaseMapper().paginate(page, query);
|
||||
return getMapper().paginate(page, query);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据查询条件分页查询数据。
|
||||
*
|
||||
* @param page 分页对象
|
||||
* @param query 查询条件
|
||||
* @param page 分页对象
|
||||
* @param condition 查询条件
|
||||
* @return 分页对象
|
||||
*/
|
||||
default Page<T> page(Page<T> page, QueryCondition query) {
|
||||
return getBaseMapper().paginate(page, QueryWrapper.create().where(query));
|
||||
default Page<T> page(Page<T> page, QueryCondition condition) {
|
||||
return getMapper().paginate(page, QueryWrapper.create().where(condition));
|
||||
}
|
||||
|
||||
}
|
||||
@ -456,8 +456,7 @@ public class TableInfo {
|
||||
continue;
|
||||
}
|
||||
|
||||
Object value = getPropertyValue(metaObject, property);
|
||||
|
||||
Object value = buildColumnSqlArg(metaObject, column);
|
||||
// ModifyAttrsRecord 忽略 ignoreNulls 的设置,
|
||||
// 当使用 ModifyAttrsRecord 时,可以理解为要对字段进行 null 值进行更新,否则没必要使用 ModifyAttrsRecord
|
||||
// if (ignoreNulls && value == null) {
|
||||
@ -530,21 +529,24 @@ public class TableInfo {
|
||||
CPI.putContext(queryWrapper, APPEND_CONDITIONS_FLAG, Boolean.TRUE);
|
||||
}
|
||||
|
||||
//select xxx.id,(select..) from xxx
|
||||
List<QueryColumn> selectColumns = CPI.getSelectColumns(queryWrapper);
|
||||
if (selectColumns != null && !selectColumns.isEmpty()) {
|
||||
for (QueryColumn queryColumn : selectColumns) {
|
||||
if (queryColumn instanceof SelectQueryColumn) {
|
||||
QueryWrapper selectColumnQueryWrapper = CPI.getQueryWrapper((SelectQueryColumn) queryColumn);
|
||||
doAppendConditions(entity, selectColumnQueryWrapper);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//select * from (select ... from ) 中的子查询处理
|
||||
List<QueryTable> queryTables = CPI.getQueryTables(queryWrapper);
|
||||
if (queryTables != null && !queryTables.isEmpty()) {
|
||||
for (QueryTable queryTable : queryTables) {
|
||||
if (queryTable instanceof SelectQueryTable) {
|
||||
QueryWrapper selectQueryWrapper = ((SelectQueryTable) queryTable).getQueryWrapper();
|
||||
List<QueryTable> selectQueryTables = CPI.getQueryTables(selectQueryWrapper);
|
||||
if (selectQueryTables != null && !selectQueryTables.isEmpty()) {
|
||||
for (QueryTable selectQueryTable : selectQueryTables) {
|
||||
TableInfo tableInfo = TableInfoFactory.ofTableName(selectQueryTable.getName());
|
||||
if (tableInfo != null) {
|
||||
tableInfo.appendConditions(entity, selectQueryWrapper);
|
||||
}
|
||||
}
|
||||
}
|
||||
doAppendConditions(entity, selectQueryWrapper);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -578,13 +580,7 @@ public class TableInfo {
|
||||
List<QueryWrapper> childSelects = CPI.getChildSelect(queryWrapper);
|
||||
if (CollectionUtil.isNotEmpty(childSelects)) {
|
||||
for (QueryWrapper childQueryWrapper : childSelects) {
|
||||
List<QueryTable> childQueryTables = CPI.getQueryTables(childQueryWrapper);
|
||||
for (QueryTable queryTable : childQueryTables) {
|
||||
TableInfo tableInfo = TableInfoFactory.ofTableName(queryTable.getName());
|
||||
if (tableInfo != null) {
|
||||
tableInfo.appendConditions(entity, childQueryWrapper);
|
||||
}
|
||||
}
|
||||
doAppendConditions(entity, childQueryWrapper);
|
||||
}
|
||||
}
|
||||
|
||||
@ -593,16 +589,22 @@ public class TableInfo {
|
||||
if (CollectionUtil.isNotEmpty(unions)) {
|
||||
for (UnionWrapper union : unions) {
|
||||
QueryWrapper unionQueryWrapper = union.getQueryWrapper();
|
||||
List<QueryTable> unionQueryTables = CPI.getQueryTables(unionQueryWrapper);
|
||||
for (QueryTable queryTable : unionQueryTables) {
|
||||
TableInfo tableInfo = TableInfoFactory.ofTableName(queryTable.getName());
|
||||
if (tableInfo != null) {
|
||||
tableInfo.appendConditions(entity, unionQueryWrapper);
|
||||
}
|
||||
doAppendConditions(entity, unionQueryWrapper);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void doAppendConditions(Object entity, QueryWrapper queryWrapper) {
|
||||
List<QueryTable> queryTables = CPI.getQueryTables(queryWrapper);
|
||||
if (queryTables != null && !queryTables.isEmpty()) {
|
||||
for (QueryTable queryTable : queryTables) {
|
||||
TableInfo tableInfo = TableInfoFactory.ofTableName(queryTable.getName());
|
||||
if (tableInfo != null) {
|
||||
tableInfo.appendConditions(entity, queryWrapper);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -640,6 +642,16 @@ public class TableInfo {
|
||||
.typeHandler(columnInfo.buildTypeHandler())
|
||||
.build();
|
||||
resultMappings.add(mapping);
|
||||
|
||||
//add property mapper for sql as ...
|
||||
if (!Objects.equals(columnInfo.getColumn(), columnInfo.getProperty())) {
|
||||
ResultMapping propertyMapping = new ResultMapping.Builder(configuration, columnInfo.getProperty(),
|
||||
columnInfo.getProperty(), columnInfo.getPropertyType())
|
||||
.jdbcType(columnInfo.getJdbcType())
|
||||
.typeHandler(columnInfo.buildTypeHandler())
|
||||
.build();
|
||||
resultMappings.add(propertyMapping);
|
||||
}
|
||||
}
|
||||
|
||||
for (IdInfo idInfo : primaryKeyList) {
|
||||
@ -660,9 +672,11 @@ public class TableInfo {
|
||||
ColumnInfo columnInfo = columnInfoMapping.get(column);
|
||||
Object value = getPropertyValue(metaObject, columnInfo.property);
|
||||
|
||||
TypeHandler typeHandler = columnInfo.buildTypeHandler();
|
||||
if (value != null && typeHandler != null) {
|
||||
return new TypeHandlerObject(typeHandler, value, columnInfo.getJdbcType());
|
||||
if (value != null) {
|
||||
TypeHandler typeHandler = columnInfo.buildTypeHandler();
|
||||
if (typeHandler != null) {
|
||||
return new TypeHandlerObject(typeHandler, value, columnInfo.getJdbcType());
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
@ -783,7 +797,9 @@ public class TableInfo {
|
||||
//默认使用第一个作为插入的租户ID
|
||||
Object tenantId = tenantIds[0];
|
||||
if (tenantId != null) {
|
||||
metaObject.setValue(columnInfoMapping.get(tenantIdColumn).property, tenantId);
|
||||
String property = columnInfoMapping.get(tenantIdColumn).property;
|
||||
Class<?> setterType = metaObject.getSetterType(property);
|
||||
metaObject.setValue(property, ConvertUtil.convert(tenantId, setterType));
|
||||
}
|
||||
}
|
||||
|
||||
@ -802,7 +818,6 @@ public class TableInfo {
|
||||
if (columnValue == null) {
|
||||
String property = columnInfoMapping.get(logicDeleteColumn).property;
|
||||
Class<?> setterType = metaObject.getSetterType(property);
|
||||
|
||||
Object normalValueOfLogicDelete = FlexGlobalConfig.getDefaultConfig().getNormalValueOfLogicDelete();
|
||||
metaObject.setValue(property, ConvertUtil.convert(normalValueOfLogicDelete, setterType));
|
||||
}
|
||||
|
||||
@ -16,12 +16,14 @@
|
||||
package com.mybatisflex.core.table;
|
||||
|
||||
import com.mybatisflex.annotation.*;
|
||||
import com.mybatisflex.core.BaseMapper;
|
||||
import com.mybatisflex.core.FlexConsts;
|
||||
import com.mybatisflex.core.FlexGlobalConfig;
|
||||
import com.mybatisflex.core.exception.FlexExceptions;
|
||||
import com.mybatisflex.core.util.ClassUtil;
|
||||
import com.mybatisflex.core.util.CollectionUtil;
|
||||
import com.mybatisflex.core.util.StringUtil;
|
||||
import org.apache.ibatis.io.ResolverUtil;
|
||||
import org.apache.ibatis.reflection.Reflector;
|
||||
import org.apache.ibatis.session.Configuration;
|
||||
import org.apache.ibatis.type.JdbcType;
|
||||
@ -57,7 +59,7 @@ public class TableInfoFactory {
|
||||
Date.class, java.sql.Date.class, Time.class, Timestamp.class,
|
||||
Instant.class, LocalDate.class, LocalDateTime.class, LocalTime.class, OffsetDateTime.class, OffsetTime.class, ZonedDateTime.class,
|
||||
Year.class, Month.class, YearMonth.class, JapaneseDate.class,
|
||||
byte[].class, Byte[].class,
|
||||
byte[].class, Byte[].class, Byte.class,
|
||||
BigInteger.class, BigDecimal.class,
|
||||
char.class, String.class, Character.class
|
||||
);
|
||||
@ -66,12 +68,26 @@ public class TableInfoFactory {
|
||||
private static final Map<Class<?>, TableInfo> mapperTableInfoMap = new ConcurrentHashMap<>();
|
||||
private static final Map<Class<?>, TableInfo> entityTableMap = new ConcurrentHashMap<>();
|
||||
private static final Map<String, TableInfo> tableInfoMap = new ConcurrentHashMap<>();
|
||||
private static final Set<String> initedPackageNames = new HashSet<>();
|
||||
|
||||
|
||||
public synchronized static void init(String mapperPackageName){
|
||||
if (!initedPackageNames.contains(mapperPackageName)) {
|
||||
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
|
||||
resolverUtil.find(new ResolverUtil.IsA(BaseMapper.class), mapperPackageName);
|
||||
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
|
||||
for (Class<? extends Class<?>> mapperClass : mapperSet) {
|
||||
ofMapperClass(mapperClass);
|
||||
}
|
||||
initedPackageNames.add(mapperPackageName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static TableInfo ofMapperClass(Class<?> mapperClass) {
|
||||
return MapUtil.computeIfAbsent(mapperTableInfoMap, mapperClass, key -> {
|
||||
Class<?> entityClass = getEntityClass(mapperClass);
|
||||
if (entityClass == null) {
|
||||
if (entityClass == null){
|
||||
return null;
|
||||
}
|
||||
return ofEntityClass(entityClass);
|
||||
@ -117,7 +133,6 @@ public class TableInfoFactory {
|
||||
tableInfo.setEntityClass(entityClass);
|
||||
tableInfo.setReflector(new Reflector(entityClass));
|
||||
|
||||
|
||||
//初始化表名
|
||||
Table table = entityClass.getAnnotation(Table.class);
|
||||
if (table != null) {
|
||||
|
||||
@ -36,6 +36,8 @@ public class ClassUtil {
|
||||
// javassist
|
||||
, "javassist.util.proxy.ProxyObject"
|
||||
, "org.apache.ibatis.javassist.util.proxy.ProxyObject");
|
||||
private static final String ENHANCER_BY = "$$EnhancerBy";
|
||||
private static final String JAVASSIST_BY = "_$$_";
|
||||
|
||||
public static boolean isProxy(Class<?> clazz) {
|
||||
for (Class<?> cls : clazz.getInterfaces()) {
|
||||
@ -47,12 +49,9 @@ public class ClassUtil {
|
||||
return Proxy.isProxyClass(clazz);
|
||||
}
|
||||
|
||||
private static final String ENHANCER_BY = "$$EnhancerBy";
|
||||
private static final String JAVASSIST_BY = "_$$_";
|
||||
|
||||
public static <T> Class<T> getUsefulClass(Class<T> clazz) {
|
||||
if (isProxy(clazz)) {
|
||||
return (Class<T>) clazz.getSuperclass();
|
||||
return getJdkProxySuperClass(clazz);
|
||||
}
|
||||
|
||||
//ControllerTest$ServiceTest$$EnhancerByGuice$$40471411#hello -------> Guice
|
||||
@ -235,4 +234,9 @@ public class ClassUtil {
|
||||
doGetMethods(cl.getSuperclass(), methods, predicate);
|
||||
}
|
||||
|
||||
private static <T> Class<T> getJdkProxySuperClass(Class<T> clazz) {
|
||||
final Class<?> proxyClass = Proxy.getProxyClass(clazz.getClassLoader(), clazz.getInterfaces());
|
||||
return (Class<T>) proxyClass.getInterfaces()[0];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -83,4 +83,24 @@ public class CollectionUtil {
|
||||
return new ArrayList<>(Arrays.asList(elements));
|
||||
}
|
||||
|
||||
public static <T> List<T> toList(Collection<T> collection) {
|
||||
if (collection instanceof List) {
|
||||
return (List<T>) collection;
|
||||
} else {
|
||||
return new ArrayList<>(collection);
|
||||
}
|
||||
}
|
||||
|
||||
public static String[] toArrayString(Collection<?> collection) {
|
||||
if (isEmpty(collection)) {
|
||||
return new String[0];
|
||||
}
|
||||
String[] results = new String[collection.size()];
|
||||
int index = 0;
|
||||
for (Object o : collection) {
|
||||
results[index++] = String.valueOf(o);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -15,11 +15,6 @@
|
||||
*/
|
||||
package com.mybatisflex.core.util;
|
||||
|
||||
import com.mybatisflex.annotation.EnumValue;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.time.LocalDate;
|
||||
@ -27,8 +22,6 @@ import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.temporal.Temporal;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class ConvertUtil {
|
||||
|
||||
@ -99,40 +92,9 @@ public class ConvertUtil {
|
||||
}
|
||||
return Short.parseShort(value.toString());
|
||||
} else if (targetClass.isEnum()) {
|
||||
Object[] enumConstants = targetClass.getEnumConstants();
|
||||
List<Field> allFields = ClassUtil.getAllFields(targetClass, field -> field.getAnnotation(EnumValue.class) != null);
|
||||
if (allFields.size() == 1) {
|
||||
Field field = allFields.get(0);
|
||||
|
||||
String fieldGetterName = "get" + StringUtil.firstCharToUpperCase(field.getName());
|
||||
List<Method> allMethods = ClassUtil.getAllMethods(targetClass, method -> {
|
||||
String methodName = method.getName();
|
||||
return methodName.equals(fieldGetterName);
|
||||
});
|
||||
|
||||
//getter
|
||||
if (allMethods.size() == 1) {
|
||||
Method getter = allMethods.get(0);
|
||||
for (Object enumConstant : enumConstants) {
|
||||
try {
|
||||
Object enumValue = getter.invoke(enumConstant);
|
||||
if (Objects.equals(enumValue, value)) {
|
||||
return enumConstant;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//public field
|
||||
else if (Modifier.isPublic(field.getModifiers())) {
|
||||
for (Object enumConstant : enumConstants) {
|
||||
if (Objects.equals(readPublicField(field, enumConstant), value)) {
|
||||
return enumConstant;
|
||||
}
|
||||
}
|
||||
}
|
||||
EnumWrapper enumWrapper = EnumWrapper.of(targetClass);
|
||||
if (enumWrapper.hasEnumValueAnnotation()) {
|
||||
return enumWrapper.toEnum(value);
|
||||
} else if (value instanceof String) {
|
||||
return Enum.valueOf(targetClass, value.toString());
|
||||
}
|
||||
@ -146,15 +108,6 @@ public class ConvertUtil {
|
||||
}
|
||||
|
||||
|
||||
private static Object readPublicField(Field field, Object target) {
|
||||
try {
|
||||
return field.get(target);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Boolean.TYPE, Character.TYPE, Byte.TYPE, Short.TYPE, Integer.TYPE, Long.TYPE, Float.TYPE, Double.TYPE, Void.TYPE
|
||||
public static Object getPrimitiveDefaultValue(Class<?> paraClass) {
|
||||
if (paraClass == int.class || paraClass == long.class || paraClass == float.class || paraClass == double.class) {
|
||||
|
||||
@ -0,0 +1,121 @@
|
||||
/**
|
||||
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.mybatisflex.core.util;
|
||||
|
||||
import com.mybatisflex.annotation.EnumValue;
|
||||
import com.mybatisflex.core.exception.FlexExceptions;
|
||||
import org.apache.ibatis.util.MapUtil;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class EnumWrapper<E extends Enum<E>> {
|
||||
|
||||
private static final Map<Class, EnumWrapper> cache = new ConcurrentHashMap<>();
|
||||
|
||||
private Class<?> enumClass;
|
||||
|
||||
private Class<?> enumPropertyType;
|
||||
private E[] enums;
|
||||
private Field property;
|
||||
private Method getter;
|
||||
private boolean hasEnumValueAnnotation = false;
|
||||
|
||||
public static <R extends Enum<R>> EnumWrapper<R> of(Class<?> enumClass) {
|
||||
return MapUtil.computeIfAbsent(cache, enumClass, EnumWrapper::new);
|
||||
}
|
||||
|
||||
public EnumWrapper(Class<E> enumClass) {
|
||||
this.enumClass = enumClass;
|
||||
|
||||
List<Field> allFields = ClassUtil.getAllFields(enumClass, field -> field.getAnnotation(EnumValue.class) != null);
|
||||
if (!allFields.isEmpty()) {
|
||||
hasEnumValueAnnotation = true;
|
||||
}
|
||||
|
||||
if (hasEnumValueAnnotation) {
|
||||
Field field = allFields.get(0);
|
||||
|
||||
String fieldGetterName = "get" + StringUtil.firstCharToUpperCase(field.getName());
|
||||
List<Method> allMethods = ClassUtil.getAllMethods(enumClass, method -> {
|
||||
String methodName = method.getName();
|
||||
return methodName.equals(fieldGetterName);
|
||||
});
|
||||
|
||||
enumPropertyType = ClassUtil.wrap(field.getType());
|
||||
enums = enumClass.getEnumConstants();
|
||||
|
||||
if (allMethods.isEmpty()) {
|
||||
if (Modifier.isPublic(field.getModifiers())) {
|
||||
property = field;
|
||||
} else {
|
||||
throw new IllegalStateException("Can not find \"" + fieldGetterName + "()\" method in enum: " + enumClass.getName());
|
||||
}
|
||||
} else {
|
||||
getter = allMethods.get(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Object getEnumValue(E object) {
|
||||
try {
|
||||
return getter != null
|
||||
? getter.invoke(object)
|
||||
: property.get(object);
|
||||
} catch (Exception e) {
|
||||
throw FlexExceptions.wrap(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public E toEnum(Object value) {
|
||||
for (E e : enums) {
|
||||
if (value.equals(getEnumValue(e))) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Class<?> getEnumClass() {
|
||||
return enumClass;
|
||||
}
|
||||
|
||||
public Class<?> getEnumPropertyType() {
|
||||
return enumPropertyType;
|
||||
}
|
||||
|
||||
public E[] getEnums() {
|
||||
return enums;
|
||||
}
|
||||
|
||||
public Field getProperty() {
|
||||
return property;
|
||||
}
|
||||
|
||||
public Method getGetter() {
|
||||
return getter;
|
||||
}
|
||||
|
||||
public boolean hasEnumValueAnnotation() {
|
||||
return hasEnumValueAnnotation;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.mybatisflex.core.util;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface LambdaGetter<T> extends Serializable {
|
||||
Object get(T source);
|
||||
}
|
||||
|
||||
@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com).
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.mybatisflex.core.util;
|
||||
|
||||
import org.apache.ibatis.reflection.property.PropertyNamer;
|
||||
import org.apache.ibatis.util.MapUtil;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.invoke.SerializedLambda;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class LambdaUtil {
|
||||
|
||||
private static final Map<Class<?>, SerializedLambda> lambdaMap = new ConcurrentHashMap<>();
|
||||
|
||||
public static <T> String getFieldName(LambdaGetter<T> getter) {
|
||||
SerializedLambda lambda = getSerializedLambda(getter);
|
||||
String methodName = lambda.getImplMethodName();
|
||||
return PropertyNamer.methodToProperty(methodName);
|
||||
}
|
||||
|
||||
|
||||
private static SerializedLambda getSerializedLambda(Serializable getter) {
|
||||
return MapUtil.computeIfAbsent(lambdaMap, getter.getClass(), aClass -> {
|
||||
try {
|
||||
Method method = getter.getClass().getDeclaredMethod("writeReplace");
|
||||
method.setAccessible(Boolean.TRUE);
|
||||
return (SerializedLambda) method.invoke(getter);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@ -50,7 +50,7 @@ public class SqlUtil {
|
||||
}
|
||||
|
||||
|
||||
private static final char[] UN_SAFE_CHARS = "'`\"<>&*+=#-;".toCharArray();
|
||||
private static final char[] UN_SAFE_CHARS = "'`\"<>&+=#-;".toCharArray();
|
||||
|
||||
private static boolean isUnSafeChar(char ch) {
|
||||
for (char c : UN_SAFE_CHARS) {
|
||||
@ -68,19 +68,8 @@ public class SqlUtil {
|
||||
* @param result 数据库操作返回影响条数
|
||||
* @return {@code true} 操作成功,{@code false} 操作失败。
|
||||
*/
|
||||
public static boolean retBool(int result) {
|
||||
return result >= 1;
|
||||
public static boolean toBool(Number result) {
|
||||
return result != null && result.longValue() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据数据库响应结果判断数据库操作是否成功。
|
||||
*
|
||||
* @param result 数据库操作返回影响条数
|
||||
* @return {@code true} 操作成功,{@code false} 操作失败。
|
||||
*/
|
||||
public static boolean retBool(long result) {
|
||||
return result >= 1L;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -253,7 +253,6 @@ public class StringUtil {
|
||||
* @param objs
|
||||
* @param function
|
||||
* @param <T>
|
||||
* @return
|
||||
*/
|
||||
public static <T> String join(String delimiter, Collection<T> objs, Function<T, String> function) {
|
||||
if (CollectionUtil.isEmpty(objs)) {
|
||||
|
||||
@ -13,8 +13,8 @@ import org.junit.Test;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static com.mybatisflex.core.query.QueryMethods.*;
|
||||
import static com.mybatisflex.coretest.table.Tables.ACCOUNT;
|
||||
import static com.mybatisflex.coretest.table.Tables.ARTICLE;
|
||||
import static com.mybatisflex.coretest.table.AccountTableDef.ACCOUNT;
|
||||
import static com.mybatisflex.coretest.table.ArticleTableDef.ARTICLE;
|
||||
|
||||
public class AccountSqlTester {
|
||||
|
||||
@ -26,7 +26,7 @@ public class AccountSqlTester {
|
||||
.from(ACCOUNT);
|
||||
|
||||
IDialect dialect = new CommonsDialectImpl();
|
||||
String sql = dialect.forSelectListByQuery(query);
|
||||
String sql = dialect.forSelectByQuery(query);
|
||||
System.out.println(sql);
|
||||
}
|
||||
|
||||
@ -37,7 +37,7 @@ public class AccountSqlTester {
|
||||
.from(ACCOUNT);
|
||||
|
||||
IDialect dialect = new CommonsDialectImpl();
|
||||
String sql = dialect.forSelectListByQuery(query);
|
||||
String sql = dialect.forSelectByQuery(query);
|
||||
System.out.println(sql);
|
||||
}
|
||||
|
||||
@ -50,7 +50,7 @@ public class AccountSqlTester {
|
||||
.where(ACCOUNT.ID.eq(ARTICLE.ACCOUNT_ID));
|
||||
|
||||
IDialect dialect = new CommonsDialectImpl(KeywordWrap.NONE, LimitOffsetProcessor.MYSQL);
|
||||
String sql = dialect.forSelectListByQuery(query);
|
||||
String sql = dialect.forSelectByQuery(query);
|
||||
System.out.println(sql);
|
||||
}
|
||||
|
||||
@ -61,7 +61,7 @@ public class AccountSqlTester {
|
||||
.from(ACCOUNT);
|
||||
|
||||
IDialect dialect = new CommonsDialectImpl();
|
||||
String sql = dialect.forSelectListByQuery(query);
|
||||
String sql = dialect.forSelectByQuery(query);
|
||||
System.out.println(sql);
|
||||
}
|
||||
|
||||
@ -73,7 +73,7 @@ public class AccountSqlTester {
|
||||
.from(ACCOUNT);
|
||||
|
||||
IDialect dialect = new CommonsDialectImpl();
|
||||
String sql = dialect.forSelectListByQuery(query);
|
||||
String sql = dialect.forSelectByQuery(query);
|
||||
System.out.println(sql);
|
||||
}
|
||||
|
||||
@ -88,21 +88,7 @@ public class AccountSqlTester {
|
||||
.unionAll(select(ARTICLE.ID).from(ARTICLE));
|
||||
|
||||
IDialect dialect = new CommonsDialectImpl();
|
||||
String sql = dialect.forSelectListByQuery(query);
|
||||
System.out.println(sql);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSelectCountSql() {
|
||||
QueryWrapper queryWrapper = QueryWrapper.create()
|
||||
.select()
|
||||
.from(ACCOUNT)
|
||||
.where(ACCOUNT.ID.ge(100))
|
||||
.and(ACCOUNT.USER_NAME.like("michael"));
|
||||
|
||||
IDialect dialect = new CommonsDialectImpl();
|
||||
String sql = dialect.forSelectCountByQuery(queryWrapper);
|
||||
String sql = dialect.forSelectByQuery(query);
|
||||
System.out.println(sql);
|
||||
}
|
||||
|
||||
@ -116,7 +102,7 @@ public class AccountSqlTester {
|
||||
.and(ACCOUNT.USER_NAME.like("michael"));
|
||||
|
||||
IDialect dialect = new CommonsDialectImpl();
|
||||
String sql = dialect.forSelectListByQuery(queryWrapper);
|
||||
String sql = dialect.forSelectByQuery(queryWrapper);
|
||||
System.out.println(sql);
|
||||
}
|
||||
|
||||
@ -129,7 +115,7 @@ public class AccountSqlTester {
|
||||
.and(column("aaa").in("michael", "aaa"));
|
||||
|
||||
IDialect dialect = new CommonsDialectImpl();
|
||||
String sql = dialect.forSelectListByQuery(queryWrapper);
|
||||
String sql = dialect.forSelectByQuery(queryWrapper);
|
||||
System.out.println(sql);
|
||||
}
|
||||
|
||||
@ -144,7 +130,7 @@ public class AccountSqlTester {
|
||||
.and(ACCOUNT.USER_NAME.like("michael"));
|
||||
|
||||
IDialect dialect = new CommonsDialectImpl();
|
||||
String sql = dialect.forSelectListByQuery(queryWrapper);
|
||||
String sql = dialect.forSelectByQuery(queryWrapper);
|
||||
System.out.println(sql);
|
||||
|
||||
Object[] valueArray = CPI.getValueArray(queryWrapper);
|
||||
@ -162,7 +148,7 @@ public class AccountSqlTester {
|
||||
.and(ACCOUNT.USER_NAME.like("michael"));
|
||||
|
||||
IDialect dialect = new CommonsDialectImpl();
|
||||
String sql = dialect.forSelectListByQuery(queryWrapper);
|
||||
String sql = dialect.forSelectByQuery(queryWrapper);
|
||||
System.out.println(sql);
|
||||
|
||||
Object[] valueArray = CPI.getValueArray(queryWrapper);
|
||||
@ -183,7 +169,7 @@ public class AccountSqlTester {
|
||||
);
|
||||
|
||||
IDialect dialect = new CommonsDialectImpl();
|
||||
String sql = dialect.forSelectListByQuery(queryWrapper);
|
||||
String sql = dialect.forSelectByQuery(queryWrapper);
|
||||
System.out.println(sql);
|
||||
}
|
||||
|
||||
@ -198,7 +184,7 @@ public class AccountSqlTester {
|
||||
.or(ACCOUNT.AGE.in(18, 19, 20).or(ACCOUNT.USER_NAME.like("michael")));
|
||||
|
||||
IDialect dialect = new CommonsDialectImpl();
|
||||
String sql = dialect.forSelectListByQuery(queryWrapper);
|
||||
String sql = dialect.forSelectByQuery(queryWrapper);
|
||||
System.out.println(sql);
|
||||
}
|
||||
|
||||
@ -212,7 +198,7 @@ public class AccountSqlTester {
|
||||
));
|
||||
|
||||
IDialect dialect = new CommonsDialectImpl();
|
||||
String sql = dialect.forSelectListByQuery(queryWrapper);
|
||||
String sql = dialect.forSelectByQuery(queryWrapper);
|
||||
System.out.println(sql);
|
||||
}
|
||||
|
||||
@ -224,7 +210,7 @@ public class AccountSqlTester {
|
||||
.groupBy(ACCOUNT.USER_NAME);
|
||||
|
||||
IDialect dialect = new CommonsDialectImpl();
|
||||
String sql = dialect.forSelectListByQuery(queryWrapper);
|
||||
String sql = dialect.forSelectByQuery(queryWrapper);
|
||||
System.out.println(sql);
|
||||
}
|
||||
|
||||
@ -237,7 +223,7 @@ public class AccountSqlTester {
|
||||
.having(ACCOUNT.AGE.between(18, 25));
|
||||
|
||||
IDialect dialect = new CommonsDialectImpl();
|
||||
String sql = dialect.forSelectListByQuery(queryWrapper);
|
||||
String sql = dialect.forSelectByQuery(queryWrapper);
|
||||
System.out.println(sql);
|
||||
}
|
||||
|
||||
@ -250,7 +236,7 @@ public class AccountSqlTester {
|
||||
.where(ACCOUNT.AGE.ge(10));
|
||||
|
||||
IDialect dialect = new CommonsDialectImpl();
|
||||
String sql = dialect.forSelectListByQuery(queryWrapper);
|
||||
String sql = dialect.forSelectByQuery(queryWrapper);
|
||||
System.out.println(sql);
|
||||
}
|
||||
|
||||
@ -265,7 +251,7 @@ public class AccountSqlTester {
|
||||
.where(ACCOUNT.AGE.ge(10));
|
||||
|
||||
IDialect dialect = new CommonsDialectImpl();
|
||||
String sql = dialect.forSelectListByQuery(queryWrapper);
|
||||
String sql = dialect.forSelectByQuery(queryWrapper);
|
||||
System.out.println(sql);
|
||||
}
|
||||
|
||||
@ -282,7 +268,7 @@ public class AccountSqlTester {
|
||||
.where(ACCOUNT.AGE.ge(10));
|
||||
|
||||
IDialect dialect = new CommonsDialectImpl();
|
||||
String sql = dialect.forSelectListByQuery(queryWrapper);
|
||||
String sql = dialect.forSelectByQuery(queryWrapper);
|
||||
System.out.println(sql);
|
||||
}
|
||||
|
||||
@ -294,7 +280,7 @@ public class AccountSqlTester {
|
||||
.orderBy(ACCOUNT.AGE.asc(), ACCOUNT.USER_NAME.desc().nullsLast());
|
||||
|
||||
IDialect dialect = new CommonsDialectImpl();
|
||||
String sql = dialect.forSelectListByQuery(queryWrapper);
|
||||
String sql = dialect.forSelectByQuery(queryWrapper);
|
||||
System.out.println(sql);
|
||||
}
|
||||
|
||||
@ -306,6 +292,73 @@ public class AccountSqlTester {
|
||||
System.out.println(sql);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testForUpdate() {
|
||||
IDialect dialect = new CommonsDialectImpl();
|
||||
|
||||
QueryWrapper queryWrapper = QueryWrapper.create()
|
||||
.select()
|
||||
.from(ACCOUNT)
|
||||
.and(ACCOUNT.USER_NAME.like("michael"))
|
||||
.forUpdate();
|
||||
|
||||
String sql = dialect.forSelectByQuery(queryWrapper);
|
||||
System.out.println(sql);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConvert() {
|
||||
IDialect dialect = new CommonsDialectImpl();
|
||||
|
||||
QueryWrapper queryWrapper = QueryWrapper.create()
|
||||
.select(ACCOUNT.ALL_COLUMNS, convert("NVARCHAR(30)", "GETDATE()", "126").as("result"))
|
||||
.from(ACCOUNT)
|
||||
.and(ACCOUNT.USER_NAME.like("michael"))
|
||||
.and(convert("NVARCHAR(30)", "GETDATE()", "126").in(
|
||||
select(ACCOUNT.ID).from(ACCOUNT).where(ACCOUNT.ID.ge(100)))
|
||||
);
|
||||
|
||||
String sql = dialect.forSelectByQuery(queryWrapper);
|
||||
System.out.println(sql);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testCase1() {
|
||||
IDialect dialect = new CommonsDialectImpl();
|
||||
|
||||
QueryWrapper queryWrapper = QueryWrapper.create()
|
||||
.select(ACCOUNT.ALL_COLUMNS,
|
||||
case_()
|
||||
.when(ACCOUNT.ID.eq(100)).then(100)
|
||||
.when(ACCOUNT.ID.ge(200)).then(200)
|
||||
.else_(300)
|
||||
.end().as("result"))
|
||||
.from(ACCOUNT)
|
||||
.and(ACCOUNT.USER_NAME.like("michael"));
|
||||
|
||||
String sql = dialect.forSelectByQuery(queryWrapper);
|
||||
System.out.println(sql);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCase2() {
|
||||
IDialect dialect = new CommonsDialectImpl();
|
||||
|
||||
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)
|
||||
.and(ACCOUNT.USER_NAME.like("michael"));
|
||||
|
||||
String sql = dialect.forSelectByQuery(queryWrapper);
|
||||
System.out.println(sql);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testLimitOffset() {
|
||||
@ -325,9 +378,9 @@ public class AccountSqlTester {
|
||||
String sql2 = dialect2.buildSelectSql(queryWrapper);
|
||||
System.out.println(sql2);
|
||||
|
||||
IDialect dialect3 = new CommonsDialectImpl(KeywordWrap.DOUBLE_QUOTATION, LimitOffsetProcessor.DB2);
|
||||
String sql3 = dialect3.buildSelectSql(queryWrapper);
|
||||
System.out.println(sql3);
|
||||
// IDialect dialect3 = new CommonsDialectImpl(KeywordWrap.DOUBLE_QUOTATION, LimitOffsetProcessor.DB2);
|
||||
// String sql3 = dialect3.buildSelectSql(queryWrapper);
|
||||
// System.out.println(sql3);
|
||||
|
||||
IDialect dialect4 = new CommonsDialectImpl(KeywordWrap.DOUBLE_QUOTATION, LimitOffsetProcessor.POSTGRESQL);
|
||||
String sql4 = dialect4.buildSelectSql(queryWrapper);
|
||||
@ -374,7 +427,7 @@ public class AccountSqlTester {
|
||||
.orderBy(ACCOUNT.ID.desc())
|
||||
.limit(10, 10);
|
||||
|
||||
String mysqlSql = new CommonsDialectImpl().forSelectListByQuery(queryWrapper);
|
||||
String mysqlSql = new CommonsDialectImpl().forSelectByQuery(queryWrapper);
|
||||
System.out.println(">>>>> mysql: \n" + mysqlSql);
|
||||
System.out.println(">>>>> mysql: \n" + Arrays.toString(CPI.getValueArray(queryWrapper)));
|
||||
|
||||
|
||||
@ -8,7 +8,8 @@ import com.mybatisflex.core.table.TableInfoFactory;
|
||||
import com.mybatisflex.core.util.CollectionUtil;
|
||||
import org.junit.Test;
|
||||
|
||||
import static com.mybatisflex.coretest.table.Tables.ARTICLE;
|
||||
import static com.mybatisflex.coretest.table.ArticleTableDef.ARTICLE;
|
||||
|
||||
|
||||
public class ArticleSqlTester {
|
||||
|
||||
@ -20,7 +21,7 @@ public class ArticleSqlTester {
|
||||
.from(ARTICLE);
|
||||
|
||||
IDialect dialect = new CommonsDialectImpl();
|
||||
String sql = dialect.forSelectListByQuery(query);
|
||||
String sql = dialect.forSelectByQuery(query);
|
||||
System.out.println(sql);
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,15 @@
|
||||
package com.mybatisflex.coretest;
|
||||
|
||||
import com.mybatisflex.core.dialect.DbType;
|
||||
import com.mybatisflex.core.dialect.DbTypeUtil;
|
||||
import org.junit.Test;
|
||||
|
||||
public class DbTypeUtilTest {
|
||||
|
||||
@Test
|
||||
public void testParseUrl(){
|
||||
String url01 = "jdbc:sqlserver://127.0.0.1";
|
||||
DbType dbType01 = DbTypeUtil.parseDbType(url01);
|
||||
System.out.println(dbType01);
|
||||
}
|
||||
}
|
||||
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>parent</artifactId>
|
||||
<groupId>com.mybatis-flex</groupId>
|
||||
<version>1.2.3</version>
|
||||
<version>1.3.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
<dependency>
|
||||
<groupId>com.mybatis-flex</groupId>
|
||||
<artifactId>mybatis-flex-annotation</artifactId>
|
||||
<version>1.2.3</version>
|
||||
<version>1.3.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user