λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
[Error Handling]

😑 JPAμ—μ„œ 같은 μ—”ν‹°ν‹°λ₯Ό 비ꡐ할 λ•Œ assertEquals()κ°€ μ‹€νŒ¨ν•˜λŠ” μ΄μœ μ™€ 해결방법(@Transactional, @EqualsAndHashCode)

by νŒ‘νŽ‘ν 2023. 8. 15.
728x90

🚨 Error :

@SpringBootTest
@Slf4j
public class PostgreDbTest {

    @Autowired
    PostgreRepository postgreRepository;

    @Test
    public void save() throws Exception {

        // ν…ŒμŠ€νŠΈ μΈμŠ€ν„΄μŠ€ 생성
        Address address = new Address("μ„œμšΈμ‹œ", "κ°•λ‚¨λŒ€λ‘œ", "12345");
        Map<String, Object> additionalInfo = new HashMap<>();
        additionalInfo.put("μ£ΌκΈ‰", 1000);
        additionalInfo.put("νŒ€", "PSG");
        Member member = new Member( "이강인", 20, "010-1234-5678", "λ…μ„œ", address, additionalInfo);
		
        // Member μ—”ν‹°ν‹° 생성 및 μ €μž₯
        Member savedMember = postgreRepository.save(member);
        
        // 검증
        Assertions.assertEquals(member, postgreRepository.findById(savedMember.getId()).orElse(null));
    }
}
  • postgreSQL λ°μ΄ν„°λ² μ΄μŠ€ μ‚¬μš©μ„ 읡히기 μœ„ν•΄ μžλ°” ν”„λ‘œμ νŠΈλ₯Ό μƒμ„±ν•˜κ³  κ°„λ‹¨ν•˜κ²Œ ν…ŒμŠ€νŠΈλ₯Ό λ§Œλ“€μ—ˆλŠ”λ°

 

expected: <test.excelparser.postgre.entity.Member@79735611> but was: <test.excelparser.postgre.entity.Member@73476e2d>
Expected :test.excelparser.postgre.entity.Member@79735611
Actual   :test.excelparser.postgre.entity.Member@73476e2d
  • 객체의 λ©”λͺ¨λ¦¬ 상 μ£Όμ†Œκ°€ μ„œλ‘œ λ‹€λ₯΄λ‹€κ³  λ‚˜μ™”λ‹€.

 

κ°„λ‹¨νžˆ μ •λ¦¬ν•˜λ©΄

member 객체λ₯Ό λ§Œλ“€κ³ 
JPARepository의 save() λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•˜μ—¬ λ°μ΄ν„°λ² μ΄μŠ€μ— μ €μž₯ν•œ ν›„
κ³§μž₯ findById둜 λ°μ΄ν„°λ² μ΄μŠ€μ— ν•΄λ‹Ή 값을 κΊΌλ‚΄μ™€μ„œ 두 객체가 λ™μΌν•œ μ§€λ₯Ό λΉ„κ΅ν•˜λŠ” ν…ŒμŠ€νŠΈμ΄λ‹€.

λ‹¨μˆœνžˆ μƒκ°ν•΄μ„œ 객체λ₯Ό μ €μž₯ν•˜κ³  κ·ΈλŒ€λ‘œ κΊΌλ‚΄μ„œ λΉ„κ΅ν•˜λŠ”λ° μ™œ μ°Έμ‘°ν•˜λŠ” μ£Όμ†Œκ°€ λ‹€λ₯Έμ§€ 이해가 κ°€μ§€ μ•Šμ•˜λ‹€.



πŸ€“ 원인 :

 

 λλ‚΄ ν‡΄κ·ΌκΉŒμ§€ ν•΄κ²°ν•˜μ§€ λͺ»ν•˜κ³  μ˜€λžœλ§Œμ— λΆ€νŠΈμΊ ν”„ 동기듀과 λ§Œλ‚¬λ‹€. κ·Έλ ‡κ²Œ μˆ μ„ λ§ˆμ‹œλ‹€κ°€ μ—λŸ¬ 생각이 λ‚˜μ„œ λ™κΈ°λ“€μ—κ²Œ μ–˜κΈ°ν–ˆλŠ”λ° 메인 ν”„λ‘œμ νŠΈ 같이 ν–ˆλ˜ νŒ€μž₯λ‹˜μ΄ save() λ©”μ„œλ“œκ°€ μž‘λ™ν•œ ν›„ νŠΈλžœμž­μ…˜μ΄ 끝이 λ‚˜μ„œ λ°μ΄ν„°λ² μ΄μŠ€μ—μ„œ κ°€μ Έμ˜¨ 객체의 μ£Όμ†Œκ°’μ΄ 달라진 것 μ•„λ‹ˆλƒλŠ” 말씀을 ν•˜μ…¨λ‹€. 집에 μ™€μ„œ ν•˜λ‚˜μ˜ νŠΈλžœμž­μ…˜μ—μ„œ λ™μž‘μ‹œν‚€κΈ° μœ„ν•΄ @Transactional μ• λ„ˆν…Œμ΄μ…˜μ„ μ‚¬μš©ν•˜λ‹ˆ ν…ŒμŠ€νŠΈκ°€ ν†΅κ³Όλ˜μ—ˆλ‹€. κ΄€λ ¨ λ‚΄μš©μ„ GPT에 물어보고 얻은 닡변을 정리해 λ³΄μ•˜λ‹€.

 

 

  • @Transactional μ• λ„ˆν…Œμ΄μ…˜μ„ μ‚¬μš©ν•˜λ©΄ νŠΈλžœμž­μ…˜ λ²”μœ„ λ‚΄μ—μ„œ JPAκ°€ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ— μ €μž₯ν•œ μ—”ν‹°ν‹°λ₯Ό κ΄€λ¦¬ν•˜κ²Œ λœλ‹€.
  • JPAμ—μ„œ 같은 μ—”ν‹°ν‹°λ₯Ό κ΅¬λΆ„ν•˜λŠ” 방법은 μ—”ν‹°ν‹°μ˜ @Id ν•„λ“œ 값을 λΉ„κ΅ν•˜λŠ” 것이닀.
  • κ·ΈλŸ¬λ‚˜, 이 @Id 값은 DBμ—μ„œ μƒμ„±λœ 값이닀.
  • JPAκ°€ μ—”ν‹°ν‹°λ₯Ό μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ— μ €μž₯ν•  λ•ŒλŠ” 이 값이 μ—†μ–΄μ„œ 객체 μ£Όμ†Œ(μ°Έμ‘°κ°’)λ₯Ό κΈ°μ€€μœΌλ‘œ μ—”ν‹°ν‹°λ₯Ό κ΅¬λΆ„ν•œλ‹€.
  • @Transactional μ• λ„ˆν…Œμ΄μ…˜μ„ μ‚¬μš©ν•˜μ§€ μ•ŠμœΌλ©΄ JPAκ°€ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ— μ €μž₯ν•œ μ—”ν‹°ν‹°λŠ” ν•΄λ‹Ή νŠΈλžœμž­μ…˜ λ²”μœ„λ₯Ό λ²—μ–΄λ‚˜λŠ” μˆœκ°„ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ—μ„œ λΆ„λ¦¬λœλ‹€.
    • 참고둜, νŠΈλžœμž­μ…˜μ΄ λλ‚˜λ©΄ commit 되고 flushκ°€ μžλ™ ν˜ΈμΆœλ˜λŠ”λ° μ΄λ•Œ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ™€ λΆ„λ¦¬λ˜λŠ” 것이지 λΉ„μ›Œμ§€λŠ” 건 μ•„λ‹ˆλ‹€.
  • 이후 findById() λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•  λ•Œ μƒˆλ‘œμš΄ νŠΈλžœμž­μ…˜μ΄ μ‹œμž‘λ˜μ–΄ λ¦¬ν„΄λ˜λŠ” κ°μ²΄λŠ” μ£Όμ†Œκ°’μ΄ λ‹¬λΌμ§€κ²Œ λœλ‹€.
    • 이전 νŠΈλžœμž­μ…˜ λ²”μœ„μ—μ„œ μ €μž₯ν•œ μ—”ν‹°ν‹°κ°€ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ—μ„œ κ΄€λ¦¬λ˜μ—ˆλ‹€κ°€ μƒˆλ‘œμš΄ νŠΈλžœμž­μ…˜μ—μ„œ findById() λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜λ©΄, μƒˆλ‘œμš΄ νŠΈλžœμž­μ…˜ λ²”μœ„λ‘œ 인해 μƒˆλ‘œμš΄ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈλ₯Ό μ‚¬μš©ν•˜κ²Œ λœλ‹€.
    • μƒˆλ‘œμš΄ νŠΈλžœμž­μ…˜μ΄ μ‹œμž‘λ˜μ–΄μ„œ μƒˆλ‘œμš΄ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈλ₯Ό μ‚¬μš©ν•  λ•ŒλŠ” 이전 μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ—μ„œ κ΄€λ¦¬ν•˜λ˜ μ—”ν‹°ν‹°μ˜ μƒνƒœλ₯Ό μ΄ˆκΈ°ν™”ν•œλ‹€. λ•Œλ¬Έμ— μƒˆ νŠΈλžœμž­μ…˜μ—μ„œλŠ” findById() λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜λ©΄ μƒˆλ‘œμš΄ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ—μ„œ 이 μ—”ν‹°ν‹°μ˜ μ‹λ³„μžμ— ν•΄λ‹Ήν•˜λŠ” μ—”ν‹°ν‹°κ°€ μ—†λ‹€κ³  νŒλ‹¨ν•˜κ³  λ°μ΄ν„°λ² μ΄μŠ€μ—μ„œ λ‹€μ‹œ μ‘°νšŒν•˜κ²Œ λœλ‹€.(findById()에 ν•΄λ‹Ήν•˜λŠ” Select 쿼리가 λ‚˜κ°„λ‹€.)
    • λ°μ΄ν„°λ² μ΄μŠ€μ—μ„œ μ‘°νšŒν•œ μ—”ν‹°ν‹° κ°μ²΄λŠ” μƒˆλ‘œ μƒμ„±λ˜μ–΄ μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ— μΆ”κ°€λœλ‹€.
    • 이 λ•Œλ¬Έμ— 이전 νŠΈλžœμž­μ…˜μ—μ„œ save()ν•œ 객체와 λ‹€λ₯Έ μ£Όμ†Œκ°’μ„ κ°€μ§€κ²Œ λ˜λŠ” 것이닀.
  • 이 μƒνƒœμ—μ„œ μ—”ν‹°ν‹°λ₯Ό λΉ„κ΅ν•˜λ‹ˆ 객체 μ£Όμ†Œ(μ°Έμ‘°κ°’)κ°€ μ„œλ‘œ λ‹€λ₯΄κΈ° λ•Œλ¬Έμ— `assertEquals()` λ©”μ„œλ“œκ°€ μ‹€νŒ¨ν•˜κ²Œ λ˜λŠ” 것이닀.
  • λ°˜λŒ€λ‘œ @Transactional μ• λ„ˆν…Œμ΄μ…˜μ„ μ‚¬μš©ν•˜λ©΄ JPAκ°€ νŠΈλžœμž­μ…˜ λ²”μœ„ λ‚΄μ—μ„œ μ—”ν‹°ν‹°λ₯Ό κ΄€λ¦¬ν•˜κΈ° λ•Œλ¬Έμ— 같은 μ—”ν‹°ν‹°λ₯Ό 비ꡐ할 λ•Œ 객체의 μ£Όμ†Œκ°’μ΄ λ™μΌν•˜κ²Œ λœλ‹€.
  • 즉, 같은 μ—”ν‹°ν‹°λ₯Ό λ‹€λ£° λ•Œ 객체 μ£Όμ†Œκ°€ μΌμ •ν•˜κ²Œ μœ μ§€λ˜κΈ° λ•Œλ¬Έμ— `assertEquals()` λ©”μ„œλ“œκ°€ μ˜ˆμƒν•œ λŒ€λ‘œ μž‘λ™ν•˜κ²Œ λ˜λŠ” 것이닀.
    • λ™μΌν•œ νŠΈλžœμž­μ…˜ λ‚΄μ—μ„œλŠ” μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ—μ„œ 객체λ₯Ό μ°ΎκΈ° λ•Œλ¬Έμ— Select 쿼리가 λ‚˜κ°€μ§€ μ•ŠλŠ”λ‹€.

 


πŸš’ ν•΄κ²° :

방법은 두 κ°€μ§€κ°€ μžˆλ‹€.

첫 번째

@SpringBootTest
@Slf4j
@Transactional
public class PostgreDbTest {

	...
}
  • @Transactional μ• λ„ˆν…Œμ΄μ…˜μ„ λΆ™μ—¬ ν…ŒμŠ€νŠΈ 둜직 전체λ₯Ό ν•˜λ‚˜μ˜ νŠΈλžœμž­μ…˜ λ²”μœ„ μ•ˆμœΌλ‘œ 두어 같은 μ—”ν‹°ν‹°λ₯Ό 비ꡐ할 λ•Œ λ™μΌν•œ μ£Όμ†Œκ°’μ΄ λ˜λ„λ‘ ν•œλ‹€.

 

두 번째

  • @EqualsAndHashCode μ• λ„ˆν…Œμ΄μ…˜μ„ Member ν΄λž˜μŠ€μ— 뢙인닀.

 

@EqualsAndHashCode

  • Lombok(둬볡) 라이브러리의 μ–΄λ…Έν…Œμ΄μ…˜μ΄λ‹€.
  • 이 μ–΄λ…Έν…Œμ΄μ…˜μ„ ν΄λž˜μŠ€μ— 뢙이면 둬볡이 μžλ™μœΌλ‘œ ν•΄λ‹Ή ν΄λž˜μŠ€μ— λŒ€ν•œ equals()와 hashCode() λ©”μ„œλ“œλ₯Ό κ΅¬ν˜„ν•œλ‹€.
  • λ”°λΌμ„œ 객체의 동일성 비ꡐ가 λ‚΄λΆ€ 속성값을 기반으둜 μˆ˜ν–‰λœλ‹€. 
  • 예λ₯Ό λ“€μ–΄ μ•„λž˜μ™€ 같은 Member ν΄λž˜μŠ€κ°€ μžˆλ‹€κ³  κ°€μ •ν•΄ 보면

 

import lombok.EqualsAndHashCode;

@EqualsAndHashCode
public class Member {
    private String name;
    private int age;
    // 기타 멀버 λ³€μˆ˜ λ“±...
}
  • μœ„μ™€ 같이 Member ν΄λž˜μŠ€μ— @EqualsAndHashCode μ–΄λ…Έν…Œμ΄μ…˜μ„ μ„ μ–Έν•˜λ©΄

 

public class Member {
    private String name;
    private int age;
    // 기타 멀버 λ³€μˆ˜ λ“±...

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Member member = (Member) o;
        return age == member.age &&
                Objects.equals(name, member.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}
  • 컴파일 μ‹œ μ•„λž˜μ™€ 같이 equals()와 hashCode() λ©”μ„œλ“œκ°€ μžλ™μœΌλ‘œ μƒμ„±λœλ‹€.
  • μ΄λ ‡κ²Œ 되면 객체 κ°„ 비ꡐ μ‹œ μ£Όμ†Œκ°’μ΄ μ•„λ‹Œ λ‚΄λΆ€ 속성값을 λΉ„κ΅ν•˜μ—¬ 두 객체의 동일성을 νŒλ‹¨ν•˜κ²Œ λœλ‹€.
  • 이λ₯Ό 톡해 μ˜μ†μ„± μ»¨ν…μŠ€νŠΈμ—μ„œ 객체λ₯Ό μ‘°νšŒν•  λ•Œ λ°œμƒν•˜λŠ” μ£Όμ†Œκ°’ 차이 문제λ₯Ό ν•΄κ²°ν•  수 μžˆλ‹€.

 


πŸ€” 의문점 :

μ—†μŒ!

 

 

 

μ°Έκ³ 

뀼튼

728x90