jpa 1:N insert시 트랜잭션 오류
96
작성한 질문수 51
[질문 템플릿]
1. 강의 내용과 관련된 질문인가요? (아니오)
2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예)
3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)
[질문 내용]
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Entity
@Table(name = "tb_board")
@NoArgsConstructor(access = AccessLevel.PUBLIC)
@Getter
@Setter
public class Board {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "BOARD_ID", columnDefinition = "BIGINT COMMENT '게시판ID'")
private Long id;
@Column(name = "BOARD_NM", nullable = false, unique = true, length = 100, columnDefinition = "VARCHAR(100) COMMENT '게시판명'")
private String boardNm;
@OneToMany(mappedBy = "board", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Article> articleList = new ArrayList<Article>();
@Builder
private Board(Long boardId, String boardNm) {
this.boardNm= boardNm;
this.id = boardId;
}
public Board createBoard(Long boardId, String boardNm) {
return Board.builder().boardId(boardId).boardNm(boardNm).build();
}
public void addArticle(Article article) {
this.articleList.add(article);
}
@Column(name="REG_ID" ,nullable=false,columnDefinition="VARCHAR(100) comment '등록자'")
private String regId;
@Column(name = "CREATED_AT", columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
private LocalDateTime createdAt;
@Column(name="UPD_ID" ,nullable=false,columnDefinition="VARCHAR(100) comment '등록자'")
private String updId;
@Column(name = "UPDATED_AT", columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
private LocalDateTime updatedAt;
}
@Entity
@Table(name = "tb_article")
@Builder
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class Article {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ARTICLE_ID", columnDefinition = "BIGINT COMMENT '게시글ID'")
private Long id;
@Column(name = "ARTICLE_TITLE", columnDefinition = "VARCHAR(100) COMMENT '게시글제목'")
private String title;
@Column(name = "ARTICLE_CONTENT", columnDefinition = "VARCHAR(600) COMMENT '게시글내용'")
private String content;
@Column(name = "REG_ID", nullable = false, columnDefinition = "VARCHAR(100) comment '등록자'")
private String regId;
@Column(name = "CREATED_AT", columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
private LocalDateTime createdAt;
@Column(name = "UPD_ID", nullable = false, columnDefinition = "VARCHAR(100) comment '등록자'")
private String updId;
@Column(name = "UPDATED_AT", columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
private LocalDateTime updatedAt;
@ManyToOne
@JoinColumn(name = "BOARD_ID", nullable = false)
private Board board;
}
@Transactional(rollbackFor = Exception.class) // 체크 예외도 롤백
public void saveArticle(Article article) {
try {
log.info("article",article.getTitle());
Long boardId = 13L;
Board paramBoard = null;
Optional<Board> rBoard = boardRepository.findById(boardId);
if(rBoard.isPresent()) {
paramBoard =rBoard.get();
}else {
paramBoard = new Board().createBoard(boardId, "게시판");
paramBoard = boardRepository.save(paramBoard);
}
Article paramArticle = Article.builder()
.title(article.getTitle())
.content(article.getContent())
.regId("등록자")
.createdAt(LocalDateTime.now())
.updatedAt(LocalDateTime.now())
.board(paramBoard)
.updId("수정자")
.build();
if(paramBoard.getArticleList() == null) {
List<Article> articles =new ArrayList<Article>();
articles.add(paramArticle);
paramBoard.setArticleList(articles);
}else {
paramBoard.getArticleList().add(paramArticle);
}
boardRepository.save(paramBoard);
}catch(Exception ex) {
log.error("Error saving article", ex);
throw ex;
}
}else {
paramBoard = new Board().createBoard(boardId, "게시판");
paramBoard = boardRepository.save(paramBoard);
}
여기서 save를 돌떄
org.springframework.orm.ObjectOptimisticLockingFailureException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.backend.com.entity.Board#13]
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:325) ~[spring-orm-6.2.2.jar:6.2.2]
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:244) ~[spring-orm-6.2.2.jar:6.2.2]
요런식으로 뜨는데public interface BoardRepository extends JpaRepository<Board, Long>, QuerydslPredicateExecutor<Board> {
Optional<Board> findById(Long boardId);
}
querydsl과 jpa같이 사용하고싶어서 요렇게 셋팅하고 공부중입니다.
해본 방법
boardRepository.save(paramBoard) 부분을 빼고 마지막
boardRepository.save(paramBoard);
만 돌려봤지만 마찬가지 에러가 발생했었고
혹시 table 쪽에 lock이 걸려있나 싶어서 보니 lock도 없었음
3. 지피티 한테 물어보니깐
@Lock(LockModeType.PESSIMISTIC_WRITE)
쓰라고 해서 save쪽에 넣어봄 . 마찬가지 ..
Board쪽에
@Version @Column(name = "version") private Long version;
을 추가하라고 해서 추가해봐도 마찬가지 ..
이유는 "Row was updated or deleted by another transaction" 오류는 동시에 여러 트랜잭션에서 동일한 데이터를 수정하거나 삭제할 때 발생할 수 있습니다. 즉, 한 트랜잭션에서 데이터를 수정하는 동안 다른 트랜잭션에서 동일한 데이터를 수정하거나 삭제했기 때문에 충돌이 발생하는 것
이라는데 잘 모르겠습니다 뭐가 문제일까요 .
답변 2
0
안녕하세요. blossom_mind님
도움을 드리고 싶지만 질문 내용만으로는 답변을 드리기 어렵습니다.
실제 동작하는 전체 프로젝트를 ZIP파일로 압축해서 구글 드라이브로 공유해서 링크를 남겨주세요.
구글 드라이브 업로드 방법은 다음을 참고해주세요.
주의: 업로드시 링크에 있는 권한 문제 꼭 확인해주세요
추가로 다음 내용도 코멘트 부탁드립니다.
1. 문제 영역을 실행할 수 있는 방법
2. 문제가 어떻게 나타나는지에 대한 상세한 설명 (오류 화면, 오류 로그 포함)
링크: 공식 서포터즈
링크: 자주하는 질문
감사합니다.
0
안녕하세요, 인프런 AI 인턴입니다.
blossom_mind님께서는 JPA와 Querydsl을 사용하여 Board 엔티티에 대한 트랜잭션 오류를 경험하고 계신 것 같습니다. 여기에 대해서 관련된 정보를 제공하겠습니다.
문제의 원인
“Row was updated or deleted by another transaction”라는 오류는 일반적으로 같은 엔티티에 대한 여러 트랜잭션이 동시에 발생할 때 일어나는 경우입니다. 이 오류는 @Version을 사용한 낙관적 락(Optimistic Locking) 전략으로도 발생할 수 있습니다.
해결 방법
낙관적 락을 사용해 충돌 감지: 이미
@Version필드를 추가한 것으로 보입니다. 이 필드는 물리적으로 존재하지 않는version열을 사용하여 JPA가 상호간의 충돌을 감지할 수 있게 합니다. 문제는@Version을 사용하더라도 정확히 설정되지 않거나, 데이터베이스의 충돌을 해결하지 못하는 경우에 이런 오류가 발생할 수 있습니다.비관적 락 사용:
@Lock(LockModeType.PESSIMISTIC_WRITE)를 시도하셨다고 했는데, 실제로 다른 트랜잭션과의 충돌을 방지하고 있는지를 확인할 필요가 있습니다.@Lock어노테이션을 사용하는 방법 외에도, 데이터베이스 레벨에서 트랜잭션 격리 수준을 높이는 방법도 고려해보세요.동시성 제어: 동일한
BoardID에 대해 많은 동시성 업데이트가 발생할 가능성이 있습니다. 이를 회피하기 위해 비즈니스 케이스에 따라서는 업데이트 될 가능성이 적은 엔티티를 먼저 갱신하고, 자주 변경되는 엔티티의 순서를 재조정하거나 구조적으로 격리할 수 있습니다.
참고 링크
현재 시도하신 모든 방법이 잘 작동하고 있는지, 트랜잭션 로직을 점검하고 엔티티 관리 방식에 대해 더 검토할 필요가 있습니다. 또한, 데이터베이스의 transaction isolation level 설정도 한 번 검토해 보는 것이 좋습니다.
저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.
OrderServiceTest 상문주문 테스트 시 update 쿼리 문의
0
27
1
sdk 설정 오류
0
64
2
오탈자 - @Transactional
0
62
1
src/test/resources 테스트 경로 문제
0
55
1
상품 등록후 H2 db 출력 순서 바꿀 수 있나요?
0
70
1
MemberRepositoryTest 실행오류
0
89
1
boot 4.x >>> trasasction rolled back log & p6spy(영한님, 수업 자료 업데이트 해주시면 감사하겠습니다!!)
1
195
2
강의 마지막 QueryDSL 사용 부분 질문있습니다
1
153
2
클라이언트에서 isbn과 author 수정 요청을 한 경우에 대해 질문드립니다.
0
56
1
도메인 모델 패턴 vs 트랜잭션 스크립트 패턴
0
79
1
기본 생성자
0
66
1
h2 DB 연결시 jdbc url 변경 이유가 궁금합니다.
0
106
1
멤버서비스테스트 부분에서 막힙니다.
0
173
4
실무에서도 EntityManager를 이용해서 많이 작업하는 편일까요?
0
123
1
초반에 h2 다운로드 과정 꼭 필요한가요?
0
128
2
자신 필드에도 get으로 접근하는 이유가 있을까요?
0
121
1
24분 27초 연관관계 편의 메서드 위치
0
118
1
단건 주문만 가능하게 한건 의도한 부분이신가요?
0
114
2
빌드 툴, Gradle
0
63
1
h2연결은 된 것 같은데 엔티티 테이블까지 작성 후 확인해보아도 테이블이 안보입니다
0
81
2
Repository에서 EntityManager 주입 방식 차이
0
97
1
롬복과 사용자 정의 setter 메서드
0
77
1
주문 목록 조회 fetch join 질문드립니다
0
90
1
dirty checking 질문드립니다.
0
86
1





