작성
·
68
·
수정됨
0
안녕하세요
토비님 강의 듣고나서 제 나름대로 API 서버 제작 연습을 좀 해보도가 JPA 및 도메인 상태 변경에 관련해서 질문 드리고 조언을 얻고자 문의 드립니다.
상황은 아래와 같습니다.
DDD에 입각하여 Aggregator 가 하위 Entity Life Cycle 담당
Entity 식별자 타입 경우 Long
Client 에서 변경 API 요청 시 Aggregator 의 식별자는 존재하지만, 하위 Entity 에 대한 id 는 포함하지않은채 요청
{
id: AggregatorId
name: AggregatorName
list: [
{
name: 'modified name1',
type: 'type1',
status: 'active'
},
{
name: 'modified name2',
type: 'type2',
status: 'inactive'
},
{
name: 'new name3',
type: 'type3',
status: 'active'
}
]
}
id 가 없이 JPA 변경 요청 시 delete-insert 가 진행 될것 같아 자체적으로 key based diff 라는 함수를 제작하여 변경점과 신규 데이터에 대해서 병합 후 save() 를 호출 할 것같은데 이럴 경우 insert, update 문의 별도로 나가지 않을까 생각이 듭니다.
이런 경우 DB 입장에서 하나의 트랜잭션에서 수행하더라도 부하나 성능에 악영향을 미칠 것 같은데 이럴 경우 어떤식으로 애플리케이션 로직을 세우고 JPA 를 어떻게 활용해야되는지 조언 부탁드려도될까요?
참고로 저는 하위 Entity 변경 시 batchUpsert 를 고려하고 있습니다.
답변 2
0
컬렉션을 애그리거트에 포함시키는 경우 단순한 변경은 매번 새로 등록하는 것도 가능하겠만, 컬렉션 원소 엔티티가 복잡하거나 양이 많거나, 한번에 업데이트하는 작업이 크다면 변경 트랜잭션을 처리하는 작업은 고민이 필요할 겁니다.
먼저, 애그리거트에서 서브 엔티티는 id를 노출하지 않는다는 것은 직접 서브 엔티티에 접근하려고 id 값을 사용하지 않는다는 것입니다. DDD 책에서는 서브 엔티티는 애그리거트 내에서 유의미한 id를 가진다라고 되어있습니다. 예를 들면 인덱스 같은 것이겠죠. 하지만 필요하면 id 값을 공유해도 아무 문제 없습니다. list 내의 원소 하나에 직접 접근하기 위해서 그 id를 쓰는 것이 아니라면 애그리거트 설계면에서 아무 문제가 없습니다. 이후에 그 원소를 한번에 업데이트 할 때, 변경이나 삭제가 필요한지를 따져볼 때 사용할 수 있죠. 그런면에서 굳이 id를 제거할 이유는 없습니다.
다만 이렇게 컬렉션을 한번에 업데이트 하는 방식은 어쩔 수 없이 복잡해질 겁니다.
근본적으로 컬렉션 관계의 엔티티가 매번 상위 트랜잭션에 묶여서 한번에 변경되는가라는 것도 따져봐야 합니다.
애그리거트 단위가 너무 크다고 판단되면 이를 분리해서 더 작게 쪼개는 것은 실제로 많이 경험하는 일이기도 합니다. One to many가 2단계 연결된 경우를 애그리거트 하나로 잡으면 한번에 수백개씩 조회하고 변경하는 일이 일어나기도 합니다.
저라면, 이 구조를 애그리거트로 묶었다고 하더라도 list 내부 원소를 변경하는 작업을 하나의 트랜잭션으로 묶어서 처리할 겁니다. 그러면 변경이 일어날 때 애그리거트를 로딩하긴 하지만, 모든 컬렉션을 매번 가져올 필요는 없을 것이고(lazy), 추가인지 변경인지, 삭제인지 구분된 요청이 오기 때문에 루트 엔티티를 통해서 요구가 들어오더라도 변경에 큰 무리가 없습니다.
대량 데이터의 변경과 한방 업데이트라고 하면, 일단 가공된 정보를 최대한 상세하게 받아서 처리를 해야겠죠.
여러가지 선택지와 트레이드 오프를 고려할 점이 많은 케이스 같습니다.
0
안녕하세요, 인프런 AI 인턴입니다.
김태형님께서 설명해주신 상황에서 JPA와 관련된 효율적인 방식에 대한 조언을 드리겠습니다.
현재 상황에서는 클라이언트로부터 전송된 데이터에서 하위 엔티티 식별자가 없기 때문에, JPA는 기존 데이터를 삭제하고 새로운 데이터를 삽입하는 delete-insert 방식으로 동작할 가능성이 큽니다. 이로 인해 불필요한 부하가 발생할 수 있습니다.
이를 해결하기 위한 몇 가지 방법을 제안드리겠습니다:
Key Based Diff 사용: 말씀하신 것처럼 key based diff 함수를 통해 기존 하위 엔티티와 변경된 데이터를 비교하여 변경된 데이터만 업데이트하고, 새 데이터만 추가하는 방법을 고려할 수 있습니다. 이 때, 비효율적인 delete-insert를 피하기 위해 명시적인 merge
또는 persist
메서드를 통한 update와 insert를 사용할 수 있습니다.
Batch Update/Insert 전략: 하위 엔티티의 변경을 일괄 처리하기 위해 JPA의 벌크 연산(Batch Process)를 활용하는 것도 좋은 방법입니다. 이 방법은 일반적으로 직접적인 EntityManager
를 이용하거나, JPQL 또는 Criteria API를 사용하여 수행할 수 있습니다.
Optimistic Locking: 성능 부하를 고려할 때 중요한 것은 동시에 발생하는 여러 수정 작업을 안전하게 관리하는 것입니다. 이를 위해 Optimistic Locking을 설정하여, 수정 충돌을 최대한 배제하고, 충돌 시 적절한 에러 핸들링을 통해 일관성을 유지할 수 있습니다.
Native Query 사용: 복잡한 변경 작업이나 성능 이슈가 우려되는 경우에는 필요에 따라 JPA가 아닌 네이티브 쿼리를 활용하여 데이터베이스의 고유 기능을 사용하는 것도 고려해볼 수 있습니다.
추가적으로, JPA의 기능을 충분히 활용하면서도 상황에 따라 데이터 처리의 책임을 서비스 레이어에서 명확히 구분하여 Entity 관리의 복잡성을 줄이고 성능 최적화를 도모하는 것이 좋습니다.
추가적인 참고 자료가 필요하시면 Spring Data JPA - Auditing을 참고해주세요. 도움이 되셨길 바랍니다!