작성
·
59
0
[질문 템플릿]
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
안녕하세요, 인프런 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
어노테이션을 사용하는 방법 외에도, 데이터베이스 레벨에서 트랜잭션 격리 수준을 높이는 방법도 고려해보세요.
동시성 제어: 동일한 Board
ID에 대해 많은 동시성 업데이트가 발생할 가능성이 있습니다. 이를 회피하기 위해 비즈니스 케이스에 따라서는 업데이트 될 가능성이 적은 엔티티를 먼저 갱신하고, 자주 변경되는 엔티티의 순서를 재조정하거나 구조적으로 격리할 수 있습니다.
현재 시도하신 모든 방법이 잘 작동하고 있는지, 트랜잭션 로직을 점검하고 엔티티 관리 방식에 대해 더 검토할 필요가 있습니다. 또한, 데이터베이스의 transaction isolation level 설정도 한 번 검토해 보는 것이 좋습니다.
저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.
Spring Data JPA 잠금 및 동시성 제어
404 뜹니다 ... 저 방법을 다 써봤는데 안되는건 왜일까요..