βJPAλ λ°λ³΅λλ 쿼리 μμ± λΉμ©μ μ€μ¬μ£Όκ³ λΉμ¦λμ€ λ‘μ§ μ€μ¬μ ν¨μ¨μ μΈ μ½λ μμ±μ μ€ννκ² ν΄μ£Όλ μ’μ κΈ°μ μ΄λ€.
βνλ μμν¬ λλ λΌμ΄λΈλ¬λ¦¬λ₯Ό μ μ¬μ©νλ©΄ λ€μν κΈ°λ₯κ³Ό νΈμλ₯Ό μ 곡 λ°μ μμ°μ±μ ν₯μμν¬ μ μλ λ°λ©΄, μ λͺ» μ¬μ© ν κ²½μ° μμΈ‘νμ§ λͺ»ν λμμΌλ‘ μΈν΄ λ¬Έμ κ° λ°μν μ μλ€.
λ³Έ ν¬μ€νΈμμλ JPAλ₯Ό μ€μ κ°λ°μ μ μ© ν΄λ³΄λ©΄μ μ 리νλ λ΄μ©μ κΈλ‘ λ€μ μ 리ν΄λ³΄κ³ μ νλ€.
μ€λͺ μ μ΄ν΄λ₯Ό λκΈ° μν΄ λ€μμ ν μ΄λΈ κ΄κ³λ₯Ό ꡬμ±νμλ€.
@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class Player {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private int age;
@Enumerated(EnumType.STRING)
private PlayerPosition position;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id")
private Team team;
public Player transferTo(Team team) {
this.team = team;
return this;
}
public long teamId() {
return this.team.getId();
}
}
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class Team {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "team", fetch = FetchType.LAZY)
private final List<Player> players = new ArrayList<>();
}
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class PlayerStat {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private int year;
private int score;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "player_id")
private Player player;
}
CASE 01. μ°κ΄κ΄κ³μ μ£ΌμΈμ μΈλν€ μμΉλ₯Ό κΈ°μ€μΌλ‘ μ νλ€.
κ΄κ³ν λ°μ΄ν°λ² μ΄μ€μμλ μΈλν€λ₯Ό κΈ°μ€μΌλ‘ ν μ΄λΈ μ¬μ΄μ κ΄κ³λ₯Ό μ μνλ€.
JPAμμλ μ°κ΄κ΄κ³μ μλ λ μν°ν° μ€ νλλ‘ μΈλ ν€λ₯Ό κ΄λ¦¬νλ€. μΈλν€λ₯Ό κ΄λ¦¬νλ 주체λ₯Ό 'μ°κ΄κ΄κ³μ μ£ΌμΈ'μ΄λΌνλ€. μ°κ΄κ΄κ³μ μ£ΌμΈμ ν μ΄λΈμμμ μΈλν€ μμΉλ₯Ό κΈ°μ€μΌλ‘ μ νλ κ²μ΄ μΌλ°μ μ΄λ€.
Player
μ Team
μ κ΄κ³λ₯Ό κ²°μ νλ κ²μ Player
κ° λλ€.
@Entity
public class Player {
// ...
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id")
private Team team;
public Player transferTo(Team team) {
this.team = team;
return this;
}
public long teamId() {
return this.team.getId();
}
}
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class Team {
// ...
@OneToMany(mappedBy = "team", fetch = FetchType.LAZY)
private final List<Player> players = new ArrayList<>();
}
public class EntityRelationTest extends JpaTest {
@DisplayName("Case 01. μ°κ΄κ΄κ³μ μ£ΌμΈμ μΈλν€ μμΉλ₯Ό κΈ°μ€μΌλ‘ μ νλ€.")
@Test
void entityRelationTest() {
// given
Team initialTeam = teamRepository.save(team().build());
Player givenPlayer = playerRepository.save(player().team(initialTeam).build());
// when
Team transferTeam = teamRepository.save(team().build());
givenPlayer.transferTo(transferTeam);
entityManager.flush();
/**
* μμμ± μ»¨ν
μ€νΈλ₯Ό λΉμ°μ§ μκ³ μ‘°ννλ©΄ first level cacheμ μλ teamμ λ€μ μ°Έμ‘°νλ―λ‘
* playerκ° λ¦¬μ€νΈμ μΆκ°λ κ²μ νμΈν μ μλ€.
*/
entityManager.clear();
Player player = playerRepository.findById(givenPlayer.getId()).orElseThrow();
Team team = teamRepository.findById(player.teamId()).orElseThrow();
// then
then(player.getTeam().getId()).isEqualTo(transferTeam.getId());
then(team.getPlayers().size()).isOne();
}
}
CASE 02. Enum νμ μ ORDINALμ μ¬μ©μ μ§μνλ€.
JPAλ Javaμ Enum νμ
μ μ¬μ©νμ¬ κ°μ μ½κ³ μ μ₯ν μ μλ€.
β@Enumerated
μ κΈ°λ³Έ κ°μΈ ORDINALμ κ·Έλλ‘ μ¬μ©νλ©΄ μν°ν°κ° μ μ₯λλ μμ μ Enumμ ordinal κ°μ΄ μ μ₯λλ€.
public enum PlayerPosition {
ST, // 0
MF, // 1
DF, // 2
GK // 3
}
λ§μ½ ST
, MF
μ¬μ΄μ WF
ν¬μ§μ
μ΄ μΆκ°λλ©΄ μ΄λ»κ² λ κΉ?
public enum PlayerPosition {
ST, // 0
WF, // <- ?
MF, // 1
DF, // 2
GK // 3
}
WF
μ ordinal κ°μ 1μ΄ λ κ²μ΄κ³ , 1μ MF
μ ordinalμ΄μλ€.
βWF κ° μΆκ°λλ μκ° position κ°μ 1μ΄ μ μ₯λμ΄ μλ playerλ€μ DBμμ κ°μ μ‘°μ ν΄μ£Όμ§ μλ μ΄μ λͺ¨λ WF
λ‘ μ·¨κΈ λ κ²μ΄λ€.
μ΄μ κ°μ μ½λμμ μμμ μν μ¬μ΄λ μ΄ννΈκ° λ°μνμ§ μλλ‘ ordinal λμ name κ°μ μ¬μ©νλ κ²μ κΆμ₯νλ€. ordinalλμ nameμ μ¬μ©νλ €λ©΄ EnumType.STRING
μ΅μ
μ μ§μ ν΄μ£Όλ©΄ λλ€.
@Entity
public class Player {
// ...
@Enumerated(EnumType.STRING)
private PlayerPosition position;
// ...
}
βλ§μ½ κΈ°μ‘΄ νκ²½μ μν΄ λΆκ°νΌνκ² ORDINALμ μ¬μ©ν΄μΌ νλ μν©μ΄λΌλ©΄,
AttirubteConverter
λ₯Ό ꡬννμ¬ Enumκ³Ό 맡νν μ μλ λ°©λ²λ νμ© κ°λ₯νλ€.
(μ°Έκ³ : μ°μν κΈ°μ λΈλ‘κ·Έ - Legacy DBμ JPA Entity Mapping (Enum Converter νΈ))
CASE 03. μ°κ΄κ΄κ³κ° μ‘΄μ¬νλ κ°μ²΄μ @ToString
μ¬μ©μ μ£Όμνλ€.
μλ°©ν₯ μ°κ΄κ΄κ³κ° μλ λ μν°ν°μ 둬볡 μ΄λ
Έν
μ΄μ
μΌλ‘ toStringμ μ€λ²λΌμ΄λ© ν κ²½μ° μνμ°Έμ‘°κ° λ°μνλ€.
λλ€μμ κ²½μ° API μλ΅ μ λ³λμ λ°μ΄ν° λͺ¨λΈ κ°μ²΄λ₯Ό λλ κ²μ΄ 컨벀μ
μΌλ‘ μΌλ°νλμ΄ μκΈ° λλ¬Έμ μνμ°Έμ‘° λ¬Έμ κ° λ°μνλ μΌμ΄μ€λ λλ¬Όλ€. λ€μκ³Ό κ°μ λ¬Έμ κ° λ°μν μ μλ€λ μ μ μΈμ§νλ, μν°ν°μλ @ToString
κ³Ό κ°μ 둬볡 μ΄λ
Έν
μ΄μ
μ μ¬μ©νμ§ μλ κ²μ΄ μ’λ€.
@ToString
@Entity
public class Player {
// ...
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id")
private Team team;
// ...
}
@ToString
@Entity
public class Team {
// ...
private String name;
@OneToMany(mappedBy = "team", fetch = FetchType.LAZY)
private final List<Player> players = new ArrayList<>();
}
public class ExcludeToStringTest extends JpaTest {
@DisplayName("Case 03. μ°κ΄κ΄κ³κ° μ‘΄μ¬νλ κ°μ²΄μ `@ToString` μ¬μ©μ μ£Όμνλ€.")
@Test
void test() {
// given
Team team = teamRepository.save(team().build());
Player givenPlayer = playerRepository.save(player().team(team).build());
// when
entityManager.clear();
Player player = playerRepository.findById(givenPlayer.getId()).orElseThrow();
// then
/**
* μνμ°Έμ‘°κ° λ°μνλ€.
*/
thenThrownBy(player::toString).isExactlyInstanceOf(StackOverflowError.class);
}
}
λ§μ½ toString
μ μ μν΄μΌ ν κ²½μ°, μ°κ΄κ΄κ³μ ν΄λΉνλ κ°μ²΄λ μ μμμ μ μΈ μμΌμΌνλ€.
(둬볡μ μ¬μ©νλ κ²½μ° @ToString
μ exclude
μ΅μ
μ μ¬μ©ν μ μλ€.)
CASE 04. μ§μ°λ‘λ©μ μ°μ μ μΌλ‘ κ³ λ €νκ³ , μ¦μλ‘λ© μ¬μ©μ μ§μνλ€.
μ¦μλ‘λ©(EAGER)μ λ°μ΄ν°λ₯Ό μ½λ μμ μ μ°κ΄λ λ°μ΄ν°λ₯Ό μ‘°μΈμΌλ‘ νλ²μ λ‘λνλ κ²μ μλ―Ένλ€.
μ¦μλ‘λ©μ λ€μμ μ΄μ λ‘ κ°λ°μ μ΄λ ΅κ² λ§λ λ€.
- μμΈ‘νμ§ λͺ»ν λ€μμ μ‘°μΈ μΏΌλ¦¬κ° λ°μν μ μλ€. (쿼리 μ΅μ νκ° μ΄λ ΅λ€.)
- Spring Data JPAμμ μ 곡λλ 쿼리 λ©μλκ° μλ μ§μ μ μν JPQLμ μ¬μ©νλ κ²½μ° EAGERκ° μ€μ λμ΄ μμμλ λΆκ΅¬νκ³ LAZYμ κ°μ λμμ νλ€. (N + 1 λ°μ)
- μ°κ΄κ΄κ³μ μν°ν° κ·Έλνκ° μ¬λ¬κ°λ‘ μ€μ²©λμ΄ μλ κ²½μ° N + 1 μ΄ μ°λ°μ μΌλ‘ λ°μν μ μλ€.
public interface PlayerRepository extends JpaRepository<Player, Long> {
@Query("SELECT p FROM Player p WHERE p.team.id = :teamId")
List<Player> findAllPlayersByTeamIdJPQL(long teamId);
}
public class EagerLoadingTest extends JpaTest {
@DisplayName("Case 04. μ§μ°λ‘λ©μ μ°μ μ μΌλ‘ κ³ λ €νκ³ , μ¦μλ‘λ© μ¬μ©μ μ§μνλ€.")
@Test
void test1() {
// given
Team givenTeam = teamRepository.save(team().build());
Player player1 = playerRepository.save(player().team(givenTeam).build());
Player player2 = playerRepository.save(player().team(givenTeam).build());
// when
entityManager.clear();
List<Player> players = playerRepository.findAllPlayersByTeamIdJPQL(givenTeam.getId());
players.forEach(player -> player.getTeam().getName());
}
}
Hibernate:
select
p1_0.id,
p1_0.age,
p1_0.name,
p1_0.position,
p1_0.team_id
from
player p1_0
where
p1_0.team_id=?
-- μΆκ°μΏΌλ¦¬(N + 1) λ°μ
Hibernate:
select
t1_0.id,
t1_0.name
from
team t1_0
where
t1_0.id=?
βμ¦μλ‘λ©μ μ½λμ λλ¬λμμ§ μμ (μμΈ‘μ΄ μ΄λ €μ΄) λμμ λ°μμν¨λ€. κ°λ₯νλ©΄ μ§μ°λ‘λ©μ μ¬μ©νκ³ , μ΅μ νκ° νμν κ³³μ μ λ³μ μΌλ‘ νμΉμ‘°μΈμ μ¬μ©νλ€.
public interface PlayerRepository extends JpaRepository<Player, Long> {
@Query("SELECT p FROM Player p JOIN FETCH p.team WHERE p.team.id = :teamId")
List<Player> fetchAllByTeamId(long teamId);
}
-- fetch join
Hibernate:
select
p1_0.id,
p1_0.age,
p1_0.name,
p1_0.position,
t1_0.id,
t1_0.name
from
player p1_0
join
team t1_0
on t1_0.id=p1_0.team_id
where
t1_0.id=?
CASE 05. [N + 1] λ¨κ±΄ μ‘°νκ° μλ κ²½μ° νμΉ μ‘°μΈμ μ¬μ©μ κ³ λ €νλ€.
CASE 04.
μμ κΈ°λ³Έ μ‘°ν μ λ΅μ μ§μ°λ‘λ©μΌλ‘ μ€μ νκΈ°λ‘ νμλ€.
βνΉμ μν°ν°λ₯Ό 리μ€νΈλ‘ μ‘°ννκ³ , ν΄λΉ 리μ€νΈλ₯Ό μννλ©° μ°κ΄κ΄κ³ κ°μ²΄λ₯Ό μ κ·Όνλ©΄ μ§μ°λ‘λ©μ΄ μλνλ©° λ€μμ μΏΌλ¦¬κ° λ°μνλ€. (N + 1)
βλ§μ½ Player
λ°μ΄ν°κ° 100κ±΄μ΄ μ‘°νλλ©΄, μ°κ΄λ Team
μ μ‘°ννκΈ° μν΄ μ΅λ 100λ²μ μΏΌλ¦¬κ° λ λ°μν μ μλ€.
public class FetchJoinTest extends JpaTest {
@DisplayName("Case 05. [N + 1] λ¨κ±΄ μ‘°νκ° μλ κ²½μ° νμΉ μ‘°μΈμ μ¬μ©μ κ³ λ €νλ€.")
@Test
void test() {
// given
Team team1 = teamRepository.save(team().build());
Player player1 = playerRepository.save(player().team(team1).build());
Team team2 = teamRepository.save(team().build());
Player player2 = playerRepository.save(player().team(team2).build());
// when
entityManager.clear();
// then
List<Player> players = playerRepository.findAll();
players.forEach(player -> player.getTeam().getName());
}
}
Hibernate:
select
p1_0.id,
p1_0.age,
p1_0.name,
p1_0.position,
p1_0.team_id
from
player p1_0
/**
* 2κ°μ Player λ°μ΄ν°μ μ°κ΄λ Teamμ μ‘°ννκΈ° μν 쿼리 2λ² μΆκ° λ°μ [1 + 2(N)]
*/
Hibernate:
select
t1_0.id,
t1_0.name
from
team t1_0
where
t1_0.id=?
Hibernate:
select
t1_0.id,
t1_0.name
from
team t1_0
where
t1_0.id=?
νμΉμ‘°μΈμ μ μ©νλ©΄ μΆκ° μΏΌλ¦¬κ° λ°μνμ§ μλλ‘ μ΅μ νκ° κ°λ₯νλ€. λ¨κ±΄μ΄ μλ 볡μκ°μ λ°μ΄ν°λ₯Ό μ‘°ν ν λλ fetch joinμ μ°μ μ μΌλ‘ κ³ λ €ν΄λ³Έλ€.
public interface PlayerRepository extends JpaRepository<Player, Long> {
@Query("SELECT p FROM Player p JOIN FETCH p.team")
List<Player> fetchAllPlayers();
}
-- fetch join μ¬μ© ν λμΌ λ‘μ§ μ€ν μ ν λ²μ μΏΌλ¦¬λ§ λ°μνλ€.
Hibernate:
select
p1_0.id,
p1_0.age,
p1_0.name,
p1_0.position,
t1_0.id,
t1_0.name
from
player p1_0
join
team t1_0
on t1_0.id=p1_0.team_id
CASE 06. [N + 1] 컬λ μ μ λμμΌλ‘ νμΉ μ‘°μΈ νλ©΄ λ°μ΄ν° μ€λ³΅μ΄ λ°μνλ€.
βλΆλͺ¨ ν μ΄λΈκ³Ό μμ ν μ΄λΈμ μ‘°μΈν λμ μ΄ row μλ μμ ν μ΄λΈμ row μμ μ’ μλλ€. λ°λΌμ λΆλͺ¨ ν μ΄λΈμ ν΄λΉνλ rowμ μ€λ³΅μ΄ λ°μν μ λ°μ μλ€.
βJPAμ νμΉμ‘°μΈλ κ²°κ΅ λ΄λΆμ μΌλ‘λ SQL joinμΌλ‘ λ°λμ΄ μλνλ―λ‘ RDBμ νΉμ±μ΄ κ³ μ€λν μ μ©λλ€.
βμλ₯Ό λ€μ΄ Team
μ players
λ₯Ό λμμΌλ‘ νμΉμ‘°μΈμ μ μ©νλ©΄, Player
μλ§νΌ Team
μ΄ μ€λ³΅μΌλ‘ μ‘°νλλ κ²μ΄λ€.
βλ°λΌμ 컬λ μ
(μμ ν
μ΄λΈ)μ λμμΌλ‘ νμΉ μ‘°μΈμ ν λλ DISTINCT
λ₯Ό μ¬μ©νμ¬ μ€λ³΅μ μ κ±°νλ κ²μ΄ νμνλ€. (μμκ° μ€μνμ§ μλ€λ©΄ Set
μ λ°ννλ κ²λ λ°©λ²μ΄ λ μ μλ€.)
public interface TeamRepository extends JpaRepository<Team, Long> {
@Query("SELECT DISTINCT t FROM Team t JOIN FETCH t.players")
List<Team> fetchAll();
}
public class CollectionFetchJoinTest extends JpaTest {
@DisplayName("Case 06. [N + 1] 컬λ μ
μ λμμΌλ‘ νμΉ μ‘°μΈ νλ©΄ λ°μ΄ν° μ€λ³΅μ΄ λ°μνλ€.")
@Test
void test() {
// given
Team team1 = teamRepository.save(team().build());
Player player1 = playerRepository.save(player().team(team1).build());
Player player2 = playerRepository.save(player().team(team1).build());
Team team2 = teamRepository.save(team().build());
Player player3 = playerRepository.save(player().team(team2).build());
Player player4 = playerRepository.save(player().team(team2).build());
// when
entityManager.clear();
List<Team> teams = teamRepository.fetchAll();
// then
then(teams).hasSameSizeAs(List.of(team1, team2));
}
}
Hibernate:
select
distinct t1_0.id,
t1_0.name,
p1_0.team_id,
p1_0.id,
p1_0.age,
p1_0.name,
p1_0.position
from
team t1_0
join
player p1_0
on t1_0.id=p1_0.team_id
μ°Έκ³ ) SpringBoot 3.x λΆν° μ¬μ©λλ νμ΄λ²λ€μ΄νΈ 6μμλ λ°μ΄ν° μ€λ³΅ μ΅μ νκ° μλμΌλ‘ μ μ©λλ€. (https://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#hql-distinct)
CASE 07. [N + 1] 컬λ μ μ λμμΌλ‘ νμΉ μ‘°μΈκ³Ό νμ΄μ§μ ν¨κ» μ¬μ©νμ§ μλλ€.
βCASE 06.
μμ μ΄ν΄λ³Έ κ²κ³Ό κ°μ΄ 컬λ μ
μ λμμΌλ‘ νμΉ μ‘°μΈμ νλ©΄ RDB λμ λ©μ»€λμ¦μ μν΄ μ€λ³΅ λ°μ΄ν°κ° λ°μνλ€.
μ€λ³΅λ λ°μ΄ν°λ₯Ό λμμΌλ‘ νμ΄μ§ μ²λ¦¬κΉμ§ μλνλ©΄, νμ΄λ²λ€μ΄νΈλ λͺ¨λ λ°μ΄ν°λ₯Ό λ©λͺ¨λ¦¬μ λ‘λν λ€ μ²λ¦¬νλ€.
βλ°μ΄ν°κ° μμλΌλ©΄ λ¬Έμ κ° λμ§ μμ μ μμ§λ§, λ°μ΄ν° μκ° λ§μ κ²½μ° μ΄λ₯Ό λ©λͺ¨λ¦¬μμ μ²λ¦¬ν κ²½μ° OOMκ³Ό κ°μ λ¬Έμ κ° λ°μν μ μλ€.
public class CollectionFetchJoinWithPagingTest extends JpaTest {
@DisplayName("Case 07. [N + 1] 컬λ μ
μ λμμΌλ‘ νμΉ μ‘°μΈκ³Ό νμ΄μ§μ ν¨κ» μ¬μ©νμ§ μλλ€.")
@Test
void test() {
// given
Team team1 = teamRepository.save(team().build());
Player player1 = playerRepository.save(player().team(team1).build());
Player player2 = playerRepository.save(player().team(team1).build());
Team team2 = teamRepository.save(team().build());
Player player3 = playerRepository.save(player().team(team2).build());
Player player4 = playerRepository.save(player().team(team2).build());
// when
entityManager.clear();
Page<Team> teams = teamRepository.fetchAllPagination(PageRequest.of(0, 2));
// then
then(teams).hasSameSizeAs(List.of(team1, team2));
}
}
-- μλμ κ°μ΄ WARNING(HHH90003004: firstResult/maxResults specified with collection fetch; applying in memory)μ΄ λ°μνλ€.
2024-03-02T19:12:27.181+09:00 WARN 77599 --- [ Test worker] org.hibernate.orm.query : HHH90003004: firstResult/maxResults specified with collection fetch; applying in memory
Hibernate:
select
distinct t1_0.id,
t1_0.name,
p1_0.team_id,
p1_0.id,
p1_0.age,
p1_0.name,
p1_0.position
from
team t1_0
join
player p1_0
on t1_0.id=p1_0.team_id
Hibernate:
select
count(distinct t1_0.id)
from
team t1_0
join
player p1_0
on t1_0.id=p1_0.team_id
CASE 08. [N + 1] 컬λ μ
λμ μ¦μ λ‘λ©μ΄ νμν λλ @BatchSize
μ¬μ©μ κ³ λ €ν΄λ³Έλ€.
β컬λ μ
μ λμμΌλ‘ fetch joinκ³Ό νμ΄μ§ μ²λ¦¬λ₯Ό κ°μ΄ μ μ©νλ©΄ νμ΄λ²λ€μ΄νΈλ λ©λͺ¨λ¦¬μμ ν΄λΉ μμ
μ μννλ€.
λ§μ½ 컬λ μ
μ μ¦μλ‘λ© λμμ νμλ‘ νλ€λ©΄, fetch joinμ λμμΌλ‘ @BatchSize
ν΅ν΄ μ΅μ νκ° κ°λ₯νλ€.
@BatchSize
λ 컬λ μ
λμ ν
μ΄λΈμ in 쿼리λ₯Ό λ°μμν¨λ€.
βsize μμ±μ μ€μ λ κ°μ λ°λΌ 쿼리 λ°μ νμλ λ€λ₯΄μ§λ§ λ°μ΄ν°λ₯Ό λ©λͺ¨λ¦¬μ μ¬λ €μ μ²λ¦¬νλ λ°©λ²λ³΄λ€ ν¨μ¬ μμ νκ³ ν¨μ¨μ μ΄λ€.
@Entity
public class Team {
// ...
@BatchSize(size = 100)
@OneToMany(mappedBy = "team", fetch = FetchType.LAZY)
private final List<Player> players = new ArrayList<>();
}
public interface TeamRepository extends JpaRepository<Team, Long> {
@Query("SELECT t FROM Team t")
Page<Team> fetchAllPagination(Pageable pageable);
}
public class BatchSizeTest extends JpaTest {
@DisplayName("Case 08. [N + 1] 컬λ μ
λμ μ¦μ λ‘λ©μ΄ νμν λλ `@BatchSize` μ¬μ©μ κ³ λ €ν΄λ³Έλ€.")
@Test
void test() {
// given
Team team1 = teamRepository.save(team().build());
Player player1 = playerRepository.save(player().team(team1).build());
Player player2 = playerRepository.save(player().team(team1).build());
Team team2 = teamRepository.save(team().build());
Player player3 = playerRepository.save(player().team(team2).build());
Player player4 = playerRepository.save(player().team(team2).build());
// when
entityManager.clear();
Page<Team> teams = teamRepository.fetchAllPagination(PageRequest.of(0, 2));
// then
teams.forEach(team -> team.getPlayers().size());
}
}
Hibernate:
select
t1_0.id,
t1_0.name
from
team t1_0
limit
?, ?
Hibernate:
select
count(t1_0.id)
from
team t1_0
-- IN μΏΌλ¦¬κ° μΆκ°λ‘ λ°μνλ€.
Hibernate:
select
p1_0.team_id,
p1_0.id,
p1_0.age,
p1_0.name,
p1_0.position
from
player p1_0
where
p1_0.team_id in (?, ?)
CASE 09. Cascade
, orphanRemoval
μ΅μ
μ¬μ©μ μ£Όμνλ€.
βJPAμμ μ 곡νλ Cascade, orphanRemoval μ΅μ μ λΆλͺ¨ μν°ν°λ‘ μμ μν°ν°μ μμνλ₯Ό κ΄λ¦¬ν μ μκ² ν΄μ€λ€.
@Entity
public class Team {
// ...
// cascade, orphanRemoval
@OneToMany(mappedBy = "team", fetch = FetchType.LAZY, orphanRemoval = true, cascade = CascadeType.ALL)
private final List<Player> players = new ArrayList<>();
}
κ° μ΅μ μ λ€μμ λμμ΄ κ°λ₯νκ² νλ€.
- cascade : λΆλͺ¨ μν°ν°μ μ μ₯, μμ μ κ°μ λμμ΄ μμ μν°ν°κΉμ§ μ νλλ€.
- orphanRemoval : λΆλͺ¨μ μ°κ΄κ΄κ³κ° λμ΄μ§ μμ μν°ν°λ₯Ό μμ νλ€.
μ¦, Team
μ μ‘°μνλ κ² λ§μΌλ‘λ Player
λ°μ΄ν°λ₯Ό 컨νΈλ‘€ ν μ μλ κ²μ΄λ€. λ§μ½ Teamμ μ½λλ¨μμ μ λͺ» 컨νΈλ‘€νλ©΄ Player λ°μ΄ν° μ ν©μ±μ μν₯μ μ€ μ μλ€.
β μλ₯Ό λ€μ΄ Team λ°μ΄ν°λ₯Ό μλμΉ μκ² μμ νλ κ²½μ°, Player λν μμ λκ³ Playerμ μ°κ΄λμ΄ μλ λ€λ₯Έ μν°ν°μ λ°μ΄ν° μ ν©μ±κΉμ§ μν₯μ μ€ μ μλ κ²μ΄λ€.
// Cascade Test
public class CascadeTest extends JpaTest {
@DisplayName("Case 10. Cascade, orphanRemoval μ΅μ
μ¬μ©μ μ£Όμνλ€.")
@Test
void cascadeTest() {
// given
Team givenTeam = teamRepository.save(team().build());
Player player1 = playerRepository.save(player().team(givenTeam).build());
Player player2 = playerRepository.save(player().team(givenTeam).build());
// when
entityManager.flush();
entityManager.clear();
// Team μμ
teamRepository.deleteById(givenTeam.getId());
// then
then(teamRepository.findById(givenTeam.getId())).isEmpty();
// Teamκ³Ό μ°κ΄λμ΄ μλ λͺ¨λ Playerκ° μμ λλ€.
then(playerRepository.findAllById(List.of(player1.getId(), player2.getId()))).isEmpty();
}
}
-- Player μμ
Hibernate:
delete
from
player
where
id=?
Hibernate:
delete
from
player
where
id=?
-- Team μμ
Hibernate:
delete
from
team
where
id=?
// OrphanRemoval Test
@DisplayName("Case 10. Cascade, orphanRemoval μ΅μ
μ¬μ©μ μ£Όμνλ€.")
@Test
void orphanRemovalTest() {
// given
Team givenTeam = teamRepository.save(team().build());
Player player1 = playerRepository.save(player().team(givenTeam).build());
Player player2 = playerRepository.save(player().team(givenTeam).build());
// when
entityManager.clear();
Team team = teamRepository.findById(givenTeam.getId()).orElseThrow();
// Teamμ 컬λ μ
μΌλ‘λΆν° Playerμμ μ°κ΄κ΄κ³ μ κ±°
team.getPlayers().clear();
entityManager.flush();
entityManager.clear();
// then
then(playerRepository.findAllById(List.of(player1.getId(), player2.getId()))).isEmpty();
}
-- λΆλͺ¨μΈ Teamκ³Όμ μ°κ΄κ΄κ³κ° μ κ±°λ Player λ°μ΄ν°λ€μ μμ νλ€.
Hibernate:
delete
from
player
where
id=?
Hibernate:
delete
from
player
where
id=?
Playerλ μ΄λ Teamμλ μμλμ΄ μμ§ μμ μ μκ³ , μΈμ λ λ€λ₯Έ Teamμ μμλ μ μκΈ° λλ¬Έμ κ°λ μ μΌλ‘ Teamμ μμ ν μ’ μμ μ΄μ§ μλ€. μ¦, λΌμ΄νμ¬μ΄ν΄μ΄ λ€λ₯΄λ€.
βλ°λ©΄ Player
μ ν΄λΉ Playerμ κΈ°λ‘μ κ΄λ¦¬νλ PlayerStat
μ κ°μ λΌμ΄νμ¬μ΄ν΄μ κ°μ§λ€.
PlayerStatμ νΉμ Playerμλ§ μ’
μμ μΈ λ°μ΄ν°μ΄κ³ , Player μμ΄λ μ‘΄μ¬ μλ―Έκ° μκΈ° λλ¬Έμ΄λ€.
μ΄ κ²½μ° cascade, orphanRemoval μ΅μ
μ μ μ©μ κ³ λ €ν΄λ³Όλ§ νλ€.
@DisplayName("Case 09. Cascade, orphanRemoval μ΅μ
μ¬μ©μ μ£Όμνλ€.")
@Test
void playerStatTest() {
// given
Team givenTeam = teamRepository.save(team().build());
Player givenPlayer = playerRepository.save(player().team(givenTeam).build());
PlayerStat playerStat1 = playerStatRepository.save(playerStat().season(2001).player(givenPlayer).build());
PlayerStat playerStat2 = playerStatRepository.save(playerStat().season(2002).player(givenPlayer).build());
// when
entityManager.flush();
entityManager.clear();
Player player = playerRepository.findById(givenPlayer.getId()).orElseThrow();
// Playerλ₯Ό μμ νλ©΄
playerRepository.delete(player);
entityManager.flush();
entityManager.clear();
// then
// Player, PlayerStatμ΄ ν¨κ» μμ λλ€.
then(playerRepository.findById(player.getId())).isEmpty();
then(playerStatRepository.findAllById(List.of(playerStat1.getId(), playerStat2.getId()))).isEmpty();
}
-- PlayerStat μμ
Hibernate:
delete
from
player_stat
where
id=?
Hibernate:
delete
from
player_stat
where
id=?
-- Player μμ
Hibernate:
delete
from
player
where
id=?
βμ΄μ κ°μ΄ cascade, orphanRemoval μ΅μ μ μμ μν°ν°κ° λΆλͺ¨ μν°ν°μ λΌμ΄νμ¬μ΄ν΄μ΄ μ¨μ ν μΌμΉ ν λ, κ·Έλ¦¬κ³ ν΄λΉ λΆλͺ¨ μν°ν° μΈμ λ€λ₯Έ μ°κ΄κ΄κ³κ° μμ λμλ§ μ¬μ©νλ κ²μ κΆμ₯νλ€.
CASE 10. μλ°©ν₯ μ°κ΄κ΄κ³(@OneToMany
)λ νμμμλ§ μ μ©νλ€.
βCASE 06.
~ CASE 09.
μμ νμΈν λ΄μ©κ³Ό κ°μ΄, @OneToMany
λ₯Ό μ¬μ©νλ©΄ μ 체μ μΈ λ³΅μ‘λκ° μμΉνμ¬ μ½λ λ 벨μμ κ΄λ¦¬ ν΄μΌ ν ν¬μΈνΈκ° λμ΄λκ² λλ€.
βFKλ₯Ό κ΄λ¦¬νλ μν°ν°μλ§ @ManyToOne
κ΄κ³λ₯Ό μ€μ νκ³ , @OneToMany
λ νμνλ€κ³ νλ¨ λ λλ§ μ λ³μ μΌλ‘ μ μ©νλ κ²μ κΆμ₯νλ€.