简单计算
简单计算属性是使用@org.babyfish.jimmer.sql.Formula声明的属性,有两者用法
- 基于Java/Kotlin的计算属性
- 基于SQL的计算属性
简单计算属性为实现简单而快速的计算而设计,如果需要复杂的计算,请采用复杂计算属性
在定义实体一文中,我们为Author定义了两个字段:firstName和lastName。
接下来,让我们为Author添加一个叫新属性fullName:
fullName = firstName + ' ' + lastName
接下来,我们用两者不同的方式,即基于Java/Kotlin的计算和基于SQL的计算,来实现Author.fullName
1. 基于Java/Kotlin的计算
依赖普通属性
- Java
- Kotlin
package com.example.model;
import org.babyfish.jimmer.sql.*;
@Entity
public interface Author {
String firstName();
String lastName();
@Formula(dependencies = {"firstName", "lastName"})
default String fullName() {
return firstName() + ' ' + lastName();
}
...省略其他属性...
}
package com.example.model
import org.babyfish.jimmer.sql.*
@Entity
interface Author {
val firstName: String
val lastName: String
@Formula(dependencies = ["firstName", "lastName"])
val fullName: String
get() = "$firstName $lastName"
...省略其他属性...
}
不难发现,基于Java/Kotlin的简单计算属性有以下特征:
-
属性不是抽象的(Java下需要使用
default关键字),直接给出计算逻辑实现。 -
@Formula的dependencies被指定,表示当前属性依赖于Author.firstName和Author.lastName。即,动态实体必须确保同时具备
firstName和lastName属性才可以计算fullName
用法如下
- Java
- Kotlin
Author author = authorRepository.findNullable(
1L,
Fetchers.AUTHOR_FETCHER
//查询id(隐含+强制)和fullName
.fullName()
);
System.out.println(author);
val author = authorRepository.findNullable(
1L,
newFetcher(Author::class).by {
//查询id(隐含+强制)和fullName
fullName()
}
);
println(author)
执行的SQL为
select
tb_1_.ID,
tb_1_.FIRST_NAME,
tb_1_.LAST_NAME
from AUTHOR as tb_1_
where tb_1_.ID = ?
fullName是计算属性,在数据库中无对应字段,但其依赖于firstName和lastName,
所以此SQL查询FIRST_NAME和LAST_NAME,让其依赖的属性存在。
接下来,我们看看代码中打印会输出什么
{"id":1,"fullName":"Eve Procello"}
我们看到,Jackson序列化(实体对象的toString方法是序列化的一种快捷方式)后只有fullName,但没有firstName和lastName。
这是,因为对象抓取器因抓取fullName而导致firstName和lastName被间接抓取,他们并未被直接抓取。
在这种情况下,虽然动态对象具备firstName和lastName,但它们被标记成对Jackson不可见的状态,不会出现在Jackson序列化结果中。
如果让对象抓取器直接抓取firstName和lastName,那么它们一定会出现在序列化结果中。读者可以自行试验,这里不再赘述。
依赖Embbeddable
假设有一个Embeddable类型
- Java
- Kotlin
@Embeddable
public interface NameInfo {
String firstName();
String lastName();
}
@Embeddable
interface NameInfo {
val firstName: String
val lastName: String
}
如果某实体使用了此Embeddable类型,那么实体属性可以依赖其内部属性,例如
- Java
- Kotlin
@Entity
public interface Author {
NameInfo nameInfo();
@Formula(dependencies = {"nameInfo.firstName", "nameInfo.lastName"})
// 也可以写成:@Formula(dependencies = "nameInfo")
default String fullName() {
return nameInfo().firstName() + ' ' + nameInfo().lastName();
}
...省略其他属性...
}
@Entity
interface Author {
val nameInfo: NameInfo
@Formula(dependencies = ["nameInfo.firstName", "nameInfo.lastName"])
// 也可以写成:@Formula(dependencies = ["nameInfo"])
...省略其他属性...
val fullName: String
get() = "${nameInfo.firstName} ${nameInfo.lastName}"
}
使用方法和执行效果完全同上,无需重复。
依赖关联属性
- Java
- Kotlin
@Entity
public interface Book {
@ManyToMany
List<Author> authors();
@Formula(dependencies = "authors")
default int authorCount() {
return authors().size();
}
@Formula(dependencies = {"authors.firstName", "authors.lastName"})
default List<String> authorNames() {
return authors()
.stream()
.map(author -> author.firstName() + ' ' + author.lastName())
.collect(Collectors.toList());
}
...省略其他属性...
}
@Entity
public interface Book {
@ManyToMany
val authors: List<Author>
@Formula(dependencies = "authors")
val authorCount: Int
get() = authors.size
@Formula(dependencies = ["authors.firstName", "authors.lastName"])
val authorNames: List<String>
get() = authors.map { "${it.firstName} ${it.lastName}" }
...省略其他属性...
}
执行如下代码
- Java
- Kotlin
BookTable table = BookTable.$;
List<Book> books = sqlClient
.createQuery(table)
.where(table.name().eq("Learning GraphQL"))
.orderBy(table.edition().desc())
.select(
table.fetch(
BookFetcher.$
.name()
.edition()
.authorCount()
.authorNames()
)
)
.execute();