红茶的个人站点

  • 首页
  • 专栏
  • 开发工具
  • 其它
  • 隐私政策
Awalon
Talk is cheap,show me the code.
  1. 首页
  2. 专栏
  3. Spring Boot 学习笔记
  4. 正文

JPA 学习笔记 6:Fenix

2025年10月8日 28点热度 0人点赞 0条评论

快速开始

需要先创建 Spring Boot 项目并整合 Spring Data JPA,可以参考这里。

添加 Fenix 依赖:

<dependency>
    <groupId>com.blinkfox</groupId>
    <artifactId>fenix-spring-boot-starter</artifactId>
    <version>3.1.0</version>
</dependency>

按需添加 fenix 配置:

fenix:
    # 对 XML 中的 SQL 动态读取,修改 XML 后无需重启服务,生产环境需要关闭
    debug: true
    # 启动时打印 banner
    print-banner: true
    # 是否打印 Fenix 生成的 SQL 信息
    print-sql: true
    # 扫描 Fenix XML 文件的所在位置,默认是 fenix 目录及子目录,可以用 yaml 文件方式配置多个值.
    xml-locations: fenix

添加实体类:

@Table(name = "tb_blog")
@Entity
@Data
public class Blog {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(length = 20)
    private String title;
    private String content;
    @Column(length = 10)
    private String author;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
}

添加存储库接口:

public interface BlogRepository extends JpaRepository<Blog, Long> {
    @QueryFenix("BlogRepository.queryBlogs")
    Page< Blog> queryBlogs(Blog blog, Pageable pageable);
}

接口方法上的@QueryFenix("BlogRepository.queryBlogs")注解用于定位 XML 文件中的 JPQL 语句。

建议安装 Idea 插件 Fenix。

添加 XML src/main/resources/fenix/BlogRepository.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<fenixs namespace="BlogRepository">
    <fenix id="queryBlogs">
        select b
        from Blog as b
        where
        1=1
        <andLike field="b.title" value="blog.title" match="blog.title!=empty"/>
        <andLike field="b.author" value="blog.author" match="blog.author!=empty"/>
        <andBetween field="b.createTime" start="blog.createTime" end="blog.updateTime" match="(?blog.createTime != empty) || (?blog.updateTime != empty)"/>
    </fenix>
</fenixs>

fenix标签中可以定义 JPQL 语句。

测试用例:

final int PAGE_NUM = 1;
final int PAGE_SIZE = 5;
Pageable pageable = PageRequest.of(PAGE_NUM - 1, PAGE_SIZE);
Blog blog = new Blog();
blog.setTitle("NuS");
Page<Blog> blogPage = blogRepository.queryBlogs(blog, pageable);
System.out.println("总行数:" + blogPage.getTotalElements());
System.out.println("总页数:" + blogPage.getTotalPages());
System.out.println("当前页数据:" + blogPage.getContent());
System.out.println("当前页:" + (blogPage.getNumber() + 1));
System.out.println("每页大小:" + blogPage.getSize());
System.out.println("是否有下一页:" + blogPage.hasNext());
System.out.println("是否有上一页:" + blogPage.hasPrevious());

provider

可以使用@QueryFenix为方法指定一个 provider 类以实现具体的查询逻辑:

@QueryFenix(provider = BlogSqlProvider.class)
List<Blog> queryBlogsWithProvider(@Param("blog") Blog blog);

provider 类:

public class BlogSqlProvider {
    public SqlInfo queryBlogsWithProvider(@Param("blog") Blog blog){
        return Fenix.start()
                .select("b")
                .from("Blog").as("b")
                .where("1=1")
                .andLike("b.title", blog.getTitle(), StringHelper.isNotBlank(blog.getTitle()))
                .andLike("b.author", blog.getAuthor(), StringHelper.isNotBlank(blog.getAuthor()))
                .andBetween("b.createTime", blog.getCreateTime(), blog.getUpdateTime())
                .end();
    }
}

provider 类中的方法名和形参列表要与存储库接口方法一致,返回的是 SqlInfo 类型。

FenixJpaSpecificationExecutor

Spring Data JPA 的JpaSpecificationExecutor同样可以在使用 Fenix 时用于以编程方式实现 JPQL,不过 Fenix 封装了一个子接口FenixJpaSpecificationExecutor,提供更方便的写法:

public interface BlogRepository extends JpaRepository<Blog, Long>, FenixJpaSpecificationExecutor<Blog> {

示例:

final int PAGE_NUM = 1;
final int PAGE_SIZE = 5;
Blog blog = new Blog();
blog.setTitle("NuS");
Pageable pageable = PageRequest.of(PAGE_NUM - 1, PAGE_SIZE);
FenixPredicate specification = (builder) -> {
    return builder
        .andLike("title", blog.getTitle())
        .andLike("author", blog.getAuthor(), StringHelper.isNotBlank(blog.getAuthor()))
        .andBetween("createTime", blog.getCreateTime(), blog.getUpdateTime(), blog.getCreateTime() != null || blog.getUpdateTime() != null)
        .build();
};
Page<Blog> blogPage = blogRepository.findAll(specification, pageable);

Bean

可以定义一个 Bean 用于传输查询参数:

@Data
public class BlogParam {
    @Like
    private String title;
    @Like
    private String author;
    @Between("createTime")
    private BetweenValue<Date> createTime;
}

可以使用FenixJpaSpecificationExecutor接口中的方法利用 Bean 完成查询:

final int PAGE_NUM = 1;
final int PAGE_SIZE = 5;
BlogParam blogParam = new BlogParam();
blogParam.setTitle("NuS");
Pageable pageable = PageRequest.of(PAGE_NUM - 1, PAGE_SIZE);
Page<Blog> blogPage = blogRepository.findAllOfBean(blogParam, pageable);

@QueryFenix

XML 的fenixs标签的名空间可以命名为对应的存储库接口的全名:

<fenixs namespace="cn.icexmoon.demo.repository.BlogRepository">

此时在存储库接口的方法的@QueryFenix注解中不需要再指定名空间:

@QueryFenix("queryBlogs")
Page<Blog> queryBlogs(Blog blog, Pageable pageable);

如果 XML 的fenix标签的id与接口方法名相同:

<fenix id="queryBlogs">

标签 ID 也可以省略:

@QueryFenix
Page<Blog> queryBlogs(Blog blog, Pageable pageable);

如果存储库接口方法设置了Pageable类型的参数,Fenix 会自动添加一条 SELECT 语句用于查询数据总数进行分页。如果结果不符合预期,可以自行编写获取数据总数的 JPQL 或 SQL:

@QueryFenix(countQuery = "queryBlogsTotal")
Page<Blog> queryBlogs(@Param("blog") Blog blog, Pageable pageable);

对应的 JPQL 片段:

<fenix id="queryBlogsTotal">
    select count(*)
    from Blog as b
    where
    1=1
    <andLike field="b.title" value="blog.title" match="blog.title!=empty"/>
    <andLike field="b.author" value="blog.author" match="blog.author!=empty"/>
    <andBetween field="b.createTime" start="blog.createTime" end="blog.updateTime" match="(?blog.createTime != empty) || (?blog.updateTime != empty)"/>
</fenix>

MVEL 模版

Fenix 的 XML 文件支持 MVEL 模版语法:

<fenix id="queryBlogs">
    select b
    from Blog as b
    where
    1=1
    @if{?blog.title != empty}
    AND b.title like concat('%',#{blog.title},'%')
    @end{}
    @if{?blog.author != empty}
    AND b.author like concat('%',#{blog.author},'%')
    @end{}
    @if{(?blog.createTime != empty) || (?blog.updateTime != empty)}
    AND (b.createTime between #{blog.createTime} and #{blog.updateTime})
    @end{}
</fenix>

这里的#{}是 Fenix 的差值语法,会生成对应的 JPQL 具名参数,可以有效防止 SQL 注入。

更多 MVEL 模版写法可以参考这里。

SQL 标签

Fenix 提供一系列用于构造 JPQL 的标签。

like

<andLike field="b.title" value="blog.title" match="blog.title!=empty"/>

生成的 JPQL 如下:

select b from Blog as b where 1=1 AND b.title LIKE :blog_title

参数:

{blog_title=%NuS%}

如果不需要%?%的匹配方式,比如只需要右模糊匹配,可以:

<andLike field="b.title" pattern="@{blog.title}%" match="blog.title!=empty"/>

需要注意的是,这个示例有问题,@{blog.title}会直接用差值语法插入内容到 JPQL,而非#{...}的具名参数 JPQL,所以这里有潜在的 SQL 注入风险。可以考虑以其他方式生成 JPQL 或者使用 MVEL 模版拼接原生 SQL。

更多 Fenix 的 SQL 标签可以查看🌶️ SQL 语义化标签 - Fenix 文档。

本文的完整示例可以从这里获取。

参考资料

  • Fenix 文档

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: fenix jpa
最后更新:2025年10月8日

魔芋红茶

加一点PHP,加一点Go,加一点Python......

点赞
< 上一篇

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

COPYRIGHT © 2021 icexmoon.cn. ALL RIGHTS RESERVED.
本网站由提供CDN加速/云存储服务

Theme Kratos Made By Seaton Jiang

宁ICP备2021001508号

宁公网安备64040202000141号