Jackson 을 이용한 object serializing, deserializing 에서, 프로퍼티의 값을 원하는대로 바꿔 사용할 수 있다.

1. 원본 프로퍼티에는 @JsonIgnore 를 지정한다.

2. serializing 에 사용될 값에는 @JsonProperty fun 을 사용한다.

3. deserializing 에 사용될 값에는 @set:JsonProperty 를 사용한다.

 

예:

Item 객체를 포함하는 MySomething 객체 인스턴스를 Json <-> Object 변환 시, Item 을 그대로 내보내기보다는 item 의 id 만을 내보내고, 받고 싶은 경우:

class MySomething {
	
    @JsonIgnore
    lateinit var item: Item
    
    @JsonProperty("itemRef")
    fun itemId() = item.id
    
    @set:JsonProperty("itemRef")
    lateinit var itemRef: String
}

 

* item: Item 프로퍼티에 lateinit 을 사용하는 이유:

serializing 에서는 val 를 사용해도 상관 없겠지만, deserializing 에서는 val 를 사용할 수 없다. Json 에서 이 프로퍼티는 다음과 같이 변환될 것인데, deserializing 에서 이걸 가지고 다시 Item 으로 변환할 수 없다.(따로 deserializer 를 정의하면 가능하겠지만 이는 논외로 한다.)

"itemRef":"<item_id>"

위의 이유로 deserializing 시에 item 프로퍼티는 초기화되지 못하여(위의 "itemRef" 는 itemRef: String 프로퍼티에 세팅된다.) val 사용 시 deserializing 에서의 인스턴스 생성 시점에 예외가 발생한다.

 

* itemRef: String 프로퍼티에 lateinit 을 사용하는 이유:

위와 비슷하다. itemRef 는 deserializing 을 위한 것으로, 이외의 상황에서 사용되진 않는다고 상정했기 때문에 평소에는 굳이 초기화할 필요 없도록 남겨두고 위해서이다. (val 를 사용하면 반드시 초기화해야 하고, json 변환이 필요하지 않은 상황에도 영향을 주게 된다.)

 

* fun itemId() 를 사용하는 이유:

아래와 같이 사용해도 serializing 시에 같은 결과가 나온다:

@get:JsonProperty("xxxRef")
val itemId = item.id

 

하지만 이렇게하면 deserializing 시에 item property not initialized exception 을 만나게 된다. 이는 코틀린에 관한 것인데, val 프로퍼티는 인스턴스 생성 과정에서 시점에 초기화가 이루어진다. val itemId 프로퍼티 초기화 시 item.id 를 참조하게 되는데, item 프로퍼티는 아직 초기화되지 못한 상태이다. 때문에 예외가 발생한다. 이를 방지하고자 fun 을 사용한다.

'기술 일반 > 일반' 카테고리의 다른 글

CLOB 컬럼 핸들링 중 인코딩 문제  (0) 2019.03.08
유니코드와 UTF-8, UTF-16  (0) 2017.04.07

또 자꾸 헷갈려서 정리

 

JPA 에서, 관계를 맺는 데이터가 존재하는지 여부를 알 수 있는 방법이 직접 조회(select) 외엔 없다면, lazy loading 이 작동하지 못한다. (이 이유는 null 데이터에 대해 프록시를 생성할 수 없기 때문이다. 자세한 내용은 인터넷에 많으므로 생략.)

 

@OneToOne 에서, 다음의 조건을 만족한다면 lazy loading 이 작동한다.

1. (단방향/양방향에 관계 없이) FK 를 가진 쪽(관계의 주인)에서 조회한다.

- FK 가 null 이면 데이터가 없다고 판단할 수 있으므로, lazy loading 이 작동한다. 

 

2. FK 를 가지진 않았지만 optional = false 이다.

- FK 를 가지고 있지는 않지만 optional = false, 즉 이 관계는 필수임이 명시되어 있어 JPA 는 반드시 대상 데이터가 존재한다고 판단할 수 있다. 

 

OneToOne 이 아닌 다른 관계에서도 어쨌든 핵심은 관계하는 대상 데이터의 존재 여부를 미리 알 수 있느냐 이다. 알 수 있다면 lazy loading 은 작동할 것이고, 아니라면 작동하지 못할 것이다.

 

레퍼런스:

https://kwonnam.pe.kr/wiki/java/jpa/one-to-one

https://1-7171771.tistory.com/143

'Java' 카테고리의 다른 글

test] @Testcontainers, @Container  (0) 2022.08.03
JPA] Hibernate 1차 캐시  (0) 2022.06.27

메서드 파라미터로 객체를 전달할 것이냐? 객체의 프로퍼티를 전달할 것이냐?

아직 명확한 결론에 도달하지 못했다. 양쪽에 장단점이 있고, 컨텍스트에 따라 다르기 때문에 어느 하나의 정답이 존재하는 물음이 아니다.

 

일단 단순한 케이스에서 눈에 보이는 장단점을 정리하면,

아래와 같은 두 함수가 있다고 하자

fun doSomethingWithEachProperties(String arg1, Int arg2, Long arg3) {
	...
}

fun doSomethingWithWholeObject(ParamObject arg) {
	... // arg.getString(), arg.getInt(), arg.getLong() 을 필요할 때 알아서 호출하여 꺼내 씀
}

첫 번째 방법의 장점

- 정확히 필요한 인자만을 취하기에 유리하다. 함수를 호출하는 쪽에서도 어떤 값이 필요한지 정확히 알 수 있으며, 함수 코드의 응집성이 높아진다.

- 보통 첫 번째 접근법을 따를 때는, 일반적으로 메서드는 기본형(String 포함) 인자를 취한다. 때문에 하나의 객체 정의에 묶이지 않고, 보다 공통적으로 사용될 수 있는 함수를 정의하기에 유리하다.

 

첫 번째 방법의 단점

- 함수를 호출하는 쪽에서 어떤 값이 필요한지 정확히 안다는 사실은, 달리 말하면 함수가 '어떻게' 작동하는지 노출한다는 뜻이 될 수 있다. (함수가 private 이라면 이야기가 달라진다.)

- 함수의 작동 방법이 달라져 다른 인자를 추가로 필요로 하게되면 함수 시그니처를 변경해야 할 것이고, 그렇게 되면 이 메서드를 호출하는 모든 코드를 변경해야 한다.

 

 

두 번째 방법의 장점

- 일단 코드가 깔끔해진다. 함수 시그니처, 함수 호출 코드 모두 간결해진다.

- 함수 작동 방법이 달라져 다른 인자가 추가로 필요해져도 함수 시그니처 변경 없이 ParamObject 만을 변경해서 처리할 수 있다. 즉 함수 호출 코드를 변경하지 않을 수 있다. (언제나 그렇지는 않다.)

- 함수가 구체적인 값을 받지 않으므로, 함수의 클라이언트에게 함수가 어떻게 작동하는지에 관하여 '덜 노출하게 되는' 성질이 있다. (역시 함수가 private 이라면 이야기가 달라진다.)

 

두 번째 방법의 단점

- ParamObject 가 이 함수가 필요로 하는 값 외에 다른 값을 가지고 있다면, 이는 함수의 응집성이 낮아진다고 볼 수... 있을까?

- 함수 호출만을 위해 새로운 객체를 정의해야 할 수 있다. 이러면 오히려 더 많은 코드를 작성해야 한다. (물론 이는 추천하지 않는다.)

 

그래서?

예전에는 '코드(함수)가 꼭 필요한 것만을 알도록 한다' 에 중점을 두었었다. 응집성에 더 비중을 둔 것이었는데, 갈수록 생각을 달리하게 되는 케이스를 만난다. 클린 코드에서는 함수의 인자가 3개를 넘기지 않도록 하라는 이야기도 있고(물론 절대적인 것은 아니지만), 구현 캡슐화의 관점에서 보면 이것이 과연 옳은가 하는 생각이 든다.

코드 디자인은 '변경' 을 보다 잘 다루기 위한 일이라는 관점에서 보면, 인자가 추가될 경우를 고려하면 객체를 넘기는 편이 바람직해 보인다. 함수의 변경이 다른 코드의 변경을 유발하는 일은 기본적으로 안티 패턴으로 취급된다. (Shotgun Surgery)

 

 

+ Recent posts