Spring

- CascadeType.REMOVE vs @OnDelete

상황은 이러하다.

카드 엔티티는 1:N 양방향관계로 댓글set과 할일set을 가지고 있었다.

카드 삭제 시 연관된 댓글들과 할일들을 모두 삭제하려고 카드 엔티티의 댓글set, 할일 set 필드에 cascade = CascadeType.REMOVE 로 선언을 해준 후 카드를 삭제하려니 

'JDBC exception executing SQL [delete from card where id=?] [Cannot delete or update a parent row: a foreign key constraint fails'

이런 에러가 뜨는 것이 아닌가.

이 말은 외래키의 주인(댓글, 할일)이 참조하고 있는 카드 엔티티를 삭제하려고 하니, 외래키 제약조건이 위배되어 부모 엔티티를 삭제할 수 없다는 의미이다.

아니 cascade 했잖아 근데 왜 뜨는데?? 한참을 고민하다가 다른 방법으로 일단 돌아가게는 했다

그 방법은 바로 @OnDelete이다.

 

@JoinColumn(name = "card_id", nullable = false)
@OnDelete(action = OnDeleteAction.CASCADE)
@ManyToOne(fetch = FetchType.LAZY, optional = false)
private Card card;

이 어노테이션은 자식 엔티티에 선언해주면 되는데 이렇게 하니까 해결되었다.

 

그럼 조금은 생소한 @OnDelete는 무엇이며 왜 cascade 옵션이 적용되지 않았던 것일까??

 

1. CascadeType.REMOVE 와 @OnDelete의 차이

일단 CascadeType.REMOVE는 부모 엔티티가 삭제될 때 부모 엔티티의 삭제되는 상태를 자식 엔티티에게 전파시켜 자동으로 삭제가 되게 해준다. 여기서 핵심은 'JPA 레벨에서 삭제를 진행한다.' 라는 것이다. 

 

@OnDelete 는 데이터베이스 ddl 자체에 ON DELETE 옵션을 추가하여 테이블 레벨에서 card가 삭제되면 자식 엔티티들도 삭제되게 하는 것이다.

 

2. 그럼 왜 내 Cascade는 먹히지 않았을까?

일단 CascadeType.REMOVE가 JPA 레벨에서 동작하는 것을 잘 알지 못한 채, 삭제를 진행할 때 QueryDSL로 디비에 직접 쿼리를 날려서 삭제를 시켜주었다. 만약 CardRepository.delete(지우려는 카드 엔티티) 를 사용하여 삭제를 진행했다면. Jpa가 알아서 자식 엔티티도 삭제시켜줬을 텐데 계속 디비에 직접 쿼리를 날리니 될 리가 없는 것이다.

@OnDelete를 사용해주면 테이블 자체 스키마가 변경되어서 무조건 삭제가 되었던 것이다.

 

추후 다른 팀원들과의 통일성을 위해 JPARepository의 delete 메소드를 사용해주긴 했지만 연관관계에 있는 엔티티를 같이 삭제하는 또 다른 방법과 디비에 대한 이해력이 늘어나는 좋은 트러블슈티이었다.

 

 

알고리즘

- BOJ : 달팽이 3(Gold 3, 수학, 많은 분기 처리)

느낀 점

새해가 밝았다. 취업은 바로 힘들겠지만 화이팅이다..

 

'TIL' 카테고리의 다른 글

[24.01.04]  (1) 2024.01.05
[24.01.03]  (2) 2024.01.04
[23.12.28]  (0) 2023.12.29
[23.12.27]  (2) 2023.12.27
[23.12.26]  (0) 2023.12.26

Spring

- Limit, Offset, FetchOne, FetchFirst

1. 카드에 속해있는 유저인지를 검증하기 위해 카드-유저 중간 테이블에 해당하는 카드 id - 유저 id 쌍이 존재하는지 확인하는 절차가 필요했다.

 

2. 그러나 쿼리 메소드를 짤 때 그냥 where - fetch 이런 식으로 쿼리를 짠다면 테이블 전체를 조회하게 된다.

내가 원하는 것은 딱 한 row를 찾으면 검증 끝인데 말이다.

 

3. 찾아보니 limit 를 잘만 사용하면 하나만 찾고 끝내는 것이 가능하다고 한다.

SELECT * FROM user LIMIT 0, 5 (혹은 그냥 5) 하면 테이블을 스캔하면서 해당하는 레코드가 5개가 되는 순간 스캔을 종료한다.

그러나 SELECT * FROM user ORDER BY score LIMIT 0,5 를 하면 정렬을 한 후부터 5개를 가져오기 때문에 전체 테이블을 스캔한다고 한다.

+SELECT * FROM user LIMIT 100000, 5 이렇게 사용하면 100005개의 데이터를 조회한 후 100001~100005번쨰 데이터를 조회하기 때문에 성능이 좋지 않다.

 

4. 쿼리DSL에서는 limit, fetchone, fetchfirst 메소드를 제공한다.

limit는 데이터 개수 제한을 하는 법이고

fetchone은 하나의 데이터만을 가져오려하는데 만약에 조건에 해당하는 데이터가 2개 이상이면 오류를 발생시킨다.

fetchfirst는 limit(1).fetchOne 으로 구현되어 있는데 데이터가 여러개여도 하나만 가져오는 내가 원하는 메소드였다.

 

5. limit와 querydsl과 친해지는 시간이었다.

 

 

알고리즘

- 프로그래머스 :  연속된 부분 수열의 합(Level 2, 슬라이딩 윈도우)

느낀 점

너무 춥다. 

 

'TIL' 카테고리의 다른 글

[24.01.03]  (2) 2024.01.04
[24.01.02]  (0) 2024.01.02
[23.12.27]  (2) 2023.12.27
[23.12.26]  (0) 2023.12.26
[23.12.21]  (1) 2023.12.22

Spring

- 심화 프로젝트 S.A 과정 이모저모

내 생애 첫 QueryDSL 뀨 >3<

JPAExpressions로 in 절을 사용하였고

user와 cardUser는 Quser.user, QcardUser.cardUser 를 static import한 것이다.

그리고 cardUserPK는 @EmbeddedId 로 설정된 복합키를 의미한다.

알고리즘

 

느낀 점

너무 춥다. 

'TIL' 카테고리의 다른 글

[24.01.02]  (0) 2024.01.02
[23.12.28]  (0) 2023.12.29
[23.12.26]  (0) 2023.12.26
[23.12.21]  (1) 2023.12.22
[23.12.20]  (1) 2023.12.20

Spring

- 심화 프로젝트 S.A 과정 이모저모

POST vs PATCH vs PUT

상황은 이러했다. 

카드는 작업자로 유저 여러명을 지정할 수 있고 유저는 여러 카드에 참여할 수 있으므로 유저와 카드는 다대다 관계이다.

그말인 즉슨 카드는 필드값 memberList로 유저를 여러명 저장하고 있다.

그러면 memberList에 유저 한명을 추가할 때에는 어떤 http method를 써야할 까에 대한 고민을 했다.

1. PATCH vs PUT. 

일단 이건 간단한 것이 카드에 대한 모든 필드값을 초기화하려는 것이 아니고 memberList만 바꾸려는 것이기에 PATCH 승리

2. PATCH vs POST

가장 고민되는 부분이 여기였는데 POST라기엔 카드라는 새로운 엔티티를 만드는 것이 아니라서 좀 고민이 되었다.

고민 끝에 다대다 관계로 인해 만들어진 카드-유저 테이블에 새로운 row를 추가하는 것이니.

POST의 목적인 새로운 리소스 할당이라는 조건에 부합한다고 생각하여 POST method로 결정해주었다.

 

AOP는 프로그래밍 방식(like OOP)

팀장님께서 보드의 멤버임을 검증해야하는 API가 많아지자 AOP를 적용하자고 하셨는데 나는 당연히 컨트롤러에 씌우시자는 것이지요? 여쭤봤더니 AOP는 컨트롤러에 국한된 공식이 아닌 프로그래밍 방식이라고 말씀해주셨다.

공통 관심사를 모듈화하여 OOP를 더 잘 지킬 수 있게 하는 것이 AOP며 대표적인 예로 메소드 단위(작은)에 쓰이는 @Transaction도 하나의 AOP이다.

 

알고리즘

- 프로그래머스 :  프로세스(Level 2, 큐)

느낀 점

너무 춥다. 

'TIL' 카테고리의 다른 글

[23.12.28]  (0) 2023.12.29
[23.12.27]  (2) 2023.12.27
[23.12.21]  (1) 2023.12.22
[23.12.20]  (1) 2023.12.20
[23.12.19]  (1) 2023.12.19

Spring

- JDBC

JDBC는 DB의 출현 이후 Java 어플리케이션과 DB의 연동을 돕기 위해 만들어진 기술이다.

직접 쿼리를 날릴 때에 같은 쿼리라도 데이터베이스 종류마다 다른 결과를 나타낼 수 있다.

JDBC API라는 추상화 객체는 개발자가 DB에 독립적인 개발환경을 가질 수 있도록 해주려 출현했으며 JDBC Driver Manager, DB마다 상이한 JDBC Driver가 이를 구현한다.

JDBC Driver는 런타임 시점에 connection, statement, resultset을 생성하여 DB와 소통한다.

이 세가지 요소를 사용 후에는 close()를 꼭 호출하여 자원을 해제시켜주어야 한다.

 

그러나 JDBC에도 단점이 존재하는데, 데이터베이스와 바로 맞닿아 있기 때문에 Connection 자원 관리를 엄격하게 해주어야 하고,

자원 관리와 쿼리 요청에 있어서 중복이 되는 코드가 많이 발생하였다.

이를 보완하고자 나온 개념이 Persistence Framework이다.

- Persistence Framework

여기서 persistence란 의미 그대로 영속성이고 이는 프로그램이 종료되도 데이터가 사라지지 않음을 의미한다.

영속성 프레임워크는 데이터베이스와의 연동과 안정적인 구동을 보장해주는 프레임워크이다.

 

장점으로는1. JDBC와 동일하게 DBMS에 대한 종속성이 줄어든다.2. 데이터에 대한 객체를 별도로 관리하기 때문에 재사용이 가능하다.(JDBC의 중복 코드 해결)3. 객체지향적인 코드를 지원하고 connection 자원 관리 등 부수적인 코드를 처리해준다.

 

영속성 프레임워크는 크게 두 종류 SQL Mapper(Query Mapper)와 ORM으로 나누어진다.

 

- SQL Mapper

SQL Mapper의 mapper란 의미는 SQL와 객체를 Mapping 시켜준다는 의미이며, 약속된 형식으로 작성된 쿼리문과 코드를 JDBC 코드로 바꾸어주는 역할을 수행한다.

대표주자로서는 JDBC Template와 MyBatis가 있다.

 

JDBC Template는 쿼리 수행결과 - 객체 필드의 매핑, RowMapper로 응답필드 매핑코드 재사용, connection 자원관리 등등의 장점이 있지만 결과값을 객체에 매핑하는 데에 많은 코드가 필요하다는 단점이 있다.

 

MyBatis는 전자정부 표준이 될 정도로 SQL Mapper의 대표주자이며 XML에 쿼리를 작성하고 JDBC 코드로 바꾸어 실행시키는 역할을

한다.

쿼리와 실행코드가 분리되어 있어 가독성이 좋고 JDBC의 대부분의 기능을 수행할 수 있다는 장점이 존재한다.

그러나 XML의 특성 상 빌드 과정에서 오류가 발생하지 않고, 객체보다는 테이블 지향적이며 DB에 종속적이라는 점을 보안하기 위해 ORM이라는 개념이 제시되었다.

 

- ORM

ORM은 Object Relational Mapping 의 약자로. 아예 객체와 DB의 테이블을 매핑시키는 객체지향적인 영속성 프레임워크 개념이다.

객체와 매핑되어 더 직관적이며 쿼리문을 따로 작성할 필요가 없어 개발자로 하여금 로직에 집중할 수 있도록 도와준다.

 

JPA(Java Persistence API) 는 Java ORM 표준으로 JPA는 인터페이스이며, 이를 구현한 구현체가 Hibernate이다.

Hibernate가 내부적으로 쿼리를 생성해주고 JDBC API를 호출해 준다.

그러나 아무리 편하다고 해도 개발자가 직접 쿼리를 수정해야하는 경우도 있기 때문에 Spring Data JPA에서는 JPA를 한단계 더 추상화한 Repository 개념을 제시하며 여러가지 옵션을 줄 수 있다.

알고리즘

- 프로그래머스 :  우박수열 정적분(Level 2, 누적합)

느낀 점

너무 춥다. 

'TIL' 카테고리의 다른 글

[23.12.27]  (2) 2023.12.27
[23.12.26]  (0) 2023.12.26
[23.12.20]  (1) 2023.12.20
[23.12.19]  (1) 2023.12.19
[23.12.18]  (0) 2023.12.18

Spring

- 순환 참조 해결을 위한 고민의 타임라인

1. 하나의 Repository에는 여러 서비스가 접근하지 않는 것이 좋다.(관련된 도메인의 서비스만 접근하고 다른 서비스에서 해당 Repository에 접근하기 위해서는 서비스 계층을 거치게끔 하라.)

 

2. PostService와 CommentService의 순환 참조 발생

(PostService 입장 - 포스트가 삭제될 때 관련된 모든 댓글을 삭제하고 싶다.)

(CommentService 입장 - "/api/{postId}" 라는 uri로 댓글 생성 요청이 오는데 이 때 오는 postId에 대한 검증 + postId로 받아온 post로 comment 엔티티를 생성하고 싶다.)

 

2-1. 사실 여기서 아주 쉽게 해결이 가능했다. -> CommentService에서 PostService 참조 안하면 그만. 애초에 프론트에서 없는 postId로 요청이 올 리도 없고 + comment 엔티티에도 postId만 존재하므로 post 자체가 필요가 없음.

 

3. 과연 1. 이 진리인가?

어느 고수분께서는 1. 이 계층적 구조를 명확하고 안전하게 하는 데에는 좋다고 하셨지만, 나와 같이 순환 참조가 발생할 위험이 있으므로 다른 도메인 Repository를 참조하는 것도 취향 차이지 나쁜 건 아니다. -> 이것도 아주 간편한 해결책 그냥 Repository 직빵으로 참조하면 끝

 

4. cascade란 옵션을 사용해라!

나는 기존에 PostService에서 Post를 찾아오고 이 Post에 대한 모든 댓글들을 찾은 후 삭제하는 방식을 채택했다.

그러나 연관관계 설정하는 @OneToMany에 cascade의 remove 옵션을 걸어주면 Post 삭제 시 연관관계를 맺은 모든 엔티티가 삭제된다.

 

5. 엇 근데 사실 현업에서는....

현업에서는 데이터를 아예 삭제하는 경우는 없다고 한다. -> 이것도 뭐 상황에 따라 다르지만..

데이터를 백업해놓거나 삭제처리에 대한 필드값을 두고 업데이트만 해준다고 한다.

여기서 Hard Delete와 Soft Delete라는 개념도 나오는데 완전히 삭제냐 아니냐의 개념이다.

 

6. 그럼 어떡하라고 ㅠㅠ

백업은 생각할 양이 너무 방대해지는 것 같고 필드값만 고려해보자면

엔티티에 @SQLDelete 라는 어노테이션을 통해 Delete 쿼리가 발생할 때 자신이 정의한 특정 쿼리로 바꿔서 실행시켜준다고 한다. -> 여기서 delete(entity)가 아닌 삭제처리 필드값을 set하게끔

여기에 @Where 이라는 어노테이션으로 삭제처리가 되지 않은 엔티티만 조회할 수 있게 해주면 된다고 한다.

 

이러면 쿼리를 직접 쏘는 것이기 때문에 CommentRepository에 접근할 필요가 없다. -> 이럴 거면 양방향 연관관계 끊고 직접 쿼리?

그리고 @Where는 내가 삭제처리한 데이터를 조회조차 할 수 없기 때문에 현업에서는 사용이 힘들다고 한다.

 

그래서 만약에 삭제된 데이터를 조회해야 한다면 @Where 보다는 1.JPQL을 이용하거나 어플리케이션 레벨에서 제어해주기

2. @FilterDef, @Filter를 활용하여 특정 엔티티 매니저 세션에 대해서만 필터를 정의하기 가 존재한다.

 

7.  일단 기능 구현이 우선!

이런 고민들 끝에 3으로 돌아가 일단 PostService가 CommentRepository를 참조하게 했다. 일단 모든 기능 구현을 마친 후에 고민해보고 글로 남기면 매우 좋을 것 같다.

 

알고리즘

- 프로그래머스 :  큰 수 만들기(Level 2, 그리디)

느낀 점

너무 춥다. 

'TIL' 카테고리의 다른 글

[23.12.26]  (0) 2023.12.26
[23.12.21]  (1) 2023.12.22
[23.12.19]  (1) 2023.12.19
[23.12.18]  (0) 2023.12.18
[23.12.15]  (1) 2023.12.15

Spring

- record class

record class는 JDK16부터 정식으로 지원된 불변 객체를 쉽게 생성할 수 있는 클래스이다.

record는 생성자, getter, hashcode, equals, tostring 을 제공해준다.

class가 final이기 때문에 subclass를 생성할 수 없다.

모든 필드는 불변이기 때문에 setter를 제공하지 않는다.

불변적인 데이터를 저장할 때 적합하므로 DTO에 사용하면 좋다.

 

- 이모저모

변동 가능성이 있고 더 작은 범위의 모듈인 DTO가 더 중요하고 변동 가능성이 없어야 하는 모듈인 엔티티에 직접 접근하는 것은 좋지 않다.

예를들어 Entity의 몇가지 필드 값을 바꿔야할 때 modifyEntity(인자....) 인자에 DTO를 넣기보다는 DTO에서 데이터를 꺼내서 넣어주자.

 

한 레포지토리에 대해 여러 서비스 혹은 컨트롤러가 접근하지 않도록 제한한다.

레포지토리에 접근하는 도메인을 하나로 제한하면 계층적 구조는 명확하여 좋겠지만,

서비스가 물고 물리는 순환참조가 발생할 위험성이 있다. -> 취향 차이다?

알고리즘

- 프로그래머스 :  게임 맵 최단거리(Level 2, BFS)

추가로 시저 암호라는 레벨 1짜리 푸는 게 있는데 ABD를 4로 밀면 DEG 이렇게 출력하라는 것이었다.

z가 넘어갈 때 -26으로 빼주려고 했는데 계속 틀렸다.

n만큼 민다고 했을 때  'z'  + n  하고 'z' 보다 크면 -26을 해줬다.

알고보니 아스키코드가 127까지 있는데   n만큼 더할 때 오버플로우가 발생해서 오류가 낫었다.

느낀 점

너무 춥다. 오른손 말초신경까지 온기가 잘 전해지지 않는다.

'TIL' 카테고리의 다른 글

[23.12.21]  (1) 2023.12.22
[23.12.20]  (1) 2023.12.20
[23.12.18]  (0) 2023.12.18
[23.12.15]  (1) 2023.12.15
[23.12.14]  (0) 2023.12.15

Spring

- DAO vs DTO vs VO 

DAO(Data Access Object)

DAO는 DB 데이터에 접근하기 위한 객체이다.

직접 DB에 접근하여 데이터를 삽입, 삭제 등등을 수행한다.

주로 Service 계층에서 DB와 소통하며 Repository 패키지가 DAO의 한 예시이다

 

DTO(Data Transfer Object)

DTO는 계층 간 데이터 교환을 하기 위한 객체로 따로 로직을 가지지 않는 순수한 데이터 객체(POJO)이다.

DB에 저장된 엔티티를 서비스 계층이나 컨트롤러에 보낼때 DTO 형태로 변환한 후 보내준다.

Getter/Setter 메서드만을 가진 것이 특징이다.

 

VO(Value object)

VO는 DTO와 달리 Read-only만 가능한 객체이다.

Getter만 가지고 있다.

DTO는 인스턴스, VO는 리터럴 값의 개념이다.

VO는 값 자체에 의미가 있고 DTO는 전달될 데이터를 보존해야하는 것이 핵심 역할이다

값 자체에 엄청난 의미가 있기 때문에 가변성이 생길 수 있는 setter는 없더라도 equals, hashCode 등의 데이터 검증 메소드을

추가하는 경우도 있다.

 

알고리즘

- 프로그래머스 :  빛의 경로 사이클(Level 2, 구현)

느낀 점

너무 춥다. 오른손 말초신경까지 온기가 잘 전해지지 않는다.

'TIL' 카테고리의 다른 글

[23.12.20]  (1) 2023.12.20
[23.12.19]  (1) 2023.12.19
[23.12.15]  (1) 2023.12.15
[23.12.14]  (0) 2023.12.15
[23.12.13]  (0) 2023.12.13

+ Recent posts