IT/디버깅

[디버깅] JdbcSQLIntegrityConstraintViolationException: Referential integrity constraint violation 에러 해결

happy_life 2023. 3. 30. 09:40

JdbcSQLIntegrityConstraintViolationException: Referential integrity constraint violation 이라는 에러가 발생하여, 이 문제를 해결하는 과정을 이번 포스팅에 담으려고 합니다.

 

JdbcSQLIntegrityConstraintViolationException: Referential integrity constraint violation 관련 테이블 설계 

Member와 Team이 다대다 연관관계인데 중간에 memberTeam을 두어 1:N, N:1로 풀어낸 상태입니다. 또한 Team에 역방향을 걸어 Team을 통해 조회할 수 있도록 하였고, cascade를 걸어 생명주기를 같이 하도록 하였습니다.

설명

 

 

 

JdbcSQLIntegrityConstraintViolationException: Referential integrity constraint violation 문제상황

memberTeam이 team을 삭제할 때 자동으로 삭제가 되는지(cascade 옵션이 제공되는지), 또한 teamId를 기준으로 잘 삭제가 되었는지를 체크하기 위해 아래와 같이 Test코드를 작성해두었으나 위에 설명한 에러가 발생하였습니다.

@Test
    public void 멤버_팀삭제 () {
        //given
        Member member = new Member();
        member.setName("김");
        member.setEmail("reree25@naver.com");
        member.setPassword("ta16");
        member.setNickName("귀요미");
        memberService.join(member);

        Member member2 = new Member();
        member2.setName("이영진");
        member2.setEmail("eloo@naver.com");
        member2.setPassword("taeu4616");
        member2.setNickName("귀요미2");
        memberService.join(member2);

        Team team = new Team();
        team.setBody("asdasdas");
        team.setTitle("A팀");
        team.setName("fsdf");
        team.setTotal_num(4);
        team.setRecruited_num(0);

        MemberTeam memberTeam = new MemberTeam();
        memberTeam.setMember(member);
        memberTeam.setTeam(team);

        MemberTeam memberTeam2 = new MemberTeam();
        memberTeam2.setMember(member2);
        memberTeam2.setTeam(team);

        team.getMemberTeams().add(memberTeam);
        team.getMemberTeams().add(memberTeam2);

        teamService.join(team);

        //when
        teamService.delete(team.getId());
        em.flush();
        em.clear();

        //then
        assertNull(teamService.findOne(team.getId())); // team도 없고
        List<MemberTeam> memberTeamList = em.createQuery("select mt from MemberTeam mt where mt.team.id=:teamId", MemberTeam.class)
                .setParameter("teamId", team.getId())
                .getResultList();
        assertEquals(memberTeamList.size(), 0); //Member_team도 없다.
    }

 

 

 

JdbcSQLIntegrityConstraintViolationException: Referential integrity constraint violation 원인

위의 코드에서 team이 먼저 삭제되는데, 이러면 teamMember에서 참조하고 있던 외래키가 사라져서 referential integrity violation이 발생하는 것입니다.

** em.remove()를 사용할 때는 위와 같은 에러가 발생하지 않는데 createQuery를 통한 delete문을 작성시 문제가 발생하였습니다.

 

이에 대한 chatGPT의 답변은 아래와 같습니다.

the main difference between these two statements is that the first one deletes the entity directly from the database without affecting the EntityManager context, while the second one removes the entity from the context and marks it for removal during the next flush operation. The choice between these two methods depends on your specific use case and requirements.

 

따라서 createQuery인 JPQL을 사용했기때문에 cascade 옵션이 동작하지 않았고, 부모 객체만 지워지기 때문에 참조 무결성 관련 에러가 발생한 것입니다. JQPL은 영속성 컨텍스트와 관련이 없고 따라서 cascade 옵션이 동작하지 않습니다. 한편 em.remove()는 영속성 컨텍스트 안에서 동작하기 때문에 cascade 옵션이 작동한 것입니다.

 

 

 

JdbcSQLIntegrityConstraintViolationException: Referential integrity constraint violation 해결과정

1. 원인을 바탕으로 코드를 재확인해서 null이 가능하도록 하게해 코드가 실행되는지 확인하려고 하였습니다. 하지만 아래와 같이 nullable = True로 default 값이었으므로 이것으로 문제를 해결할 수 없었습니다.

nullable = True가 기본값임

 

pk는 not null이지만 fk는 nullable 합니다. 따라서 위와 같은 오류는 사실상 발생하지 않아야합니다. 그래서 h2 DB의 콘솔 화면에서 직접 null을 추가해보니 아래와 같이 똑같은 에러가 발생하였습니다. 따라서 h2의 경우는 cascade 옵션을 걸면 fk 또한 not null constraint가 적용되어있다고 유추해볼 수 있고, 따라서 nullable = true 옵션이 아예 적용되지 않는 듯합니다. 실제로 DDL을 보니 자동으로 아래와 같은 constraint가 걸리는 것을 알 수 있었습니다. (1:N으로 다른 테이블을 만들어 delete(JPQL사용) 시 똑같은 에러가 발생합니다)

 

 

h2 콘솔에서의 에러/ DDL에서의 constraint 자동생성

 

 

2. orphanRemoval = true 옵션을 주어 team이 삭제되면 cascade가 설정된 memberTeam이 모두 삭제되도록 해보았으나 실패하였습니다. 

orphanRemoval = true

 

이에 대한 이유는 위에 설명해두었습니다.  JQPL을 사용한 쿼리문이기 때문이었습니다. 따라서 이를 해결하기 위해 JPQL이 아닌 em.remove()를 사용하기 위해 아래와 같이 코드를 수정하였습니다.

em.remove로 수정

 

하지만 em.remove가 동작하지 않는 이슈가 발생하였습니다. 그래서 찾아보던중 commit()을 해야한다는 것을 알고 코드를 아래와 같이 수정하여 문제를 해결하였습니다.

em.flush()를 통해 문제 해결

 

위의 코드는 Repository단이고 거기엔 @Transaction 어노테이션이 없습니다. 하지만 @Transaction 어노테이션이 Service 계층에 붙어있음에도 불구하고 동작하지 않았는데 그 이유를 찾는 과정을 포스팅하려고 합니다. 감사합니다.

 

https://abcdefgh123123.tistory.com/531