error handling

[errorHandling] spring data jpa 관련, transaction.TransactionSystemException 발생 -> transaction이 commit되지 않고 Rollback되는 문제

기록하는 습관. 2023. 1. 22. 03:30

안녕하세요. Java백엔드 개발자를 꿈꾸며 공부중인 김은철입니다.

해당 포스팅의 내용은 이해한 내용을 토대로 작성되었습니다.

그렇기 때문에 포스팅에 잘못된 내용이 포함되어 있을 수 있음과 미흡한 점이 있을 수 있습니다.


의아한 내용이나 틀린 내용이 있다면 적극적인 지적과 므흡한 내용, 그리고 추가적으로 학습하며 좋을 내용을 알려주시면 감사하겠습니다.


여러분의 지적과 관심은 저의 성장에 큰 도움이 될 것이라 확신합니다.


감사합니다.



안녕하세요. 이번 포스팅에서는 Spring Security 폼로그인 포스팅 자료를 준비하며 마주했던 transaction.TransactionSystemException의 원인과 해결방법에 대해서 포스팅해보겠습니다.


1. transaction.TransactionSystemException

[1] 문제가 되었던 코드 & 로그

  • Member.java
@Table(name = "Members")
@Getter
@Setter
@NoArgsConstructor
@Entity
public class Member {
    @Id @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column @NotNull
    private String nickName;

    @Column @NotNull
    private String email;

    @Column @NotNull
    private String password;

    @Enumerated
    private Role role = Role.ROLE_USER;

    public enum Role {
        ROLE_USER,
        ROLE_ADMIN
    }

    @Builder
    public Member(String email, String password, Role role, String nickName) {
        this.email = email;
        this.password = password;
        this.role = role;
    }
}

위의 코드는 Member 엔티티입니다. 특이사항으로는 Builder Pattern을 사용하는 Member(String email, String password, Role role, String nickName)입니다. 해당 코드를 삽입했던 이유는 MemberDto.Post를 mapper에서 Meber로 바꾸어줄 때 사용해주려고 했습니다. 이제 MemberMapper코드를 봐보겠습니다.


  • MemberMapper.java
@Mapper(componentModel = "spring")
public interface MemberMapper {
    default Member memberPostDtoToMember(MemberDto.Post request) {
        MemberDto.Post optionalMemberPostDto = Optional.ofNullable(request)
                                                .orElseThrow(()-> new RuntimeException());

        Member member = Member.builder()
                .email(request.getEmail())
                .password(request.getPassword())
                .nickName(request.getNickName())
                .build();

        return member;
    }
}

해당 코드에서Memeber 엔티티의 객체를 만들 때 builder를 사용했습니다. 이후, PostMan을 사용하여 사용자를 생성하려고 하였으나 아래와 같은 문제에 직면했습니다.


// ...로그생략

2023-01-22 03:03:57.497 ERROR 42520 --- [nio-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : 
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; 
nested exception is org.springframework.transaction.TransactionSystemException: 
Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: 
Error while committing the transaction] with root cause

// ...로그생략

TransactionSystemException문제로, JPA의 트랜잭션을 커밋할 수 없어서, Rollback이 되었던 문제였습니다.


[2] 문제에 대해서 생각하고 코드수정

MemberPostDto를 Member로 바꾸어줄 때 builder를 사용한 것이 문제가 되었습니다. 생각해보면 당연한 문제였습니다.

        Member member = Member.builder()
                .email(request.getEmail())
                .password(request.getPassword())
                .nickName(request.getNickName())
                .build();

생성자 Builder Pattern으로 Member 엔티티 객체를 만든다면, Member의 필드인 id값은 null이 들어가게 되고, 데이터베이스로 commit이 이루어질 때 id는 null이 되게됩니다. 이러한 문제로 transaction이 commit되지 않는 문제가 발생하게 된 것이었습니다. 이러한 문제를 인지하고 아래와 같이 코드를 바꾸어주니 문제가 해결되었습니다.

기본적으로 database에서 pk는 unique해야하며, null이면 안됩니다.


  • Member.java
@Table(name = "Members")
@Getter
@Setter
@NoArgsConstructor
@Entity
public class Member {
    @Id @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column @NotNull
    private String nickName;

    @Column @NotNull
    private String email;

    @Column @NotNull
    private String password;

    @Enumerated
    private Role role = Role.ROLE_USER;

    public enum Role {
        ROLE_USER,
        ROLE_ADMIN
    }
}

// 생성자 builder 삭제
  • MemberMapper.java
@Mapper(componentModel = "spring")
public interface MemberMapper {
    default Member memberPostDtoToMember(MemberDto.Post request) {
        MemberDto.Post optionalMemberPostDto = Optional.ofNullable(request)
                                                .orElseThrow(()-> new RuntimeException());

        Member member = new Member();
        member.setEmail(request.getEmail());
        member.setPassword(request.getPassword());
        member.setNickName(request.getNickName());

        return member;
    }
}

/* 
    생성자 없는 Member객체를 생성하여
    id의 값을 @GeneratedValue(strategy = GenerationType.AUTO)에 맞춘 null이 아닌 값이 들어갈 수 있게 해주고, 
    setter를 이용하여 값을 넣어주었습니다.
 */