Hibernate/JPA中如何合并实体集合?

19-02-19 banq
         

正确合并集合并不是一件容易的事!推荐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
public class Tournament implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToMany(cascade = CascadeType.ALL, 
            mappedBy = "tournament", orphanRemoval = true)
    private List<TennisPlayer> tennisPlayers = new ArrayList<>();

TennisPlayer 实体:

@Entity
public class TennisPlayer implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "tournament_id")
    private Tournament tournament;

仓储:

@Repository
@Transactional(readOnly = true)
public interface TournamentRepository extends JpaRepository<Tournament, Long> { 
    
    @Query(value="SELECT t FROM Tournament t JOIN FETCH t.tennisPlayers WHERE t.name = ?1")
    Tournament tournamentAndPlayers(String name);
}

@Repository
@Transactional(readOnly = true)
public interface TennisPlayerRepository extends JpaRepository<TennisPlayer, Long> {

    @Query(value = "SELECT p FROM TennisPlayer p JOIN p.tournament t WHERE t.name = ?1")
    List<TennisPlayer> playersOfTournament(String name);
}

在服务中进行两个实体集合的合并:

@Service
public class TennisService {

    private final TournamentRepository tournamentRepository;
    private final TennisPlayerRepository tennisPlayerRepository;

    public TennisService(TournamentRepository tournamentRepository,
            TennisPlayerRepository tennisPlayerRepository) {

        this.tournamentRepository = tournamentRepository;
        this.tennisPlayerRepository = tennisPlayerRepository;
    }

    public List<TennisPlayer> fetchPlayersOfTournament(String name) {

        return tennisPlayerRepository.playersOfTournament(name);
    }

    @Transactional
    public void updatePlayersOfTorunament(String name, List<TennisPlayer> players) {

        Tournament tournament = tournamentRepository.tournamentAndPlayers(name);
        System.out.println("-------------------------------------------------");

        // Remove the existing database rows that are no 
        // longer found in the incoming collection (players)
        //删除传入集合中不再存在的现有数据库行
        tournament.getTennisPlayers().removeIf((t) -> !players.contains(t));

        // Update the existing database rows which can be found 
        // in the incoming collection (players)
        //更新现有的可以在传入集合中找到的数据库行

        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);
                });

        // Add the rows found in the incoming collection, 
        // which cannot be found in the current database snapshot
        newPlayers.forEach((t) -> tournament.addTennisPlayer(t));
    }

}

手工合并集合的调用:

 System.out.println("------------------- Players from US Open --------------------");
            List<TennisPlayer> players = tennisService.fetchPlayersOfTournament("US Open");            
            
            players.forEach((t) -> System.out.println("Us Open: " + t.getName() + " | id:(" + t.getId() + ")"));
            
            System.out.println("---------- Players from US Open Updated Detached ------------");
            
            // ,update first player name
            players.get(0).setName("Fernando Verdasco");
            
            // remove second player
            players.remove(1);
                        
            // add a new player
            TennisPlayer player = new TennisPlayer();
            player.setName("Alexander Zverev");
            players.add(player);
            
            players.forEach((t) -> System.out.println("Us Open: " + t.getName() + " | id:(" + t.getId() + ")"));
            
            System.out.println("----------------- Players from US Open Merged ----------------");
            tennisService.updatePlayersOfTorunament("Us Open", players);
            
            players.forEach((t) -> System.out.println("Us Open: " + t.getName() + " | id:(" + t.getId() + ")"));

源代码可以在这里找到  。