기록은 기억의 연장선

더 많은 것을 기억하기 위해 기록합니다


  • Home

  • Tags

  • Categories

  • Archives

  • Search

[jpa] JPA 예외

Posted on 2019-02-15 | Edited on 2020-11-02 | In jpa | Comments:

JPA의 표준 예외들은 javax.persistence.PersistenceException의 자식 클래스이다.
그리고 이 클래스는 RuntimeException의 자식이다. 즉, JPA의 예외는 모두 언체크 예외이다.

JPA 표준 예외

JPA 표준 예외는 크게 아래의 2가지로 나뉜다.
난 실행단계에서 아래 2개를 어떻게 구분해야할지 잘 모르겠다

트랜잭션 롤백을 표시하는 예외

심각한 예외이므로 복구해선 안되는 예외들이다.
이 예외가 발생하면 트랜잭션을 강제로 커밋해도 커밋되지 안혹 javax.persistence.RollbackException이 발생한다.

예외 설명
javax.persistence.EntityExistsException EntityManager.persist(..) 호출 시 같은 엔티티가 있으면 발생
javax.persistence.EntityNotFoundException EntityManager.getReference(..) 호출하고 실제 사용 시 엔티티가 존재하지 않으면 발생. refresh(..), lock(..) 에서도 발생
javax.persistence.OptimisticLockException 낙관적 락 충돌시
javax.persistence.PessimisticLockException 비관적 락 충돌시
javax.persistence.RollbackException EntityTransaction.commit() 실패 시 발생. 롤백이 표시되어 있는 트랜잭션 커밋시에도 발생
javax.persistence.TransactionRequiredException 트랜잭션이 필요할 떄 트랜잭션이 없으면 발생. 트랜잭션 없이 엔티티를 변경할 떄 주로 발생

트랜잭션 롤백을 표시하지 않는 예외

심각한 예외가 아니다.
개발자가 트랜잭션을 커밋할지 롤백할지 판단하면 된다.

예외 설명
javax.persistence.NoResultException Query.getSingleResult() 호출 시 결과가 하나도 없을 때 발생
javax.persistence.NonUniqueResultException Query.getSingleResult() 호출시 결과가 둘 이상일 떄 발생
javax.persistence.LockTimeoutException 비관적 락에서 시간 초과시 발생한다
javax.persistence.QueryTimeException 쿼리 실행 시간 초과시 발생

스프링 프레임워크의 JPA 예외 변환

서비스 계층에서 데이터 접근 기술에 직접 의존하는 것은 좋은 설계가 아니다.
이것은 예외도 마찬가지다. 서비스 계층에서 위 JPA 예외에 직접 의존하면 결국 JPA에 의존하게 되는것이다.
스프링 프레임워크는 이런 문제를 해결하기 위해 JPA 예외를 추상화해서 제공한다.

blah blah…

스프링 프레임워크에 JPA 예외 변환기 적용

위처럼 JPA예외를 스프링 예외로 변경해서 받으려면 PersistenceExceptionTranslationPostProcessor를 스프링 빈으로 등록하면 된다.
이것은 @Repository 어노테이션을 사용한곳에 예외 변환 AOP를 적용해서 JPA 예외를 스프링 추상화 예외로 변환해준다.

1
2
3
4
@Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation(){
return new PersistenceExceptionTranslationPostProcessor();
}

아래 예외는 변환되어서 던져질 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
@Repository
public class NoResultExceptionTestRepository{
@PersistenceContext
private EntityManager em;

public Member findMember(){
// 조회결과 없음
return em.createQuery("SELECT m FROM Member WHERE m.id = :id", Member.class)
.setParameter("id", 999)
.getSingleResult();
}
}

원래라면 NoResultException이 발생해야 하지만, 등록한 AOP 인터셉터가 동작해서 EmptyResultDataAccessException으로 변환해서 반환한다.

만약 예외를 변환하지 않고 그대로 반환하고 싶다면 반환할 JPA 예외나 JPA 예외의 부모 클래스를 직접 명시하면 된다.

1
2
3
4
5
6
7
8
@Repository
public class NoResultExceptionTestRepository{
public Member findMember() throws javax.persistence.NoResultException{
return em.createQuery("SELECT m FROM Member WHERE m.id = :id", Member.class)
.setParameter("id", 999)
.getSingleResult();
}
}

예외의 최상위 클래스인 Exception을 throws 하면 예외를 아예 변환하지 않을 것이다.

트랜잭션 롤백 시 주의사항

트랜잭션을 롤백하는 것은 데이터베이스의 반영사항만 롤백하는 것이지, 수정한 자바 객체까지 원상태로 복구해주지는 않는다.
이 말인 즉 트랜잭션이 롤백되었다고 영속성 컨텍스트도 롤백되는 것은 아니라는 것을 의미하는데,
기본 전략인 트랜잭션당 영속성 컨텍스트 ㅈ

Read more »

[jpa] 부모 테이블을 공유하는 자식 테이블(feat.@PrimaryKeyJoinColumn)

Posted on 2019-02-15 | Edited on 2020-11-02 | In jpa | Comments:

원하는 형태

여러 자식 테이블들의 1개 로우가 부모 테이블 로우 1개를 바라보는 형태

1
2
3
4
Item : row 1개, pk=1  
Album : row 1개, pk1
Movie : row 1개, pk1
...

결과적으로, 상속으론 저렇게 할수없다.
상속을 사용하면 완벽한 식별관계를 형성해서 자식(상속에서) 로우에 맞춰 부모 로우도 같이 생성하기 때문이다.

하긴 객체지향에서도 자식 생성시 부모 인스턴스 생성하니까 당연한 소리인가…

구현

@OneToOne Eager를 사용하면 위의 형태를 구성할 수 있다.
기본적인 @OneToOne 을 사용하면 위의 관계를 외래키를 이용해서 유지하게 되는데,
만약 정말 위처럼 완벽한 식별관계의 형태로 유지하고 싶다면 @PrimaryKeyJoinColumn을 사용하면 된다.

1
2


결과적으로 JPA 엔티티 상속의 기능은 딱히 특별할것이 없다… 는 결론입니다.

자식 테이블의 여러 로우들이 부모 테이블 로우 1개를 바라보는 방식

이건 잘못 생각한 것이다.
여러 로우에서 1개의 로우를 바라보려면 외래키로 연결되어야 한다
@ManyToOne Eager 를 사용해야 할 듯

여러 자식 테이블들의 여러 로우들이 부모 테이블 로우 1개를 바라보는 방식

이 또한 잘못 생각한 것
@ManyToOne Eager 를 사용해야 할 것이고, @DiscriminatorValue가 추가적으로 필요할 것이다

Read more »

리팩토링 중요 개념

Posted on 2019-02-13 | Edited on 2020-11-02 | In refactoring | Comments:

리팩토링

겉으로 드러나는 기능은 그대로 둔 채, 알아보기 쉽고 수정하기 간편하게 소프트웨어 내부를 수정하는 작업

  • 코드 구조는 한번 엉망이 되면 갈수록 더 엉망이 된다. 그러므로 주기적인 리팩토링이 필요하다.
  • 어떤 코드를 실행할때 컴퓨터는 몇바퀴 헤매더라도 그다지 문제될 것 없지만, 기껏해야 한시간정도면 이해하고도 남을 코드를 다른 개발자가 신속히 이해하지 못해서 한 부분을 수정하는데 일주일 이상 걸린다면 그건 큰 문제다.
  • 설계가 깔끔하지 않으면 개발 초기 잠시 동안은 진행이 빠를지 몰라도, 얼마 못가서 개발 속도가 떨어진다
  • 리팩토링은 작정해서 하는것이 아니라, 뭔가 다른걸 해야겠는데 리팩토링을 실시하면 그 작업이 쉬워지기 때문에 하는 것이다
  • 리팩토링은 언제 필요할까?
    • 같은 작업을 3번째 반복하게 됐을 때
    • 기능을 추가할 때
      • 설계가 지저분해서 어떤 기능을 추가하기 힘들떄는, 주저하지 않고 리팩토링을 실시한다
    • 버그를 수정할 떄
      • 코드의 기능을 파악하다 이해하기 힘들면, 이해하기 쉽게 만드려고 리팩토링을 실시한다
      • 돌아가는 원리를 쉽게 파악 가능하게 되고, 버그 찾기도 쉬워진다
  • 테스트 메서드 작성은 필수이다(자체 테스트 가능해야한다)
  • 리팩토링은 프로그램을 조금씩 단계적으로 수행하므로 실수를 발견해도 고치기 쉽다
  • 좋은 코드는 그것이 무슨 기능을 하는지 분명히 드러나야 한다
  • 리팩토링의 각 단계는 간단해야 실수할 가능성이 줄어든다
  • 리팩토링 떄문에 성능이 안좋아질 수 있다. 하지만 그건 최적화 단계에서 수정하면 된다
  • 코드의 기능을 파악하는데 시간이 많이 걸리는데, 어쩌피 지나야 할 과정이다
  • 주기적 리팩토링을 통해 코드를 깔끔하게 해놓으면,

  • published 인터페이스

    • 인터페이스의 장점은 사용하는 곳을 수정하지 않고도 내부 구조를 수정할 수 있다는 점이다
    • 하지만 대다수의 리팩토링 기법은 인터페이스를 건드린다
      • 이 인터페이스를 사용하는 모든 코드에 접근할 수 있다면, 이 리팩토링 기법은 문제되지 않는다
      • 하지만 사용하는 모든 코드에 접근하지 못하는 상황이 있다. 외부에서 사용하기 위한 API를 작성하는 등의 행위를 예로 들 수 있다.
        • 이런 인터페이스를 published(배포된) 인터페이스라고 한다
    • published 인터페이스를 수정하는 경우, 사용하는 부분이 모든 변경될 때 까지 기존 인터페이스와 새 인터페이스를 모두 유지해야 한다
      • 기존 인터페이스에서 새로운 인터페이스를 호출하는 형태로 작성해야 한다
      • 기존 인터페이스는 @Deprecated 같은것을 사용해서 사용하지 않게끔 알려야한다
      • 최소한 일정 기간동안 사용하지 않는 메서드를 유지시켜야 하므로 불편하다
    • 방법은 published 메서드를 최대한 만들지 않는 것이다
      • java api 같은 외부 API를 만드는 상황을 빼고도 과하게 published 메서드를 만드는 경우가 있다
      • 이럴 경우는 코드 소유권 정책을 수정해서 인터페이스 수정을 촉진하도록 하는 것이 낫다
        • 인터페이스를 수정하는 사람이 인터페이스를 사용하는 곳의 코드까지 고칠 수 있게 하는것이다
  • 설계가 수정되었을 때

    • 설계에 오류가 있거나, 설계에 대한 결정이 바뀌었을 경우, 수정하기 힘든 민감한 부분일 경우라도 대부분 리팩토링으로 해결된다
    • 새로 추가되는 코드들에 새 구조(바뀐 설계)를 사용하도록 개발한다
    • 기존의 코드에 대한 기능추가나 버그 발견 등이 발생했을 경우 기존 코드를 새 구조를 사용하게 바꾼다
    • 리팩토링 한다고 all stop 하는 상황을 막을 수 있다
  • 리팩토링하면 안되는 상황

    • 코드가 제대로 돌아가지 않는다면, 그냥 새로 작성하는것이 낫다
      • 코드는 제대로 돌아가는 것이 우선이고, 리팩토링은 나중 일이다
    • 납기가 임박했을 때도 리팩토링은 삼가야한다
      • 납기가 임박한 경우가 아니면 시간이 없다는 핑계로 리팩토링을 미루면 안된다
    • 리팩토링은 미완료 대출금이다
      • 대출이자는 복잡한 코드로 유지보수하기, 확장의 어려움 이다
    • 언제나 시간에 쫓긴다면, 그건 리팩토링해야 한다는 신호이다

  • 처음부터 완벽한 설계를 할 필요는 없다

    • 애초에 처음부터 완벽한 설계란 너무 어렵고, 빈틈이 많을 가능성이 있다
    • 그러므로 완벽한 솔루션을 찾을 필요없이, 적당한 솔루션을 찾으면 된다
    • 구축해나가면서 문제를 더 잘 이해하게 되며, 처음 구축한 솔루션이 최상의 솔루션이 아니었다는걸 알게 된다
      • 이러면 처음 제시한 솔루션이 잘못되었더라도, 그 솔루션을 수정하는데 비용이 들지 않게된다
    • 간단하게 설계하고, 일단 구현한 다음, 리팩토링으로 다듬으면 된다
      • 소프트웨어는 실물의 기계와 달리 유연하기 때문이다
  • 시스템 전체를 유연하게 설계할 필요는 없다

    • 잠재적 가능성을 생각하여 유연한 설계를 하는 것은 매우 복잡하고 힘들다
      • 애초에 유연하게 복잡한 설계가 필요한 상황은 거의 없다
    • 단순한 솔루션을 구현해놓고, 나중에 그것을 리팩토링 하려면 얼마만큼의 수고가 들지 생각해본다
      • 별로 어려움이 없을 것이라고 판단되면, 단순하게 솔루션을 구현하면 된다
    • 리팩토링을 쉽게 할 수 있는 감각을 익히게 되면, 유연한 솔루션은 떠오르지도 않는다
      • 때가 되면 어련히 리팩토링하게 되리란 확신이 있기 때문이다

  • 리팩토링을 실시하면 소프트웨어 속도는 느려지지만, 성능을 더 간단하게 조절할 수 있다
    • 소프트웨어 성능을 올리려면 먼저 소프트웨어를 튜닝 가능하게 만들어놔야 한다
  • 성능 감소의 이유를 추측에 의존하지 말고 프로파일러를 통해 측정하도록 해야한다
  • 프로그램을 잘 쪼개어야 한다
    • 프로파일러로 성능을 감소시키는 작은 부분을 발견할 수 있게 되고, 이 부분에 집중하여 성능 개선을 할 수 있다
    • 성능을 분석할 때 더욱 정밀한 분석이 가능해진다

  • 켄트백의 모자 두개

    1. 기능을 추가할 땐 코드를 수정하지 말고 기능만 추가해야 한다
      • 테스트를 추가하고 테스트가 잘 되는지만 보면 됨
    2. 리팩토링할때는 코드를 추가하지 말고 구조 개선만 해야 한다
      • 테스트를 수정하지 않고 코드 구조만 수정해야 함
    3. 둘을 같이 하면 실패할 확률이 높다(경험…)
  • 랄프 존슨의 우선 창 밖이 보이게 뿌연 유리창부터 닦는 일

    • 리팩토링을 실시하면 낯선 코드를 쉽게 이해할 수 있다
    • 그렇게 첫 리팩토링을 마치고 코드가 깔끔해지면, 기존에 안보이던 설계가 보인다
  • 켄트백의 리팩토링의 효용성

    • 프로그램이 지닌 가치는 현재의 기능과 미래의 기능이라는 2개의 가치이다
    • 프로그램의 현재기능은 그저 일부에 불과하다
    • 과거의 판단이 현재의 기준으로 불합리하다는 사실을 발견했으면, 수정해야 한다
    • 프로그램이 수정하기 힘들어지는 4가지 상황
      1. 코드를 알아보기 힘들 때
      2. 중복된 로직이 들어있을 떄
      3. 추가기능을 넣어야해서 실행중인 코드를 변경해야 할 때
      4. 조건문 구조가 복잡할 떄
    • 프로그램은 코드를 알아보기 쉽고, 모든 로직이 한곳에 있으며, 기존 기능을 건드릴 필요 없이 조건문 구조가 최대한 간결하게끔 작성해야 한다.
  • 메서드는 대체로 자신이 사용하는 데이터와 같은 객체에 들어있어야 한다

  • 임시변수는 최대한 제거하는 것이 좋다

    • 변경되지 않는 변수는 매개변수로 전달할 수 있다
    • 변경되는 변수가 하나뿐이라면 리턴값으로 사용할 수 있다
    • 위 두 원칙을 적용하면서 임시변수를 계속 제거해보자
  • switch문의 인자로는 자신의 데이터를 사용해야 한다(타객체 데이터 X)

  • 질의 메서드를 따로 만들어서 편리하게끔 할 수 있음

참고 : 마틴 파울러, 『리팩토링』, 김지원 옮김, 한빛미디어(2012)

Read more »

[jpa] jpa entity equals, hashcode

Posted on 2019-02-13 | Edited on 2020-11-02 | In jpa | Comments:

JPA는 영속성 컨텍스트의 키로 엔티티의 식별자를 사용한다.
식별자가 primitive 타입일 경우 ==, reference 타입일 경우 equals를 사용한다(아마도 당연히)
기본적인 wrapper 타입의 경우 equals가 잘 구현되어 있기 때문에 상관없으나
@EmbeddedId의 경우 equals, hashCode를 구현해주지 않으면 두 엔티티가 같다는 것을 보장해줄 수 없다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Entity
class Member{
@EmbeddedId
private MemberPK id;

public Member(MemberPK id){
this.id = id;
}
}

@Embeddable
class MemberPK{
private Integer memberId;
private String name;
}
1
2
3
4
5
Member member1 = new Member(new MemberPK(1, "joont"));
Member member2 = new Member(new MemberPK(1, "joont"));

em.persist(member1);
em.persist(member2);

MemberPK에 대해 eqauls, hashCode를 구현했다면 두 엔티티가 같다고 보장되므로 Member가 하나만 들어가지만,
equals, hashCode를 구현하지 않았다면 똑같은 식별자를 가졌음에도 불구하고 영속성 컨텍스트에 두번 들어가게 된다.
그러므로 @EmbeddedId에 대해서는 꼭 equals, hashCode를 구현해줘야 한다.

아래는 또 다른 사용 사례에 관한 것이다.
https://jojoldu.tistory.com/134

Read more »

OAS 파일 나누기

Posted on 2019-02-12 | Edited on 2020-11-02 | In openAPI | Comments:

참고로 이 방식은 OAS 3.0 이상에서만 된다.

방식은 간단하다. # 앞에 파일 경로만 써주면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# api.yml
items/{itemId}:
get:
operationId: getItem
summary: 아이템 조회
parameters:
- $ref: 'items.yml#/parameters/itemId'
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: 'item.yml#/schemas/Item'

# item.yml
parameters:
itemId:
in: path
name: itemId
required: true
schema:
type: integer
schemas:
Item:
title: Item
type: object
properties:
# ...
  • 파일명 뒤에 #만 붙여주면 바로 접근 가능하다.
  • item.yml은 api.yml과 같은 위치에 있다(상대경로로 접근했음)
  • 폴더로 나누고 definitions/item.yml#schemas/Item 의 형태로 선언해도 된다.

보다시피 item.yml 파일에 최상위 레벨이 없다. 없어도 되기 때문이다.
단일파일에 작성할 때 components 아래 작성했던 부분을 파일로 나눴다고 생각하면 된다.

Read more »

iterm2 꾸미기

Posted on 2019-02-11 | Edited on 2020-11-02 | In etc | Comments:

http://heetop.blogspot.com/2017/10/oh-my-zsh_12.html

zsh, oh my zsh 까지 적용했다

Read more »

[jpa] 엔티티 리스너

Posted on 2019-02-09 | Edited on 2020-11-02 | In jpa | Comments:

엔티티의 생명주기에 따른 이벤트를 처리하고 싶을 수 있다.
(삽입 시점, 업데이트 시점, 삭제 시점에 로그남기기 등)

그렇다고 직접 로직을 수행하는 곳을 찾아가 일일히 추가 로직을 처리하는 것은 너무 비효율적이다.
JPA 리스너 기능을 사용하면 엔티티의 생명주기에 따른 이벤트를 처리할 수 있다.

엔티티 리스너

Read more »

[jpa] 엔티티 값을 변환해서 저장하기(@Converter)

Posted on 2019-02-09 | Edited on 2020-11-02 | In jpa | Comments:

@Converter를 사용하면 엔티티의 데이터를 변환해서 데이터베이스에 저장할 수 있다.
예를 들면 엔티티에는 boolean, 데이터베이스에는 YN 값을 저장하고 싶을 경우 정도가 있겠다.
(기본적으로 boolean 타입으로 지정하면 0,1로 저장된다)

기본 사용법

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Entity
class Member{
@Id @GeneratedValue
private Integer id;

@Convert(converter=BooleanToYNConverter.class)
private boolean useYn;
}

@Converter
class BooleanToYNConverter implements AttributeConverter<Boolean, String>{
@Override
public String convertToDatabaseColumn(Boolean attribute){
return (attribute != null && attribute) ? "Y" : "N";
}

@Override
public Boolean convertToEntityAttribute(String dbData){
return "Y".eqauls(dbData);
}
}

보다시피 간단하다.
AttributeConverter를 구현하면서 제네릭으로 엔티티 컬럼 타입, 데이터베이스 컬럼 타입을 주고,
아래 2개의 메서드를 오버라이드 해주면 된다(엔티티->데이터베이스, 데이터베이스->엔티티)

클래스 레벨 설정

클래스 레벨에도 설정할 수 있다. 단 이때는 attrbuteName 속성을 사용해서 어떤 필드에 컨버터를 적용할 지 명시해야한다.

1
2
3
4
5
@Entity
@Converter(converter = BooleanToYNConverter.class, attributeName = "useYn")
class Member{
// ...
}

글로벌 설정

모든 Boolean 타입에 설정하고 싶을 경우 아래와 같이 직접 컨버터에 명시해주면 된다.

1
2
3
4
@Converter(autoApply = true)
class BooleanToYNConverter implements AttributeConverter<Boolean, String>{
// ...
}

이렿게 하면 엔티티에 따로 컨버터를 지정해주지 안항도 자동으로 컨버팅 된다.

Read more »

[jpa] 컬렉션과 부가기능

Posted on 2019-02-08 | Edited on 2020-11-02 | In jpa | Comments:

JPA는 자바에서 기본으로 제공하는 Collection, List, Set, Map 컬렉션을 지원하고, 아래와 같은 상황에서 컬렉션을 사용할 수 있다.

  • @OneToMany, @ManyToMany 를 사용해서 일대다나 다대다 관계를 매핑할 때
  • @ElementCollection 을 사용해서 값 타입을 하나 이상 보관할 때

하이버네이트는 엔티티를 영속상태로 만들 때 컬렉션 필드를 하이버네이트에서 준비한 컬렉션으로 감싸서 사용한다.
이는 하이버네이트가 컬렉션을 효율적으로 관기하기 위함이다.
하이버네이트는 본 컬렉션을 감싸고 있는 내장 컬렉션을 생성한 뒤, 이 내장 컬렉션을 사용하도록 참조를 변경한다.

Collection, List

중복을 허용하는 컬렉션이다.
하이버네이트에서 PersistentBag으로 래핑된다. 사용할 때는 ArrayList로 초기화하면 된다.

1
2
3
4
5
@OneToMany(mappedBy = "parent")
Collection<Child> children = new ArrayList<>();
// or
@OneToMany(mappedBy = "parent")
List<Child> children = new ArrayList<>();

중복을 허용하는 특성때문에 겍체를 추가할때 아무 조건검사가 필요없으므로, 지연로딩이 발생하지 않는다.
하지만 엔티티가 있는지 체크하거나 삭제할 경우 eqauls로 비교해야 하므로 지연로딩이 발생한다.

1
2
3
4
children.add(child); // no action  

children.contains(child); // Lazy loading occurs because of using equals
children.remove(child); // Lazy loading occurs because of using equals

Set

중복을 허용하지 않는 컬렉션이다.
하이버네이트에서 PersistentSet으로 래핑된다. 사용할 때는 HashSet으로 초기화하면 된다.

1
2
@OneToMany(mappedBy = "parent")
Set<Child> children = new HashSet<>();

중복을 허용하지 않으므로 객체를 추가할 때 마다 equals 메서드로 같은 객체가 있는지 비교한다. 즉 add 메서드만 수행해도 지연로딩이 발생한다.
참고로 HashSet은 해시 알고리즘을 사용하므로 equals와 hashCode를 같이 사용한다.

1
2
3
4
children.add(child); // eqauls + hashCode

children.contains(child); // eqauls + hashCode
children.remove(child); // eqauls + hashCode

List + @OrderColumn

순서가 있는 복수형 컬렉션을 의미하는데, 데이터베이스에 순서값을 저장해서 조회할 때 사용한다는 의미이다.

OrderColumn

위처럼 데이터베이스에 순서값을 함꼐 관리하는 테이블에 사용된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Entity
class Board{
@Id @GeneratedValue
private Integer id;

@OneToMany(mappedBy = "board")
@OrderColumn(name = "POSITION")
private List<Comment> comments = new ArrayList<>();
}

@Entity
class Comment{
@Id @GeneratedValue
private Integer id;

@ManyToOne
@JoinColumn(name = "BOARD_ID")
private Board board;
}

List의 위치(순서)값을 POSITION이라는 컬럼에 저장하게 되는것이고, 이는 일대다 관계의 특성에 따라 다(N)쪽에 저장하게 된다.
아래는 사용예제이다.

1
2
3
4
5
6
7
8
9
10
Board board = new Board("title", "content");
em.persist(board);

Comment comment1 = new Comment("comment1");
comment1.setBoard(board); // POSITION 0
em.persist(comment1);

Comment comment2 = new Comment("comment1");
comment2.setBoard(board); // POSITION 1
em.persist(comment2);

어떻게보면 위치값을 알아서 관리해주니 편해보이지만 사실은 실무에서 사용하기에는 단점이 많다.

  • Comment가 POSITION의 값을 알 수 없다. Board에서 관리되기 때문이다.
    이러한 특징때문에 위의 명령을 수행하면 comment1, comment2 insert 후에 POSITION 값을 수정하는 update가 2번 추가로 발생한다(ㄷㄷ)
  • 요소가 하나만 변경되도 모든 위치값이 변경된다. 예를들어 첫번쨰 댓글을 삭제하면 그 뒤의 댓글들의 POSITION-- 하는 update가 댓글의 개수만큼 발생한다.
  • 중간에 POSITION 값이 없으면 null이 저장된다. 예를들어 강제로 0,1,2의 POSITION 값을 0,2,3으로 변경하면 1번 위치에 null이 보관된 컬렉션이 반환된다. 이러면 NullPointerException이 발생한다.

@OrderBy

ORDER BY 절을 이용해서 컬렉션을 정렬하는 방법이다. @OrderColumn 처럼 순서용 컬럼을 매핑하지 않아도 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Entity
class Member{
@Id @GeneratedValue
private Integer id;

@OneToMany(mappedBy = "board")
@OrderBy("createdDate desc")
private List<Comment> comments = new ArrayList<>();
}

@Entity
class Comment{
@Id @GeneratedValue
private Integer id;

@ManyToOne
@JoinColumn(name = "BOARD_ID")
private Board board;

@Temporal(TemporalType.TIMESTAMP)
private Date createdDate;
}

이렇게 하면 comments를 초기화 할 때 명시해놓은 ORDER BY 구문이 같이 수행되어 순서가 보장된다.
사용하는 컬럼명은 JPQL 때처럼 엔티티의 필드를 대상으로 한다.

Read more »

[spring] filter, interceptor 차이

Posted on 2019-02-08 | Edited on 2020-11-02 | In spring | Comments:
1…789…19

JunYoung Park

182 posts
18 categories
344 tags
RSS
© 2020 JunYoung Park
Powered by Hexo v3.6.0
|
Theme – NexT.Muse v7.1.0