Skip to content

Conversation

@Soundbar91
Copy link

질문

게시판 삽입 예외 발생 후 식별자 증가

image
6번 게시물을 삽입하고 7번 게시물을 삽입할 때 예외를 발생시켰습니다.
예외를 발생시켰기 때문에 롤백돼서 그다음으로 들어가는 게시물의 식별자는 7번일거라고 생각했습니다.
하지만 데이터베이스를 확인하면 7번이 아닌 8번으로 들어가 있습니다.
현재 식별자를 애플리케이션에서 증가시키는 전략이기 때문에 이와 같은 현상이 나타나는 건지, 아니면 다른 이유가 있는지 궁금합니다 !

게시판, 회원 삭제 예외

image
JDBC 코드에서는 예외를 정상적으로 잡았는데, JPA로 바꾸니 예외를 잡지 못하는 현상이 나타났습니다.

@Override
    public void deleteById(Long id) {
        try {
            entityManager.remove(findById(id));
        } catch (Exception e) {
            throw new ApplicationException(MEMBER_REFERENCE);
        }
    }

현재는 임시(?)로 Exception e로 작성했는데 역시나 예외를 잡지 못하고 있으며, PersistenceException와 DataIntegrityViolationException로 설정해도 아예 못 잡고 있습니다.
에러 메시지에는 Controller만 찍혀있어서 다른 곳에서 예외가 발생한건지, 아니면 놓치고 간 부분이 있는지 조언을 구하고 싶습니다.

EntityTransaction

EntityManager가 수행하는 로직은 트랜잭션 내부에서 실행되야 한다를 만족하기 위해 EntityTransaction에서 begin() 메소드를 통해 트랜잭션을 얻고 시작하는 코드를 작성했습니다.

@Override
    public Article insert(Article article) {
        try {
            entityTransaction.begin();
            entityManager.persist(article);
            return entityManager.find(Article.class, article.getId());
        } catch (PersistenceException e) {
            entityTransaction.rollback();
            throw new ApplicationException(FK_NOT_EXISTS);
        }
    }

실습과제를 진행하던 중 Service에서 @Transactional으로 트랜잭션을 여는데 begin() 메소드로 또 트랜잭션을 열어야 하나?라는 생각이 들었습니다.
그래서 EntityTransaction와 관련있는 코드는 모두 삭제하고 진행을 했습니다.
이렇게 했을 때 향후에 다른 문제가 발생하는지 궁금합니다 !

Copy link

@duehee duehee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

다양하게 잘 적용해보셨네요~
다음 실습도 화이팅해주세요!

runtimeOnly 'com.mysql:mysql-connector-j'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

개인적인 의견이긴 한데, 같은 dependencies들은 묶고, 나머지는 띄어쓰기 하는 게 조금 더 보기 좋더라구요

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

따로 생각을 안 하고 추가만 했었는데, 말씀해 주신 것처럼 가독성 좋게 바꿔보겠습니다.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

굿 반영 잘 되었네요

String description
) {

public ArticleCreateRequest {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

request에서 예외를 던지는 게 맞을까요? DTO는 데이터 전달 객체이고, 비즈니스 로직 및 예외처리는 Service단에서 해결하는 게 맞다고 생각이 들어요

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DTO와 관련이 없는 책임을 준거 같습니다. 반영하겠습니다 !


import java.time.LocalDateTime;

@Getter
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Getter 사용 및 도메인 내에서의 Setter 사용에 대해 잘 생각해보셨군요!

article.getId()
);
return findById(article.getId());
} catch (DataIntegrityViolationException e) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

다양한 예외처리 굿👍

@Repository
public class ArticleRepositoryJpa implements ArticleRepository {

@PersistenceContext
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

배우신 내용을 잘 활용하셨네용

Copy link

@duehee duehee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이번 주도 고생하셨습니당
코멘트 확인해주세요~

runtimeOnly 'com.mysql:mysql-connector-j'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

굿 반영 잘 되었네요

@GetMapping("/articles")
public ResponseEntity<List<ArticleResponse>> getArticles(
@RequestParam Long boardId
@RequestParam Long boardId
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

들여쓰신 이유가 있나용?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

인텔리제이 코드 정렬 기능(?)을 사용하니 들여쓰기가 들어간거 같습니다.

Long boardId,
String title,
String description
@NotNull(message = "회원 아이디는 필수로 입력해야 합니다.") Long authorId,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

어노테이션의 적극적인 사용..!

return Member.builder()
.name(this.name)
.email(this.email)
.password(this.password).build();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 친구는 혼자 붙어있네요

Suggested change
.password(this.password).build();
.password(this.password)
.build();

private Long boardId;

@NotNull
@ManyToOne(fetch = LAZY)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

static import를 사용하셨네요!😮

@AllArgsConstructor
public class Member {

@Id @GeneratedValue(strategy = IDENTITY)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저는 어노테이션 떨어트려놓는게 더 보기 좋더라구요 ㅎ.ㅎ.....

Suggested change
@Id @GeneratedValue(strategy = IDENTITY)
@Id
@GeneratedValue(strategy = IDENTITY)

@Repository
import static com.example.demo.exception.ErrorCode.*;

public class ArticleRepositoryJdbc implements ArticleRepository {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JPA를 사용하시면서 위에서 메소드를 다 삭제하셨는데, 아래에 있는 RepoJDBC, RepoJPA를 유지하신 이유가 있나요?
아래 Repository들이 그런 내용으로 서술되어 있어서 여쭤봅니다

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

처음에는 주석처리해서 유지하지 않는 방향으로 갔었으나, 나중에 깃헙에서 코드를 볼 수 있는 상황이 있을 경우 보기 편하게 하기 위해서 주석처리를 지우고 유지하자고 판단했습니다.

properties:
hibernate:
show_sql: true
format_sql: true
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

마지막에 경고 표시가 떠있는데, 이거 처리하는 방법이 있답니다

오.. 어디서 본 거 같은 블로그

Copy link

@duehee duehee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생하셨습니다~
이제 슬슬 파일이 쌓이기 시작하다보니 조금씩 헷갈리네요😵
로그인 잘 구현해주셨고, 수업도 화이팅하세요!

Member member = memberRepository.findById(article.getAuthorId());
Board board = boardRepository.findById(article.getBoardId());
return ArticleResponse.of(article, member, board);
public ArticleResponse getByArticleId(Long id) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ㅎㅎㅎㅎ 의견 다 반영해주셨네요~!
메소드 명이 더 명확해져서 좋아요

.orElseThrow(() -> new ApplicationException(BOARD_NOT_FOUND));

boardRepository.delete(board);
boardRepository.flush();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

어느 부분에는 flush()를 선언하고, 어느 부분에서는 사용하지 않았는데 이유가 있을까요?
위의 게시물에는 삭제하는 부분엔 flush가 없는데, 아래의 게시판을 삭제하는 경우에는 적용하고 계시길래 말씀 드려봤어요

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

게시판를 삭제할 때 작성된 글이 있는 경우 커스텀으로 정의한 예외를 발생 시켜야하는데, flush() 메소드를 통해 강제로 flush을 발생시키기 위해 게시판에만 적용했었습니다. flush() 메소드가 있는 메소드들은 다 예외 처리때문에 작성했었습니다.

@@ -0,0 +1,19 @@
<!DOCTYPE html>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

웹까지..! 대단하시네용

Comment on lines +13 to +22
@Service
public class LoginService {

private final MemberRepository memberRepository;
private final PasswordEncoder passwordEncoder;

public LoginService(MemberRepository memberRepository, PasswordEncoder passwordEncoder) {
this.memberRepository = memberRepository;
this.passwordEncoder = passwordEncoder;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@Service
public class LoginService {
private final MemberRepository memberRepository;
private final PasswordEncoder passwordEncoder;
public LoginService(MemberRepository memberRepository, PasswordEncoder passwordEncoder) {
this.memberRepository = memberRepository;
this.passwordEncoder = passwordEncoder;
}
@Service
@RequiredArgsConstructor
public class LoginService {
private final MemberRepository memberRepository;
private final PasswordEncoder passwordEncoder;

소소한 팁인데, @RequiredArgsConstructor를 사용하면 final( + NotNull) 선언한 변수들에 대해서 생성자 주입을 해준답니다.
코드 짤 때 더 간결해지고, 좋아요!
블로그 참고

Member member = memberRepository.findByEmail(loginRequest.email())
.orElseThrow(() -> new ApplicationException(MEMBER_NOT_FOUND));

if (!passwordEncoder.matches(loginRequest.password(), member.getPassword())) return null;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

맞지 않는 경우에 null을 반환하는 이유가 있나요?
예외 처리를 하는 것은 어떨까요?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

비밀번호가 틀린 경우 다시 로그인 화면으로 리다이렉트를 하기 위해 null로 처리했습니다.
처음에는 예외 처리 방향으로 갔다가, 리다이렉트를 어떻게 하지라는 생각에 null로 변경했었습니다. 너무 쉬운 방향으로 갔던 것 같습니다.
예외 처리하는 방식으로 해보겠습니다 !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants