Compare commits

..

10 Commits

42 changed files with 733 additions and 154 deletions

View File

@ -1,4 +1,24 @@
# fluent-sql # 前言
不知道大家有没有这样的感觉就笔者而言用了这么多年的Mybatis再怎么说也应该“日久生情”了吧结果可惜啊可惜Mybatis这尊大佛还是让我爱不起来。随着开发水平的提高和对写代码这件事情本身如何更为优雅的深入研究后让早已真正爱上面向对象模式开发的我硬着头皮偶尔得**强制**切换到面向SQL编程属实是令人头秃。这不我这次任性了一回手撸了一个java代码写SQL的东东这就是**Fluent SQL。**
# **这是个啥**
大家肯定很好奇Fluent SQL是个啥咱们先从Fluent来理解。
| fluent | 英[ˈfluːənt] 美[ˈfluːənt] |
| ------ | ------------------------------------------------------------ |
| adj. | (说话)流利的; (文体) 流畅的; (动作、曲线等) 优美自然的; (河水等) 畅流的; |
顾名思义,我们想要写出**”优美自然的SQL“**,同时又想要**流畅地**写出SQL那就快来试试我这个组件吧。组件很轻量级可以非常快速集成项目中可以用于替换mybatis的XML。
作为Java程序员首选是使用Java代码来处理业务而非将大多业务都放在XML里面向SQL编程。都2302年了我们也该寻求点不同的东西了。
# 组件介绍
基于Fluent Api实现的SQL构建器秒杀mybatis plus的存在易用性的API让你爽到飞起。 基于Fluent Api实现的SQL构建器秒杀mybatis plus的存在易用性的API让你爽到飞起。
@ -59,6 +79,24 @@ public class FluentSqlConfig {
} }
``` ```
## 单表查询
单表查询可以省略结果映射,程序自动使用主表实体进行自动映射
```java
class Test {
public static void main(String[] args) {
// 单表查询所有字段
List<SaasTenant> tenants = select().from(SaasTenant.class).list();
// 单表查询指定字段,条件 年龄小于50
List<SaasTenant> points = select(SaasTenant::getId, SaasTenant::getName).from(SaasTenant.class)
.matching(where(SaasTenant::getAge).lt(50))
.list();
}
}
```
## 对比直接书写SQL ## 对比直接书写SQL
本小组件主要解决的是sql的书写问题旨在用更加优雅的方式实现sql并且不用再担心数据库方言SQL Dialect 本小组件主要解决的是sql的书写问题旨在用更加优雅的方式实现sql并且不用再担心数据库方言SQL Dialect
@ -123,11 +161,11 @@ public class TestSql {
建议使用静态导入,美观代码,如下: 建议使用静态导入,美观代码,如下:
1. SQL.select => select 1. `SQL.select` => `select`
2. SelectComposite.composite => composite 2. `SelectComposite.composite` => `composite`
3. SelectComposite.all => all 3. `SelectComposite.all` => `all`
4. Order.by => by 4. `Order.by` => `by`
5. Query.where => where 5. `Query.where` => `where`
为了方便演示,我们下面的代码都基于静态导入函数: 为了方便演示,我们下面的代码都基于静态导入函数:

View File

@ -33,6 +33,11 @@
<groupId>javax.persistence</groupId> <groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId> <artifactId>persistence-api</artifactId>
</dependency> </dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<optional>true</optional>
</dependency>
<dependency> <dependency>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId> <artifactId>slf4j-api</artifactId>

View File

@ -3,10 +3,15 @@ package group.flyfish.fluent.chain;
import group.flyfish.fluent.chain.common.AfterJoinSqlChain; import group.flyfish.fluent.chain.common.AfterJoinSqlChain;
import group.flyfish.fluent.chain.common.HandleSqlChain; import group.flyfish.fluent.chain.common.HandleSqlChain;
import group.flyfish.fluent.chain.common.PreSqlChain; import group.flyfish.fluent.chain.common.PreSqlChain;
import group.flyfish.fluent.chain.execution.BoundEntity;
import group.flyfish.fluent.chain.execution.BoundProxy;
import group.flyfish.fluent.chain.execution.ReactiveBoundEntity;
import group.flyfish.fluent.chain.select.AfterOrderSqlChain; import group.flyfish.fluent.chain.select.AfterOrderSqlChain;
import group.flyfish.fluent.chain.select.AfterWhereSqlChain; import group.flyfish.fluent.chain.select.AfterWhereSqlChain;
import group.flyfish.fluent.chain.select.PieceSqlChain;
import group.flyfish.fluent.chain.update.AfterSetSqlChain; import group.flyfish.fluent.chain.update.AfterSetSqlChain;
import group.flyfish.fluent.debug.FluentSqlDebugger; import group.flyfish.fluent.debug.FluentSqlDebugger;
import group.flyfish.fluent.entity.DataPage;
import group.flyfish.fluent.entity.SQLEntity; import group.flyfish.fluent.entity.SQLEntity;
import group.flyfish.fluent.operations.FluentSQLOperations; import group.flyfish.fluent.operations.FluentSQLOperations;
import group.flyfish.fluent.query.JoinCandidate; import group.flyfish.fluent.query.JoinCandidate;
@ -20,13 +25,19 @@ import group.flyfish.fluent.utils.sql.ConcatSegment;
import group.flyfish.fluent.utils.sql.EntityNameUtils; import group.flyfish.fluent.utils.sql.EntityNameUtils;
import group.flyfish.fluent.utils.sql.SFunction; import group.flyfish.fluent.utils.sql.SFunction;
import group.flyfish.fluent.utils.sql.SqlNameUtils; import group.flyfish.fluent.utils.sql.SqlNameUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static group.flyfish.fluent.utils.cache.CachedWrapper.wrap;
/** /**
* 查询工具类 * 查询工具类
* *
@ -36,12 +47,16 @@ final class SQLImpl extends ConcatSegment<SQLImpl> implements SQLOperations, Pre
// 共享的操作 // 共享的操作
private static FluentSQLOperations SHARED_OPERATIONS; private static FluentSQLOperations SHARED_OPERATIONS;
// 参数map有序 // 参数map有序
private final List<Object> parameters = new ArrayList<>(); private final List<Object> parameters = new ArrayList<>();
// 主表class默认是第一个from的表为主表 // 主表class默认是第一个from的表为主表
private Class<?> primaryClass; private Class<?> primaryClass;
// sql实体提供者
private final Supplier<SQLEntity> entity = wrap(this::toEntity);
/** /**
* 绑定实现类 * 绑定实现类
* *
@ -141,14 +156,24 @@ final class SQLImpl extends ConcatSegment<SQLImpl> implements SQLOperations, Pre
return concat("ON").concat(query); return concat("ON").concat(query);
} }
/**
* 下一步操作
*
* @return 结果
*/
@Override
public HandleSqlChain then() {
return this;
}
/** /**
* 不带连接条件 * 不带连接条件
* *
* @return 处理链 * @return 处理链
*/ */
@Override @Override
public HandleSqlChain then() { public <T> BoundProxy<T> next() {
return this; return new DefaultBoundProxy<>(SQLEntity.of(primaryClass, this::sql, this::parsedParameters));
} }
/** /**
@ -157,6 +182,7 @@ final class SQLImpl extends ConcatSegment<SQLImpl> implements SQLOperations, Pre
* @param query 条件 * @param query 条件
* @return 结果 * @return 结果
*/ */
@Override
public AfterWhereSqlChain matching(Query query) { public AfterWhereSqlChain matching(Query query) {
if (withoutParameter(query)) return this; if (withoutParameter(query)) return this;
return concat("WHERE").concat(query); return concat("WHERE").concat(query);
@ -178,45 +204,14 @@ final class SQLImpl extends ConcatSegment<SQLImpl> implements SQLOperations, Pre
} }
@Override
public <T> T one() {
return one(SqlNameUtils.cast(primaryClass));
}
/** /**
* 执行并获取结果 * 获取实体做下一步的事情
* *
* @param clazz 结果类 * @return 结果
* @param <T> 泛型
*/
public <T> T one(Class<T> clazz) {
return SHARED_OPERATIONS.selectOne(entity(), clazz);
}
@Override
public <T> List<T> list() {
return list(SqlNameUtils.cast(primaryClass));
}
/**
* 执行并获取多条结果
*
* @param clazz 结果类
* @return 结果列表
*/ */
@Override @Override
public <T> List<T> list(Class<T> clazz) { public <T> BoundProxy<T> as(Class<T> type) {
return SHARED_OPERATIONS.select(entity(), clazz); return new DefaultBoundProxy<>(SQLEntity.of(type, wrap(this::sql), wrap(this::parsedParameters)));
}
/**
* 执行并获取更新条数
*
* @return 更新条数
*/
@Override
public int execute() {
return SHARED_OPERATIONS.execute(entity());
} }
/** /**
@ -259,12 +254,80 @@ final class SQLImpl extends ConcatSegment<SQLImpl> implements SQLOperations, Pre
return false; return false;
} }
@Override
public PieceSqlChain limit(int count) {
return concat("LIMIT").concat(String.valueOf(count));
}
@Override
public PieceSqlChain offset(int rows) {
return concat("OFFSET").concat(String.valueOf(rows));
}
@RequiredArgsConstructor
private static class DefaultBoundProxy<T> implements BoundProxy<T> {
private final SQLEntity<T> entity;
@Override
public BoundEntity<T> block() {
return new DefaultBoundEntity<>();
}
@Override
public ReactiveBoundEntity<T> reactive() {
return new DefaultReactiveBoundEntity<>();
}
}
/** /**
* 将本实体转换为sql实体 * 默认的绑定实体
* *
* @return 转换结果 * @param <T>
*/ */
private SQLEntity entity() { private static class DefaultBoundEntity<T> implements BoundEntity<T> {
return SQLEntity.of(this::sql, this::parsedParameters);
@Override
public T one() {
return null;
}
@Override
public List<T> all() {
return List.of();
}
@Override
public DataPage<T> page() {
return null;
}
@Override
public int execute() {
return 0;
}
}
private static class DefaultReactiveBoundEntity<T> implements ReactiveBoundEntity<T> {
@Override
public Mono<T> one() {
return null;
}
@Override
public Flux<T> all() {
return null;
}
@Override
public Mono<DataPage<T>> page() {
return null;
}
@Override
public Mono<Integer> execute() {
return null;
}
} }
} }

View File

@ -1,5 +1,7 @@
package group.flyfish.fluent.chain.common; package group.flyfish.fluent.chain.common;
import group.flyfish.fluent.chain.execution.BoundProxy;
/** /**
* 可执行的sql * 可执行的sql
* *
@ -8,9 +10,10 @@ package group.flyfish.fluent.chain.common;
public interface ExecutableSql { public interface ExecutableSql {
/** /**
* 执行并获取更新条数 * 进入下一步以主表作为输出结果
* *
* @return 更新条数 * @param <T> 泛型
* @return 绑定操作
*/ */
int execute(); <T> BoundProxy<T> next();
} }

View File

@ -0,0 +1,49 @@
package group.flyfish.fluent.chain.execution;
import group.flyfish.fluent.entity.DataPage;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import java.util.List;
/**
* 已经绑定的实体
*
* @author wangyu
*/
public interface BoundEntity<T> {
/**
* 执行一条sql并且序列化为对象
* 注意如果查询不止一条该方法仅返回第一条数据
* 如果没有结果将返回null
*
* @return 查询结果
*/
@Nullable
T one();
/**
* 执行一条sql并且查询出所有行
*
* @return 返回的列表
*/
@NonNull
List<T> all();
/**
* 分页查询
*
* @return 返回的分页对象
*/
@NonNull
DataPage<T> page();
/**
* 直接执行sql根据update count返回更新行数如果是查询永远返回0
*
* @return 更新行数
*/
int execute();
}

View File

@ -0,0 +1,24 @@
package group.flyfish.fluent.chain.execution;
/**
* 绑定状态下的代理
*
* @author wangyu
*/
public interface BoundProxy<T> {
/**
* 阻塞的数据库操作
*
* @return 结果
*/
BoundEntity<T> block();
/**
* 异步数据库逻辑
*
* @return 结果
*/
ReactiveBoundEntity<T> reactive();
}

View File

@ -0,0 +1,48 @@
package group.flyfish.fluent.chain.execution;
import group.flyfish.fluent.entity.DataPage;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* 已经绑定的异步实体
*
* @author wangyu
*/
public interface ReactiveBoundEntity<T> {
/**
* 执行一条sql并且序列化为对象
* 注意如果查询不止一条该方法仅返回第一条数据
* 如果没有结果将返回null
*
* @return 查询结果
*/
@Nullable
Mono<T> one();
/**
* 执行一条sql并且查询出所有行
*
* @return 返回的列表
*/
@NonNull
Flux<T> all();
/**
* 分页查询
*
* @return 返回的分页对象
*/
@NonNull
Mono<DataPage<T>> page();
/**
* 直接执行sql根据update count返回更新行数如果是查询永远返回0
*
* @return 更新行数
*/
Mono<Integer> execute();
}

View File

@ -1,48 +1,10 @@
package group.flyfish.fluent.chain.select; package group.flyfish.fluent.chain.select;
import group.flyfish.fluent.chain.common.ExecutableSql;
import java.util.List;
/** /**
* order做完后支持的操作 * order做完后支持的操作
* *
* @author wangyu * @author wangyu
*/ */
public interface AfterOrderSqlChain extends ExecutableSql { public interface AfterOrderSqlChain extends PieceSqlChain {
/**
* 执行并获取结果
*
* @param <T> 泛型
* @return 单一结果值
*/
<T> T one();
/**
* 执行并获取结果
*
* @param clazz 结果类
* @param <T> 泛型
* @return 单一结果值
*/
<T> T one(Class<T> clazz);
/**
* 执行并获取多条结果以主表class为结果
*
* @param <T> 结果泛型
* @return 结果列表
*/
<T> List<T> list();
/**
* 执行并获取多条结果
*
* @param clazz 结果类
* @param <T> 结果泛型
* @return 结果列表
*/
<T> List<T> list(Class<T> clazz);
} }

View File

@ -0,0 +1,16 @@
package group.flyfish.fluent.chain.select;
import group.flyfish.fluent.chain.common.ExecutableSql;
import group.flyfish.fluent.chain.execution.BoundProxy;
import java.util.List;
public interface FetchSqlChain extends ExecutableSql {
/**
* 转换为SQL实体
*
* @return 结果
*/
<T> BoundProxy<T> as(Class<T> type);
}

View File

@ -0,0 +1,25 @@
package group.flyfish.fluent.chain.select;
/**
* 支持分片的sql链
*
* @author wangyu
*/
public interface PieceSqlChain extends FetchSqlChain {
/**
* 限制返回条数
*
* @param count 条数
* @return 结果
*/
PieceSqlChain limit(int count);
/**
* 跳过多少行
*
* @param rows 行数
* @return 结果
*/
PieceSqlChain offset(int rows);
}

View File

@ -0,0 +1,22 @@
package group.flyfish.fluent.entity;
import lombok.Data;
import java.util.List;
/**
* 数据分页
*
* @author wangyu
*/
@Data
public class DataPage<T> {
private List<T> list;
private long total;
private int size = 10;
private int page = 1;
}

View File

@ -1,38 +1,51 @@
package group.flyfish.fluent.entity; package group.flyfish.fluent.entity;
import lombok.Getter;
import org.springframework.lang.NonNull;
import java.util.function.Supplier; import java.util.function.Supplier;
/** /**
* sql运行实体 * sql运行实体
* *
* @param <T> 结果类型泛型
* @author wangyu * @author wangyu
*/ */
public class SQLEntity { public class SQLEntity<T> {
private static final Supplier<Object[]> EMPTY_PARAMETERS = () -> new Object[]{}; private static final Supplier<Object[]> EMPTY_PARAMETERS = () -> new Object[]{};
// sql提供者 // sql提供者
private Supplier<String> sqlProvider; @NonNull
private final Supplier<String> sql;
// sql参数表提供者 // sql参数表提供者
private Supplier<Object[]> parametersProvider; private final Supplier<Object[]> parameters;
public static SQLEntity of(Supplier<String> sqlProvider) { // 结果类型
return of(sqlProvider, EMPTY_PARAMETERS); @NonNull
@Getter
private final Class<T> resultType;
private SQLEntity(Class<T> resultType, Supplier<String> sql, Supplier<Object[]> parameters) {
this.resultType = resultType;
this.sql = sql;
this.parameters = parameters;
} }
public static SQLEntity of(Supplier<String> sqlProvider, Supplier<Object[]> parametersProvider) { public static <T> SQLEntity<T> of(Class<T> resultType, Supplier<String> sqlProvider) {
SQLEntity entity = new SQLEntity(); return of(resultType, sqlProvider, EMPTY_PARAMETERS);
entity.sqlProvider = sqlProvider; }
entity.parametersProvider = parametersProvider;
return entity; public static <T> SQLEntity<T> of(Class<T> resultType, Supplier<String> sql, Supplier<Object[]> parameters) {
return new SQLEntity<T>(resultType, sql, parameters);
} }
public String getSql() { public String getSql() {
return sqlProvider.get(); return sql.get();
} }
public Object[] getParameters() { public Object[] getParameters() {
return parametersProvider.get(); return parameters.get();
} }
} }

View File

@ -1,5 +1,6 @@
package group.flyfish.fluent.operations; package group.flyfish.fluent.operations;
import group.flyfish.fluent.entity.DataPage;
import group.flyfish.fluent.entity.SQLEntity; import group.flyfish.fluent.entity.SQLEntity;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
@ -18,22 +19,29 @@ public interface FluentSQLOperations {
* 如果没有结果将返回null * 如果没有结果将返回null
* *
* @param entity sql实体 * @param entity sql实体
* @param clazz 目标类型
* @param <T> 目标泛型 * @param <T> 目标泛型
* @return 查询结果 * @return 查询结果
*/ */
@Nullable @Nullable
<T> T selectOne(SQLEntity entity, Class<T> clazz); <T> T selectOne(SQLEntity<T> entity);
/** /**
* 执行一条sql并且查询出所有行 * 执行一条sql并且查询出所有行
* *
* @param entity sql实体 * @param entity sql实体
* @param clazz 目标类型
* @param <T> 目标泛型 * @param <T> 目标泛型
* @return 返回的列表 * @return 返回的列表
*/ */
<T> List<T> select(SQLEntity entity, Class<T> clazz); <T> List<T> select(SQLEntity<T> entity);
/**
* 分页查询
*
* @param entity sql实体
* @param <T> 目标泛型
* @return 返回的分页对象
*/
<T> DataPage<T> selectPage(SQLEntity<T> entity);
/** /**
* 直接执行sql根据update count返回更新行数如果是查询永远返回0 * 直接执行sql根据update count返回更新行数如果是查询永远返回0
@ -41,5 +49,5 @@ public interface FluentSQLOperations {
* @param entity sql实体 * @param entity sql实体
* @return 更新行数 * @return 更新行数
*/ */
int execute(SQLEntity entity); <T> int execute(SQLEntity<T> entity);
} }

View File

@ -0,0 +1,53 @@
package group.flyfish.fluent.operations;
import group.flyfish.fluent.entity.DataPage;
import group.flyfish.fluent.entity.SQLEntity;
import org.springframework.lang.Nullable;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* sql query操作
*
* @author wangyu
*/
public interface ReactiveFluentSQLOperations {
/**
* 执行一条sql并且序列化为对象
* 注意如果查询不止一条该方法仅返回第一条数据
* 如果没有结果将返回null
*
* @param entity sql实体
* @param <T> 目标泛型
* @return 查询结果
*/
@Nullable
<T> Mono<T> selectOne(SQLEntity<T> entity);
/**
* 执行一条sql并且查询出所有行
*
* @param entity sql实体
* @param <T> 目标泛型
* @return 返回的列表
*/
<T> Flux<T> select(SQLEntity<T> entity);
/**
* 分页查询
*
* @param entity sql实体
* @param <T> 目标泛型
* @return 返回的分页对象
*/
<T> Mono<DataPage<T>> selectPage(SQLEntity<T> entity);
/**
* 直接执行sql根据update count返回更新行数如果是查询永远返回0
*
* @param entity sql实体
* @return 更新行数
*/
<T> Mono<Integer> execute(SQLEntity<T> entity);
}

View File

@ -0,0 +1,20 @@
package group.flyfish.fluent.utils.cache;
import java.util.function.Supplier;
/**
* 绑定的单值对象
*
* @param <T> 泛型
*/
public class BoundObject<T> {
private T object;
public T computeIfAbsent(Supplier<T> supplier) {
if (null == object) {
object = supplier.get();
}
return object;
}
}

View File

@ -0,0 +1,23 @@
package group.flyfish.fluent.utils.cache;
import java.util.function.Supplier;
/**
* 缓存的supplier
*
* @author wangyu
*/
public interface CachedWrapper {
/**
* 包装简单的supplier
*
* @param supplier 提供者
* @param <T> 泛型
* @return 结果
*/
static <T> Supplier<T> wrap(Supplier<T> supplier) {
BoundObject<T> object = new BoundObject<>();
return () -> object.computeIfAbsent(supplier);
}
}

View File

@ -46,8 +46,7 @@ public final class EntityNameUtils {
} }
public static Map<String, String> getFields(Class<?> clazz) { public static Map<String, String> getFields(Class<?> clazz) {
tryCache(clazz); return tryCache(clazz);
return COLUMN_CACHE.get(clazz);
} }
/** /**
@ -78,7 +77,7 @@ public final class EntityNameUtils {
SerializedLambda lambda = resolve(func); SerializedLambda lambda = resolve(func);
String property = SqlNameUtils.methodToProperty(lambda.getImplMethodName()); String property = SqlNameUtils.methodToProperty(lambda.getImplMethodName());
Class<?> beanClass = resolveEntityClass(lambda); Class<?> beanClass = resolveEntityClass(lambda);
String column = COLUMN_CACHE.get(beanClass).getOrDefault(property, SqlNameUtils.camelToUnderline(property)); String column = tryCache(beanClass).getOrDefault(property, SqlNameUtils.camelToUnderline(property));
// 取得别名缓存 // 取得别名缓存
AliasComposite.AliasCache cache = AliasComposite.sharedCache(); AliasComposite.AliasCache cache = AliasComposite.sharedCache();
// 确定最终名称 // 确定最终名称
@ -117,7 +116,9 @@ public final class EntityNameUtils {
* @return 最终获取的类 * @return 最终获取的类
*/ */
private static Class<?> resolveEntityClass(SerializedLambda lambda) { private static Class<?> resolveEntityClass(SerializedLambda lambda) {
return tryCache(lambda.getInstantiatedType()); Class<?> type = lambda.getInstantiatedType();
tryCache(type);
return type;
} }
/** /**
@ -150,8 +151,7 @@ public final class EntityNameUtils {
* *
* @param entityClass bean的类型 * @param entityClass bean的类型
*/ */
private static Class<?> tryCache(Class<?> entityClass) { private static Map<String, String> tryCache(Class<?> entityClass) {
COLUMN_CACHE.computeIfAbsent(entityClass, EntityNameUtils::buildFieldsCache); return COLUMN_CACHE.computeIfAbsent(entityClass, EntityNameUtils::buildFieldsCache);
return entityClass;
} }
} }

View File

@ -0,0 +1,18 @@
package group.flyfish.fluent.utils.sql;
/**
* sql方法用于缓存执行
*
* @param <R> 返回值泛型
*/
@FunctionalInterface
public interface SqlMethod<R> {
/**
* 执行方法
*
* @param parameters 参数
* @return 结果
*/
R execute(Object... parameters);
}

View File

@ -24,7 +24,17 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>group.flyfish.framework</groupId> <groupId>group.flyfish.framework</groupId>
<artifactId>fluent-sql-spring-jdbc</artifactId> <artifactId>fluent-sql-spring</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-r2dbc</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<optional>true</optional>
</dependency> </dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -1,13 +1,20 @@
package group.flyfish.fluent.autoconfigure; package group.flyfish.fluent.autoconfigure;
import group.flyfish.fluent.autoconfigure.FluentSqlAutoConfiguration.JdbcFluentSqlAutoConfigure;
import group.flyfish.fluent.autoconfigure.FluentSqlAutoConfiguration.R2dbcFluentSqlAutoConfigure;
import group.flyfish.fluent.operations.FluentSQLOperations; import group.flyfish.fluent.operations.FluentSQLOperations;
import group.flyfish.fluent.operations.JdbcTemplateFluentSQLOperations; import group.flyfish.fluent.operations.JdbcTemplateFluentSQLOperations;
import group.flyfish.fluent.operations.R2dbcFluentSQLOperations;
import group.flyfish.fluent.operations.ReactiveFluentSQLOperations;
import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.r2dbc.core.DatabaseClient;
import javax.sql.DataSource; import javax.sql.DataSource;
@ -17,8 +24,28 @@ import javax.sql.DataSource;
* @author wangyu * @author wangyu
*/ */
@AutoConfigureAfter(DataSourceAutoConfiguration.class) @AutoConfigureAfter(DataSourceAutoConfiguration.class)
@Import({R2dbcFluentSqlAutoConfigure.class, JdbcFluentSqlAutoConfigure.class})
public class FluentSqlAutoConfiguration { public class FluentSqlAutoConfiguration {
@ConditionalOnClass(DatabaseClient.class)
static class R2dbcFluentSqlAutoConfigure {
/**
* 动态注入初始化的bean完成注入配置
*
* @param databaseClient 从spring r2dbc注入
*/
@Bean
@ConditionalOnMissingBean(ReactiveFluentSQLOperations.class)
@ConditionalOnBean(DatabaseClient.class)
public ReactiveFluentSQLOperations fluentSQLOperations(DatabaseClient databaseClient) {
return new R2dbcFluentSQLOperations(databaseClient);
}
}
@ConditionalOnClass(DataSource.class)
static class JdbcFluentSqlAutoConfigure {
/** /**
* 动态注入初始化的bean完成注入配置 * 动态注入初始化的bean完成注入配置
* *
@ -30,4 +57,5 @@ public class FluentSqlAutoConfiguration {
public FluentSQLOperations fluentSQLOperations(DataSource dataSource) { public FluentSQLOperations fluentSQLOperations(DataSource dataSource) {
return new JdbcTemplateFluentSQLOperations(new JdbcTemplate(dataSource)); return new JdbcTemplateFluentSQLOperations(new JdbcTemplate(dataSource));
} }
}
} }

View File

@ -9,7 +9,7 @@
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>fluent-sql-spring-jdbc</artifactId> <artifactId>fluent-sql-spring</artifactId>
<properties> <properties>
<maven.compiler.source>8</maven.compiler.source> <maven.compiler.source>8</maven.compiler.source>
@ -24,6 +24,12 @@
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId> <artifactId>spring-jdbc</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-r2dbc</artifactId>
<optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>

View File

@ -1,6 +1,7 @@
package group.flyfish.fluent.operations; package group.flyfish.fluent.operations;
import group.flyfish.fluent.chain.SQL; import group.flyfish.fluent.chain.SQL;
import group.flyfish.fluent.entity.DataPage;
import group.flyfish.fluent.entity.SQLEntity; import group.flyfish.fluent.entity.SQLEntity;
import group.flyfish.fluent.mapping.SQLMappedRowMapper; import group.flyfish.fluent.mapping.SQLMappedRowMapper;
import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.dao.EmptyResultDataAccessException;
@ -41,7 +42,7 @@ public class JdbcTemplateFluentSQLOperations implements FluentSQLOperations {
@SuppressWarnings("all") @SuppressWarnings("all")
public <T> T selectOne(SQLEntity entity, Class<T> clazz) { public <T> T selectOne(SQLEntity entity, Class<T> clazz) {
try { try {
String sql = entity.getSql().concat(" limit 1"); String sql = entity.getSql();
if (ClassUtils.isPrimitiveOrWrapper(clazz)) { if (ClassUtils.isPrimitiveOrWrapper(clazz)) {
return jdbcOperations.queryForObject(sql, clazz, entity.getParameters()); return jdbcOperations.queryForObject(sql, clazz, entity.getParameters());
} }
@ -67,6 +68,18 @@ public class JdbcTemplateFluentSQLOperations implements FluentSQLOperations {
return jdbcOperations.query(sql, new SQLMappedRowMapper<>(clazz), entity.getParameters()); return jdbcOperations.query(sql, new SQLMappedRowMapper<>(clazz), entity.getParameters());
} }
/**
* 分页查询
*
* @param entity sql实体
* @param clazz 目标类型
* @return 返回的分页对象
*/
@Override
public <T> DataPage<T> selectPage(SQLEntity entity, Class<T> clazz) {
return null;
}
/** /**
* 直接执行sql根据update count返回更新行数如果是查询永远返回0 * 直接执行sql根据update count返回更新行数如果是查询永远返回0
* *

View File

@ -0,0 +1,63 @@
package group.flyfish.fluent.operations;
import group.flyfish.fluent.entity.DataPage;
import group.flyfish.fluent.entity.SQLEntity;
import lombok.RequiredArgsConstructor;
import org.springframework.r2dbc.core.DatabaseClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@RequiredArgsConstructor
public class R2dbcFluentSQLOperations implements ReactiveFluentSQLOperations {
private final DatabaseClient databaseClient;
/**
* 执行一条sql并且序列化为对象
* 注意如果查询不止一条该方法仅返回第一条数据
* 如果没有结果将返回null
*
* @param entity sql实体
* @param clazz 目标类型
* @return 查询结果
*/
@Override
public <T> Mono<T> selectOne(SQLEntity entity, Class<T> clazz) {
return null;
}
/**
* 执行一条sql并且查询出所有行
*
* @param entity sql实体
* @param clazz 目标类型
* @return 返回的列表
*/
@Override
public <T> Flux<T> select(SQLEntity entity, Class<T> clazz) {
return null;
}
/**
* 分页查询
*
* @param entity sql实体
* @param clazz 目标类型
* @return 返回的分页对象
*/
@Override
public <T> Mono<DataPage<T>> selectPage(SQLEntity entity, Class<T> clazz) {
return null;
}
/**
* 直接执行sql根据update count返回更新行数如果是查询永远返回0
*
* @param entity sql实体
* @return 更新行数
*/
@Override
public Mono<Integer> execute(SQLEntity entity) {
return null;
}
}

View File

@ -7,6 +7,7 @@ import group.flyfish.fluent.operations.JdbcTemplateFluentSQLOperations;
import group.flyfish.framework.cases.FluentSqlTestCase; import group.flyfish.framework.cases.FluentSqlTestCase;
import group.flyfish.framework.cases.JdbcTestCase; import group.flyfish.framework.cases.JdbcTestCase;
import group.flyfish.framework.cases.MybatisTestCase; import group.flyfish.framework.cases.MybatisTestCase;
import group.flyfish.framework.cases.SingleTableTestCase;
import org.junit.Test; import org.junit.Test;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.JdbcOperations;
@ -34,15 +35,20 @@ public class FluentJdbcTest {
public void testSql() throws SQLException, JsonProcessingException { public void testSql() throws SQLException, JsonProcessingException {
DataSource dataSource = new SimpleDriverDataSource( DataSource dataSource = new SimpleDriverDataSource(
new Driver(), new Driver(),
"jdbc:mysql://127.0.0.1:3306/epi_project?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=CONVERT_TO_NULL&useSSL=false&serverTimezone=Asia/Shanghai", "jdbc:mysql://127.0.0.1:3306/epi_project?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=CONVERT_TO_NULL&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true",
"root", "root",
"Unicom#2018" "Unicom#2018" // "oI3WtMO8h%mSYARp"
); );
// 准备待测试用例 // 准备待测试用例
List<TestCase<?>> cases = Arrays.asList( List<TestCase<?>> cases = Arrays.asList(
// jdbc测试
new JdbcTestCase(dataSource), new JdbcTestCase(dataSource),
// Mybatis测试
new MybatisTestCase(dataSource), new MybatisTestCase(dataSource),
new FluentSqlTestCase(dataSource) // FluentSQL测试
new FluentSqlTestCase(dataSource),
// 单表测试
new SingleTableTestCase()
); );
// 执行测试 // 执行测试
cases.forEach(TestCase::test); cases.forEach(TestCase::test);

View File

@ -43,20 +43,23 @@ public abstract class AbstractTestCase<T> implements TestCase<T> {
Name anno = getClass().getAnnotation(Name.class); Name anno = getClass().getAnnotation(Name.class);
assert anno != null; assert anno != null;
String name = anno.value(); String name = anno.value();
long current = System.currentTimeMillis(); print("=====准备执行任务《{0}》=====", name);
T result = null;
try { try {
result = run(); long current = System.currentTimeMillis();
T result = run();
print("【初次执行】执行任务《{0}》用时:{1}ms", name, System.currentTimeMillis() - current); print("【初次执行】执行任务《{0}》用时:{1}ms", name, System.currentTimeMillis() - current);
for (int i = 0; i < 10; i ++) {
current = System.currentTimeMillis(); current = System.currentTimeMillis();
result = run(); result = run();
print("【正常执行】执行任务《{0}》用时:{1}ms", name, System.currentTimeMillis() - current);
}
printResult(result);
return result; return result;
} catch (Exception e) { } catch (Exception e) {
print("执行失败!{0}", e.getMessage()); print("执行失败!{0}", e.getMessage());
throw new RuntimeException(e); throw new RuntimeException(e);
} finally { } finally {
print("【正常执行】执行任务《{0}》用时:{1}ms", name, System.currentTimeMillis() - current); print("=====完成执行任务《{0}》=====");
printResult(result);
} }
} }
} }

View File

@ -1,7 +1,8 @@
package group.flyfish.framework.cases; package group.flyfish.framework.cases;
import group.flyfish.fluent.debug.FluentSqlDebugger; import group.flyfish.fluent.chain.select.AfterWhereSqlChain;
import group.flyfish.fluent.operations.JdbcTemplateFluentSQLOperations; import group.flyfish.fluent.operations.JdbcTemplateFluentSQLOperations;
import group.flyfish.fluent.utils.cache.CachedWrapper;
import group.flyfish.framework.TestCase; import group.flyfish.framework.TestCase;
import group.flyfish.framework.entity.SaasOrder; import group.flyfish.framework.entity.SaasOrder;
import group.flyfish.framework.entity.SaasPlan; import group.flyfish.framework.entity.SaasPlan;
@ -11,6 +12,7 @@ import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource; import javax.sql.DataSource;
import java.util.List; import java.util.List;
import java.util.function.Supplier;
import static group.flyfish.fluent.chain.SQL.select; import static group.flyfish.fluent.chain.SQL.select;
import static group.flyfish.fluent.chain.select.SelectComposite.composite; import static group.flyfish.fluent.chain.select.SelectComposite.composite;
@ -19,6 +21,8 @@ import static group.flyfish.fluent.query.Query.where;
@TestCase.Name("使用fluent-sql") @TestCase.Name("使用fluent-sql")
public class FluentSqlTestCase extends AbstractTestCase<List<TenantContext>> { public class FluentSqlTestCase extends AbstractTestCase<List<TenantContext>> {
private AfterWhereSqlChain sql;
public FluentSqlTestCase(DataSource dataSource) { public FluentSqlTestCase(DataSource dataSource) {
super(dataSource); super(dataSource);
} }
@ -32,8 +36,20 @@ public class FluentSqlTestCase extends AbstractTestCase<List<TenantContext>> {
public void initialize() throws Exception { public void initialize() throws Exception {
// 基于构造器自动绑定注册在实际应用中使用@Bean声明即可可参考下面的demo // 基于构造器自动绑定注册在实际应用中使用@Bean声明即可可参考下面的demo
new JdbcTemplateFluentSQLOperations(new JdbcTemplate(dataSource)); new JdbcTemplateFluentSQLOperations(new JdbcTemplate(dataSource));
// 缓存构建结果
this.sql = select(
// 查询租户全量字段
composite(SaasTenant::getId, SaasTenant::getName, SaasTenant::getIdentifier, SaasTenant::getDatasource,
SaasTenant::getStorage, SaasTenant::getStatus, SaasTenant::getEnable),
// 查询套餐
composite(SaasOrder::getQuotaConfig, SaasOrder::getOrderTime, SaasOrder::getExpireTime,
SaasOrder::getOrderType))
.from(SaasTenant.class)
.leftJoin(SaasOrder.class).on(where(SaasOrder::getTenantId).eq(SaasTenant::getId))
.leftJoin(SaasPlan.class).on(where(SaasPlan::getId).eq(SaasOrder::getPlanId))
.matching(where(SaasTenant::getEnable).eq(true));
// 启用调试 // 启用调试
FluentSqlDebugger.enable(); // FluentSqlDebugger.enable();
} }
/** /**
@ -45,17 +61,6 @@ public class FluentSqlTestCase extends AbstractTestCase<List<TenantContext>> {
@Override @Override
public List<TenantContext> run() throws Exception { public List<TenantContext> run() throws Exception {
// 一个平平无奇的查询 // 一个平平无奇的查询
return select( return sql.list(TenantContext.class);
// 查询租户全量字段
composite(SaasTenant::getId, SaasTenant::getName, SaasTenant::getIdentifier, SaasTenant::getDatasource,
SaasTenant::getStorage, SaasTenant::getStatus, SaasTenant::getEnable),
// 查询套餐
composite(SaasOrder::getQuotaConfig, SaasOrder::getOrderTime, SaasOrder::getExpireTime,
SaasOrder::getOrderType))
.from(SaasTenant.class)
.leftJoin(SaasOrder.class).on(where(SaasOrder::getTenantId).eq(SaasTenant::getId))
.leftJoin(SaasPlan.class).on(where(SaasPlan::getId).eq(SaasOrder::getPlanId))
.matching(where(SaasTenant::getEnable).eq(true))
.list(TenantContext.class);
} }
} }

View File

@ -1,6 +1,7 @@
package group.flyfish.framework.cases; package group.flyfish.framework.cases;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import group.flyfish.fluent.utils.data.ObjectMappers;
import group.flyfish.framework.TestCase; import group.flyfish.framework.TestCase;
import group.flyfish.framework.entity.SaasQuota; import group.flyfish.framework.entity.SaasQuota;
import group.flyfish.framework.entity.SaasTenant; import group.flyfish.framework.entity.SaasTenant;
@ -40,6 +41,7 @@ public class MybatisTestCase extends AbstractTestCase<List<TenantContext>> {
Configuration configuration = new Configuration(environment); Configuration configuration = new Configuration(environment);
configuration.addMapper(TenantContextMapper.class); configuration.addMapper(TenantContextMapper.class);
TypeHandlerRegistry registry = configuration.getTypeHandlerRegistry(); TypeHandlerRegistry registry = configuration.getTypeHandlerRegistry();
JacksonTypeHandler.setObjectMapper(ObjectMappers.shared());
registry.register(SaasTenant.DataSourceConfig.class, JacksonTypeHandler.class); registry.register(SaasTenant.DataSourceConfig.class, JacksonTypeHandler.class);
registry.register(SaasQuota.class, JacksonTypeHandler.class); registry.register(SaasQuota.class, JacksonTypeHandler.class);
registry.register(SaasTenant.StorageConfig.class, JacksonTypeHandler.class); registry.register(SaasTenant.StorageConfig.class, JacksonTypeHandler.class);

View File

@ -0,0 +1,43 @@
package group.flyfish.framework.cases;
import group.flyfish.framework.TestCase;
import group.flyfish.framework.entity.SaasTenant;
import java.util.List;
import static group.flyfish.fluent.chain.SQL.select;
/**
* 单表测试用例
*
* @author wangyu
*/
@TestCase.Name("单表查询测试")
public class SingleTableTestCase extends AbstractTestCase<List<SaasTenant>> {
public SingleTableTestCase() {
super(null);
}
/**
* 初始化
*
* @throws Exception 异常
*/
@Override
public void initialize() throws Exception {
}
/**
* 测试运行逻辑
*
* @return 运行结果
* @throws Exception 异常
*/
@Override
public List<SaasTenant> run() throws Exception {
// 单表查询
return select().from(SaasTenant.class).list();
}
}

View File

@ -10,7 +10,7 @@
<result column="storage" property="storage" typeHandler="com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler" /> <result column="storage" property="storage" typeHandler="com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler" />
<result column="status" property="status" /> <result column="status" property="status" />
<result column="enable" property="enable" /> <result column="enable" property="enable" />
<result column="quotaConfig" property="quota" typeHandler="com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler" /> <result column="quota" property="quota" jdbcType="LONGVARCHAR" typeHandler="com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler" />
<result column="orderTime" property="orderTime" /> <result column="orderTime" property="orderTime" />
<result column="expireTime" property="expireTime" /> <result column="expireTime" property="expireTime" />
<result column="orderType" property="orderType" /> <result column="orderType" property="orderType" />
@ -24,7 +24,7 @@
t1.`storage` as `storage`, t1.`storage` as `storage`,
t1.`status` as `status`, t1.`status` as `status`,
t1.`enable` as `enable`, t1.`enable` as `enable`,
t2.`quota_config` as `quotaConfig`, t2.`quota_config` as `quota`,
t2.`order_time` as `orderTime`, t2.`order_time` as `orderTime`,
t2.`expire_time` as `expireTime`, t2.`expire_time` as `expireTime`,
t2.`order_type` as `orderType` t2.`order_type` as `orderType`

14
pom.xml
View File

@ -29,7 +29,7 @@
<modules> <modules>
<module>fluent-sql-core</module> <module>fluent-sql-core</module>
<module>fluent-sql-spring-jdbc</module> <module>fluent-sql-spring</module>
<module>fluent-sql-annotations</module> <module>fluent-sql-annotations</module>
<module>fluent-sql-spring-boot-starter</module> <module>fluent-sql-spring-boot-starter</module>
</modules> </modules>
@ -88,7 +88,7 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>group.flyfish.framework</groupId> <groupId>group.flyfish.framework</groupId>
<artifactId>fluent-sql-spring-jdbc</artifactId> <artifactId>fluent-sql-spring</artifactId>
<version>${project.version}</version> <version>${project.version}</version>
</dependency> </dependency>
<dependency> <dependency>
@ -121,6 +121,11 @@
<artifactId>spring-context</artifactId> <artifactId>spring-context</artifactId>
<version>${spring.version}</version> <version>${spring.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-r2dbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.mybatis</groupId> <groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId> <artifactId>mybatis</artifactId>
@ -153,6 +158,11 @@
<version>8.0.29</version> <version>8.0.29</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-core</artifactId>
<version>3.6.7</version>
</dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>