작성
·
52
0
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]
요런식으로 뜨는데
해본 방법
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"
오류는 동시에 여러 트랜잭션에서 동일한 데이터를 수정하거나 삭제할 때 발생할 수 있습니다. 즉, 한 트랜잭션에서 데이터를 수정하는 동안 다른 트랜잭션에서 동일한 데이터를 수정하거나 삭제했기 때문에 충돌이 발생하는 것
이라는데 잘 모르겠습니다 뭐가 문제일까요 .
public interface BoardRepository extends JpaRepository<Board, Long>, QuerydslPredicateExecutor<Board> {
Optional<Board> findById(Long boardId);
}
querydsl과 jpa같이 사용하고싶어서 셋팅한거 입니다.
답변 1
0
안녕하세요, 인프런 AI 인턴입니다.
blossom_mind님, 작성하신 코드에서 발생하는 Row was updated or deleted by another transaction
예외는 데이터 동시 수정 문제로 인해 발생할 수 있습니다. 이 예외는 보통 두 가지 주요 이유로 발생할 수 있습니다.
동시성 제어 문제: 동시에 여러 트랜잭션이 동일한 엔티티 데이터를 수정하려고 할 때 발생합니다. 이를 해결하기 위해서는 다음과 같은 방법을 고려해 볼 수 있습니다.
@Version
annotation을 사용하여 버전 관리를 통해 동시성 문제를 해결할 수 있습니다. 이미 적용하신 것처럼 Board
엔티티에 @Version
을 사용하셨다면, 데이터가 올바르게 업데이트되고 있는지도 확인해 보십시오.@Lock(LockModeType.PESSIMISTIC_WRITE)
어노테이션을 사용하여 특정 트랜잭션이 완료될 때까지 다른 트랜잭션이 해당 데이터를 수정하지 못하게 할 수 있습니다. 하지만, 이 방법도 시도하셨던 것처럼 예외를 해결하지 못했다면, 다른 부분에서 동시성 제어가 필요할 가능성이 있습니다.ID 충돌: IDENTITY
전략을 사용하는 경우 저장할 때마다 새로운 ID가 생성되어야 하지만, 특정한 상황에서 ID가 충돌할 수 있습니다. 특히 수동으로 ID 값을 설정하는 부분이 있으니, 생성 시에 대한 로직도 유의하셔야 합니다.
추천하는 해결 방법:
- 데이터 수동 설정 점검: createBoard
메서드를 통해 직접 ID를 설정하지 않고, JPA에 ID를 자동 생성하도록 맡기는 것이 안전합니다. 이후 persist
메소드를 사용할 때 생성된 ID를 확인하시고 사용해보세요.
- Logging 및 Debugging: 예외가 발생하는 시점에 해당 데이터에 대한 로그를 남겨 어느 부분에서 충돌이 발생하는지 구체적으로 알아보는 것도 도움이 됩니다.
위의 방법들도 확인하고, 트랜잭션 관리 방식에서도 동시성 문제를 확인할 수 있도록 주의하시면 좋습니다. 추가적인 동시 트랜잭션이 없도록 서비스 구조나 메소드 접근 제어도 점검해보세요.
문제가 해결되지 않는다면, 조금 더 구체적인 로깅이나 테스트 케이스를 통해 어떤 상황에서 예외가 발생하는지 확인해 보시는 것도 좋습니다.
문서: Spring Data JPA - Transactionality
도움이 되시길 바랍니다.