서론
개발을 진행하여 Repository Test를 진행하다보면, 다른 Entity와의 연관관계가 있는 Entity를 삽입처리해주어야할 떄가 있습니다.
이떄, 해당 연관관계의 Entity를 참조하지않고, 무작정 넣을경우 아래의 에러메세지가 발생합니다.
org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing
이때, 개발환경이 MSA 환경일경우 다른 Entity를 가져오기 위해서는 다른 Service와 소통해야하는데
이럴 경우, 단위테스트의 목적과 맞지 않습니다.
그렇기에 간단하게, ManyToOne 의 관계에서 바로 해당 FK로 지명된 Entity를 바로 넣을 수 있는 작업을 통해 테스트를 원활하게 진행하도록 합니다.
또한 이때, 해당 값은 유일성을 보존하도록 설정되어있다면 랜덤값 삽입을 통해, sql constraint 에서 오류가 나지 않도록 설정합니다.
본론
해당 Entity 정보입니다.
Member.class
@Entity @ToString(exclude = {"member_seminar_list"}) public class Member extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long member_no; @Column(length = 100, nullable = false, unique = true, name = "member_id") private String member_id; @Column(length = 100, nullable = false) private String member_password; @Column(length = 100, nullable = true) private String member_nickname; @Column(nullable = false) private boolean member_from_social; @OneToMany(mappedBy = "member") private List<Member_Seminar> member_seminar_list; }
Member_Seminar.class
@Entity @ToString(exclude = {"member", "seminar"}) public class Member_Seminar extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long member_seminar_no; @ManyToOne( targetEntity = Member.class, fetch = FetchType.LAZY, cascade = CascadeType.ALL ) @JoinColumn(name = "member_id") private Member member; @ManyToOne( targetEntity = Seminar.class, fetch = FetchType.LAZY, cascade = CascadeType.ALL ) @JoinColumn(name = "seminar_name") private Seminar seminar; }
Seminar.class
@Entity @ToString(exclude = {"member_seminar_list"}) public class Seminar extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long seminar_no; @Column(length = 100, nullable = false, unique = true) private String seminar_name; @OneToMany(mappedBy = "seminar", fetch = FetchType.LAZY, cascade=CascadeType.ALL, orphanRemoval = true) private List<Member_Seminar> member_seminar_list; }
위의 Entity가 세팅되었습니다.
이제 Member_Seminar에 Test진행시에 Data를 삽입할때, 자동으로 Member Entity와 Seminar Entity의 값도 같이 삽입되게하기 위하여
아래와 같이 ManyToOne 관계의 옵션값에
@ManyToOne( targetEntity = Member.class, fetch = FetchType.LAZY, cascade = CascadeType.ALL ) @JoinColumn(name = "member_id") private Member member;
- targetEntity: 참조할 엔터티의 클래스 타입을 지정합니다. Member.class 타입을 참조하는 것으로 설정했습니다.
- fetch: 데이터 로딩 전략을 설정합니다. FetchType.LAZY는 지연 로딩을 의미하며, 데이터가 실제로 필요한 시점에 로딩됩니다.
- FetchType.EAGER는 즉시 로딩을 의미하며, 엔터티가 조회될 때 관련 데이터도 함께 로딩됩니다.
- cascade: 연관된 엔터티에 대한 영속성 작업 전파 전략을 설정합니다. 여기서 CascadeType.ALL은 모든 영속성 작업을 연관된 엔터티에도 전파하겠다는 의미입니다. 예를 들어, 부모 엔터티가 저장되면 자식 엔터티도 함께 저장됩니다.
- @JoinColumn(name = "member_id"): 외래 키(Foreign Key)를 정의하는데 사용됩니다. name 속성은 외래 키 컬럼의 이름을 지정합니다. 이 경우 "member_id" 컬럼이 외래 키 역할을 하게 됩니다.
- 이러한 설정을 통해 Member_Seminar 엔터티가 Member 엔터티와 다대일 관계를 가지며, member_id 컬럼을 통해 두 엔터티 간의 관계가 맺어집니다.
위의 로직을 각 엔티티에 넣음으로써 해결할 수 있습니다.
저같은경우 @BeforeEach를 활용하여 각 테스트 전에 해당 테스트 정보가 있는지 확인하고, 데이터가 없다면 stub을 생성하여 직접 삽입합니다.
MemberSeminarRepositoryTest
private final Long member_seminar_no = 1L; private final String member_id = "passionfruit200@hello.world"+ UUID.randomUUID(); private final String member_password = "password"; private final String seminar_name= "SeminarTest" + UUID.randomUUID(); @BeforeEach public void setup() throws Exception{ if(memberSeminarRepository.findAllByMember_id(member_id).isEmpty()){ // Member와 Seminar를 모방하는 Stubs/Mocks 생성 Member memberStub = Member.builder() .member_id(member_id) .member_password(member_password) .build(); Seminar seminarStub = new Seminar(); seminarStub.setSeminar_name(seminar_name); // CREATE Member_Seminar Member_Seminar member_seminar = Member_Seminar.builder() .member(memberStub) .seminar(seminarStub) .build(); memberSeminarRepository.save(member_seminar); } }
결론
위의 코드르 테스트 진행시에 Dummy 값을 넣음으로써 원활하게 RepositoryTests를 진행할 수 있습니다.