视图属性
IdView
对于数据结构,前后端各自偏好有所不同,后端倾向于结构化数据,而前端倾向于平铺化数据。
对于IdOnly的对象,Jimmer提供了@IdView注解,可以很方便的将IdOnly对象展开成一个字段。
修改示例实体接口
在示例实体接口中,分别添加了两个@IdView
的属性,这是一个视图属性,该属性的值将自动同步对应关联对象的值。
对于集合属性的IdView
,需要手动写明其对应的是哪个关联属性。
- Java
- Kotlin
@Entity
public interface Book {
...省略其他属性...
@Nullable
@ManyToOne
BookStore store();
@ManyToMany
List<Author> authors();
@IdView
Long storeId();
@IdView("authors")
List<Long> authorIds();
}
@Entity
interface Book {
...省略其他属性...
@ManyToOne
val store: BookStore?
@ManyToMany
val authors: List<Author>
@IdView
val storeId: Long?
@IdView("authors")
val authorsIds: List<Long>
}
抓取@IdView
属性
视图属性需要手动抓取,allScalarFields
和allTableFields
不会自动抓取视图属性。
- Java
- Kotlin
BookTable book = Tables.BOOK_TABLE;
List<Book> bookWithIdViews = sqlClient
.createQuery(book)
.where(book.edition().eq(3))
.select(
book.fetch(
Fetchers.BOOK_FETCHER
.allScalarFields()
.storeId()
.authorIds()
)
)
.execute();
System.out.println(bookWithIdViews);
var bookWithIdViews = sqlClient
.createQuery(Book::class) {
where(table.edition eq 3)
select(
table.fetchBy {
allScalarFields
storeId
authorIds
}
)
}
.execute()
println(bookWithIdViews)
输出结果为:
[
{
"id": 3,
"name": "Learning GraphQL",
"edition": 3,
"price": 51.00,
"storeId": 1,
"authorIds": [1,2]
}
...省略其他对象
]
ManyToManyView
某些场景下,多对多关联的中间表,不再是纯粹的 由双方id组成,而是夹杂了一些其他字段。
除双方id外还有其他字段的中间表无法直接映射为ManyToMany
关联。我们不得不创建三个实体类型:两个原本的实体与一个中间实体,双方实体各持有一个指向中间实体的一对多关联属性,中间实体持有到双方实体的两个多对一关联属性。
幸运的是,Jimmer提供了@ManyToManyView,可以在这中三实体结构的基础上抽象出双方实体之间的多对多关联。
为了更好的举例,这里不在使用书籍、作者,而是更具真实含义的学生(student)、课程(course)、课程得分(learning_link)三张表为例。
数据准备
为了示例方便,这里使用的是H2数据库,首先使用以下sql建立表结构和初始化数据:
drop table course if exists;
drop table learning_link if exists;
drop table student if exists;
create table course(
id identity(100, 1) not null,
name varchar(50) not null,
academic_credit integer not null
);
create table student(
id identity(100, 1) not null,
name varchar(50) not null
);
create table learning_link(
id identity(100, 1) not null,
student_id int not null,
course_id int not null,
score int null
);
alter table learning_link
add constraint fk_learning_link__student
foreign key(student_id)
references student(id)
on delete cascade;
alter table learning_link
add constraint fk_learning_link__course
foreign key(course_id)
references course(id)
on delete cascade;
insert into student(id, name)
values (1, 'John'),
(2, 'Jane'),
(3, 'Jack'),
(4, 'Jill'),
(5, 'Joe');
insert into course(id, name, academic_credit)
values (1, 'Math', 100),
(2, 'English', 100),
(3, 'History', 100),
(4, 'Science', 100),
(5, 'Art', 100);
insert into learning_link(student_id, course_id, score)
values (1, 1, 100), (1, 2, 90), (1, 3, 80), (1, 4, 70), (1, 5, 60),
(2, 1, 90), (2, 2, 80), (2, 3, 70), (2, 4, 60), (2, 5, 100),
(3, 1, 80), (3, 2, 70), (3, 3, 60), (3, 4, 100), (3, 5, 90),
(4, 1, 70), (4, 2, 60), (4, 3, 100), (4, 4, 90), (4, 5, 80),
(5, 1, 60), (5, 2, 100), (5, 3, 90), (5, 4, 80), (5, 5, 70);
建立实体
-
Student
- Java
- Kotlin
@Entity
public interface Student {
@Id
long id();
String name();
@OneToMany(mappedBy = "student")
List<LearningLink> learningLinks();
// 多对多视图属性
// prop: 从当前实体指向中间实体的关联属性`learningLinks`
// deeperProp: 从中间实体指向目标实体的关联属性`course`(可省略)
@ManyToManyView(prop = "learningLinks", deeperProp = "course")
List<Course> courses();
}@Entity
interface Student {
@Id
val id: Long
val name: String
@OneToMany(mappedBy = "student")
val learningLinks: List<LearningLink>
// 多对多视图属性
// prop: 从当前实体指向中间实体的关联属性`learningLinks`
// deeperProp: 从中间实体指向目标实体的关联属性`course`(可省略)
@ManyToManyView(prop = "learningLinks", deeperProp = "course")
val courses: List<Course>
} -
Course
- Java
- Kotlin
@Entity
public interface Course {
@Id
long id();
String name();
int academicCredit();
@OneToMany(mappedBy = "course")
List<LearningLink> learningLinks();
// 多对多视图属性
// prop: 从当前实体指向中间实体的关联属性`learningLinks`
// deeperProp: 从中间实体指向目标实体的关联属性`student`(可省略)
@ManyToManyView(prop = "learningLinks", deeperProp = "student")
List<Student> students();
}@Entity
interface Course {
@Id
val id: Long
val name: String
val academicCredit: Int
@OneToMany(mappedBy = "course")
val learningLinks: List<LearningLink>
// 多对多视图属性
// prop: 从当前实体指向中间实体的关联属性`learningLinks`
// deeperProp: 从中间实体指向目标实体的关联属性`student`(可省略)
@ManyToManyView(prop = "learningLinks", deeperProp = "student")
val students: List<Student>
} -
中间实体LearningLink
- Java
- Kotlin
@Entity
public interface LearningLink {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
long id();
// 多对一关联,指向学生实体
@ManyToOne
@OnDissociate(DissociateAction.DELETE)
Student student();
// 多对一关联,指向课程实体
@ManyToOne
@OnDissociate(DissociateAction.DELETE)
Course course();
Integer score();
}@Entity
interface LearningLink {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long
// 多对一关联,指向学生实体
@ManyToOne
@OnDissociate(DissociateAction.DELETE)
val student: Student
// 多对一关联,指向课程实体
@ManyToOne
@OnDissociate(DissociateAction.DELETE)
val course: Course
val score: Int
}
抓取@ManyToManyView
属性
由于引入了中间实体,学生实体与课程实体不再直接互相关联,虽然可以通过Student->LearningLink->Course
这样的链路间接关联,但是路径太长。
而且中间实体LearningLink
只应该在需要读写考试分数时候被使用,其他情况下应该尽量隐藏中间实体。
可以在对象抓起其中使用@ManyToManyView属性 (对本例而言,就是Student.courses
) 来减少调用链路长度。
例如以下查询:id为1的学生的所有课程名称
- Java
- Kotlin
Student student = sqlClient
.findNullable(
1L,
Fetchers.STUDENT_FETCHER
.name()
.courses(
Fetchers.COURSE_FETCHER
.name()
)
)
System.out.println(student);
val books = sqlClient
findNullable(
1L,
newFetcher(Student::class).by {
name()
courses {
name()
}
}
)
生成SQL为:
-
查询
Student
select
tb_1_.ID, tb_1_.NAME
from STUDENT tb_1_
where tb_1_.ID = ? /* 1 */ -
查询
LearningLink
select
tb_1_.STUDENT_ID,
tb_1_.ID, tb_1_.COURSE_ID
from LEARNING_LINK tb_1_
where tb_1_.STUDENT_ID in (? /* 1 */) -
查询
Course
select
tb_1_.ID, tb_1_.NAME
from COURSE tb_1_
where tb_1_.ID in (
? /* 1 */, ? /* 2 */, ? /* 3 */,
? /* 4 */, ? /* 5 */
)
输出结果为:
{
"id": 1,
"name": "John",
"courses": [
{
"id": 1,
"name": "Math"
},
{
"id": 2,
"name": "English"
},
{
"id": 3,
"name": "History"
},
{
"id": 4,
"name": "Science"
},
{
"id": 5,
"name": "Art"
}
]
}
使用多对多视图,可以像没有引入中间实体时一样方便快捷的直接操作关联对象。