快速开始
需要先创建 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 后无需重启服务,生产环境需要关闭
debugtrue
# 启动时打印 banner
print-bannertrue
# 是否打印 Fenix 生成的 SQL 信息
print-sqltrue
# 扫描 Fenix XML 文件的所在位置,默认是 fenix 目录及子目录,可以用 yaml 文件方式配置多个值.
xml-locations fenix
添加实体类:
name = "tb_blog")
(
public class Blog {
strategy = GenerationType.IDENTITY)
( private Long id;
length = 20)
( private String title;
private String content;
length = 10)
( private String author;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
添加存储库接口:
public interface BlogRepository extends JpaRepository<Blog, Long> {
"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 类以实现具体的查询逻辑:
provider = BlogSqlProvider.class)
(List<Blog> queryBlogsWithProvider( ("blog") Blog blog);
provider 类:
public class BlogSqlProvider {
public SqlInfo queryBlogsWithProvider( ("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 用于传输查询参数:
public class BlogParam {
private String title;
private String author;
"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
注解中不需要再指定名空间:
"queryBlogs")
(Page<Blog> queryBlogs(Blog blog, Pageable pageable);
如果 XML 的fenix
标签的id
与接口方法名相同:
<fenix id="queryBlogs">
标签 ID 也可以省略:
Page<Blog> queryBlogs(Blog blog, Pageable pageable);
如果存储库接口方法设置了Pageable
类型的参数,Fenix 会自动添加一条 SELECT 语句用于查询数据总数进行分页。如果结果不符合预期,可以自行编写获取数据总数的 JPQL 或 SQL:
countQuery = "queryBlogsTotal")
(Page<Blog> queryBlogs( ("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 文件支持 语法:
<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 标签可以查看。
本文的完整示例可以从获取。
参考资料
文章评论