我有 2 個物體,Team 和 Member,它們是 1:N 相關的。
// Team.java
@Getter
@NoArgsConstructor
@Entity
public class Team {
@Id
@Column(name = "TEAM_ID")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany(mappedBy = "team", fetch = FetchType.LAZY)
private List<Member> members = new ArrayList<>();
}
// Member.java
@Getter
@NoArgsConstructor
@Entity
public class Member {
@Id
@Column(name = "MEMBER_ID")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
public Member(String name) {
this.name = name;
}
public Member(String name, Team team) {
this.name = name;
this.team = team;
}
}
I'm testing fetch joining, for example, find a team and their members by team id. The test code is like this,
// TeamRepository.java
public interface TeamRepository extends JpaRepository<Team, Long> {
@Query(value =
"select distinct t from Team t "
"join fetch t.members "
"where t.id = :id")
Optional<Team> findByIdWithAllMembers(Long id);
}
// Test.java
@Transactional
@Test
void transactionalFetchJoin() {
System.out.println("save team");
Team team = new Team();
Team saved = teamRepository.save(team);
System.out.println("save members");
for (int i = 0; i < 10; i ) {
Member member = new Member("name" String.valueOf(i), team);
memberRepository.save(member);
}
System.out.println("teamRepository.findByIdWithAllMembers(saved.getId())");
Team t = teamRepository.findByIdWithAllMembers(saved.getId())
.orElseThrow(() -> new RuntimeException("??"));
assertThat(t.getMembers().size()).isEqualTo(0); // <-- no members are loaded
}
@Test
void nonTransactionalFetchJoin() {
System.out.println("save team");
Team team = new Team();
Team saved = teamRepository.save(team);
System.out.println("save members");
for (int i = 0; i < 10; i ) {
Member member = new Member("name" String.valueOf(i), team);
memberRepository.save(member);
}
System.out.println("teamRepository.findByIdWithAllMembers(saved.getId())");
Team t = teamRepository.findByIdWithAllMembers(saved.getId())
.orElseThrow(() -> new RuntimeException("??"));
assertThat(t.getMembers().size()).isEqualTo(10); // <-- 10 members are loaded
}
These two test methods have the same logic but the only difference is @Transactional or not. Also, two test methods are passed successfully.
I found that 'nonTransactionalFetchJoin()' loaded team with 10 member objects, but 'transactionalFetchJoin()' didn't.
Also, I observed that 2 test methods generate the same JPQL/SQL queries for all JPA methods, including save().
Especially, the findByIdWithAllMembers() method generates query like,
/* select
distinct t
from
Team t
join
fetch t.members
where
t.id = :id */ select
distinct team0_.team_id as team_id1_1_0_,
members1_.member_id as member_i1_0_1_,
members1_.name as name2_0_1_,
members1_.team_id as team_id3_0_1_,
members1_.team_id as team_id3_0_0__,
members1_.member_id as member_i1_0_0__
from
team team0_
inner join
member members1_
on team0_.team_id=members1_.team_id
where
team0_.team_id=?
The only difference is that, in case of transactionalFetchJoin(), o.h.type.descriptor.sql.BasicExtractor extracts just Team.id and Member.id, while nonTransactionalFetchJoin() extracts whole fields of Team and Member.
// transactionalFetchJoin
2022-03-31 13:39:19.842 TRACE 4725 --- [ Test worker] o.h.type.descriptor.sql.BasicExtractor : extracted value ([team_id1_1_0_] : [BIGINT]) - [1]
2022-03-31 13:39:19.842 TRACE 4725 --- [ Test worker] o.h.type.descriptor.sql.BasicExtractor : extracted value ([member_i1_0_1_] : [BIGINT]) - [1]
// nonTransactionalFetchJoin
2022-03-31 13:39:19.933 TRACE 4725 --- [ Test worker] o.h.type.descriptor.sql.BasicExtractor : extracted value ([team_id1_1_0_] : [BIGINT]) - [2]
2022-03-31 13:39:19.934 TRACE 4725 --- [ Test worker] o.h.type.descriptor.sql.BasicExtractor : extracted value ([member_i1_0_1_] : [BIGINT]) - [21]
2022-03-31 13:39:19.935 TRACE 4725 --- [ Test worker] o.h.type.descriptor.sql.BasicExtractor : extracted value ([name2_0_1_] : [VARCHAR]) - [name0]
Why does this difference occur?
Thanks.
uj5u.com熱心網友回復:
歡迎加入被 JPA 一級快取搞砸的開發人員俱樂部。
JPA 保留一級快取中的物體。當您加載或持久化一個物體時,它將被添加到一級快取中,直到事務/會話/持久性背景關系結束。每當它通過任何型別的加載操作為您提供物體時,它都會檢查該物體是否存在于快取中并將其回傳給您。
這意味著在帶有@Transactional注釋的示例中,您實際上并未加載物體。您實際上只需加載 id,然后使用它們在快取中查找該物體。但是快取中的物體不知道添加的團隊成員。=> 你的團隊沒有任何成員。
在沒有顯式事務的情況下,事務只跨越一次對存盤庫的呼叫。每次呼叫后,一級快取都會被丟棄,您的加載操作實際上會根據Team從資料庫加載的資料創建一個新實體。
有幾件事你應該解決。
要么一起擺脫雙向關系,使其成為單向關系。或者,如果真的想讓它確保在您的代碼中使雙方匹配。即如果你設定
Member.team,你也應該添加Member到Team.members。當然,洗掉成員也是如此。這將避免在您的應用程式中看到不一致的資料。確保您在測驗中經歷了正確的 JPA 生命周期。我首選的方法是使用
TransactionTemplate將測驗的不同步驟包裝在單獨的事務中。如果您想仔細檢查預加載是否正常作業,您應該將根物體(在本例中為團隊)的加載放在單獨的事務中,然后在關閉該事務后嘗試訪問事務之外的預加載屬性。這樣,
LazyLoadingException如果參考的物體沒有按預期急切加載,您將得到一個。
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/455304.html
標籤:spring jpa spring-data-jpa 春季交易
上一篇:如何使UIActivityViewController基于動態陣列動態排除專案?
下一篇:將OpenLiberty與EclipseLinkJPA提供程式一起使用-javax.persistence.sql-load-script-source未加載
