AI 摘要

文章示范如何基于 Solon 与 EasyQuery 快速实现 CRUD。先介绍 Solon 的轻量高并发特性及与 Spring 的差异,再讲解 EasyQuery 的隐式 JOIN、强类型链式查询等能力;随后通过 IntelliJ 插件一键生成实体与 DTO,完成数据源配置,实现用户部门关联的列表、分页、单条、新增、修改、删除接口,并给出 SQL 日志与测试截图,帮助开发者零 XML 完成全栈 CRUD。

Solon

Solon 简介

Solon 官网:https://solon.noear.org/

Solon 是新一代国产的 Java 企业级应用开发框架,克制、高效、开放,生态丰富,选择自由。其对标美国博通公司的 Spring 生态,并发高 700%,内存省 50%,启动快 10 倍,打包小 90%,同时支持 Java8 ~ Java25、Native 运行时。

学习技巧:积极参与微信/QQ 等社群交流、Gitee/Github Issue 交流。

Solon 快速入门

Solon 社区提供 IntelliJ IDEA 插件,支持在 IntelliJ IDEA 中快速创建 Solon 项目。

插件安装后,使用 IntelliJ IDEA 创建 Solon 项目。

Solon 项目模板的基本结构:

名称描述
package.App项目启动类
package.DemoController测试控制器
classpath:static静态资源文件夹
classpath:templates视图文件夹
Classpath:app.yml项目配置文件

测试控制器源代码:

@Controller // 标记控制器
public class DemoController {
    
    // @Mapping:绑定请求路径
    // @Param:绑定参数
    @Mapping("/hello")
    public String hello(@Param(defaultValue = "world") String name) {
        return String.format("Hello %s!", name);
    }
    
    // ModelAndView:模型视图对象
    @Mapping("/hello2")
    public ModelAndView hello2(@Param(defaultValue = "world") String name) {
        return new ModelAndView("hello2.ftl").put("name", name);
    }
    
}

运行项目后,在浏览器访问测试控制器:

开发差异注意事项

差异 1:部分依赖无法在 Solon 中直接使用,需要导入与 Solon 适配的依赖。

比如 SaToken 在 Spring Boot 环境中需要导入 Starter:

<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-spring-boot-starter</artifactId>
    <version>1.44.0</version>
</dependency>

该依赖与 Spring Boot 有关联,无法直接在 Solon 中直接导入使用,需要导入与 Solon 适配的 SaToken 依赖:

<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-solon-plugin</artifactId>
    <version>1.44.0</version>
</dependency>

Solon 适配的依赖,可以参考文档:https://solon.noear.org/article/590

差异 2:注解差异。

Spring Boot 注解Solon 注解描述
@Resource
@Autowired
@Inject注入 Bean 对象
/@Db注入数据访问层 Bean 对象
@Component
@Repository
@Service
@Component注册为 Bean 对象
@Controller
@RestController
@Controller控制器注解
在 Solon 中,控制器会自动通过不同的返回值做不同的处理
@RequestMapping@Mapping路由关系映射注解,绑定请求路径
@XxxMapping@Mapping + @Get/@Post...路由关系映射注解的辅助注解,便于 RESTful 开发
@RequestParam
@PathVariable
@Param接收请求参数
@SpringBootApplication@SolonMain启动类注解

其余注解差异,可以参考文档:https://solon.noear.org/article/590

差异 3:配置差异。

Solon 和 Spring Boot 都支持 yml 和 properties 格式的配置文件,但项目配置项、依赖配置项在 Solon 上产生了一些变化。

比如数据源在 Spring Boot 中的配置:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/your_db_name?serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

在 Solon 中的配置:

solon.dataSources:
  db1!:
    class: "com.zaxxer.hikari.HikariDataSource"
    jdbcUrl: jdbc:mysql://localhost:3306/rock?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedStatements=true
    driverClassName: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456

建议开发前仔细阅读入门文档,开发过程中勤于查阅官方文档,解决差异问题。

正式开发前,建议仔细阅读以下文档:

EasyQuery

EasyQuery 简介

EasyQuery 是一个国产开源 ORM 框架,专为 Java 和 Kotlin 语言设计,以轻量、高性能、强类型为核心特点。

EasyQuery 官网:https://www.easy-query.com/

EasyQuery 的核心功能:

  • 五大核心隐式特性:区别于传统 ORM 框架的关键,EasyQuery 支持包括隐式 JOIN 可自动识别表关联关系无需手动写联表语句、隐式子查询能智能生成优化的子查询语句、隐式分组优化可合并多个子查询、隐式分区分组能通过简单语法获取分组内特定数据、隐式 CASE WHEN 表达式则通过链式语法实现强类型条件聚合计算。
  • 全场景数据库操作支持:涵盖完整的 CRUD 操作,还支持动态表名、多表联合查询、复杂子查询嵌套和分页查询优化等,适配 MySQL、PostgreSQL 等主流关系型数据库,可自动适配不同数据库的 SQL 语法差异,迁移数据库时无需大量修改代码。
  • 进阶扩展能力:支持读写分离、数据库分片,能应对高并发和大数据量场景,还支持批量增删改,优化大数据量操作效率。
  • 微服务与多场景项目:可适配 Spring Boot、Solon 等微服务框架,也适合跨数据库开发的团队以及追求性能与简洁平衡的中小型项目团队。

正如官方所说:EasyQuery 是一款 Java 下最强的、最懂查询的 ORM。

EasyQuery 快速入门

Solon + EasyQuery 快速入门:https://www.easy-query.com/easy-query-doc/guide/solon.html

快速入门核心 1:在 IntelliJ IDEA 中安装 EasyQueryAssistant 插件,用于快速生成实体类、代理类、添加接口。

快速入门核心 2:引入 sql-processor,为 EasyQuery APT 提供支持。

<dependency>
    <groupId>com.easy-query</groupId>
    <artifactId>sql-processor</artifactId>
    <version>3.1.62</version>
</dependency>

快速入门核心 3:实体类中的 ProxyEntityAvailable 和 EntityProxy。

ProxyEntityAvailable 是 EasyQuery 中的一个接口,用于标识支持代理功能的实体类,实体类通过实现该接口并指定对应的代理类(EntityProxy),可以实现对数据库操作的代理和增强。

实体类上添加了 @EntityProxy 注解,可以使用 EasyQueryAssistant 插件快速实现接口,并生成对应的代理类。

@EntityProxy
public class UsersEntity implements ProxyEntityAvailable<UsersEntity, UsersEntityProxy> {
    // ...
}

快速入门核心 4:实体类中的注解。

名称描述
@EntityProxy用于生成代理对象
@Table描述对象对应数据库表名
@Column描述属性对应的列名
@ColumnIgnore标记非数据库字段/属性添加这个注解的属性将会被直接忽略映射到数据库
@LogicDelete标记逻辑删除字段/属性
@Navigate用于数据库对象和返回结果上面,处理一对一、一对多、多对一、多对多关系

快速入门核心 5:EasyQuery 的核心 API 客户端 EasyEntityQuery。

EasyEntityQuery 是 EasyQuery 框架中的一个核心组件,主要用于强类型数据库查询操作,其支持通过链式 API 构建 SQL 查询,无需手动编写 SQL 语句。

@Db    // 注入EasyEntityQuery
EasyEntityQuery easyEntityQuery;

easyEntityQuery.queryable(Topic.class)
    .where(o->o.stars().ge(2))    // o=Topic实体/表,即条件为 stars >= 2
    .toList();
==> Preparing: SELECT `id`,`stars`,`title`,`create_time` FROM `t_topic` WHERE `stars` >= ?
==> Parameters: 2(Integer)
<== Time Elapsed: 17(ms)
<== Total: 101

EasyQuery CRUD

正式开发前,建议仔细阅读以下文档:https://xuejmnet.github.io/easy-query-doc/ability/select/query-bean.html

查询单条数据:

Users user = easyEntityQuery.queryable(Users.class)
    .where(u -> {
        // 查询条件:name = "xiao ming"
        u.username().eq("xiao ming");
    })
    .firstOrNull();    // 查询单条数据,查无则返回null

查询列表数据:

List<Users> userList = easyEntityQuery.queryable(Users.class)
    .where(u -> {
        // 多条件查询
        u.username().contains("zhang san");
        u.id().gt(20);
    })
    .toList();    // 查询列表数据

查询列表并排序:

List<Users> userList = easyEntityQuery.queryable(Users.class)
    .where(u -> {
        u.username().contains("zhang san");
        u.id().gt(20);
    })
    .orderBy(m -> {
        // 排序条件:id DESC
        m.id().desc();
    })
    .toList();

查询分页数据:

EasyPageResult<Users> userPageResult = easyEntityQuery.queryable(Users.class)
    .toPageResult(1, 20);    // 查询分页数据,页码为1,页大小为20

多表查询:

@FieldNameConstants    // Lombok中给实体类属性名生成常量
@Table(value = "users")
@EntityProxy
public class UsersEntity implements ProxyEntityAvailable<UsersEntity , UsersEntityProxy> {

    // ...

    /**
     * 关联部门实体
     * 条件:users.deptId = dept.id
     * 通过Lombok生成的属性名常量指定关联属性
     **/
    @Navigate(value = RelationTypeEnum.ManyToOne, selfProperty = Fields.deptId, targetProperty = DeptEntity.Fields.id)
    private DeptEntity deptEntity;

}
easyEntityQuery.queryable(UsersEntity.class)
    .include(u -> u.dept())    // 自动查询并关联用户表中关联的部门表数据
    .toList();

多表查询使用结构化 DTO:

/**
 * 使用EasyQueryAssistant为实体类生成结构化DTO
 * 可以控制查询返回的字段
 */
@Data
public class UsersEntityDTO {

    // ...


    /**
     * 关联部门实体
     * 条件:users.deptId = dept.id
     **/
    @Navigate(value = RelationTypeEnum.ManyToOne)
    private InternalDeptEntity deptEntity;


    /**
     * {@link DeptEntity }
     *
     */
    @Data
    public static class InternalDeptEntity {
        
        // ...

    }

}
easyEntityQuery.queryable(UsersEntity.class)
    .selectAutoInclude(UsersEntityDTO.class)    // 自动查询并关联UsersEntityDTO中的关联数据
    .toList();

新增数据:

long rows = easyEntityQuery.insertable(user).executeRows();

修改数据:

long rows = easyEntityQuery.updatable(user).executeRows();

删除数据:

long rows = easyEntityQuery.deletable(Users.class)
    .where(u -> u.username().eq("li si"))
    .executeRows();

Solon + EasyQuery

数据库结构

用户表 users:

字段类型描述
idint用户 id,主键自增
dept_idint关联部门 id
usernamevarchar用户名
emailvarchar邮箱
deletedbigint逻辑删除:0 未删除,大于 0 已删除

部门表 dept:

字段类型描述
idint部门 id,主键自增
namevarchar部门名称

数据库、表构建完成后,分别为用户表、部门表添加测试数据。

构建项目

在 IntelliJ IDEA 中创建 Solon 项目。

引入相关依赖:

<dependencies>
    <!-- Solon套件 -->
    <dependency>
        <groupId>org.noear</groupId>
        <artifactId>solon-web</artifactId>
    </dependency>
    <!-- EasyQuery -->
    <dependency>
        <groupId>com.easy-query</groupId>
        <artifactId>sql-solon-plugin</artifactId>
        <version>3.1.62</version>
    </dependency>
    <dependency>
        <groupId>com.easy-query</groupId>
        <artifactId>sql-processor</artifactId>
        <version>3.1.62</version>
    </dependency>
    <!-- HikariCP数据源 -->
    <dependency>
        <groupId>com.zaxxer</groupId>
        <artifactId>HikariCP</artifactId>
        <version>3.2.0</version>
    </dependency>
    <!-- MySQL数据库驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.28</version>
    </dependency>
    <!-- Lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <scope>provided</scope>
    </dependency>
    <!-- Logback日志 -->
    <dependency>
        <groupId>org.noear</groupId>
        <artifactId>solon-logging-logback</artifactId>
    </dependency>
    <!-- Solon测试 -->
    <dependency>
        <groupId>org.noear</groupId>
        <artifactId>solon-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

编写配置文件:

server:
  port: 8080 # 端口号

solon:
  dataSources:
    ds1!: # 数据源1,名称为ds1,!表示默认数据源
      class: com.zaxxer.hikari.HikariDataSource
      driverClassName: com.mysql.cj.jdbc.Driver
      jdbcUrl: jdbc:mysql://127.0.0.1:3306/demo?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
      username: root
      password: 123456..

easy-query:
  ds1:  # 配置数据源1的EasyQuery
    database: mysql
    print-sql: true # 打印SQL日志

使用 EasyQueryAssistant 插件生成用户表、部门表实体类、代理类:

/**
 * 部门表 实体类。
 *
 * @author easy-query-plugin automatic generation
 * @since 1.0
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@FieldNameConstants
@Table(value = "dept")
@EntityProxy
public class DeptEntity implements ProxyEntityAvailable<DeptEntity , DeptEntityProxy> {

    /**
     * 部门id
     */
    @Column(primaryKey = true, value = "id")
    private Integer id;

    /**
     * 部门名称
     */
    private String name;

}
/**
 * 用户表 实体类。
 *
 * @author easy-query-plugin automatic generation
 * @since 1.0
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@FieldNameConstants
@Table(value = "users")
@EntityProxy
public class UsersEntity implements ProxyEntityAvailable<UsersEntity , UsersEntityProxy> {

    /**
     * 用户id
     */
    @Column(primaryKey = true, value = "id")
    private Integer id;

    /**
     * 关联部门id
     */
    private Integer deptId;

    /**
     * 用户名
     */
    private String username;

    /**
     * 邮箱
     */
    private String email;

    /**
     * 逻辑删除:0未删除,大于0已删除
     */
    @LogicDelete(strategy = LogicDeleteStrategyEnum.DELETE_LONG_TIMESTAMP)
    private Long deleted;

    /**
     * 关联部门实体,需要手动加入
     * 条件:users.deptId = dept.id
     * 关联关系:多个用户对应一个部门,即多对一
     **/
    @Navigate(value = RelationTypeEnum.ManyToOne, selfProperty = {UsersEntity.Fields.deptId}, targetProperty = {DeptEntity.Fields.id})
    @ColumnIgnore   // 标记为非数据库字段
    private DeptEntity dept;

}

使用 EasyQueryAssistant 插件为用户表实体类生成结构化 DTO:

/**
 * this file automatically generated by easy-query struct dto mapping
 * 当前文件是easy-query自动生成的 结构化dto 映射
 * {@link UsersEntity }
 *
 * @author ${appContext.getAuthor()}
 * @easy-query-dto schema: normal
 */
@Data
public class UsersEntityDTO {


    /**
     * 用户id
     */
    @Column(value = "id")
    private Integer id;
    /**
     * 关联部门id
     */
    private Integer deptId;
    /**
     * 用户名
     */
    private String username;
    /**
     * 邮箱
     */
    private String email;
    /**
     * 逻辑删除:0未删除,大于0已删除
     * 不查询该字段,直接注释掉
     */
    /**
    @LogicDelete(strategy = LogicDeleteStrategyEnum.DELETE_LONG_TIMESTAMP)
    private Long deleted;
    **/
    /**
     * 关联部门实体
     * 条件:users.deptId = dept.id
     * 关联关系:多个用户对应一个部门,即多对一
     **/
    @Navigate(value = RelationTypeEnum.ManyToOne)
    private InternalDept dept;


    /**
     * {@link DeptEntity }
     *
     */
    @Data
    public static class InternalDept {
        /**
         * 部门id
         * 不查询该字段,直接注释掉
         */
        /**
        @Column(value = "id")
        private Integer id;
        */
        /**
         * 部门名称
         */
        private String name;


    }

}

创建用户控制器:

/**
 * 用户控制器
 */
@Controller
@Mapping(path = "/users")
public class UsersController {

    /**
     * EasyQuery操作客户端
     */
    @Db // 注入数据访问层Bean对象
    EasyEntityQuery easyEntityQuery;

}

查询列表

在用户控制器中编写查询列表的方法:

/**
 * 查询列表
 * @param username 查询用户名
 * @param email 查询邮箱
 * @return 查询结果
 */
@Get
@Mapping(path = "/list")
public Object list(String username, String email) {
    // 根据用户名和邮箱查询用户列表,包含部门信息
    return easyEntityQuery.queryable(UsersEntity.class)
            .where(u -> {
                // 查询条件不为空,拼接查询条件
                u.username().eq(username != null, username);
                u.email().eq(email != null, email);
            })
            // 通过结构化对象自定义查询返回结果
            .selectAutoInclude(UsersEntityDTO.class)
            .toList();
}

在 Apifox 中请求测试结果:

IntelliJ IDEA 控制台打印的 SQL 语句:

==> Preparing: SELECT t.`id`,t.`dept_id`,t.`username`,t.`email` FROM `users` t WHERE t.`deleted` = ? AND t.`username` = ?
==> Parameters: 0(Integer),张三(String)
<== Time Elapsed: 3(ms)
<== Total: 1

==> Preparing: SELECT t.`name`,t.`id` AS `__relation__id` FROM `dept` t WHERE t.`id` IN (?)
==> Parameters: 1(Integer)
<== Time Elapsed: 1(ms)
<== Total: 1

查询分页

在用户控制器中编写查询分页的方法:

/**
 * 查询分页
 * @param pageIndex 页码
 * @return 查询结果
 */
@Get
@Mapping(path = "/page")
public Object page(@Param(required = false, defaultValue = "1") Long pageIndex) {
    // 查询用户分页列表,默认页大小为10,默认页码为1
    return easyEntityQuery.queryable(UsersEntity.class)
            .selectAutoInclude(UsersEntityDTO.class)
            .toPageResult(pageIndex, 5);
}

在 Apifox 中请求测试结果:

IntelliJ IDEA 控制台打印的 SQL 语句:

==> Preparing: SELECT t.`id`,t.`dept_id`,t.`username`,t.`email` FROM `users` t WHERE t.`deleted` = ? LIMIT 4
==> Parameters: 0(Integer)
<== Time Elapsed: 1(ms)
<== Total: 4

==> Preparing: SELECT t.`name`,t.`id` AS `__relation__id` FROM `dept` t WHERE t.`id` IN (?,?,?)
==> Parameters: 1(Integer),2(Integer),3(Integer)
<== Time Elapsed: 1(ms)
<== Total: 3

查询单条数据

在用户控制器中编写查询单条数据的方法:

/**
 * 查询单条数据
 * @param id 用户id
 * @return 查询结果
 */
@Get
@Mapping(path = "/get/{id}")    // 使用RESTFul风格
public Object get(Integer id) {
    // 根据id查询用户数据
    return easyEntityQuery.queryable(UsersEntity.class)
            .where(u -> u.id().eq(id))
            .include(u -> u.dept()) // 自动查询并关联用户表中关联的部门表数据
            .firstOrNull(); // 返回查询结果,无则返回null
}

在 Apifox 中请求测试结果:

IntelliJ IDEA 控制台打印的 SQL 语句:

==> Preparing: SELECT `id`,`dept_id`,`username`,`email`,`deleted` FROM `users` WHERE `deleted` = ? AND `id` = ? LIMIT 1
==> Parameters: 0(Integer),1(Integer)
<== Time Elapsed: 1(ms)
<== Total: 1

==> Preparing: SELECT t.`id`,t.`name` FROM `dept` t WHERE t.`id` IN (?)
==> Parameters: 1(Integer)
<== Time Elapsed: 1(ms)
<== Total: 1

新增数据

在用户控制器中编写新增数据的方法:

/**
 * 新增数据
 * @param usersEntity 用户数据
 * @return 新增结果
 */
@Post
@Mapping(path = "/add")
public Object add(UsersEntity usersEntity) {
    // 新增用户数据,根据影响行数判断返回true/false
    return easyEntityQuery.insertable(usersEntity)
            .executeRows() > 0;
}

在 Apifox 中请求测试结果:

IntelliJ IDEA 控制台打印的 SQL 语句:

==> Preparing: INSERT INTO `users` (`dept_id`,`username`,`email`) VALUES (?,?,?)
==> Parameters: 2(Integer),duozai(String),vis@duox.dev(String)
<== Total: 1

删除数据

在用户控制器中编写删除数据的方法:

/**
 * 删除数据
 * @param id 用户id
 * @return 删除结果
 */
@Delete
@Mapping(path = "/delete/{id}")
public Object delete(Integer id) {
    // 删除用户数据
    return easyEntityQuery.deletable(UsersEntity.class)
            .where(u -> u.id().eq(id))
            .executeRows() > 0;
}

在 Apifox 中请求测试结果:

IntelliJ IDEA 控制台打印的 SQL 语句:

==> Preparing: UPDATE `users` SET `deleted` = ? WHERE `deleted` = ? AND `id` = ?
==> Parameters: 1764174628390(Long),0(Integer),1(Integer)
<== Total: 1

修改数据

在用户控制器中编写修改数据的方法:

/**
 * 修改数据
 * @param usersEntity 用户数据
 * @return 修改结果
 */
@Put
@Mapping(path = "/update")
public Object update(UsersEntity usersEntity) {
    // 修改用户数据
    return easyEntityQuery.updatable(usersEntity)
            .executeRows() > 0;
}

在 Apifox 中请求测试结果:

IntelliJ IDEA 控制台打印的 SQL 语句:

==> Preparing: UPDATE `users` SET `dept_id` = ?,`username` = ?,`email` = ? WHERE `deleted` = ? AND `id` = ?
==> Parameters: 3(Integer),shaozai(String),123@qq.com(String),0(Integer),2(Integer)