Merge remote-tracking branch 'origin/main'

This commit is contained in:
kamosama 2023-08-09 12:50:50 +08:00
commit d7bdc2aea0
55 changed files with 2325 additions and 665 deletions

View File

@ -20,7 +20,7 @@ features:
- title: 更轻量
details: MyBatis-Flex 除了 MyBatis 本身,再无任何第三方依赖,因此会带来更高的自主性、把控性和稳定性。在任何一个系统中,依赖越多,稳定性越差。
- title: 更灵活
details: MyBatis-Flex 提供了非常灵活的 QueryWrapper支持关联查询、多表查询、多主键、逻辑删除、乐观锁更新、数据填充、数据脱敏、等等....
details: MyBatis-Flex 提供了非常灵活的 QueryWrapper支持关联查询、多表查询、多主键、逻辑删除、乐观锁更新、数据填充、数据脱敏等等。
- title: 更高的性能
details: MyBatis-Flex 通过独特的架构,没有任何 MyBatis 拦截器、在 SQL 执行的过程中,没有任何的 SQL Parse因此会带来指数级的性能增长。
---

View File

@ -84,6 +84,9 @@
- [MyBatis-Flex 视频教程 - 30 数据脱敏的简单使用](https://www.bilibili.com/video/BV1gz4y1s7Wg)
- [MyBatis-Flex 视频教程 - 31 枚举属性的使用](https://www.bilibili.com/video/BV1mm4y1W7SD)
- [MyBatis-Flex 视频教程 - 32 关联查询Join Query](https://www.bilibili.com/video/BV1B8411d7iC)
- [MyBatis-Flex 视频教程 - 33 关联查询Field Query](https://www.bilibili.com/video/BV17k4y1g7vt)
- [MyBatis-Flex 视频教程 - 34 关联查询Relation Query](https://www.bilibili.com/video/BV1bj411r7A4)
- [MyBatis-Flex 视频教程 - 35 关联查询对比](https://www.bilibili.com/video/BV1oF411f7dr)

View File

@ -1017,6 +1017,64 @@ QueryWrapper query2 = QueryWrapper.create()
在以上的 `query1` 中,由于 `userName``id` 都为 nullMyBatis-Flex 会自动忽略 null 值的条件,因此,它们构建出来的 SQL 条件是和 `query2` 完全一致的 。
## QueryColumnBehavior <Badge type="tip" text="^ v1.5.6" />
在以上的内容中,我们知道 MyBatis-Flex 会自动忽略 `null` 值的条件,但是在实际开发中,有的开发者希望除了自动忽略 `null`
值以外,还可以自动忽略其他值,比如 `空字符串` 等。
此时,我们可以通过配置 QueryColumnBehavior 来自定义忽略的值。如下的代码会自动忽略 `null``空字符串`
```java
QueryColumnBehavior.setIgnoreFunction(new Predicate<Object>() {
@Override
public boolean test(Object o) {
return "".equals(o);
}
});
```
另外,在某些场景下,开发者希望在构建 QueryWrapper 中,如果传入的值是集合或数组,则使用 `in` 逻辑,否则使用 `=`(等于)
逻辑:
```java
QueryColumnBehavior.setSmartConvertInToEquals(true);
```
当添加以上配置时,我们在构建 QueryWrapper 的 `in` 的 SQL 时,逻辑如下:
```java
// ids 有多个值
List<Integer> ids = Arrays.asList(1, 2, 3);
QueryWrapper qw = new QueryWrapper();
qw.where(ACCOUNT.ID.in(ids))
System.out.println(qw.toSQL());
```
输出的 SQL 如下:
```sql
select * from tb_account where id in (1,2,3);
```
`ids` 只有 1 个值时,逻辑如下:
```java
// ids 只有 1 个值
List<Integer> ids = Arrays.asList(1);
QueryWrapper qw = new QueryWrapper();
qw.where(ACCOUNT.ID.in(ids))
System.out.println(qw.toSQL());
```
输出的 SQL 如下:
```sql
select * from tb_account where id = 1;
```
## 存在疑问?
**疑问1示例代码中的 QueryWrapper 所需要的 "ACCOUNT" 从哪里来的?**

View File

@ -87,14 +87,12 @@ MyBatis-Flex 已支持 Spring 框架的 `@Transactional`,在使用 SpringBoot
> 注意:若项目未使用 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、支持嵌套事务
- 2、支持多数据源
注意在多数据源的情况下所有数据源的数据库请求Connection会执行相同的 `commit` 或者 `rollback`MyBatis-Flex 只保证了程序端的原子操作,
但并不能保证多个数据源之间的原子操作。例如:
> 注意在多数据源的情况下所有数据源的数据库请求Connection会执行相同的 commit 或者 rollback但并非原子操作。例如
```java
```java 1,6,13,19
@Transactional
public void doSomething(){
@ -117,10 +115,132 @@ public void doSomething(){
}
```
在以上的例子中,两次 `Db.update(...)` 虽然是两个不同的数据源,但它们都在同一个事务 `@Transactional` 里,因此,当抛出异常的时候,
在以上的例子中,执行了两次 `Db.updateBySql(...)`,它们是两个不同的数据源,但它们都在同一个事务 `@Transactional` 里,因此,当抛出异常的时候,
它们都会进行回滚rollback
以上提到的 `并非原子操作`,指的是:
>假设在回滚的时候,恰好其中一个数据库出现了异常(比如 网络问题数据库崩溃此时可能只有一个数据库的数据正常回滚rollback
> 但无论如何MyBatis-Flex 都会保证在同一个 `@Transactional` 中的多个数据源,保持相同的 commit 或者 rollback 行为。
## Seata 分布式事务
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。
Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
官方网站https://seata.io/zh-cn/index.html
### 开始使用
**第 1 步:在 `application.yml` 配置开启 Seata 分布式事务功能:**
```yaml
mybatis-flex:
seata-config:
enable: true
seata-mode: XA # 支持 xa 或者 ta
```
- XA指的是: 分布式事务协议X/Open Distributed Transaction Processing它是一种由 X/Open 组织制定的分布式事务标准,
XA 使用两阶段提交2PCTwo-Phase Commit来保证所有资源同时提交或回滚任何特定的事务。
目前,几乎所有主流的数据库都对 XA 规范 提供了支持,是 Seata 默认使用的模式。
- AT 是一种无侵入的分布式事务解决方案。在 AT 模式下,用户只需关注自己的 “`业务SQL`”,
用户的 “`业务SQL`” 作为一阶段Seata 会根据 SQL 内容,自动生成事务的二阶段提交和回滚操作。
**第 2 步:在 `application.yml` 添加 Seata 的相关配置:**
```yaml
seata:
enabled: true
application-id: business-service
tx-service-group: my_test_tx_group
enable-auto-data-source-proxy: false #必须
# use-jdk-proxy: false
client:
rm:
async-commit-buffer-limit: 1000
report-retry-count: 5
table-meta-check-enable: false
report-success-enable: false
lock:
retry-interval: 10
retry-times: 30
retry-policy-branch-rollback-on-conflict: true
tm:
commit-retry-count: 5
rollback-retry-count: 5
undo:
data-validation: true
log-serialization: jackson
log-table: undo_log
log:
exceptionRate: 100
service:
vgroup-mapping:
my_test_tx_group: default
grouplist:
default: 127.0.0.1:8091
#enable-degrade: false
#disable-global-transaction: false
transport:
shutdown:
wait: 3
thread-factory:
boss-thread-prefix: NettyBoss
worker-thread-prefix: NettyServerNIOWorker
server-executor-thread-prefix: NettyServerBizHandler
share-boss-worker: false
client-selector-thread-prefix: NettyClientSelector
client-selector-thread-size: 1
client-worker-thread-prefix: NettyClientWorkerThread
worker-thread-size: default
boss-thread-size: 1
type: TCP
server: NIO
heartbeat: true
serialization: seata
compressor: none
enable-client-batch-send-request: true
config:
type: file
registry:
type: file
```
> 以上配置的含义,请参考 Seata 官方网站https://seata.io/zh-cn/docs/user/configurations.html
**3、通过使用 `@GloabalTransactional` 开始 Seata 分布式事务。**
```java 1
@GlobalTransactional
public void purchase(String userId, String commodityCode, int orderCount) {
LOGGER.info("purchase begin ... xid: " + RootContext.getXID());
stockClient.deduct(commodityCode, orderCount);
orderClient.create(userId, commodityCode, orderCount);
}
```
> 更多关于 Seata 的知识,请异步 Seata 官方网站了解https://seata.io/zh-cn/docs ,也可以参考 Seata
> 的官方示例快速开始https://seata.io/zh-cn/docs/user/quickstart.html
### 注意事项
1.使用`seata-spring-boot-starter`的时候请关闭自动代理
```yaml
seata:
enable-auto-data-source-proxy: false
```
2.使用 `seata-all` 请不要使用 `@EnableAutoDataSourceProxy`
3.如果是 SpringBoot 项目需要引入相关 Maven 依赖,例如:
```xml
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.7.0</version>
</dependency>
```
### 示例
[mybatis-flex-spring-boot-seata-demo](https://gitee.com/mybatis-flex/mybatis-flex-samples/tree/master/mybatis-flex-spring-boot-seata-demo) : Seata 官方 demo 与 flex 结合。

View File

@ -21,6 +21,7 @@
就可以了,不需要再添加其他 MyBatis 依赖。
- 3、是否与 `mybatis-plus-boot-starter` 共用,使 MyBatis 被优先初始化,而导致 MyBatis-Flex 没有被加载。
- 4、是否添加了 `pagehelper-spring-boot-starter` 依赖,导致传递了 `mybatis-spring-boot-starter` 依赖。如还想继续使用 pagehelper 插件,点击 [这里](#与-pagehelper-集成出现错误) 查看解决方案。
- 5、是否 Spring Boot 版本过低,请使用 Spring Boot 2.2 及其以上版本,点击 [这里](#springboot-项目启动报错-javalangclassnotfoundexception-orgspringframeworktransactiontransactionmanager) 获取详细信息。
## 示例中的 AccountMapper 和 "ACCOUNT" 在哪里,报错了。
@ -62,6 +63,11 @@ in alimaven (http://maven.aliyun.com/nexus/content/groups/public/)
</mirror>
```
## SpringBoot 项目,启动报错 java.lang.ClassNotFoundException: org.springframework.transaction.TransactionManager
这个应该是 Spring Boot 版本的问题,`org.springframework.transaction.TransactionManager` 这个类是 Spring Framework 5.2
新增的,对应 Spring Boot 的版本应该是 Spring Boot 2.2 及其以上版本,所以应该使用 Spring Boot 2.2 及其以上版本。
## SpringBoot 项目,启动报错 Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required
如果当前依赖没有连接池相关依赖,则建议添加 HikariCP 依赖。

82
docs/zh/intro/gradle.md Normal file
View File

@ -0,0 +1,82 @@
# Gradle 依赖
> 以下的 xml gradle 依赖示例中,可能并非最新的 MyBatis-Flex 版本,请自行查看最新版本,并修改版本号。
>
> 建议配置 annotationProcessor那么可以省略mybatis-flex-processor的依赖
>
1、只用到了 MyBatis没用到 Spring 的场景:
**【Kotlin】**
```kotlin
dependencies {
implementation("com.mybatis-flex:mybatis-flex-core:1.5.6")
compileOnly("com.mybatis-flex:mybatis-flex-processor:1.5.6")
}
```
**【Groovy】**
```groovy
dependencies {
implementation 'com.mybatis-flex:mybatis-flex-core:1.5.6'
compileOnly 'com.mybatis-flex:mybatis-flex-processor:1.5.6'
}
```
2、用到了 Spring 的场景
**【Kotlin】**
```kotlin
dependencies {
implementation("com.mybatis-flex:mybatis-flex-spring:1.5.6")
compileOnly("com.mybatis-flex:mybatis-flex-processor:1.5.6")
}
```
**【Groovy】**
```groovy
dependencies {
implementation 'com.mybatis-flex:mybatis-flex-spring:1.5.6'
compileOnly 'com.mybatis-flex:mybatis-flex-processor:1.5.6'
}
```
3、用到了 Spring Boot 的场景
**【Kotlin】**
```kotlin
dependencies {
implementation("com.mybatis-flex:mybatis-flex-spring-boot-starter:1.5.6")
compileOnly("com.mybatis-flex:mybatis-flex-processor:1.5.6")
}
```
**【Groovy】**
```groovy
dependencies {
implementation 'com.mybatis-flex:mybatis-flex-spring-boot-starter:1.5.6'
compileOnly 'com.mybatis-flex:mybatis-flex-processor:1.5.6'
}
```
4. 配置 annotationProcessor
`mybatis-flex-processor`提供APT服务可以配置到annotationProcessorPaths配置后无需在依赖中声明`mybatis-flex-processor`依赖。
参考:[APT 设置-和 Lombok、Mapstruct 整合](../others/apt.md)
> 在Kotlin中使用时请参考[在Kotlin中使用注解处理器](../kotlin/kapt.md)
**【Kotlin】**
```kotlin
dependencies {
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor:1.5.6")
}
```
**【Groovy】**
```groovy
dependencies {
annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor:1.5.6'
}
```

View File

@ -60,6 +60,8 @@
参考:[APT 设置-和 Lombok、Mapstruct 整合](../others/apt.md)
> 在Kotlin中使用时请参考[在Kotlin中使用注解处理器](../kotlin/kapt.md)
```xml
<plugin>
<groupId>org.apache.maven.plugins</groupId>
@ -76,4 +78,3 @@
</configuration>
</plugin>
```

63
docs/zh/kotlin/kapt.md Normal file
View File

@ -0,0 +1,63 @@
# 在Kotlin中使用注解处理器
> 在Kotlin中想要使`@Table`等注解生效十分简单。只需要使用kapt即可。
>
## 在Gradle中使用
1. 应用Gradle插件kotlin-kapt
**【Kotlin】**
```kotlin
plugins {
kotlin("kapt") version "1.9.0"
}
```
**【Groovy】**
```groovy
plugins {
id "org.jetbrains.kotlin.kapt" version "1.9.0"
}
```
2. 在 dependencies 块中使用 kapt 配置添加相应的依赖项
**【Kotlin】**
```kotlin
dependencies {
kapt("org.springframework.boot:spring-boot-configuration-processor:1.5.6")
}
```
**【Groovy】**
```groovy
dependencies {
kapt 'org.springframework.boot:spring-boot-configuration-processor:1.5.6'
}
```
## 在Maven中使用
在 compile 之前在 kotlin-maven-plugin 中添加 kapt 目标的执行:
```xml
<execution>
<id>kapt</id>
<goals>
<goal>kapt</goal>
</goals>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>com.mybatis-flex</groupId>
<artifactId>mybatis-flex-processor</artifactId>
<version>1.5.6</version>
</path>
</annotationProcessorPaths>
</configuration>
</execution>
```
> 关于Kapt更详细的说明请看[Kotlin官网说明](https://book.kotlincn.net/text/kapt.html),或[Kotlin语言中文站](https://www.kotlincn.net/docs/reference/kapt.html)。

View File

@ -25,6 +25,7 @@ import java.util.Map;
/**
* 默认方言抽象类
*
* @author michael
*/
public abstract class JdbcDialect implements IDialect {
@ -47,7 +48,7 @@ public abstract class JdbcDialect implements IDialect {
column.setRawLength(columnMetaData.getColumnDisplaySize(i));
String jdbcType = columnMetaData.getColumnClassName(i);
column.setPropertyType(JdbcTypeMapping.getType(jdbcType));
column.setPropertyType(JdbcTypeMapping.getType(jdbcType, column.getRawLength()));
column.setAutoIncrement(columnMetaData.isAutoIncrement(i));

View File

@ -29,6 +29,7 @@ public class JdbcTypeMapping {
}
private static final Map<String, String> mapping = new HashMap<>();
private static JdbcTypeMapper mapper;
static {
registerMapping("[B", "byte[]");
@ -46,6 +47,14 @@ public class JdbcTypeMapping {
return mapping;
}
public static JdbcTypeMapper getMapper() {
return mapper;
}
public static void setMapper(JdbcTypeMapper mapper) {
JdbcTypeMapping.mapper = mapper;
}
/**
* 当只使用 date 类型来映射数据库的所有 "时间" 类型时调用此方法
*/
@ -56,9 +65,17 @@ public class JdbcTypeMapping {
registerMapping("java.time.LocalDate", "java.util.Date");
}
static String getType(String jdbcType) {
static String getType(String jdbcType, int length) {
if (mapper != null) {
return mapper.getType(jdbcType, length);
}
String registered = mapping.get(jdbcType);
return StringUtil.isNotBlank(registered) ? registered : jdbcType;
}
public interface JdbcTypeMapper {
String getType(String jdbcType, int length);
}
}

View File

@ -23,7 +23,6 @@ import com.mybatisflex.codegen.config.TableConfig;
import com.mybatisflex.codegen.config.TableDefConfig;
import com.mybatisflex.codegen.constant.TemplateConst;
import com.zaxxer.hikari.HikariDataSource;
import org.junit.Test;
import java.util.function.UnaryOperator;
@ -150,7 +149,7 @@ public class GeneratorTest {
generator.generate();
}
@Test
// @Test
public void testCodeGen3() {
//配置数据源
HikariDataSource dataSource = new HikariDataSource();
@ -226,7 +225,7 @@ public class GeneratorTest {
return globalConfig;
}
@Test
// @Test
public void testCodeGen4() {
// 配置数据源
HikariDataSource dataSource = new HikariDataSource();

View File

@ -16,15 +16,15 @@
package com.mybatisflex.core.activerecord;
import com.mybatisflex.core.BaseMapper;
import com.mybatisflex.core.activerecord.query.FieldsQuery;
import com.mybatisflex.core.activerecord.query.QueryModel;
import com.mybatisflex.core.activerecord.query.RelationsQuery;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.MapperQueryChain;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.core.util.SqlUtil;
import java.io.Serializable;
import java.util.List;
import java.util.Optional;
/**
* Active Record 模型
@ -36,7 +36,7 @@ import java.util.Optional;
@SuppressWarnings({"unused", "unchecked"})
public abstract class Model<T extends Model<T>>
extends QueryModel<T>
implements MapperModel<T>, Serializable {
implements MapperModel<T>, MapperQueryChain<T>, Serializable {
/**
* 根据实体类构建的条件删除数据
@ -66,175 +66,22 @@ public abstract class Model<T extends Model<T>>
return SqlUtil.toBool(baseMapper().updateByQuery((T) this, ignoreNulls, queryWrapper()));
}
/**
* 根据实体类构建的条件查询数据数量
*
* @return 数据数量
*/
public long count() {
return baseMapper().selectCountByQuery(queryWrapper());
@Override
public BaseMapper<T> baseMapper() {
return MapperModel.super.baseMapper();
}
/**
* 根据实体类构建的条件判断数据是否存在
*
* @return {@code true} 数据存在{@code false} 数据不存在
*/
public boolean exists() {
return SqlUtil.toBool(count());
@Override
public QueryWrapper toQueryWrapper() {
return queryWrapper();
}
/**
* 根据实体类构建的条件获取一条数据
*
* @return 数据
*/
public T one() {
return baseMapper().selectOneByQuery(queryWrapper());
}
/**
* 根据实体类构建的条件获取一条数据返回的数据为 asType 类型
*
* @param asType 接收数据类型
* @return 数据
*/
public <R> R oneAs(Class<R> asType) {
return baseMapper().selectOneByQueryAs(queryWrapper(), asType);
}
/**
* 根据实体类构建的条件获取一条数据并封装为 {@link Optional} 返回
*
* @return 数据
*/
public Optional<T> oneOpt() {
return Optional.ofNullable(one());
}
/**
* 根据实体类构建的条件获取一条数据返回的数据为 asType 类型并封装为 {@link Optional} 返回
*
* @param asType 接收数据类型
* @return 数据
*/
public <R> Optional<R> oneAsOpt(Class<R> asType) {
return Optional.ofNullable(oneAs(asType));
}
/**
* 根据实体类构建的条件获取第一列且第一条数据
*
* @return 第一列数据
*/
public Object obj() {
return baseMapper().selectObjectByQuery(queryWrapper());
}
/**
* 根据实体类构建的条件获取第一列且第一条数据并转换为指定类型比如 {@code Long}, {@code String}
*
* @param asType 接收数据类型
* @return 第一列数据
*/
public <R> R objAs(Class<R> asType) {
return baseMapper().selectObjectByQueryAs(queryWrapper(), asType);
}
/**
* 根据实体类构建的条件获取第一列且第一条数据并封装为 {@link Optional} 返回
*
* @return 第一列数据
*/
public Optional<Object> objOpt() {
return Optional.ofNullable(obj());
}
/**
* 根据实体类构建的条件获取第一列且第一条数据并转换为指定类型比如 {@code Long}, {@code String}
* 封装为 {@link Optional} 返回
*
* @param asType 接收数据类型
* @return 第一列数据
*/
public <R> Optional<R> objAsOpt(Class<R> asType) {
return Optional.ofNullable(objAs(asType));
}
/**
* 根据实体类构建的条件获取第一列的所有数据
*
* @return 第一列数据
*/
public List<Object> objList() {
return baseMapper().selectObjectListByQuery(queryWrapper());
}
/**
* 根据实体类构建的条件获取第一列的所有数据并转换为指定类型比如 {@code Long}, {@code String}
*
* @param asType 接收数据类型
* @return 第一列数据
*/
public <R> List<R> objListAs(Class<R> asType) {
return baseMapper().selectObjectListByQueryAs(queryWrapper(), asType);
}
/**
* 根据实体类构建的条件获取多条数据
*
* @return 数据列表
*/
public List<T> list() {
return baseMapper().selectListByQuery(queryWrapper());
}
/**
* 根据实体类构建的条件获取多条数据返回的数据为 asType 类型
*
* @param asType 接收数据类型
* @return 数据列表
*/
public <R> List<R> listAs(Class<R> asType) {
return baseMapper().selectListByQueryAs(queryWrapper(), asType);
}
/**
* 根据实体类构建的条件获取分页数据
*
* @param page 分页对象
* @return 分页数据
*/
public Page<T> page(Page<T> page) {
return baseMapper().paginate(page, queryWrapper());
}
/**
* 根据实体类构建的条件获取分页数据返回的数据为 asType 类型
*
* @param page 分页对象
* @param asType 接收数据类型
* @return 分页数据
*/
public <R> Page<R> pageAs(Page<R> page, Class<R> asType) {
return baseMapper().paginateAs(page, queryWrapper(), asType);
}
/**
* 使用 {@code Fields Query} 的方式进行关联查询
*
* @return {@code Fields Query} 查询
*/
@Override
public FieldsQuery<T> withFields() {
return new FieldsQuery<>(this);
}
/**
* 使用 {@code Relations Query} 的方式进行关联查询
*
* @return {@code Relations Query} 查询
*/
@Override
public RelationsQuery<T> withRelations() {
return new RelationsQuery<>(this);
}

View File

@ -1,160 +0,0 @@
/*
* 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.activerecord.query;
import com.mybatisflex.core.BaseMapper;
import com.mybatisflex.core.activerecord.Model;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import java.util.List;
import java.util.Optional;
/**
* 抽象关联查询
*
* @author 王帅
* @since 2023-07-30
*/
public abstract class AbstractQuery<T extends Model<T>> {
protected final Model<T> model;
protected AbstractQuery(Model<T> model) {
this.model = model;
}
/**
* @return 主键
*/
protected Object[] pkValues() {
return model.pkValues();
}
/**
* @return BaseMapper
*/
protected BaseMapper<T> baseMapper() {
return model.baseMapper();
}
/**
* @return QueryWrapper
*/
protected QueryWrapper queryWrapper() {
return model.queryWrapper();
}
/**
* 根据实体类主键获取一条数据
*
* @return 数据
*/
public abstract T oneById();
/**
* 根据实体类主键获取一条数据并封装为 {@link Optional} 返回
*
* @return 数据
*/
public Optional<T> oneByIdOpt() {
return Optional.ofNullable(oneById());
}
/**
* 根据实体类主键获取一条数据
*
* @return 数据
*/
public abstract <R> R oneByIdAs(Class<R> asType);
/**
* 根据实体类主键获取一条数据并封装为 {@link Optional} 返回
*
* @return 数据
*/
public <R> Optional<R> oneByIdAsOpt(Class<R> asType) {
return Optional.ofNullable(oneByIdAs(asType));
}
/**
* 根据实体类构建的条件获取一条数据
*
* @return 数据
*/
public abstract T one();
/**
* 根据实体类构建的条件获取一条数据返回的数据为 asType 类型
*
* @param asType 接收数据类型
* @return 数据
*/
public abstract <R> R oneAs(Class<R> asType);
/**
* 根据实体类构建的条件获取一条数据并封装为 {@link Optional} 返回
*
* @return 数据
*/
public Optional<T> oneOpt() {
return Optional.ofNullable(one());
}
/**
* 根据实体类构建的条件获取一条数据返回的数据为 asType 类型并封装为 {@link Optional} 返回
*
* @param asType 接收数据类型
* @return 数据
*/
public <R> Optional<R> oneOptAs(Class<R> asType) {
return Optional.ofNullable(oneAs(asType));
}
/**
* 根据实体类构建的条件获取多条数据
*
* @return 数据列表
*/
public abstract List<T> list();
/**
* 根据实体类构建的条件获取多条数据返回的数据为 asType 类型
*
* @param asType 接收数据类型
* @return 数据列表
*/
public abstract <R> List<R> listAs(Class<R> asType);
/**
* 根据实体类构建的条件获取分页数据
*
* @param page 分页对象
* @return 分页数据
*/
public abstract Page<T> page(Page<T> page);
/**
* 根据实体类构建的条件获取分页数据返回的数据为 asType 类型
*
* @param page 分页对象
* @param asType 接收数据类型
* @return 分页数据
*/
public abstract <R> Page<R> pageAs(Page<R> page, Class<R> asType);
}

View File

@ -17,19 +17,14 @@
package com.mybatisflex.core.activerecord.query;
import com.mybatisflex.core.activerecord.Model;
import com.mybatisflex.core.field.FieldQuery;
import com.mybatisflex.core.field.FieldQueryManager;
import com.mybatisflex.core.field.QueryBuilder;
import com.mybatisflex.core.mybatis.MappedStatementTypes;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.util.FieldWrapper;
import com.mybatisflex.core.query.FieldsBuilder;
import com.mybatisflex.core.util.LambdaGetter;
import com.mybatisflex.core.util.LambdaUtil;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 使用 {@code Fields Query} 的方式进行关联查询
@ -37,53 +32,34 @@ import java.util.Map;
* @author 王帅
* @since 2023-07-30
*/
public class FieldsQuery<T extends Model<T>> extends AbstractQuery<T> {
private final Map<String, FieldQuery> fieldQueryMap;
public class FieldsQuery<T extends Model<T>> extends FieldsBuilder<T> {
public FieldsQuery(Model<T> model) {
super(model);
this.fieldQueryMap = new HashMap<>();
}
/**
* 设置属性对应的 {@code QueryWrapper} 查询
*
* @param field 属性
* @param builder {@code QueryWrapper} 构建
* @param <F> 属性类型
* @return 属性查询构建
*/
@Override
public <F> FieldsQuery<T> fieldMapping(LambdaGetter<F> field, QueryBuilder<F> builder) {
return fieldMapping(field, false, builder);
}
/**
* 设置属性对应的 {@code QueryWrapper} 查询
*
* @param field 属性
* @param prevent 阻止对嵌套类属性的查询
* @param builder {@code QueryWrapper} 构建
* @param <F> 属性类型
* @return 属性查询构建
*/
public <F> FieldsQuery<T> fieldMapping(LambdaGetter<F> field, boolean prevent, QueryBuilder<F> builder) {
String fieldName = LambdaUtil.getFieldName(field);
Class<?> entityClass = LambdaUtil.getImplClass(field);
FieldQuery fieldQuery = new FieldQuery();
fieldQuery.setPrevent(prevent);
fieldQuery.setFieldName(fieldName);
fieldQuery.setQueryBuilder(builder);
fieldQuery.setEntityClass(entityClass);
fieldQuery.setFieldWrapper(FieldWrapper.of(entityClass, fieldName));
this.fieldQueryMap.put(entityClass.getName() + '#' + fieldName, fieldQuery);
super.fieldMapping(field, builder);
return this;
}
/**
* {@inheritDoc}
*/
@Override
public <F> FieldsBuilder<T> fieldMapping(LambdaGetter<F> field, boolean prevent, QueryBuilder<F> builder) {
super.fieldMapping(field, prevent, builder);
return this;
}
protected Object[] pkValues() {
// 懒加载实际用到的时候才会生成 主键值
return ((Model<T>) delegate).pkValues();
}
/**
* 根据主键查询一条数据
*
* @return 一条数据
*/
public T oneById() {
List<T> entities = Collections.singletonList(baseMapper().selectOneById(pkValues()));
FieldQueryManager.queryFields(baseMapper(), entities, fieldQueryMap);
@ -91,9 +67,12 @@ public class FieldsQuery<T extends Model<T>> extends AbstractQuery<T> {
}
/**
* {@inheritDoc}
* 根据主键查询一条数据返回的数据为 asType 类型
*
* @param asType 接收数据类型
* @param <R> 接收数据类型
* @return 一条数据
*/
@Override
@SuppressWarnings("unchecked")
public <R> R oneByIdAs(Class<R> asType) {
try {
@ -106,64 +85,4 @@ public class FieldsQuery<T extends Model<T>> extends AbstractQuery<T> {
}
}
/**
* {@inheritDoc}
*/
@Override
public T one() {
List<T> entities = Collections.singletonList(baseMapper().selectOneByQuery(queryWrapper()));
FieldQueryManager.queryFields(baseMapper(), entities, fieldQueryMap);
return entities.get(0);
}
/**
* {@inheritDoc}
*/
@Override
public <R> R oneAs(Class<R> asType) {
List<R> entities = Collections.singletonList(baseMapper().selectOneByQueryAs(queryWrapper(), asType));
FieldQueryManager.queryFields(baseMapper(), entities, fieldQueryMap);
return entities.get(0);
}
/**
* {@inheritDoc}
*/
@Override
public List<T> list() {
List<T> entities = baseMapper().selectListByQuery(queryWrapper());
FieldQueryManager.queryFields(baseMapper(), entities, fieldQueryMap);
return entities;
}
/**
* {@inheritDoc}
*/
@Override
public <R> List<R> listAs(Class<R> asType) {
List<R> entities = baseMapper().selectListByQueryAs(queryWrapper(), asType);
FieldQueryManager.queryFields(baseMapper(), entities, fieldQueryMap);
return entities;
}
/**
* {@inheritDoc}
*/
@Override
public Page<T> page(Page<T> page) {
baseMapper().paginate(page, queryWrapper());
FieldQueryManager.queryFields(baseMapper(), page.getRecords(), fieldQueryMap);
return page;
}
/**
* {@inheritDoc}
*/
@Override
public <R> Page<R> pageAs(Page<R> page, Class<R> asType) {
baseMapper().paginateAs(page, queryWrapper(), asType);
FieldQueryManager.queryFields(baseMapper(), page.getRecords(), fieldQueryMap);
return page;
}
}

View File

@ -17,10 +17,8 @@
package com.mybatisflex.core.activerecord.query;
import com.mybatisflex.core.activerecord.Model;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.relation.RelationManager;
import java.util.List;
import com.mybatisflex.core.query.RelationsBuilder;
import com.mybatisflex.core.util.LambdaGetter;
/**
* 使用 {@code Relations Query} 的方式进行关联查询
@ -28,102 +26,59 @@ import java.util.List;
* @author 王帅
* @since 2023-07-30
*/
public class RelationsQuery<T extends Model<T>> extends AbstractQuery<T> {
public class RelationsQuery<T extends Model<T>> extends RelationsBuilder<T> {
public RelationsQuery(Model<T> model) {
super(model);
}
/**
* 忽略查询部分 {@code Relations} 注解标记的属性
*
* @param fields 属性
* @return {@code Relations} 查询构建
*/
@Override
public RelationsQuery<T> ignoreRelations(String... fields) {
RelationManager.addIgnoreRelations(fields);
return this;
}
/**
* 设置父子关系查询中默认的递归查询深度
*
* @param maxDepth 查询深度
* @return {@code Relations} 查询构建
*/
public RelationsQuery<T> maxDepth(int maxDepth) {
RelationManager.setMaxDepth(maxDepth);
return this;
}
/**
* 添加额外的 {@code Relations} 查询条件
*
* @param key
* @param value
* @return {@code Relations} 查询构建
*/
public RelationsQuery<T> extraConditionParam(String key, Object value) {
RelationManager.addExtraConditionParam(key, value);
super.ignoreRelations(fields);
return this;
}
@Override
public RelationsQuery<T> ignoreRelations(LambdaGetter<T>... fields) {
super.ignoreRelations(fields);
return this;
}
@Override
public RelationsQuery<T> maxDepth(int maxDepth) {
super.maxDepth(maxDepth);
return this;
}
@Override
public RelationsQuery<T> extraConditionParam(String key, Object value) {
super.extraConditionParam(key, value);
return this;
}
protected Object[] pkValues() {
// 懒加载实际用到的时候才会生成 主键值
return ((Model<T>) delegate).pkValues();
}
/**
* 根据主键查询一条数据
*
* @return 一条数据
*/
public T oneById() {
return baseMapper().selectOneWithRelationsById(pkValues());
}
@Override
/**
* 根据主键查询一条数据返回的数据为 asType 类型
*
* @param asType 接收数据类型
* @param <R> 接收数据类型
* @return 一条数据
*/
public <R> R oneByIdAs(Class<R> asType) {
return baseMapper().selectOneWithRelationsByIdAs(pkValues(), asType);
}
/**
* {@inheritDoc}
*/
@Override
public T one() {
return baseMapper().selectOneWithRelationsByQuery(queryWrapper());
}
/**
* {@inheritDoc}
*/
@Override
public <R> R oneAs(Class<R> asType) {
return baseMapper().selectOneWithRelationsByQueryAs(queryWrapper(), asType);
}
/**
* {@inheritDoc}
*/
@Override
public List<T> list() {
return baseMapper().selectListWithRelationsByQuery(queryWrapper());
}
/**
* {@inheritDoc}
*/
@Override
public <R> List<R> listAs(Class<R> asType) {
return baseMapper().selectListWithRelationsByQueryAs(queryWrapper(), asType);
}
/**
* {@inheritDoc}
*/
@Override
public Page<T> page(Page<T> page) {
return baseMapper().paginateWithRelations(page, queryWrapper());
}
/**
* {@inheritDoc}
*/
@Override
public <R> Page<R> pageAs(Page<R> page, Class<R> asType) {
return baseMapper().paginateWithRelationsAs(page, queryWrapper(), asType);
}
}

View File

@ -21,6 +21,9 @@ import org.apache.ibatis.logging.LogFactory;
import javax.sql.DataSource;
import java.lang.reflect.Method;
/**
* @author michael
*/
public class DataSourceManager {
private static DataSourceDecipher decipher;

View File

@ -49,8 +49,13 @@ public class FlexDataSource extends AbstractDataSource {
private final DataSource defaultDataSource;
public FlexDataSource(String dataSourceKey, DataSource dataSource) {
this(dataSourceKey, dataSource, true);
}
DataSourceManager.decryptDataSource(dataSource);
public FlexDataSource(String dataSourceKey, DataSource dataSource, boolean needDecryptDataSource) {
if (needDecryptDataSource) {
DataSourceManager.decryptDataSource(dataSource);
}
this.defaultDataSourceKey = dataSourceKey;
this.defaultDataSource = dataSource;
@ -61,11 +66,19 @@ public class FlexDataSource extends AbstractDataSource {
}
public void addDataSource(String dataSourceKey, DataSource dataSource) {
DataSourceManager.decryptDataSource(dataSource);
addDataSource(dataSourceKey, dataSource, true);
}
public void addDataSource(String dataSourceKey, DataSource dataSource, boolean needDecryptDataSource) {
if (needDecryptDataSource) {
DataSourceManager.decryptDataSource(dataSource);
}
dataSourceMap.put(dataSourceKey, dataSource);
dbTypeHashMap.put(dataSourceKey, DbTypeUtil.getDbType(dataSource));
}
public void removeDatasource(String dataSourceKey) {
dataSourceMap.remove(dataSourceKey);
dbTypeHashMap.remove(dataSourceKey);

View File

@ -0,0 +1,49 @@
/*
* 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.BaseMapper;
/**
* 抽象关联查询
*
* @author 王帅
* @since 2023-08-08
*/
public abstract class AbstractQueryBuilder<T> implements ChainQuery<T> {
protected final MapperQueryChain<T> delegate;
protected AbstractQueryBuilder(MapperQueryChain<T> delegate) {
this.delegate = delegate;
}
/**
* @return BaseMapper
*/
protected BaseMapper<T> baseMapper() {
return delegate.baseMapper();
}
/**
* @return QueryWrapper
*/
protected QueryWrapper queryWrapper() {
return delegate.toQueryWrapper();
}
}

View File

@ -0,0 +1,110 @@
/*
* 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.paginate.Page;
import java.util.List;
import java.util.Optional;
/**
* <p>链式查询接口
*
* <p>该接口定义了通用的链式查询方法
* <ul>
* <li><b>one</b>: 查询一条数据
* <li><b>list</b>: 查询多条数据
* <li><b>page</b>: 分页查询数据
* </ul>
*
* @param <T> 实体类类型
* @author 王帅
* @since 2023-08-08
*/
public interface ChainQuery<T> {
/**
* 获取一条数据
*
* @return 一条数据
*/
T one();
/**
* 获取一条数据返回的数据为 asType 类型
*
* @param asType 接收数据类型
* @param <R> 接收数据类型
* @return 一条数据
*/
<R> R oneAs(Class<R> asType);
/**
* 获取一条数据并封装为 {@link Optional} 返回
*
* @return 一条数据
*/
default Optional<T> oneOpt() {
return Optional.ofNullable(one());
}
/**
* 获取一条数据返回的数据为 asType 类型并封装为 {@link Optional} 返回
*
* @param asType 接收数据类型
* @param <R> 接收数据类型
* @return 一条数据
*/
default <R> Optional<R> oneAsOpt(Class<R> asType) {
return Optional.ofNullable(oneAs(asType));
}
/**
* 获取多条数据
*
* @return 数据列表
*/
List<T> list();
/**
* 获取多条数据返回的数据为 asType 类型
*
* @param asType 接收数据类型
* @param <R> 接收数据类型
* @return 数据列表
*/
<R> List<R> listAs(Class<R> asType);
/**
* 获取分页数据
*
* @param page 分页对象
* @return 分页数据
*/
Page<T> page(Page<T> page);
/**
* 获取分页数据返回的数据为 asType 类型
*
* @param page 分页对象
* @param asType 接收数据类型
* @param <R> 接收数据类型
* @return 分页数据
*/
<R> Page<R> pageAs(Page<R> page, Class<R> asType);
}

View File

@ -0,0 +1,141 @@
/*
* 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.field.FieldQuery;
import com.mybatisflex.core.field.FieldQueryManager;
import com.mybatisflex.core.field.QueryBuilder;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.util.FieldWrapper;
import com.mybatisflex.core.util.LambdaGetter;
import com.mybatisflex.core.util.LambdaUtil;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 使用 {@code Fields Query} 的方式进行关联查询
*
* @author 王帅
* @since 2023-08-08
*/
public class FieldsBuilder<T> extends AbstractQueryBuilder<T> {
protected final Map<String, FieldQuery> fieldQueryMap;
public FieldsBuilder(MapperQueryChain<T> delegate) {
super(delegate);
this.fieldQueryMap = new HashMap<>();
}
/**
* 设置属性对应的 {@code QueryWrapper} 查询
*
* @param field 属性
* @param builder {@code QueryWrapper} 构建
* @param <F> 属性类型
* @return 属性查询构建
*/
public <F> FieldsBuilder<T> fieldMapping(LambdaGetter<F> field, QueryBuilder<F> builder) {
return fieldMapping(field, false, builder);
}
/**
* 设置属性对应的 {@code QueryWrapper} 查询
*
* @param field 属性
* @param prevent 阻止对嵌套类属性的查询
* @param builder {@code QueryWrapper} 构建
* @param <F> 属性类型
* @return 属性查询构建
*/
public <F> FieldsBuilder<T> fieldMapping(LambdaGetter<F> field, boolean prevent, QueryBuilder<F> builder) {
String fieldName = LambdaUtil.getFieldName(field);
Class<?> entityClass = LambdaUtil.getImplClass(field);
FieldQuery fieldQuery = new FieldQuery();
fieldQuery.setPrevent(prevent);
fieldQuery.setFieldName(fieldName);
fieldQuery.setQueryBuilder(builder);
fieldQuery.setEntityClass(entityClass);
fieldQuery.setFieldWrapper(FieldWrapper.of(entityClass, fieldName));
this.fieldQueryMap.put(entityClass.getName() + '#' + fieldName, fieldQuery);
return this;
}
/**
* {@inheritDoc}
*/
@Override
public T one() {
List<T> entities = Collections.singletonList(baseMapper().selectOneByQuery(queryWrapper()));
FieldQueryManager.queryFields(baseMapper(), entities, fieldQueryMap);
return entities.get(0);
}
/**
* {@inheritDoc}
*/
@Override
public <R> R oneAs(Class<R> asType) {
List<R> entities = Collections.singletonList(baseMapper().selectOneByQueryAs(queryWrapper(), asType));
FieldQueryManager.queryFields(baseMapper(), entities, fieldQueryMap);
return entities.get(0);
}
/**
* {@inheritDoc}
*/
@Override
public List<T> list() {
List<T> entities = baseMapper().selectListByQuery(queryWrapper());
FieldQueryManager.queryFields(baseMapper(), entities, fieldQueryMap);
return entities;
}
/**
* {@inheritDoc}
*/
@Override
public <R> List<R> listAs(Class<R> asType) {
List<R> entities = baseMapper().selectListByQueryAs(queryWrapper(), asType);
FieldQueryManager.queryFields(baseMapper(), entities, fieldQueryMap);
return entities;
}
/**
* {@inheritDoc}
*/
@Override
public Page<T> page(Page<T> page) {
baseMapper().paginate(page, queryWrapper());
FieldQueryManager.queryFields(baseMapper(), page.getRecords(), fieldQueryMap);
return page;
}
/**
* {@inheritDoc}
*/
@Override
public <R> Page<R> pageAs(Page<R> page, Class<R> asType) {
baseMapper().paginateAs(page, queryWrapper(), asType);
FieldQueryManager.queryFields(baseMapper(), page.getRecords(), fieldQueryMap);
return page;
}
}

View File

@ -0,0 +1,195 @@
/*
* 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.BaseMapper;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.util.SqlUtil;
import java.util.List;
import java.util.Optional;
/**
* <p>链式 {@link BaseMapper} 查询
*
* <p>要求实现类除了包含有 {@link BaseMapper} 接口的引用外还必须具有 {@link QueryWrapper}
* 的查询条件构建功能在使用时
* <ul>
* <li>通过 {@link #baseMapper()} 获取该实现类对应的 {@link BaseMapper} 引用
* <li>通过 {@link #toQueryWrapper()} 将该实现类转换为 {@link QueryWrapper} 对象
* </ul>
*
* @param <T> 实体类类型
* @author 王帅
* @since 2023-08-08
*/
public interface MapperQueryChain<T> extends ChainQuery<T> {
/**
* 该实现类对应的 {@link BaseMapper} 对象
*
* @return {@link BaseMapper}
*/
BaseMapper<T> baseMapper();
/**
* 将该实现类转换为 {@link QueryWrapper} 对象
*
* @return {@link QueryWrapper}
*/
QueryWrapper toQueryWrapper();
/**
* 查询数据数量
*
* @return 数据数量
*/
default long count() {
return baseMapper().selectCountByQuery(toQueryWrapper());
}
/**
* 判断数据是否存在
*
* @return {@code true} 数据存在{@code false} 数据不存在
*/
default boolean exists() {
return SqlUtil.toBool(count());
}
/**
* {@inheritDoc}
*/
default T one() {
return baseMapper().selectOneByQuery(toQueryWrapper());
}
/**
* {@inheritDoc}
*/
default <R> R oneAs(Class<R> asType) {
return baseMapper().selectOneByQueryAs(toQueryWrapper(), asType);
}
/**
* 获取第一列且第一条数据
*
* @return 第一列数据
*/
default Object obj() {
return baseMapper().selectObjectByQuery(toQueryWrapper());
}
/**
* 获取第一列且第一条数据并转换为指定类型比如 {@code Long}, {@code String}
*
* @param asType 接收数据类型
* @param <R> 接收数据类型
* @return 第一列数据
*/
default <R> R objAs(Class<R> asType) {
return baseMapper().selectObjectByQueryAs(toQueryWrapper(), asType);
}
/**
* 获取第一列且第一条数据并封装为 {@link Optional} 返回
*
* @return 第一列数据
*/
default Optional<Object> objOpt() {
return Optional.ofNullable(obj());
}
/**
* 获取第一列且第一条数据并转换为指定类型比如 {@code Long}, {@code String}
* 封装为 {@link Optional} 返回
*
* @param asType 接收数据类型
* @param <R> 接收数据类型
* @return 第一列数据
*/
default <R> Optional<R> objAsOpt(Class<R> asType) {
return Optional.ofNullable(objAs(asType));
}
/**
* 获取第一列的所有数据
*
* @return 第一列数据
*/
default List<Object> objList() {
return baseMapper().selectObjectListByQuery(toQueryWrapper());
}
/**
* 获取第一列的所有数据并转换为指定类型比如 {@code Long}, {@code String}
*
* @param asType 接收数据类型
* @param <R> 接收数据类型
* @return 第一列数据
*/
default <R> List<R> objListAs(Class<R> asType) {
return baseMapper().selectObjectListByQueryAs(toQueryWrapper(), asType);
}
/**
* {@inheritDoc}
*/
default List<T> list() {
return baseMapper().selectListByQuery(toQueryWrapper());
}
/**
* {@inheritDoc}
*/
default <R> List<R> listAs(Class<R> asType) {
return baseMapper().selectListByQueryAs(toQueryWrapper(), asType);
}
/**
* {@inheritDoc}
*/
default Page<T> page(Page<T> page) {
return baseMapper().paginate(page, toQueryWrapper());
}
/**
* {@inheritDoc}
*/
default <R> Page<R> pageAs(Page<R> page, Class<R> asType) {
return baseMapper().paginateAs(page, toQueryWrapper(), asType);
}
/**
* 使用 {@code Fields Query} 的方式进行关联查询
*
* @return {@code Fields Query} 查询
*/
default FieldsBuilder<T> withFields() {
return new FieldsBuilder<>(this);
}
/**
* 使用 {@code Relations Query} 的方式进行关联查询
*
* @return {@code Relations Query} 查询
*/
default RelationsBuilder<T> withRelations() {
return new RelationsBuilder<>(this);
}
}

View File

@ -20,18 +20,17 @@ import com.mybatisflex.core.BaseMapper;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.table.TableInfo;
import com.mybatisflex.core.table.TableInfoFactory;
import com.mybatisflex.core.util.SqlUtil;
import java.util.List;
import java.util.Optional;
/**
* {@link QueryWrapper}链式调用
* {@link QueryWrapper} 链式调用
*
* @author 王帅
* @since 2023-07-22
*/
public class QueryChain<T> extends QueryWrapperAdapter<QueryChain<T>> {
public class QueryChain<T> extends QueryWrapperAdapter<QueryChain<T>> implements MapperQueryChain<T> {
private final BaseMapper<T> baseMapper;
@ -43,98 +42,76 @@ public class QueryChain<T> extends QueryWrapperAdapter<QueryChain<T>> {
return new QueryChain<>(baseMapper);
}
public long count() {
return baseMapper.selectCountByQuery(this);
@Override
public BaseMapper<T> baseMapper() {
return baseMapper;
}
public boolean exists() {
return SqlUtil.toBool(count());
}
public T one() {
return baseMapper.selectOneByQuery(this);
}
public <R> R oneAs(Class<R> asType) {
return baseMapper.selectOneByQueryAs(this, asType);
@Override
public QueryWrapper toQueryWrapper() {
return this;
}
/**
* @deprecated 该方法将在 1.6.0 版本移除
*/
@Deprecated
public T oneWithRelations() {
return baseMapper.selectOneWithRelationsByQuery(this);
}
/**
* @deprecated 该方法将在 1.6.0 版本移除
*/
@Deprecated
public <R> R oneWithRelationsAs(Class<R> asType) {
return baseMapper.selectOneWithRelationsByQueryAs(this, asType);
}
public Optional<T> oneOpt() {
return Optional.ofNullable(baseMapper.selectOneByQuery(this));
}
public <R> Optional<R> oneAsOpt(Class<R> asType) {
return Optional.ofNullable(baseMapper.selectOneByQueryAs(this, asType));
}
/**
* @deprecated 该方法将在 1.6.0 版本移除
*/
@Deprecated
public Optional<T> oneWithRelationsOpt() {
return Optional.ofNullable(baseMapper.selectOneWithRelationsByQuery(this));
}
/**
* @deprecated 该方法将在 1.6.0 版本移除
*/
@Deprecated
public <R> Optional<R> oneWithRelationsAsOpt(Class<R> asType) {
return Optional.ofNullable(baseMapper.selectOneWithRelationsByQueryAs(this, asType));
}
public Object obj() {
return baseMapper.selectObjectByQuery(this);
}
public <R> R objAs(Class<R> asType) {
return baseMapper.selectObjectByQueryAs(this, asType);
}
public Optional<Object> objOpt() {
return Optional.ofNullable(baseMapper.selectObjectByQuery(this));
}
public <R> Optional<R> objAsOpt(Class<R> asType) {
return Optional.ofNullable(baseMapper.selectObjectByQueryAs(this, asType));
}
public List<Object> objList() {
return baseMapper.selectObjectListByQuery(this);
}
public <R> List<R> objListAs(Class<R> asType) {
return baseMapper.selectObjectListByQueryAs(this, asType);
}
public List<T> list() {
return baseMapper.selectListByQuery(this);
}
/**
* @deprecated 该方法将在 1.6.0 版本移除
*/
@Deprecated
public List<T> listWithRelations() {
return baseMapper.selectListWithRelationsByQuery(this);
}
public <R> List<R> listAs(Class<R> asType) {
return baseMapper.selectListByQueryAs(this, asType);
}
/**
* @deprecated 该方法将在 1.6.0 版本移除
*/
@Deprecated
public <R> List<R> listWithRelationsAs(Class<R> asType) {
return baseMapper.selectListWithRelationsByQueryAs(this, asType);
}
public Page<T> page(Page<T> page) {
return baseMapper.paginate(page, this);
}
/**
* @deprecated 该方法将在 1.6.0 版本移除
*/
@Deprecated
public Page<T> pageWithRelations(Page<T> page) {
return baseMapper.paginateWithRelations(page, this);
}
public <R> Page<R> pageAs(Page<R> page, Class<R> asType) {
return baseMapper.paginateAs(page, this, asType);
}
/**
* @deprecated 该方法将在 1.6.0 版本移除
*/
@Deprecated
public <R> Page<R> pageWithRelationsAs(Page<R> page, Class<R> asType) {
return baseMapper.paginateWithRelationsAs(page, this, asType);
}
@ -145,4 +122,5 @@ public class QueryChain<T> extends QueryWrapperAdapter<QueryChain<T>> {
CPI.setFromIfNecessary(this, tableInfo.getSchema(), tableInfo.getTableName());
return super.toSQL();
}
}

View File

@ -140,7 +140,7 @@ public class QueryColumn implements CloneSupport<QueryColumn> {
* @param value
*/
public QueryCondition eq(Object value) {
if (value == null) {
if (value == null || QueryColumnBehavior.shouldIgnoreValue(value)) {
return QueryCondition.createEmpty();
}
return QueryCondition.create(this, SqlConsts.EQUALS, value);
@ -148,7 +148,7 @@ public class QueryColumn implements CloneSupport<QueryColumn> {
public <T> QueryCondition eq(Object value, Predicate<T> fn) {
if (value == null) {
if (value == null || QueryColumnBehavior.shouldIgnoreValue(value)) {
return QueryCondition.createEmpty();
}
return QueryCondition.create(this, SqlConsts.EQUALS, value).when(fn);
@ -161,14 +161,14 @@ public class QueryColumn implements CloneSupport<QueryColumn> {
* @param value
*/
public QueryCondition ne(Object value) {
if (value == null) {
if (value == null || QueryColumnBehavior.shouldIgnoreValue(value)) {
return QueryCondition.createEmpty();
}
return QueryCondition.create(this, SqlConsts.NOT_EQUALS, value);
}
public <T> QueryCondition ne(Object value, Predicate<T> fn) {
if (value == null) {
if (value == null || QueryColumnBehavior.shouldIgnoreValue(value)) {
return QueryCondition.createEmpty();
}
return QueryCondition.create(this, SqlConsts.NOT_EQUALS, value).when(fn);
@ -181,14 +181,14 @@ public class QueryColumn implements CloneSupport<QueryColumn> {
* @param value
*/
public QueryCondition like(Object value) {
if (value == null) {
if (value == null || QueryColumnBehavior.shouldIgnoreValue(value)) {
return QueryCondition.createEmpty();
}
return QueryCondition.create(this, SqlConsts.LIKE, "%" + value + "%");
}
public <T> QueryCondition like(Object value, Predicate<T> fn) {
if (value == null) {
if (value == null || QueryColumnBehavior.shouldIgnoreValue(value)) {
return QueryCondition.createEmpty();
}
return QueryCondition.create(this, SqlConsts.LIKE, "%" + value + "%").when(fn);
@ -196,14 +196,14 @@ public class QueryColumn implements CloneSupport<QueryColumn> {
public QueryCondition likeLeft(Object value) {
if (value == null) {
if (value == null || QueryColumnBehavior.shouldIgnoreValue(value)) {
return QueryCondition.createEmpty();
}
return QueryCondition.create(this, SqlConsts.LIKE, value + "%");
}
public <T> QueryCondition likeLeft(Object value, Predicate<T> fn) {
if (value == null) {
if (value == null || QueryColumnBehavior.shouldIgnoreValue(value)) {
return QueryCondition.createEmpty();
}
return QueryCondition.create(this, SqlConsts.LIKE, value + "%").when(fn);
@ -211,14 +211,14 @@ public class QueryColumn implements CloneSupport<QueryColumn> {
public QueryCondition likeRight(Object value) {
if (value == null) {
if (value == null || QueryColumnBehavior.shouldIgnoreValue(value)) {
return QueryCondition.createEmpty();
}
return QueryCondition.create(this, SqlConsts.LIKE, "%" + value);
}
public <T> QueryCondition likeRight(Object value, Predicate<T> fn) {
if (value == null) {
if (value == null || QueryColumnBehavior.shouldIgnoreValue(value)) {
return QueryCondition.createEmpty();
}
return QueryCondition.create(this, SqlConsts.LIKE, "%" + value).when(fn);
@ -226,35 +226,34 @@ public class QueryColumn implements CloneSupport<QueryColumn> {
public QueryCondition likeRaw(Object value) {
if (value == null) {
if (value == null || QueryColumnBehavior.shouldIgnoreValue(value)) {
return QueryCondition.createEmpty();
}
return QueryCondition.create(this, SqlConsts.LIKE, value);
}
public <T> QueryCondition likeRaw(Object value, Predicate<T> fn) {
if (value == null) {
if (value == null || QueryColumnBehavior.shouldIgnoreValue(value)) {
return QueryCondition.createEmpty();
}
return QueryCondition.create(this, SqlConsts.LIKE, value).when(fn);
}
/**
* not like %%
*
* @param value
*/
public QueryCondition notLike(Object value) {
if (value == null) {
if (value == null || QueryColumnBehavior.shouldIgnoreValue(value)) {
return QueryCondition.createEmpty();
}
return QueryCondition.create(this, SqlConsts.NOT_LIKE, "%" + value + "%");
}
public <T> QueryCondition notLike(Object value, Predicate<T> fn) {
if (value == null) {
if (value == null || QueryColumnBehavior.shouldIgnoreValue(value)) {
return QueryCondition.createEmpty();
}
return QueryCondition.create(this, SqlConsts.NOT_LIKE, "%" + value + "%").when(fn);
@ -262,14 +261,14 @@ public class QueryColumn implements CloneSupport<QueryColumn> {
public QueryCondition notLikeLeft(Object value) {
if (value == null) {
if (value == null || QueryColumnBehavior.shouldIgnoreValue(value)) {
return QueryCondition.createEmpty();
}
return QueryCondition.create(this, SqlConsts.NOT_LIKE, value + "%");
}
public <T> QueryCondition notLikeLeft(Object value, Predicate<T> fn) {
if (value == null) {
if (value == null || QueryColumnBehavior.shouldIgnoreValue(value)) {
return QueryCondition.createEmpty();
}
return QueryCondition.create(this, SqlConsts.NOT_LIKE, value + "%").when(fn);
@ -277,35 +276,34 @@ public class QueryColumn implements CloneSupport<QueryColumn> {
public QueryCondition notLikeRight(Object value) {
if (value == null) {
if (value == null || QueryColumnBehavior.shouldIgnoreValue(value)) {
return QueryCondition.createEmpty();
}
return QueryCondition.create(this, SqlConsts.NOT_LIKE, "%" + value);
}
public <T> QueryCondition notLikeRight(Object value, Predicate<T> fn) {
if (value == null) {
if (value == null || QueryColumnBehavior.shouldIgnoreValue(value)) {
return QueryCondition.createEmpty();
}
return QueryCondition.create(this, SqlConsts.NOT_LIKE, "%" + value).when(fn);
}
/**
* 大于 greater than
*
* @param value
*/
public QueryCondition gt(Object value) {
if (value == null) {
if (value == null || QueryColumnBehavior.shouldIgnoreValue(value)) {
return QueryCondition.createEmpty();
}
return QueryCondition.create(this, SqlConsts.GT, value);
}
public <T> QueryCondition gt(Object value, Predicate<T> fn) {
if (value == null) {
if (value == null || QueryColumnBehavior.shouldIgnoreValue(value)) {
return QueryCondition.createEmpty();
}
return QueryCondition.create(this, SqlConsts.GT, value).when(fn);
@ -317,14 +315,14 @@ public class QueryColumn implements CloneSupport<QueryColumn> {
* @param value
*/
public QueryCondition ge(Object value) {
if (value == null) {
if (value == null || QueryColumnBehavior.shouldIgnoreValue(value)) {
return QueryCondition.createEmpty();
}
return QueryCondition.create(this, SqlConsts.GE, value);
}
public <T> QueryCondition ge(Object value, Predicate<T> fn) {
if (value == null) {
if (value == null || QueryColumnBehavior.shouldIgnoreValue(value)) {
return QueryCondition.createEmpty();
}
return QueryCondition.create(this, SqlConsts.GE, value).when(fn);
@ -336,14 +334,14 @@ public class QueryColumn implements CloneSupport<QueryColumn> {
* @param value
*/
public QueryCondition lt(Object value) {
if (value == null) {
if (value == null || QueryColumnBehavior.shouldIgnoreValue(value)) {
return QueryCondition.createEmpty();
}
return QueryCondition.create(this, SqlConsts.LT, value);
}
public <T> QueryCondition lt(Object value, Predicate<T> fn) {
if (value == null) {
if (value == null || QueryColumnBehavior.shouldIgnoreValue(value)) {
return QueryCondition.createEmpty();
}
return QueryCondition.create(this, SqlConsts.LT, value).when(fn);
@ -355,14 +353,14 @@ public class QueryColumn implements CloneSupport<QueryColumn> {
* @param value
*/
public QueryCondition le(Object value) {
if (value == null) {
if (value == null || QueryColumnBehavior.shouldIgnoreValue(value)) {
return QueryCondition.createEmpty();
}
return QueryCondition.create(this, SqlConsts.LE, value);
}
public <T> QueryCondition le(Object value, Predicate<T> fn) {
if (value == null) {
if (value == null || QueryColumnBehavior.shouldIgnoreValue(value)) {
return QueryCondition.createEmpty();
}
return QueryCondition.create(this, SqlConsts.LE, value).when(fn);
@ -372,7 +370,7 @@ public class QueryColumn implements CloneSupport<QueryColumn> {
/**
* IS NULL
*
* @return
* @return QueryCondition
*/
public QueryCondition isNull() {
return QueryCondition.create(this, SqlConsts.IS_NULL, null);
@ -386,7 +384,7 @@ public class QueryColumn implements CloneSupport<QueryColumn> {
/**
* IS NOT NULL
*
* @return
* @return QueryCondition
*/
public QueryCondition isNotNull() {
return QueryCondition.create(this, SqlConsts.IS_NOT_NULL, null);
@ -401,21 +399,32 @@ public class QueryColumn implements CloneSupport<QueryColumn> {
* in arrays
*
* @param arrays
* @return
* @return QueryCondition
*/
public QueryCondition in(Object... arrays) {
//忽略 QueryWrapper.in("name", null) 的情况
if (arrays == null || arrays.length == 0 || (arrays.length == 1 && arrays[0] == null)) {
if (arrays == null || arrays.length == 0 || (arrays.length == 1 && arrays[0] == null) || QueryColumnBehavior.shouldIgnoreValue(arrays)) {
return QueryCondition.createEmpty();
}
if (arrays.length == 1 && QueryColumnBehavior.isSmartConvertInToEquals()) {
return QueryCondition.create(this, SqlConsts.EQUALS, arrays[0]);
}
return QueryCondition.create(this, SqlConsts.IN, arrays);
}
public <T> QueryCondition in(Object[] arrays, Predicate<T> fn) {
//忽略 QueryWrapper.in("name", null) 的情况
if (arrays == null || arrays.length == 0 || (arrays.length == 1 && arrays[0] == null)) {
if (arrays == null || arrays.length == 0 || (arrays.length == 1 && arrays[0] == null) || QueryColumnBehavior.shouldIgnoreValue(arrays)) {
return QueryCondition.createEmpty();
}
if (arrays.length == 1 && QueryColumnBehavior.isSmartConvertInToEquals()) {
return QueryCondition.create(this, SqlConsts.EQUALS, arrays[0]);
}
return QueryCondition.create(this, SqlConsts.IN, arrays).when(fn);
}
@ -423,13 +432,20 @@ public class QueryColumn implements CloneSupport<QueryColumn> {
* in child select
*
* @param queryWrapper
* @return
* @return QueryCondition
*/
public QueryCondition in(QueryWrapper queryWrapper) {
if (queryWrapper == null || QueryColumnBehavior.shouldIgnoreValue(queryWrapper)) {
return QueryCondition.createEmpty();
}
return QueryCondition.create(this, SqlConsts.IN, queryWrapper);
}
public <T> QueryCondition in(QueryWrapper queryWrapper, Predicate<T> fn) {
if (queryWrapper == null || QueryColumnBehavior.shouldIgnoreValue(queryWrapper)) {
return QueryCondition.createEmpty();
}
return QueryCondition.create(this, SqlConsts.IN, queryWrapper).when(fn);
}
@ -438,17 +454,17 @@ public class QueryColumn implements CloneSupport<QueryColumn> {
* in Collection
*
* @param collection
* @return
* @return QueryCondition
*/
public QueryCondition in(Collection<?> collection) {
if (collection != null && !collection.isEmpty()) {
if (collection != null && !collection.isEmpty() && !QueryColumnBehavior.shouldIgnoreValue(collection)) {
return in(collection.toArray());
}
return QueryCondition.createEmpty();
}
public <T> QueryCondition in(Collection<?> collection, Predicate<T> fn) {
if (collection != null && !collection.isEmpty()) {
if (collection != null && !collection.isEmpty() && !QueryColumnBehavior.shouldIgnoreValue(collection)) {
return in(collection.toArray(), fn);
}
return QueryCondition.createEmpty();
@ -458,21 +474,31 @@ public class QueryColumn implements CloneSupport<QueryColumn> {
* not int arrays
*
* @param arrays
* @return
* @return QueryCondition
*/
public QueryCondition notIn(Object... arrays) {
//忽略 QueryWrapper.notIn("name", null) 的情况
if (arrays == null || arrays.length == 0 || (arrays.length == 1 && arrays[0] == null)) {
if (arrays == null || arrays.length == 0 || (arrays.length == 1 && arrays[0] == null) || QueryColumnBehavior.shouldIgnoreValue(arrays)) {
return QueryCondition.createEmpty();
}
if (arrays.length == 1 && QueryColumnBehavior.isSmartConvertInToEquals()) {
return QueryCondition.create(this, SqlConsts.NOT_EQUALS, arrays[0]);
}
return QueryCondition.create(this, SqlConsts.NOT_IN, arrays);
}
public <T> QueryCondition notIn(Object[] arrays, Predicate<T> fn) {
//忽略 QueryWrapper.notIn("name", null) 的情况
if (arrays == null || arrays.length == 0 || (arrays.length == 1 && arrays[0] == null)) {
if (arrays == null || arrays.length == 0 || (arrays.length == 1 && arrays[0] == null) || QueryColumnBehavior.shouldIgnoreValue(arrays)) {
return QueryCondition.createEmpty();
}
if (arrays.length == 1 && QueryColumnBehavior.isSmartConvertInToEquals()) {
return QueryCondition.create(this, SqlConsts.NOT_EQUALS, arrays[0]);
}
return QueryCondition.create(this, SqlConsts.NOT_IN, arrays).when(fn);
}
@ -481,17 +507,17 @@ public class QueryColumn implements CloneSupport<QueryColumn> {
* not in Collection
*
* @param collection
* @return
* @return QueryCondition
*/
public QueryCondition notIn(Collection<?> collection) {
if (collection != null && !collection.isEmpty()) {
if (collection != null && !collection.isEmpty() && !QueryColumnBehavior.shouldIgnoreValue(collection)) {
return notIn(collection.toArray());
}
return QueryCondition.createEmpty();
}
public <T> QueryCondition notIn(Collection<?> collection, Predicate<T> fn) {
if (collection != null && !collection.isEmpty()) {
if (collection != null && !collection.isEmpty() && !QueryColumnBehavior.shouldIgnoreValue(collection)) {
return notIn(collection.toArray(), fn);
}
return QueryCondition.createEmpty();
@ -503,10 +529,17 @@ public class QueryColumn implements CloneSupport<QueryColumn> {
* @param queryWrapper
*/
public QueryCondition notIn(QueryWrapper queryWrapper) {
if (queryWrapper == null || QueryColumnBehavior.shouldIgnoreValue(queryWrapper)) {
return QueryCondition.createEmpty();
}
return QueryCondition.create(this, SqlConsts.NOT_IN, queryWrapper);
}
public <T> QueryCondition notIn(QueryWrapper queryWrapper, Predicate<T> fn) {
if (queryWrapper == null || QueryColumnBehavior.shouldIgnoreValue(queryWrapper)) {
return QueryCondition.createEmpty();
}
return QueryCondition.create(this, SqlConsts.NOT_IN, queryWrapper).when(fn);
}
@ -615,7 +648,7 @@ public class QueryColumn implements CloneSupport<QueryColumn> {
return null;
}
if (selfTable != null && StringUtil.isNotBlank(selfTable.alias)){
if (selfTable != null && StringUtil.isNotBlank(selfTable.alias)) {
return selfTable;
}

View File

@ -0,0 +1,60 @@
/*
* 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 java.util.function.Predicate;
/**
* @author michael
*/
public class QueryColumnBehavior {
/**
* 自定义全局的自动忽略参数的方法
*/
private static Predicate<Object> ignoreFunction;
/**
* 是否自动把 in(...) 只有 1 个参数的内容转换为相等 =
*/
private static boolean smartConvertInToEquals = false;
public static Predicate<Object> getIgnoreFunction() {
return ignoreFunction;
}
public static void setIgnoreFunction(Predicate<Object> ignoreFunction) {
QueryColumnBehavior.ignoreFunction = ignoreFunction;
}
public static boolean isSmartConvertInToEquals() {
return smartConvertInToEquals;
}
public static void setSmartConvertInToEquals(boolean smartConvertInToEquals) {
QueryColumnBehavior.smartConvertInToEquals = smartConvertInToEquals;
}
static boolean shouldIgnoreValue(Object value){
if (ignoreFunction == null){
return false;
}
return ignoreFunction.test(value);
}
}

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.paginate.Page;
import com.mybatisflex.core.relation.RelationManager;
import com.mybatisflex.core.util.LambdaGetter;
import java.util.List;
/**
* 使用 {@code Relations Query} 的方式进行关联查询
*
* @author 王帅
* @since 2023-08-08
*/
public class RelationsBuilder<T> extends AbstractQueryBuilder<T> {
public RelationsBuilder(MapperQueryChain<T> delegate) {
super(delegate);
}
/**
* 忽略查询部分 {@code Relations} 注解标记的属性
*
* @param fields 属性
* @return {@code Relations} 查询构建
*/
public RelationsBuilder<T> ignoreRelations(String... fields) {
RelationManager.addIgnoreRelations(fields);
return this;
}
/**
* 忽略查询部分 {@code Relations} 注解标记的属性
*
* @param fields 属性
* @return {@code Relations} 查询构建
*/
public RelationsBuilder<T> ignoreRelations(LambdaGetter<T>... fields) {
RelationManager.addIgnoreRelations(fields);
return this;
}
/**
* 设置父子关系查询中默认的递归查询深度
*
* @param maxDepth 查询深度
* @return {@code Relations} 查询构建
*/
public RelationsBuilder<T> maxDepth(int maxDepth) {
RelationManager.setMaxDepth(maxDepth);
return this;
}
/**
* 添加额外的 {@code Relations} 查询条件
*
* @param key
* @param value
* @return {@code Relations} 查询构建
*/
public RelationsBuilder<T> extraConditionParam(String key, Object value) {
RelationManager.addExtraConditionParam(key, value);
return this;
}
/**
* {@inheritDoc}
*/
@Override
public T one() {
return baseMapper().selectOneWithRelationsByQuery(queryWrapper());
}
/**
* {@inheritDoc}
*/
@Override
public <R> R oneAs(Class<R> asType) {
return baseMapper().selectOneWithRelationsByQueryAs(queryWrapper(), asType);
}
/**
* {@inheritDoc}
*/
@Override
public List<T> list() {
return baseMapper().selectListWithRelationsByQuery(queryWrapper());
}
/**
* {@inheritDoc}
*/
@Override
public <R> List<R> listAs(Class<R> asType) {
return baseMapper().selectListWithRelationsByQueryAs(queryWrapper(), asType);
}
/**
* {@inheritDoc}
*/
@Override
public Page<T> page(Page<T> page) {
return baseMapper().paginateWithRelations(page, queryWrapper());
}
/**
* {@inheritDoc}
*/
@Override
public <R> Page<R> pageAs(Page<R> page, Class<R> asType) {
return baseMapper().paginateWithRelationsAs(page, queryWrapper(), asType);
}
}

View File

@ -25,9 +25,7 @@ import com.mybatisflex.core.FlexGlobalConfig;
import com.mybatisflex.core.datasource.DataSourceKey;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.core.row.Row;
import com.mybatisflex.core.util.ClassUtil;
import com.mybatisflex.core.util.CollectionUtil;
import com.mybatisflex.core.util.StringUtil;
import com.mybatisflex.core.util.*;
import org.apache.ibatis.util.MapUtil;
import java.lang.reflect.Field;
@ -121,6 +119,19 @@ public class RelationManager {
RelationManager.ignoreRelations.set(ignoreRelations);
}
public static <T> void addIgnoreRelations(LambdaGetter<T>... ignoreRelations) {
Set<String> relations = RelationManager.ignoreRelations.get();
if (relations == null) {
relations = new HashSet<>();
setIgnoreRelations(relations);
}
for (LambdaGetter<T> lambdaGetter : ignoreRelations) {
String fieldName = LambdaUtil.getFieldName(lambdaGetter);
relations.add(fieldName);
}
}
public static void addIgnoreRelations(String... ignoreRelations) {
Set<String> relations = RelationManager.ignoreRelations.get();
if (relations == null) {

View File

@ -17,6 +17,8 @@ package com.mybatisflex.core.table;
import com.mybatisflex.annotation.Id;
import com.mybatisflex.annotation.KeyType;
import com.mybatisflex.core.FlexGlobalConfig;
import com.mybatisflex.core.util.StringUtil;
public class IdInfo extends ColumnInfo {
@ -53,16 +55,38 @@ public class IdInfo extends ColumnInfo {
if (Number.class.isAssignableFrom(columnInfo.getPropertyType())) {
keyType = KeyType.Auto;
} else {
keyType = KeyType.None;
initDefaultKeyType();
}
}
public IdInfo(Id id) {
this.keyType = id.keyType();
this.value = id.value();
this.before = id.before();
initDefaultKeyType();
}
/**
* 用户未配置 keyType 配置默认的 key Type
*/
private void initDefaultKeyType() {
if (this.keyType == null || this.keyType == KeyType.None) {
FlexGlobalConfig.KeyConfig defaultKeyConfig = FlexGlobalConfig.getDefaultConfig().getKeyConfig();
if (defaultKeyConfig != null) {
if (defaultKeyConfig.getKeyType() != null) {
this.keyType = defaultKeyConfig.getKeyType();
this.before = defaultKeyConfig.isBefore();
}
if (StringUtil.isBlank(this.value) && StringUtil.isNotBlank(defaultKeyConfig.getValue())) {
this.value = defaultKeyConfig.getValue();
}
}
}
}
public KeyType getKeyType() {
return keyType;
}

View File

@ -370,11 +370,14 @@ public class TableInfo {
this.primaryColumns = new String[primaryKeyList.size()];
List<String> insertIdFields = new ArrayList<>();
for (int i = 0; i < primaryKeyList.size(); i++) {
IdInfo idInfo = primaryKeyList.get(i);
primaryColumns[i] = idInfo.getColumn();
if (idInfo.getKeyType() != KeyType.Auto && (idInfo.getBefore() != null && idInfo.getBefore())) {
if (idInfo.getKeyType() != KeyType.Auto
|| (idInfo.getBefore() != null && idInfo.getBefore())
) {
insertIdFields.add(idInfo.getColumn());
}

View File

@ -82,35 +82,62 @@ public class UpdateChain<T> extends QueryWrapperAdapter<UpdateChain<T>> {
return new UpdateChain<>(baseMapper);
}
public UpdateChain<T> set(String property, Object value, boolean condition) {
entityWrapper.set(property, value, condition);
return this;
}
public UpdateChain<T> set(String property, Object value) {
entityWrapper.set(property, value);
return this;
}
public UpdateChain<T> set(LambdaGetter<T> getter, Object value, boolean condition) {
entityWrapper.set(getter, value, condition);
return this;
}
public UpdateChain<T> set(LambdaGetter<T> getter, Object value) {
entityWrapper.set(getter, value);
return this;
}
public UpdateChain<T> set(QueryColumn queryColumn, Object value, boolean condition) {
entityWrapper.set(queryColumn, value, condition);
return this;
}
public UpdateChain<T> set(QueryColumn queryColumn, Object value) {
entityWrapper.set(queryColumn, value);
return this;
}
public UpdateChain<T> setRaw(String property, Object value, boolean condition) {
entityWrapper.setRaw(property, value, condition);
return this;
}
public UpdateChain<T> setRaw(String property, Object value) {
entityWrapper.setRaw(property, value);
return this;
}
public UpdateChain<T> setRaw(LambdaGetter<T> getter, Object value, boolean condition) {
entityWrapper.setRaw(getter, value, condition);
return this;
}
public UpdateChain<T> setRaw(LambdaGetter<T> getter, Object value) {
entityWrapper.setRaw(getter, value);
return this;
}
public UpdateChain<T> setRaw(QueryColumn queryColumn, Object value, boolean condition) {
entityWrapper.setRaw(queryColumn, value, condition);
return this;
}
public UpdateChain<T> setRaw(QueryColumn queryColumn, Object value) {
entityWrapper.set(queryColumn, value);
return this;

View File

@ -36,6 +36,12 @@ public interface UpdateWrapper extends Serializable {
return handler.getUpdates();
}
default UpdateWrapper set(String property, Object value, boolean condition) {
if (condition) {
return set(property, value);
}
return this;
}
default UpdateWrapper set(String property, Object value) {
if (value instanceof QueryWrapper || value instanceof QueryCondition || value instanceof QueryColumn) {
@ -46,6 +52,12 @@ public interface UpdateWrapper extends Serializable {
return this;
}
default <T> UpdateWrapper set(LambdaGetter<T> getter, Object value, boolean condition) {
if (condition) {
return set(getter, value);
}
return this;
}
default <T> UpdateWrapper set(LambdaGetter<T> getter, Object value) {
if (value instanceof QueryWrapper || value instanceof QueryCondition || value instanceof QueryColumn) {
@ -57,6 +69,12 @@ public interface UpdateWrapper extends Serializable {
return this;
}
default <T> UpdateWrapper set(QueryColumn queryColumn, Object value, boolean condition) {
if (condition) {
return set(queryColumn, value);
}
return this;
}
default <T> UpdateWrapper set(QueryColumn queryColumn, Object value) {
if (value instanceof QueryWrapper || value instanceof QueryCondition || value instanceof QueryColumn) {
@ -67,17 +85,41 @@ public interface UpdateWrapper extends Serializable {
return this;
}
default UpdateWrapper setRaw(String property, Object value, boolean condition) {
if (condition) {
return setRaw(property, value);
}
return this;
}
default UpdateWrapper setRaw(String property, Object value) {
getUpdates().put(property, new RawValue(value));
return this;
}
default <T> UpdateWrapper setRaw(LambdaGetter<T> getter, Object value, boolean condition) {
if (condition) {
return setRaw(getter, value);
}
return this;
}
default <T> UpdateWrapper setRaw(LambdaGetter<T> getter, Object value) {
getUpdates().put(LambdaUtil.getFieldName(getter), new RawValue(value));
return this;
}
default <T> UpdateWrapper setRaw(QueryColumn queryColumn, Object value, boolean condition) {
if (condition) {
return setRaw(queryColumn, value);
}
return this;
}
default <T> UpdateWrapper setRaw(QueryColumn queryColumn, Object value) {
getUpdates().put(queryColumn.getName(), new RawValue(value));
return this;

View File

@ -17,6 +17,7 @@ package com.mybatisflex.core.util;
import java.lang.reflect.Array;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.StringJoiner;
import java.util.regex.Matcher;
@ -146,7 +147,7 @@ public class SqlUtil {
if (value == null) {
return "null";
}
// number
// number or bool
else if (value instanceof Number || value instanceof Boolean) {
return value.toString();
}
@ -165,7 +166,7 @@ public class SqlUtil {
if (value instanceof Date) {
sb.append(DateUtil.toDateTimeString((Date) value));
} else if (value instanceof LocalDateTime) {
sb.append(DateUtil.toDateTimeString(DateUtil.toDate((LocalDateTime) value)));
sb.append(((LocalDateTime) value).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
} else {
sb.append(value);
}

View File

@ -91,6 +91,12 @@
<optional>true</optional>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-rm-datasource</artifactId>
<version>1.7.0</version>
<optional>true</optional>
</dependency>
</dependencies>

View File

@ -19,7 +19,11 @@ import com.mybatisflex.core.datasource.DataSourceBuilder;
import com.mybatisflex.core.datasource.DataSourceDecipher;
import com.mybatisflex.core.datasource.DataSourceManager;
import com.mybatisflex.core.datasource.FlexDataSource;
import com.mybatisflex.spring.SeataMode;
import com.mybatisflex.spring.boot.MybatisFlexProperties.SeataConfig;
import com.mybatisflex.spring.datasource.DataSourceAdvice;
import io.seata.rm.datasource.DataSourceProxy;
import io.seata.rm.datasource.xa.DataSourceProxyXA;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.ObjectProvider;
@ -53,6 +57,8 @@ public class MultiDataSourceAutoConfiguration {
private final Map<String, Map<String, String>> dataSourceProperties;
private final SeataConfig seataConfig;
//数据源解密器
protected final DataSourceDecipher dataSourceDecipher;
@ -62,6 +68,7 @@ public class MultiDataSourceAutoConfiguration {
) {
dataSourceProperties = properties.getDatasource();
dataSourceDecipher = dataSourceDecipherProvider.getIfAvailable();
seataConfig = properties.getSeataConfig();
}
@Bean
@ -75,11 +82,22 @@ public class MultiDataSourceAutoConfiguration {
DataSourceManager.setDecipher(dataSourceDecipher);
for (Map.Entry<String, Map<String, String>> entry : dataSourceProperties.entrySet()) {
DataSource dataSource = new DataSourceBuilder(entry.getValue()).build();
DataSourceManager.decryptDataSource(dataSource);
if (seataConfig != null && seataConfig.isEnable()) {
if (seataConfig.getSeataMode() == SeataMode.XA) {
dataSource = new DataSourceProxyXA(dataSource);
} else {
dataSource = new DataSourceProxy(dataSource);
}
}
if (flexDataSource == null) {
flexDataSource = new FlexDataSource(entry.getKey(), dataSource);
flexDataSource = new FlexDataSource(entry.getKey(), dataSource, false);
} else {
flexDataSource.addDataSource(entry.getKey(), dataSource);
flexDataSource.addDataSource(entry.getKey(), dataSource, false);
}
}
}

View File

@ -17,6 +17,7 @@ package com.mybatisflex.spring.boot;
import com.mybatisflex.core.FlexConsts;
import com.mybatisflex.core.FlexGlobalConfig;
import com.mybatisflex.spring.SeataMode;
import org.apache.ibatis.io.VFS;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.mapping.ResultSetType;
@ -122,6 +123,19 @@ public class MybatisFlexProperties {
*/
private CoreConfiguration configuration;
/**
* A Configuration object for seata
*/
private SeataConfig seataConfig;
public SeataConfig getSeataConfig() {
return seataConfig;
}
public void setSeataConfig(SeataConfig seataConfig) {
this.seataConfig = seataConfig;
}
public Map<String, Map<String, String>> getDatasource() {
return datasource;
}
@ -890,4 +904,38 @@ public class MybatisFlexProperties {
}
/**
* Seata 配置
*
* @author life
*/
public static class SeataConfig{
/**
* 是否开启
*/
private boolean enable = false;
/**
* 事务模式支持只支持XA或者AT
*/
private SeataMode seataMode = SeataMode.AT;
public boolean isEnable() {
return enable;
}
public void setEnable(boolean enable) {
this.enable = enable;
}
public SeataMode getSeataMode() {
return seataMode;
}
public void setSeataMode(SeataMode seataMode) {
this.seataMode = seataMode;
}
}
}

View File

@ -40,6 +40,12 @@
<artifactId>spring-jdbc</artifactId>
</dependency>
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.7.0</version>
</dependency>
</dependencies>

View File

@ -88,6 +88,6 @@ public class FlexSpringTransaction implements Transaction {
@Override
public Integer getTimeout() throws SQLException {
return getConnection().getNetworkTimeout();
return null;
}
}

View File

@ -19,6 +19,8 @@ import com.mybatisflex.core.FlexConsts;
import com.mybatisflex.core.datasource.FlexDataSource;
import com.mybatisflex.core.mybatis.FlexConfiguration;
import com.mybatisflex.core.mybatis.FlexSqlSessionFactoryBuilder;
import io.seata.rm.datasource.DataSourceProxy;
import io.seata.rm.datasource.xa.DataSourceProxyXA;
import org.apache.ibatis.builder.xml.XMLConfigBuilder;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.cache.Cache;

View File

@ -0,0 +1,26 @@
/*
* 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.spring;
/**
* @author life
*/
public enum SeataMode {
XA,
AT
}

View File

@ -0,0 +1,149 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>mybatis-flex-test</artifactId>
<groupId>com.mybatis-flex</groupId>
<version>1.5.6</version>
</parent>
<groupId>com.mybatisfle.test</groupId>
<artifactId>mybatis-flex-spring-boot-seata</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>mybatis-flex-spring-boot-seata</name>
<description>mybatis-flex-spring-boot-seata</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>com.mybatis-flex</groupId>
<artifactId>mybatis-flex-spring-boot-starter</artifactId>
<version>${mybatis-flex.version}</version>
</dependency>
<dependency>
<groupId>com.mybatis-flex</groupId>
<artifactId>mybatis-flex-codegen</artifactId>
<version>${mybatis-flex.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.32</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.18</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-jdbc</artifactId>-->
<!-- <version>2.7.9</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.springframework</groupId>-->
<!-- <artifactId>spring-jdbc</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>com.h2database</groupId>-->
<!-- <artifactId>h2</artifactId>-->
<!-- <version>2.1.214</version>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.yaml</groupId>-->
<!-- <artifactId>snakeyaml</artifactId>-->
<!-- <version>1.33</version>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</pluginRepository>
</pluginRepositories>
</project>

View File

@ -0,0 +1,15 @@
package com.mybatisfle.test.mybatisflexspringbootseata;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@MapperScan("com.mybatisfle.test.mybatisflexspringbootseata")
@SpringBootApplication
public class MybatisFlexSpringBootSeataApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisFlexSpringBootSeataApplication.class, args);
}
}

View File

@ -0,0 +1,28 @@
package com.mybatisfle.test.mybatisflexspringbootseata.controller;
import com.mybatisfle.test.mybatisflexspringbootseata.service.TestService;
import com.mybatisflex.core.audit.AuditManager;
import com.mybatisflex.core.audit.ConsoleMessageCollector;
import com.mybatisflex.core.audit.MessageCollector;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class TestController {
@Autowired
TestService testService;
@RequestMapping("buy")
public String buy(){
//开启审计功能
AuditManager.setAuditEnable(true);
//设置 SQL 审计收集器
MessageCollector collector = new ConsoleMessageCollector();
AuditManager.setMessageCollector(collector);
String flag =String.valueOf(testService.buy());
return flag;
}
}

View File

@ -0,0 +1,51 @@
package com.mybatisfle.test.mybatisflexspringbootseata.entity;
import com.mybatisflex.annotation.Id;
import com.mybatisflex.annotation.KeyType;
import com.mybatisflex.annotation.Table;
import java.io.Serializable;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 实体类
*
* @author life
* @since 2023-08-03
*/
@Table(value = "account_tbl", schema = "db_account")
public class AccountTbl implements Serializable {
@Id(keyType = KeyType.Auto)
private Integer id;
private String userId;
private Integer money;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public Integer getMoney() {
return money;
}
public void setMoney(Integer money) {
this.money = money;
}
}

View File

@ -0,0 +1,71 @@
package com.mybatisfle.test.mybatisflexspringbootseata.entity;
import com.mybatisflex.annotation.Id;
import com.mybatisflex.annotation.KeyType;
import com.mybatisflex.annotation.Table;
import java.io.Serializable;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 实体类
*
* @author life
* @since 2023-08-03
*/
@Table(value = "order_tbl", schema = "db_order")
public class OrderTbl implements Serializable {
@Id(keyType = KeyType.Auto)
private Integer id;
private String userId;
private String commodityCode;
private Integer count;
private Integer money;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getCommodityCode() {
return commodityCode;
}
public void setCommodityCode(String commodityCode) {
this.commodityCode = commodityCode;
}
public Integer getCount() {
return count;
}
public void setCount(Integer count) {
this.count = count;
}
public Integer getMoney() {
return money;
}
public void setMoney(Integer money) {
this.money = money;
}
}

View File

@ -0,0 +1,51 @@
package com.mybatisfle.test.mybatisflexspringbootseata.entity;
import com.mybatisflex.annotation.Id;
import com.mybatisflex.annotation.KeyType;
import com.mybatisflex.annotation.Table;
import java.io.Serializable;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 实体类
*
* @author life
* @since 2023-08-03
*/
@Table(value = "stock_tbl", schema = "db_stock")
public class StockTbl implements Serializable {
@Id(keyType = KeyType.Auto)
private Integer id;
private String commodityCode;
private Integer count;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getCommodityCode() {
return commodityCode;
}
public void setCommodityCode(String commodityCode) {
this.commodityCode = commodityCode;
}
public Integer getCount() {
return count;
}
public void setCount(Integer count) {
this.count = count;
}
}

View File

@ -0,0 +1,15 @@
package com.mybatisfle.test.mybatisflexspringbootseata.mapper;
import com.mybatisfle.test.mybatisflexspringbootseata.entity.AccountTbl;
import com.mybatisflex.core.BaseMapper;
/**
* 映射层
*
* @author life
* @since 2023-08-03
*/
public interface AccountTblMapper extends BaseMapper<AccountTbl> {
}

View File

@ -0,0 +1,15 @@
package com.mybatisfle.test.mybatisflexspringbootseata.mapper;
import com.mybatisfle.test.mybatisflexspringbootseata.entity.OrderTbl;
import com.mybatisflex.core.BaseMapper;
/**
* 映射层
*
* @author life
* @since 2023-08-03
*/
public interface OrderTblMapper extends BaseMapper<OrderTbl> {
}

View File

@ -0,0 +1,15 @@
package com.mybatisfle.test.mybatisflexspringbootseata.mapper;
import com.mybatisfle.test.mybatisflexspringbootseata.entity.StockTbl;
import com.mybatisflex.core.BaseMapper;
/**
* 映射层
*
* @author life
* @since 2023-08-03
*/
public interface StockTblMapper extends BaseMapper<StockTbl> {
}

View File

@ -0,0 +1,62 @@
package com.mybatisfle.test.mybatisflexspringbootseata.service;
import com.mybatisfle.test.mybatisflexspringbootseata.entity.AccountTbl;
import com.mybatisfle.test.mybatisflexspringbootseata.entity.OrderTbl;
import com.mybatisfle.test.mybatisflexspringbootseata.entity.StockTbl;
import com.mybatisfle.test.mybatisflexspringbootseata.entity.table.AccountTblTableDef;
import com.mybatisfle.test.mybatisflexspringbootseata.mapper.AccountTblMapper;
import com.mybatisfle.test.mybatisflexspringbootseata.mapper.OrderTblMapper;
import com.mybatisfle.test.mybatisflexspringbootseata.mapper.StockTblMapper;
import com.mybatisflex.core.datasource.DataSourceKey;
import com.mybatisflex.core.query.QueryWrapper;
import io.seata.core.context.RootContext;
import io.seata.spring.annotation.GlobalTransactional;
import org.mybatis.logging.Logger;
import org.mybatis.logging.LoggerFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class TestService {
private static final Logger LOGGER = LoggerFactory.getLogger(TestService.class);
@Autowired
AccountTblMapper accountTblMapper;
@Autowired
OrderTblMapper orderTblMapper;
@Autowired
StockTblMapper stockTblMapper;
// @Transactional
@GlobalTransactional
public boolean buy() {
// DataSourceKey.use("accountdb");
LOGGER.warn(() -> "xid:"+RootContext.getXID());
QueryWrapper account =new QueryWrapper();
account.where(AccountTblTableDef.ACCOUNT_TBL.USER_ID.eq("1001"));
AccountTbl accountTbl = accountTblMapper.selectOneByQuery(account);
accountTbl.setMoney(accountTbl.getMoney() - 5);
accountTblMapper.update(accountTbl);
DataSourceKey.use("stockdb");
QueryWrapper stock = new QueryWrapper();
stock.where("id=1");
StockTbl stockTbl = stockTblMapper.selectOneByQuery(stock);
stockTbl.setCount(stockTbl.getCount() - 1);
stockTblMapper.update(stockTbl);
DataSourceKey.use("orderdb");
OrderTbl orderTbl = new OrderTbl();
orderTbl.setCount(5);
orderTbl.setMoney(5);
orderTbl.setUserId(accountTbl.getUserId());
orderTbl.setCount(1);
orderTbl.setCommodityCode(stockTbl.getCommodityCode());
orderTblMapper.insert(orderTbl);
return true;
}
}

View File

@ -0,0 +1,88 @@
package com.mybatisfle.test.mybatisflexspringbootseata.utils;
import com.mybatisflex.codegen.Generator;
import com.mybatisflex.codegen.config.ColumnConfig;
import com.mybatisflex.codegen.config.GlobalConfig;
import com.zaxxer.hikari.HikariDataSource;
public class Codegen {
public static void main(String[] args) {
//配置数据源
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/db_stock?characterEncoding=utf-8");
dataSource.setUsername("root");
dataSource.setPassword("131496");
//创建配置内容两种风格都可以
GlobalConfig globalConfig = createGlobalConfigUseStyle1();
//GlobalConfig globalConfig = createGlobalConfigUseStyle2();
//通过 datasource globalConfig 创建代码生成器
Generator generator = new Generator(dataSource, globalConfig);
//生成代码
generator.generate();
}
public static GlobalConfig createGlobalConfigUseStyle1() {
//创建配置内容
GlobalConfig globalConfig = new GlobalConfig();
//设置根包
globalConfig.setBasePackage("com.mybatisfle.test.mybatisflexspringbootseata");
//设置表前缀和只生成哪些表
globalConfig.setGenerateSchema("db_stock");
// globalConfig.setTablePrefix("tb_");
globalConfig.setGenerateTable("stock_tbl");
//设置生成 entity 并启用 Lombok
globalConfig.setEntityGenerateEnable(true);
globalConfig.setEntityWithLombok(true);
//设置生成 mapper
globalConfig.setMapperGenerateEnable(true);
//可以单独配置某个列
// ColumnConfig columnConfig = new ColumnConfig();
// columnConfig.setColumnName("tenant_id");
// columnConfig.setLarge(true);
// columnConfig.setVersion(true);
// globalConfig.setColumnConfig("account", columnConfig);
return globalConfig;
}
public static GlobalConfig createGlobalConfigUseStyle2() {
//创建配置内容
GlobalConfig globalConfig = new GlobalConfig();
//设置根包
globalConfig.getPackageConfig()
.setBasePackage("com.test");
//设置表前缀和只生成哪些表setGenerateTable 未配置时生成所有表
globalConfig.getStrategyConfig()
.setGenerateSchema("schema")
.setTablePrefix("tb_")
.setGenerateTable("account", "account_session");
//设置生成 entity 并启用 Lombok
globalConfig.enableEntity()
.setWithLombok(true);
//设置生成 mapper
globalConfig.enableMapper();
//可以单独配置某个列
ColumnConfig columnConfig = new ColumnConfig();
columnConfig.setColumnName("tenant_id");
columnConfig.setLarge(true);
columnConfig.setVersion(true);
globalConfig.getStrategyConfig()
.setColumnConfig("account", columnConfig);
return globalConfig;
}
}

View File

@ -0,0 +1,75 @@
mybatis-flex:
seata-config:
enable: true #启动seata
seata-mode: XA #xa或者ta
datasource:
accountdb:
url: jdbc:mysql://127.0.0.1:3306/db_account
username: root
password: 131496
orderdb:
url: jdbc:mysql://127.0.0.1:3306/db_order
username: root
password: 131496
stockdb:
url: jdbc:mysql://127.0.0.1:3306/db_stock
username: root
password: 131496
server:
port: 2010
seata:
enabled: true
application-id: business-service
tx-service-group: my_test_tx_group
enable-auto-data-source-proxy: false #必须
# use-jdk-proxy: false
client:
rm:
async-commit-buffer-limit: 1000
report-retry-count: 5
table-meta-check-enable: false
report-success-enable: false
lock:
retry-interval: 10
retry-times: 30
retry-policy-branch-rollback-on-conflict: true
tm:
commit-retry-count: 5
rollback-retry-count: 5
undo:
data-validation: true
log-serialization: jackson
log-table: undo_log
log:
exceptionRate: 100
service:
vgroup-mapping:
my_test_tx_group: default
grouplist:
default: 127.0.0.1:8091
#enable-degrade: false
#disable-global-transaction: false
transport:
shutdown:
wait: 3
thread-factory:
boss-thread-prefix: NettyBoss
worker-thread-prefix: NettyServerNIOWorker
server-executor-thread-prefix: NettyServerBizHandler
share-boss-worker: false
client-selector-thread-prefix: NettyClientSelector
client-selector-thread-size: 1
client-worker-thread-prefix: NettyClientWorkerThread
worker-thread-size: default
boss-thread-size: 1
type: TCP
server: NIO
heartbeat: true
serialization: seata
compressor: none
enable-client-batch-send-request: true
config:
type: file
registry:
type: file

View File

@ -0,0 +1,97 @@
# Account
DROP SCHEMA IF EXISTS db_account;
CREATE SCHEMA db_account;
USE db_account;
CREATE TABLE `account_tbl`
(
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_id` VARCHAR(255) DEFAULT NULL,
`money` INT(11) DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
INSERT INTO account_tbl (id, user_id, money)
VALUES (1, '1001', 10000);
INSERT INTO account_tbl (id, user_id, money)
VALUES (2, '1002', 10000);
CREATE TABLE `undo_log`
(
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
# Order
DROP SCHEMA IF EXISTS db_order;
CREATE SCHEMA db_order;
USE db_order;
CREATE TABLE `order_tbl`
(
`id` INT(11) NOT NULL AUTO_INCREMENT,
`user_id` VARCHAR(255) DEFAULT NULL,
`commodity_code` VARCHAR(255) DEFAULT NULL,
`count` INT(11) DEFAULT '0',
`money` INT(11) DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
CREATE TABLE `undo_log`
(
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
# stock
DROP SCHEMA IF EXISTS db_stock;
CREATE SCHEMA db_stock;
USE db_stock;
CREATE TABLE `stock_tbl`
(
`id` INT(11) NOT NULL AUTO_INCREMENT,
`commodity_code` VARCHAR(255) DEFAULT NULL,
`count` INT(11) DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `commodity_code` (`commodity_code`)
) ENGINE = InnoDB
DEFAULT CHARSET = utf8;
INSERT INTO stock_tbl (id, commodity_code, count)
VALUES (1, '2001', 1000);
CREATE TABLE `undo_log`
(
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`context` varchar(128) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

View File

@ -0,0 +1,13 @@
package com.mybatisfle.test.mybatisflexspringbootseata;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class MybatisFlexSpringBootSeataApplicationTests {
@Test
void contextLoads() {
}
}

View File

@ -7,16 +7,16 @@ spring:
# driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/flex_test
username: root
password: 123456
password: 12345678
# driver-class-name:
# datasource:
# driver-class-name: org.h2.Driver
# username: root
# password: test
sql:
init:
schema-locations: classpath:schema.sql
data-locations: classpath:data.sql
# sql:
# init:
# schema-locations: classpath:schema.sql
# data-locations: classpath:data.sql
mybatis-flex:
admin-config:
enable: true
@ -38,11 +38,11 @@ mybatis-flex:
# password: 12345678
#mybatis-flex:
# datasource:
# ds1:
# url: jdbc:mysql://127.0.0.1:3306/db
# ds3333:
# url: jdbc:mysql://127.0.0.1:3306/flex_test
# username: root
# password: 123456
# password: 131496
# ds2:
# url: jdbc:mysql://127.0.0.1:3306/db2
# url: jdbc:mysql://127.0.0.1:3306/flex_test1
# username: root
# password: 123456
# password: 131496

View File

@ -0,0 +1,85 @@
/*
* 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.test.mapper;
import com.mybatisflex.core.field.QueryBuilder;
import com.mybatisflex.core.query.QueryChain;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.test.model.User;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import static com.mybatisflex.test.model.table.RoleTableDef.ROLE;
import static com.mybatisflex.test.model.table.UserRoleTableDef.USER_ROLE;
import static com.mybatisflex.test.model.table.UserTableDef.USER;
/**
* @author 王帅
* @since 2023-08-08
*/
@SpringBootTest
class QueryChainTest {
@Autowired
UserMapper userMapper;
@Test
void testFields() {
QueryBuilder<User> builder = user ->
QueryWrapper.create()
.select()
.from(ROLE)
.where(ROLE.ROLE_ID.in(
QueryWrapper.create()
.select(USER_ROLE.ROLE_ID)
.from(USER_ROLE)
.where(USER_ROLE.USER_ID.eq(user.getUserId()))
));
User user1 = QueryChain.of(userMapper)
.where(USER.USER_ID.eq(1))
.withFields()
.fieldMapping(User::getRoleList, builder)
.one();
User user2 = User.create()
.where(USER.USER_ID.eq(1))
.withFields()
.fieldMapping(User::getRoleList, builder)
.one();
Assertions.assertEquals(user1.toString(), user2.toString());
}
@Test
void testRelations() {
User user1 = QueryChain.of(userMapper)
.where(USER.USER_ID.eq(2))
.withRelations()
.one();
User user2 = User.create()
.where(USER.USER_ID.eq(2))
.withRelations()
.one();
Assertions.assertEquals(user1.toString(), user2.toString());
}
}

View File

@ -18,6 +18,7 @@
<module>mybatis-flex-spring-test</module>
<module>mybatis-flex-spring-boot-test</module>
<module>mybatis-flex-spring-cloud-test</module>
<module>mybatis-flex-spring-boot-seata</module>
</modules>
<properties>
@ -26,14 +27,6 @@
</properties>
<build>
<!-- <resources>-->
<!-- <resource>-->
<!-- <directory>src/main/resources</directory>-->
<!-- <includes>-->
<!-- <include>**/*</include>-->
<!-- </includes>-->
<!-- </resource>-->
<!-- </resources>-->
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>