Hibernate/JPA中如何合併實體集合?
正確合併集合並不是一件容易的事!推薦Vlad的例子文章 的Spring Boot示例,只有手工進行集合合併。
關鍵點:
- 刪除傳入集合中不再存在的現有資料庫行。
- 更新現有的可以在傳入集合中找到的資料庫行。
- 新增在傳入集合中找到的行,這些行在當前資料庫快照中是找不到的。
假設tournament和tennis_player兩個表中有資料:
INSERT INTO tournament (id, name) VALUES (1, 'Roland Garros'); INSERT INTO tournament (id, name) VALUES (2, 'US Open'); INSERT INTO tennis_player (id, name, tournament_id) VALUES (1, 'Rafael Nadal', 1); INSERT INTO tennis_player (id, name, tournament_id) VALUES (2, 'Roger Federer', 1); INSERT INTO tennis_player (id, name, tournament_id) VALUES (3, 'David Ferer', 2); INSERT INTO tennis_player (id, name, tournament_id) VALUES (4, 'Andy Murray', 2); INSERT INTO tennis_player (id, name, tournament_id) VALUES (5, 'Del Potro', 2); INSERT INTO tennis_player (id, name, tournament_id) VALUES (6, 'Novak D', 2); INSERT INTO tennis_player (id, name, tournament_id) VALUES (7, 'John Isner', 2);
Tournament實體:和TennisPlayer 是雙向一對多關係
@Entity <b>public</b> <b>class</b> Tournament implements Serializable { <b>private</b> <b>static</b> <b>final</b> <b>long</b> serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) <b>private</b> Long id; <b>private</b> String name; @OneToMany(cascade = CascadeType.ALL, mappedBy = <font>"tournament"</font><font>, orphanRemoval = <b>true</b>) <b>private</b> List<TennisPlayer> tennisPlayers = <b>new</b> ArrayList<>(); </font>
TennisPlayer 實體:
@Entity <b>public</b> <b>class</b> TennisPlayer implements Serializable { <b>private</b> <b>static</b> <b>final</b> <b>long</b> serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) <b>private</b> Long id; <b>private</b> String name; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = <font>"tournament_id"</font><font>) <b>private</b> Tournament tournament; </font>
倉儲:
@Repository @Transactional(readOnly = <b>true</b>) <b>public</b> <b>interface</b> TournamentRepository <b>extends</b> JpaRepository<Tournament, Long> { @Query(value=<font>"SELECT t FROM Tournament t JOIN FETCH t.tennisPlayers WHERE t.name = ?1"</font><font>) Tournament tournamentAndPlayers(String name); } @Repository @Transactional(readOnly = <b>true</b>) <b>public</b> <b>interface</b> TennisPlayerRepository <b>extends</b> JpaRepository<TennisPlayer, Long> { @Query(value = </font><font>"SELECT p FROM TennisPlayer p JOIN p.tournament t WHERE t.name = ?1"</font><font>) List<TennisPlayer> playersOfTournament(String name); } </font>
在服務中進行兩個實體集合的合併:
@Service <b>public</b> <b>class</b> TennisService { <b>private</b> <b>final</b> TournamentRepository tournamentRepository; <b>private</b> <b>final</b> TennisPlayerRepository tennisPlayerRepository; <b>public</b> TennisService(TournamentRepository tournamentRepository, TennisPlayerRepository tennisPlayerRepository) { <b>this</b>.tournamentRepository = tournamentRepository; <b>this</b>.tennisPlayerRepository = tennisPlayerRepository; } <b>public</b> List<TennisPlayer> fetchPlayersOfTournament(String name) { <b>return</b> tennisPlayerRepository.playersOfTournament(name); } @Transactional <b>public</b> <b>void</b> updatePlayersOfTorunament(String name, List<TennisPlayer> players) { Tournament tournament = tournamentRepository.tournamentAndPlayers(name); System.out.println(<font>"-------------------------------------------------"</font><font>); </font><font><i>// Remove the existing database rows that are no </i></font><font> </font><font><i>// longer found in the incoming collection (players)</i></font><font> </font><font><i>//刪除傳入集合中不再存在的現有資料庫行</i></font><font> tournament.getTennisPlayers().removeIf((t) -> !players.contains(t)); </font><font><i>// Update the existing database rows which can be found </i></font><font> </font><font><i>// in the incoming collection (players)</i></font><font> </font><font><i>//更新現有的可以在傳入集合中找到的資料庫行</i></font><font> </font>
List<TennisPlayer> newPlayers = players.stream() .filter((t) -> !tournament.getTennisPlayers().contains(t)) .collect(Collectors.toList()); players.stream() .filter((t) -> !newPlayers.contains(t)) .forEach((t) -> { t.setTournament(tournament); TennisPlayer mergedPlayer = tennisPlayerRepository.save(t); tournament.getTennisPlayers().set( tournament.getTennisPlayers().indexOf(mergedPlayer), mergedPlayer); }); <font><i>// Add the rows found in the incoming collection, </i></font><font> </font><font><i>// which cannot be found in the current database snapshot</i></font><font> newPlayers.forEach((t) -> tournament.addTennisPlayer(t)); } } </font>
手工合併集合的呼叫:
System.out.println(<font>"------------------- Players from US Open --------------------"</font><font>); List<TennisPlayer> players = tennisService.fetchPlayersOfTournament(</font><font>"US Open"</font><font>); players.forEach((t) -> System.out.println(</font><font>"Us Open: "</font><font> + t.getName() + </font><font>" | id:("</font><font> + t.getId() + </font><font>")"</font><font>)); System.out.println(</font><font>"---------- Players from US Open Updated Detached ------------"</font><font>); </font><font><i>// ,update first player name</i></font><font> players.get(0).setName(</font><font>"Fernando Verdasco"</font><font>); </font><font><i>// remove second player</i></font><font> players.remove(1); </font><font><i>// add a new player</i></font><font> TennisPlayer player = <b>new</b> TennisPlayer(); player.setName(</font><font>"Alexander Zverev"</font><font>); players.add(player); players.forEach((t) -> System.out.println(</font><font>"Us Open: "</font><font> + t.getName() + </font><font>" | id:("</font><font> + t.getId() + </font><font>")"</font><font>)); System.out.println(</font><font>"----------------- Players from US Open Merged ----------------"</font><font>); tennisService.updatePlayersOfTorunament(</font><font>"Us Open"</font><font>, players); players.forEach((t) -> System.out.println(</font><font>"Us Open: "</font><font> + t.getName() + </font><font>" | id:("</font><font> + t.getId() + </font><font>")"</font><font>)); </font>
原始碼可以在這裡 找到 。