'영속성전이'에 해당되는 글 2건
- 2019.04.29 :: Spring JPA - JPA cascade 란?!
- 2019.02.04 :: JPA - 영속성 전이(Cascade)와 고아 객체(Orphan)
오늘 포스팅할 내용은 간단히 JPA의 cascade 기능이다. 이전 포스팅 중에 해당 내용에 대해 포스팅한적이 있지만 조금 부족한 것같아서 다시 한번 정리할겸 글을 남긴다.
영속성 전이(cascade)란 쉽게 말해 부모 엔티티가 영속화될때, 자식 엔티티도 같이 영속화되고 부모 엔티티가 삭제 될때, 자식 엔티티도 삭제되는 등 부모의 영속성 상태가 전이되는 것을 이야기한다. 영속성전이의 종류로는 ALL, PERSIST, DETACH, REFRESH, MERGE, REMOVE등이 있다. 이름만 봐도 어디까지 영속성이 전이되는지 확 눈에 보일 것이다. 여기서는 별도로 각각을 설명하지는 않는다.
오늘의 상황 : A와 B라는 엔티티가 존재하고, 두 엔티티의 관계는 @ManyToMany 관계이다. 이 관계는 중간에 Bridge 테이블을 두어 @OneToMany<->@ManyToOne @OneToMany<->@ManyToOne 관계로 매핑하였다. 그리고 C라는 엔티티는 A엔티티와 @ManyToOne 관계이다. 이렇게 여러개가 조인이 걸려있는 상황에서 cascade를 이용한 PERSIST 예제를 한번 짜보았다.
<AEntity Class>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
@Entity
@Table(name = "TB_A")
@Getter
@Setter
@ToString
public class AEntity {
@Id
@Column(name = "A_ID")
@GeneratedValue(strategy=GenerationType.TABLE, generator = "SEQ_GENERATOR")
@TableGenerator(
name="SEQ_GENERATOR",
table="MY_SEQUENCE",
pkColumnName="SEQ_NAME", //MY_SEQUENCE 테이블에 생성할 필드이름(시퀀스네임)
pkColumnValue="A_SEQ", //SEQ_NAME이라고 지은 칼럼명에 들어가는 값.(키로 사용할 값)
allocationSize=1
)
private Long id;
private String name;
@OneToMany(mappedBy="a",cascade=CascadeType.ALL)
private List<CEntity> cList = new ArrayList<>();
@OneToMany(mappedBy = "aEntity",fetch=FetchType.EAGER,cascade=CascadeType.ALL)
private List<BridgeEntity> bridges;
}
|
cs |
<BEntity class>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
@Entity
@Table(name = "TB_B")
@Getter
@Setter
@ToString
public class BEntity {
@Id
@Column(name = "B_ID")
@GeneratedValue(strategy=GenerationType.TABLE, generator = "SEQ_GENERATOR")
@TableGenerator(
name="SEQ_GENERATOR",
table="MY_SEQUENCE",
pkColumnName="SEQ_NAME", //MY_SEQUENCE 테이블에 생성할 필드이름(시퀀스네임)
pkColumnValue="B_SEQ", //SEQ_NAME이라고 지은 칼럼명에 들어가는 값.(키로 사용할 값)
allocationSize=1
)
private Long id;
private String name;
@OneToMany(mappedBy = "bEntity")
private List<BridgeEntity> bridges;
}
|
cs |
<CEntity class>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
@Entity
@Table(name = "TB_C")
@Getter
@Setter
@ToString
public class CEntity {
@Id
@Column(name = "C_ID")
@GeneratedValue(strategy=GenerationType.TABLE, generator = "SEQ_GENERATOR")
@TableGenerator(
name="SEQ_GENERATOR",
table="MY_SEQUENCE",
pkColumnName="SEQ_NAME", //MY_SEQUENCE 테이블에 생성할 필드이름(시퀀스네임)
pkColumnValue="C_SEQ", //SEQ_NAME이라고 지은 칼럼명에 들어가는 값.(키로 사용할 값)
allocationSize=1
)
private Long id;
private String name;
@ManyToOne
@JoinColumn(name="A_ID", nullable=false)
private AEntity a;
}
|
cs |
<BridgeEntity class>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
@Entity
@Table(name = "TB_BRIDGE")
@Getter
@Setter
@ToString
public class BridgeEntity {
@Id
@Column(name = "BRIDGE_ID")
@GeneratedValue(strategy=GenerationType.TABLE, generator = "SEQ_GENERATOR")
@TableGenerator(
name="SEQ_GENERATOR",
table="MY_SEQUENCE",
pkColumnName="SEQ_NAME", //MY_SEQUENCE 테이블에 생성할 필드이름(시퀀스네임)
pkColumnValue="BRIDGE_SEQ", //SEQ_NAME이라고 지은 칼럼명에 들어가는 값.(키로 사용할 값)
allocationSize=1
)
private Long id;
@ManyToOne
@JoinColumn(name = "A_ID")
private AEntity aEntity;
@ManyToOne
@JoinColumn(name = "B_ID")
private BEntity bEntity;
}
|
cs |
<A,B Entity Repository class>
1
2
3
4
5
6
7
|
public interface AEntityRepository extends JpaRepository<AEntity, Long>{
}
public interface BEntityRepository extends JpaRepository<BEntity, Long>{
}
|
cs |
테스트 상황 : A,B,C,BridgeEntity 모두 데이터를 persist 하는 상황이다. 그럼 여기서 생각해야 할것이 있다. 우선 C의 부모는 A이다. 즉, A 엔티티가 persist될때 C 자식 엔티티까지 persist되도록 영속성 전이기능을 이용하면 된다. 그렇다면 A,B,Bridge 세개의 관계는 어떻게 될까? 우선 조건이 있다. A,B,Bridge 관계에서 Bridge엔티티가 외래키의 주인이다. 이 말은 즉슨, Bridge 엔티티가 persist 되기 전에 A,B모두가 영속화된 상태여야하는 것이다. 예제는 아래와 같은 시나리오로 테스트했다.
1)B 엔티티 영속화 ->2)A 엔티티의 영속화 + Bridge 엔티티 영속성전이
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringDataJpaTestApplicationTests {
@Autowired AEntityRepository aRepository;
@Autowired BEntityRepository bRepository;
@Test
public void contextLoads() {
AEntity a = new AEntity();
a.setName("A");
BEntity b = new BEntity();
b.setName("B");
IntStream.rangeClosed(0, 9).forEach((i)->{
CEntity c = new CEntity();
c.setName(i+"");
a.getCList().add(c);
});
a.getCList().stream().forEach((c)->{
c.setA(a);
});
BridgeEntity bridge = new BridgeEntity();
bridge.setBEntity(bRepository.save(b));
bridge.setAEntity(a);
a.setBridges(Arrays.asList(bridge));
aRepository.save(a);
}
}
|
cs |
중요한 것이 있다. 부모 엔티티가 영속화되는 동시에 자식 엔티티가 영속화되게 영속성 전이를 이용하기 위해서는 반드시 자식 객체에 부모객체를 set해주어야 하는 점이다. 만약 부모엔티티를 자식 엔티티에 set해주지 않으면 외래키에 null값이 채워질 것이다.
결과
=>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
A_ID NAME
25 A
B_ID NAME
21 B
C_ID NAME A_ID
63 c-0 25
64 c-1 25
65 c-2 25
66 c-3 25
67 c-4 25
68 c-5 25
69 c-6 25
70 c-7 25
71 c-8 25
72 c-9 25
BRIDGE_ID A_ID B_ID
21 25 21
|
cs |
'Web > JPA' 카테고리의 다른 글
JPA - LazyInitializationException No Session(다중 스레드 트랜잭션,Multi Thread Transaction) (1) | 2019.05.29 |
---|---|
JPA - 영속성 컨텍스트와 JPQL (0) | 2019.02.12 |
JPA - JPAQuery와 Q클래스 (0) | 2019.02.11 |
JPA - NativeQuery ( SQL ) 네이티브 SQL 사용하기! (0) | 2019.02.11 |
JPA - QueryDSL, JPQL Query Builder Class (0) | 2019.02.11 |
JPA - 영속성 전이(Cascade)와 고아 객체(Orphan)
간단히 설명하면 영속성 전이란, 연관된 엔티티가 영속화되면, 그와 연관된 엔티티까지 모두 영속화시키는것 혹은 하나의 엔티티가 영속성 컨텍스트에서 제거가 된다면, 그와 관련된 엔티티마저 영속성 컨텍스트에서 제거가 되는 것 등의 작업흐름을 영속성 전이라고한다. 즉, 데이터베이스의 Cascade와 같은 의미이다.
고아객체란 하나의 엔티티에서 연관된 엔티티와의 참조가 끊어지면 끊어진 엔티티를 자동으로 삭제해주는 기능이다.
두개를 예제소스로 설명하겠다.
영속성 전이(Cascade = CascadeType.xxx)
우선 예제소스를 설명하기 전에 CascadeType의 종류를 나열한다면,
1 2 3 4 5 6 7 8 | public enum CascadeType{ ALL, //모두적용 PERSIST, //영속 MERGE, //병합 REMOVE, //삭제 REPRESH, //리프래쉬 DETACH //준영속상태로 전환 } | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | import lombok.Getter; import lombok.Setter; import org.javers.core.metamodel.annotation.Entity; @Entity @Table(name = "TEAM_TB") @Getter @Setter public class TeamFetchType { @Id private String id; private String name; @OneToMany(mappedBy="team",cascade = CascadeType.ALL) private List<MemberFetchTypeLazy> members = new ArrayList<MemberFetchTypeLazy>(); } public class CascadeTest { //엔티티매니저 팩토리 생성 EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabook"); //엔티티매니저 생성 EntityManager em = emf.createEntityManager(); public void create() { //트랜잭션 획득 EntityTransaction tx = em.getTransaction(); try { tx.begin(); TeamFetchType team = new TeamFetchType(); team.setId("team_1"); team.setName("team_1"); /*em.persist(team);*/ MemberFetchTypeLazy member = new MemberFetchTypeLazy(); member.setId("member_1"); member.setName("윤여성"); member.setTeam(team); team.getMembers().add(member); em.persist(team); tx.commit(); em.clear(); }catch (Exception e1) { // TODO: handle exception tx.rollback(); }finally { } } public void remove() { //트랜잭션 획득 EntityTransaction tx = em.getTransaction(); try { tx.begin(); TeamFetchType findTeam = em.find(TeamFetchType.class, "team_1"); em.remove(findTeam); tx.commit(); }catch (Exception e1) { // TODO: handle exception tx.rollback(); }finally { em.close(); } emf.close(); } public static void main(String[] args) { // TODO Auto-generated method stub CascadeTest t = new CascadeTest(); t.create(); t.remove(); System.out.println(); } } | cs |
고아객체(orphan)
위에서도 간단히 설명했지만 고아객체란 부모 엔티티의 컬렉션에서 자식 엔티티의 참조만 제거하면 자식 엔티티가 자동으로 삭제되는 기능이다.
소스에서도 보듯이 findTeam.getMembers().clear() 메소드를 호출했을 뿐인데, 플러쉬 단계에서 해당 Member엔티티를 삭제한다. 즉, 고아객체 속성이 적용된 하나의 엔티티에서 연관관계에 있는 엔티티의 참조를 제거하는 것만으로 연관된 엔티티가 삭제되는 것이다.
하지만 주의할 점은 고아객체 속성은 반드시 참조가 제거된 엔티티가 어디에서든 참조하지 않는 객체일때만 사용해야한다. 만약 다른데에서도 참조하는 엔티티인데 삭제가 된다면 문제가 생길 수 있다.
'Web > JPA' 카테고리의 다른 글
JPA - JPQL(객체지향쿼리),Java Persistence Query Language (0) | 2019.02.09 |
---|---|
JPA - @Embedded,@Embeddable 임베디드타입 (0) | 2019.02.04 |
JPA - 즉시로딩과 지연로딩(FetchType.EAGER,FetchType.LAZY) 그리고 프록시 (0) | 2019.02.04 |
JPA - 하나의 엔티티에 다수 테이블 매핑(@SecondaryTable,@SecondaryTables) (0) | 2019.02.03 |
JPA - @MappedSuperClass (매핑정보 상속) (0) | 2019.02.02 |