작성
·
75
1
여러가지 repository를 만들어야하나요?
예를들어서 mybatis, jdbctemplate, 스프링jpa, 순수jpa, querydsl을 사용한다고 할때
mybatisRepo, JdbcRepo, 스프링JpaRepo, 순수Jpa + querydslRepo, 이런식으로 다 나눠서 하는게 맞는걸까요??
그러면 강의에서 말씀해주신 것처럼 DIP 원칙은 위배될텐데 그래도 되는걸까요? DIP원칙 위배되지 않게 하기위해서 각 repo별로 인터페이스 만드는것도 뭔가 이상해보이기도 하고... 헷갈리네요
Jpa를 사용한 후에, mybatis나 jdbctemplate같이 수정과 조회를 디비에서 체크하는 db기술을 사용한다고 할때,
이전 jpa 사용에서 어떤 수정이 이루어졌었다면 그건 영속성 컨텍스트에 저장되어있기만하고 아직 flush를 하지 않아서 디비엔 반영이 안되어 있는 상태라서 mybatis나 jdbctemplate를 이용할 때 뭔가 데이터 정합성이 안맞는다거나 하는 문제가 발생할 수 있기에(뇌피셜) 아무튼 jpa 사용 후 mybatis, jdbctemplate 사용하려면 사용하기 전엔 flush를 꼭해줘야 한다고 하셨는데, 그러면 service 레이어에서 entitymanager를 의존받아야 하는데 이게 맞는건지 궁금합니다. jpa 기술을 서비스레이어까지 끌고오는게 어찌보면 좀 안맞는거같고 그걸 피하기 위해서 트랜잭션 aop를 사용한것 같은데... 그래도 db기술 혼합사용시에는 그냥 service 레이어에 entitymanager를 의존받는게 맞는걸까요?
jpa를 이용한 db 처리방식은 효율적인 건가요??
항상 효율적인 것 같지는 않지만, 영속성 컨텍스트를 체크해서 결국 마지막에 최종 변환 된 것만 쿼리를 날려서 수정을 한다던가 하는 부분은 뭔가 효율적인것처럼 보이지만서도, 단순히 update하나만 한다고 가정하면, find로 select 쿼리를 한번 날리고 setXXX으로 객체에 뭔가 변화를 줘서 커밋직전 flush에서 update쿼리를 한번 더 날린다고 생각하면 또 비효율적인거 같기도하고 잘 모르겠습니다.
답변 2
1
안녕하세요. 한현진님
하나씩 답변 드릴께요.
결론부터 말씀드리면, 일반적으로 각기 다른 데이터 접근 기술을 사용한다고 해서 MyBatisOrderRepo
, JPAOrderRepo
처럼 기술 이름을딴 별도의 Repository 인터페이스를 여러 개 만드는 것은 권장하지 않습니다. 이는 DIP(Dependency Inversion Principle, 의존관계 역전 원칙)를 위배할 가능성이 높고, 서비스 계층이 구체적인 구현 기술에 의존하게 만들기 때문입니다.
바람직한 접근 방식:
도메인 중심의 Repository 인터페이스 정의:
OrderRepository
라는 인터페이스를 만듭니다. 이 인터페이스는 주문(Order) 도메인과 관련된 데이터 접근 기능을 추상화합니다. (예: save(Order order)
, findById(Long id)
, findByCustomerName(String name)
)
서비스 계층은 이 OrderRepository
인터페이스에만 의존합니다.
구현체에서 기술 선택:
OrderRepository
의 구현체인 OrderRepositoryImpl
(또는 Spring Data JPA를 사용한다면 프록시 객체) 내부에서 필요에 따라 JPA, MyBatis, JdbcTemplate 등을 조합하여 사용할 수 있습니다.
예를 들어, 대부분의 CRUD는 Spring Data JPA로 구현하고, 특정 복잡한 통계 쿼리나 벌크 연산은 MyBatis나 JdbcTemplate을 OrderRepositoryImpl
내부의 private 메서드나 별도의 컴포넌트로 만들어 호출하는 방식입니다.
DIP를 지키는 것:
이렇게 하면 서비스 계층은 OrderRepository
라는 추상화된 인터페이스에만 의존하게 됩니다. 실제 내부 구현에서 JPA를 쓰든 MyBatis를 쓰든 서비스 계층은 알 필요가 없습니다. 이것이 DIP를 지키는 방법입니다.
각 repo별로 인터페이스 만드는 것
이 이상하게 느껴지신 이유는 아마도 기술별
로 인터페이스를 만드려고 생각하셨기 때문일 겁니다. 대신 도메인 작업별
로 인터페이스를 정의하고, 그 구현체 안에서 적절한 기술을 조합하는 것이 좋습니다.
만약, 특정 기술에 매우 특화된 기능들이 많고, 도메인적으로 분리된 관심사라면 (예: 매우 복잡한 통계 전용 리포지토리), 별도의 인터페이스와 구현체를 고려할 수는 있지만, 일반적인 CRUD와 일부 복잡 쿼리가 섞인 경우라면 하나의 통합된 Repository 인터페이스 접근법이 더 유지보수하기 좋습니다.
JPA를 사용한 후 동일 트랜잭션 내에서 MyBatis나 JdbcTemplate으로 DB를 직접 조회할 때 데이터 정합성 문제가 발생할 수 있습니다. JPA의 영속성 컨텍스트에 있는 변경사항이 아직 DB에 반영(flush)되지 않았기 때문입니다.
따라서 JPA 작업 후 MyBatis/JdbcTemplate을 사용하기 전에는 명시적으로 entityManager.flush()
를 호출해주는 것이 안전합니다.
Service Layer에서 EntityManager 의존:
entityManager.flush()
를 호출하기 위해 서비스 계층에서 EntityManager
를 직접 의존하는 것이 괜찮은지에 대한 고민은 타당합니다.
일반적으로 서비스 계층은 비즈니스 로직에 집중하고, 데이터 접근 기술에 대한 직접적인 의존은 최소화하는 것이 좋습니다. @Transactional
어노테이션을 통해 선언적으로 트랜잭션을 관리하는 것도 이러한 관심사 분리를 위한 것입니다.
해결 방안 및 고려사항:
Repository 계층에서 flush
호출:
첫번째 방법은 flush
가 필요한 작업을 Repository 메서드 내에서 처리하는 것입니다.
예를 들어, OrderRepository
의 특정 메서드가 내부적으로 JPA로 कुछ 변경하고, 이어서 JdbcTemplate으로 뭔가를 조회해야 한다면, 해당 Repository 메서드 내부에서 entityManager.flush()
를 호출할 수 있습니다. 이 경우 서비스 계층은 EntityManager
를 몰라도 됩니다.
Service 계층에서 EntityManager
주입 (필요악으로 간주):
만약 서비스 계층에서 JPA 작업과 비 JPA 작업의 흐름을 직접 제어해야 하고, 그 사이에 flush
가 반드시 필요하다면, 서비스 계층에 EntityManager
를 주입받아 사용하는 것도 현실적인 선택일 수 있습니다.
@Transactional
의 동작 이해:
기본적으로 @Transactional
메서드가 종료될 때 (커밋 시점) flush
가 발생합니다.
또한, JPA는 트랜잭션 내에서 JPQL 쿼리가 실행되기 전에 영속성 컨텍스트에 있는 변경사항 중 해당 쿼리 결과에 영향을 줄 수 있는 것들을 flush
하는 메커니즘(auto-flush)을 가지고 있습니다.
하지만, MyBatis나 JdbcTemplate은 JPA의 영속성 컨텍스트를 직접 인지하지 못하므로, 이들을 사용한 SQL 실행 전에는 JPA가 자동으로 flush
를 해주지 않습니다. 그래서 명시적인 flush()
가 필요한 것입니다.
결론적으로, 가능하면 Repository 내부에서 flush
를 관리하는 것이 좋지만, 서비스 로직 흐름상 명시적인 flush
호출이 서비스 계층에서 필요하다면 EntityManager
를 주입받아 사용하는 것도 데이터 정합성을 위해서는 고려할 수 있는 방법입니다.
더 나은 대안으로는 플러시용 AOP를 하나 만드는 것입니다. 그리고 특정 리포지토리에 해당 애노테이션을 도입할 수 있습니다.
참고로 실무에서는 이 부분이 그렇게 문제가 되지 않는 경우가 많습니다. 왜냐하면 같은 테이블의 데이터를 JPA로 변경하고, 그 다음에 같은 테이블의 데이터를 SQL을 사용해서 조회하는 상황에 이 문제가 발생합니다. 그런데 실무에서 이런 경우가 자주 발생하지는 않습니다.
JPA의 효율성은 "상황에 따라 다르다" 가 가장 정확한 답변입니다.
효율적인 측면:
개발 생산성 및 유지보수성: SQL 중심이 아닌 객체 중심으로 개발할 수 있게 해주고, 반복적인 CRUD 코드를 크게 줄여줍니다. 이는 개발 시간 단축과 유지보수 용이성으로 이어져 전체적인 "효율"을 높입니다.
1차 캐시 (영속성 컨텍스트 내 캐시): 동일 트랜잭션 내에서 같은 엔티티를 여러 번 조회할 경우, DB를 다시 조회하지 않고 1차 캐시에서 가져오므로 성능 이점이 있습니다.
쓰기 지연 (Write-behind) 및 변경 감지 (Dirty checking):
엔티티의 변경사항을 즉시 DB에 반영하는 것이 아니라, 트랜잭션 커밋 시점에 모아서 한 번에 UPDATE 쿼리를 생성하여 실행합니다.
이를 통해 불필요한 UPDATE 쿼리를 줄일 수 있고, 최적화된 순서로 SQL을 실행할 수도 있습니다. (예: 여러 엔티티의 여러 필드가 변경되어도 엔티티당 하나의 UPDATE문만 생성)
Lazy Loading (지연 로딩): 연관된 엔티티를 실제 사용할 때 조회하여 불필요한 데이터 로딩을 피할 수 있습니다.
객체지향적인 쿼리 (JPQL, Criteria): 특정 DB 벤더에 종속되지 않는 쿼리를 작성할 수 있으며, 동적 쿼리 생성에도 유리합니다.
비효율적일 수 있는 측면 (주의점):
N+1 문제: 연관관계 매핑 시 fetch 전략을 잘못 설정하면 (특히 EAGER 로딩 남발 또는 JPQL에서 적절한 fetch join 미사용 시) 의도치 않게 많은 추가 쿼리가 발생하여 성능 저하를 유발합니다.
단순 UPDATE의 경우:
말씀하신 것처럼, 단순히 특정 필드 하나만 업데이트하기 위해 find()
로 엔티티를 조회(SELECT)하고, setXXX()
로 변경 후 커밋 시점에 UPDATE 쿼리가 나가는 것은 순수 SQL UPDATE문 하나보다 비효율적일 수 있습니다.
대안: 벌크 연산을 사용할 수 있습니다.
복잡한 쿼리: 매우 복잡하고 통계적인 쿼리는 JPQL이나 Criteria로 작성하기 어렵거나, 생성된 SQL이 비효율적일 수 있습니다. 이런 경우 Native Query, MyBatis, JdbcTemplate 등을 사용하는 것이 더 나을 수 있습니다.
ORM 자체의 오버헤드: SQL을 직접 사용하는 것보다 ORM 프레임워크를 거치면서 발생하는 약간의 오버헤드는 존재합니다.
결론:
JPA는 개발 생산성과 유지보수성, 그리고 객체지향적인 데이터 처리에 큰 강점이 있습니다. 많은 일반적인 웹 애플리케이션 시나리오에서 충분히 효율적입니다.
그러나 JPA의 내부 동작 원리(영속성 컨텍스트, fetch 전략 등)를 잘 이해하고 사용해야 하며, 때로는 성능 최적화를 위해 JPQL 벌크 연산이나 다른 기술(MyBatis, JdbcTemplate)을 함께 사용하는 유연성이 필요합니다.
단순히 "JPA가 SELECT 후 UPDATE를 하니 비효율적이다"라고 단정하기보다는, 어떤 상황에 어떤 기능을 사용하는 것이 적합한지 이해하고 활용하는 것이 중요합니다. 대부분의 경우, JPA가 제공하는 이점이 이러한 오버헤드를 상쇄하고도 남습니다.
감사합니다.
0
안녕하세요, 인프런 AI 인턴입니다. 한현진님. 질문해주신 내용은 JPA와 다양한 DB 접근 기술을 함께 사용할 때의 구조와 효율성에 관한 부분이네요. 이해를 돕기 위해 관련된 답변들을 정리해드리겠습니다.
여러 Repository 사용 시 구조 결정:
JPA와 다른 DB 접근 기술 혼용 사용:
EntityManager.flush()
를 사용하여 변경 사항을 DB에 반영한 후 다른 DB 접근 기술을 사용하도록 설계하는 것이 일반적입니다.EntityManager
를 의존하는 것은 적절한 접근 방식입니다.JPA의 효율성:
저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다. 현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏 추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.
상세한 답변 감사드립니다.
그런데 1번에서 궁금한것이 있는데
하나의 repository에 각 기술에 맞는 클래스? 객체? ex) entitymanager, datasource 등등 들을 의존 주입받아서 하나의 repository에서 사용하는 것 까지는 이해가 되었습니다.
그런데 스프링 jpa를 사용할때에는 인터페이스 자체에 jparepository를 상속받아서 스프링 자체에서 구현체를 만들어주는 것으로 알고있는데, 이 상황에서는 다른 기술들을 어떻게 하나의 repository로 사용가능한 것인지 궁금합니다.
답글로 질문을 남기고 지피티와 구글링으로 서치해보니 커스텀 repository 인터페이스를 따로 만들고 구현체에 Impl을 붙여서 스프링jpa 인터페이스의 이름과 맞춰서사용하면 스프링이 연결해준다고 한다는 결과를 얻었습니다. 혹시 이 질문댓글을 확인하시면 맞는지 아닌지 아니라면 정확한 방법이나 더 효율적인 방법이 있다면 말씀해주시면 감사하겠습니다!