Merge branch 'main' of gitee.com:mybatis-flex/mybatis-flex into main

Signed-off-by: 王帅 <1474983351@qq.com>
This commit is contained in:
王帅 2023-05-25 13:24:45 +00:00 committed by Gitee
commit bad11ec060
127 changed files with 3700 additions and 1001 deletions

View File

@ -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

View File

@ -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

View File

@ -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"]
}

View File

@ -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
}
},

View File

@ -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'},

View 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>

View 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
}

View File

@ -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因此会带来指数级的性能增长。
---

View File

@ -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 字段更新为 nullage 字段更新为 10其他字段不会被更新。也就是说通过 UpdateEntity 创建的对象,只会更新调用了 setter 方法的字段,若不调用 setter 方法,不管这个对象里的属性的值是什么,都不会更新到数据库。
以上的示例中,会把 id (主键)为 100 这条数据中的 user_name 字段更新为 nullage 字段更新为 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
View 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);
```

View File

@ -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()

View 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` 对象里的属性,需要二次查询赋值的,都是可以通过这种方式进行。

View File

@ -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);

View File

@ -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
```
## joinleft joininner 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 使用了 APTAnnotation Processing Tool在项目编译的时候
MyBatis-Flex 使用了 APTAnnotation 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 值的条件。

View File

@ -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` 接口。

View File

@ -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 {

View File

@ -1,6 +1,6 @@
# @Column 注解的使用
Mybatis-Flex 提供了 `@Column` 用来对字段进行更多的配置,以下是 `@Column` 的代码定义:
MyBatis-Flex 提供了 `@Column` 用来对字段进行更多的配置,以下是 `@Column` 的代码定义:
```java
public @interface Column {

View 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` 方法以外,还有其他的查询方法,可能也需要复写一下。

View File

@ -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 注意事项

View File

@ -3,7 +3,7 @@
数据填充指的是,当 Entity 数据被插入 或者 更新的时候,会为字段进行一些默认的数据设置。这个非常有用,比如说当某个 entity 被插入时候
会设置一些数据插入的时间、数据插入的用户 id多租户的场景下设置当前租户信息等等。
Mybatis-Flex 提供了两种方式,帮助开发者进行数据填充。
MyBatis-Flex 提供了两种方式,帮助开发者进行数据填充。
- 1、通过 `@Table` 注解的 `onInsert``onUpdate` 配置进行操作。这部分可以参考 [@Table 注解章节](./table) 。
- 2、通过 `@Column` 注解的 `onInsertValue``onUpdateValue` 配置进行操作。这部分可以参考 [@Column 注解章节](./column)。

View File

@ -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;
}

View File

@ -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

View File

@ -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种方便开发者直接使用
- 手机号脱敏
- 固定电话脱敏

View File

@ -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)

View File

@ -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`

View File

@ -1,6 +1,6 @@
# @Table 注解的使用
在 Mybatis-Flex 中,`@Table` 主要是用于给 Entity 实体类添加标识,用于描述 实体类 和 数据库表 的关系,以及对实体类进行的一些
在 MyBatis-Flex 中,`@Table` 主要是用于给 Entity 实体类添加标识,用于描述 实体类 和 数据库表 的关系,以及对实体类进行的一些
功能辅助。

View File

@ -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、支持嵌套事务

View File

@ -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。

View File

@ -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

View File

@ -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<>();

View File

@ -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()

View File

@ -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 使用了 APTAnnotation Processing Tool技术在项目编译的时会自动生成辅助操作类。
**第4步编译项目自动生成查询辅助类**
MyBatis-Flex 使用了 APTAnnotation 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)

View File

@ -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>

View File

@ -1,4 +1,4 @@
# Mybatis-Flex QQ 交流群
# MyBatis-Flex QQ 交流群
群号: 532992631

View File

@ -1,6 +1,6 @@
# Mybatis-Flex 支持的数据库类型
# MyBatis-Flex 支持的数据库类型
Mybatis-Flex 支持的数据库类型,如下表格所示,我们还可以通过自定义方言的方式,持续添加更多的数据库支持。
MyBatis-Flex 支持的数据库类型,如下表格所示,我们还可以通过自定义方言的方式,持续添加更多的数据库支持。
| 数据库 | 描述 |

View File

@ -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 的方式

View File

@ -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、强大**:支持任意关系型数据库,还可以通过方言持续扩展,同时支持 **多(复合)主键**、**逻辑删除**、**乐观锁配置**、**数据脱敏**、**数据审计**、

View File

@ -1,6 +1,6 @@
# Mybatis-Flex APT 配置
# MyBatis-Flex APT 配置
Mybatis-Flex 使用了 APTAnnotation Processing Tool技术在项目编译的时候会自动根据 Entity 类定义的字段帮你生成 "ACCOUNT" 类以及 Entity 对应的 Mapper 类,
MyBatis-Flex 使用了 APTAnnotation Processing Tool技术在项目编译的时候会自动根据 Entity 类定义的字段帮你生成 "ACCOUNT" 类以及 Entity 对应的 Mapper 类,
通过开发工具构建项目(如下图),或者执行 maven 编译命令: `mvn clean package` 都可以自动生成。这个原理和 lombok 一致。
![](../../assets/images/build_idea.png)
@ -9,7 +9,7 @@ Mybatis-Flex 使用了 APTAnnotation Processing Tool技术在项目编
## 配置文件和选项
要对Mybatis-Flex 的APT细节选项进行配置你需要在`resources`目录下创建名为`mybatis-flex.properties`的文件。
要对MyBatis-Flex 的APT细节选项进行配置你需要在`resources`目录下创建名为`mybatis-flex.properties`的文件。
支持的配置选项如下:
@ -18,6 +18,7 @@ Mybatis-Flex 使用了 APTAnnotation 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'
}
```

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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;

View File

@ -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>

View File

@ -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 本身的字段内容可以转换为 dtovo 等场景时
*
* @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;
}
}

View File

@ -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 的正常状态

View File

@ -54,10 +54,6 @@ public enum DbType {
* POSTGRE
*/
POSTGRE_SQL("postgresql", "PostgreSQL 数据库"),
/**
* SQLSERVER 2005
*/
SQLSERVER_2005("sqlserver2005", "SQLServer2005 数据库"),
/**
* SQLSERVER
*/

View File

@ -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;

View File

@ -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:

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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}

View File

@ -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() + "\"";

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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());
}

View File

@ -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";
}

View File

@ -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;
}

View File

@ -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();
}
}

View File

@ -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();
}

View File

@ -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));

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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 +
'}';
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -0,0 +1,6 @@
package com.mybatisflex.core.query;
public interface HasParamsColumn {
Object[] getParamValues();
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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();

View File

@ -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{" +

View File

@ -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);
}

View File

@ -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);

View File

@ -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();
}
}

View File

@ -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 +
'}';
}
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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));
}

View File

@ -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));
}
}

View File

@ -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));
}

View File

@ -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) {

View File

@ -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];
}
}

View File

@ -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;
}
}

View File

@ -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) {

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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);
}
});
}
}

View File

@ -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;
}
}

View File

@ -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)) {

View File

@ -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)));

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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