안녕하세요. 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를 이용하여 값을 넣어주었습니다.
*/