[Spring] Repository 계층: Repository VS DAO, 도메인 객체와 entity 객체를 분리해야 하는 이유

2023. 6. 6. 15:37· Spring
목차
  1. 💋 Repository VS DAO
  2. 1. Repository에서, 하나의 테이블에만 접근하는 DAO에, 여러 번의 쿼리를 보내고, 반환된 entity 정보를 바탕으로 도메인 객체로 조립
  3. 2. DAO에서 여러 테이블을 join하는 쿼리를 보내고, DAO에서 도메인 객체를 조립해서 반환
  4. ✔ Repository에서 여러 번 쿼리 보내기 
  5. ✔ DAO에서 join을 포함한 쿼리 보내기 
  6. ✔ 다른 방법: Repository에서 JdbcTemplate을 이용해서 다른 테이블을 조인해서 한 번에 CartItem을 조립한다. 
  7. 💋 도메인 객체와 entity 객체를 분리하는 이유는 뭘까? 
반응형

이 포스팅은 내 생각을 담은 내용이므로 참고만 해주세요! 

 

 

 

 

💋 Repository VS DAO

 

아래와 같은 레거시 코드를 만났다.

 

CartItem 테이블에 접근하기 위해 만들어진 CartItemDao에서 다른 member, product 테이블을 join한 쿼리를 보내고 있었다. 

 

1. Repository에서, 하나의 테이블에만 접근하는 DAO에, 여러 번의 쿼리를 보내고, 반환된 entity 정보를 바탕으로 도메인 객체로 조립

 

[CartItemDao]

@Repository
public class CartItemDao {
    private final JdbcTemplate jdbcTemplate;

    public CartItemDao(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public List<CartItem> findByMemberId(Long memberId) {
        String sql = "SELECT cart_item.id, cart_item.member_id, member.email, product.id, product.name, product.price, product.image_url, cart_item.quantity " +
                "FROM cart_item " +
                "INNER JOIN member ON cart_item.member_id = member.id " +
                "INNER JOIN product ON cart_item.product_id = product.id " +
                "WHERE cart_item.member_id = ?";
        return jdbcTemplate.query(sql, new Object[]{memberId}, (rs, rowNum) -> {
            String email = rs.getString("email");
            Long productId = rs.getLong("product.id");
            String name = rs.getString("name");
            int price = rs.getInt("price");
            String imageUrl = rs.getString("image_url");
            Long cartItemId = rs.getLong("cart_item.id");
            int quantity = rs.getInt("cart_item.quantity");
            Member member = new Member(memberId, email, null);
            Product product = new Product(productId, name, price, imageUrl);
            return new CartItem(cartItemId, quantity, product, member);
        });
    }
// ...
}

 

왜인지 어색하게 느껴져서 (근거 없음) 아래와 같이, Repository 계층에서 세 가지 dao를 호출하도록 변경했다.

 

 

2. DAO에서 여러 테이블을 join하는 쿼리를 보내고, DAO에서 도메인 객체를 조립해서 반환

 

[CartItemRepository]

    public List<CartItem> findByMemberId(final Long memberId) {
        final List<CartItemEntity> entity = cartItemDao.findByMemberId(memberId);
        return entity.stream()
                .map(it -> new CartItem(
                        it.getId(),
                        it.getQuantity(),
                        productDao.getProductById(it.getProductId()).toProduct(),
                        memberDao.getMemberById(it.getMemberId()).toMember()
                ))
                .collect(Collectors.toList());
    }

 

join하는 대신, cartItemDao, productDao, memberDao로 세 번의 쿼리를 보내고 있었다.

각 쿼리는 모두 딱 하나의 테이블에만 접근하도록 만들어졌다. 

 

[CartItemDao]

    public List<CartItemEntity> findByMemberId(Long memberId) {
        String sql = "SELECT * FROM cart_item WHERE member_id = :member_id";
        final Map<String, Long> parameter = Map.of("member_id", memberId);
        return namedParameterJdbcTemplate.query(sql, parameter, CART_ITEM_ENTITY_ROW_MAPPER);
    }

 

[ProductDao]

    public ProductEntity getProductById(Long productId) {
        String sql = "SELECT * FROM product WHERE id = ?";
        return jdbcTemplate.queryForObject(sql, new Object[]{productId}, (rs, rowNum) -> {
            String name = rs.getString("name");
            int price = rs.getInt("price");
            String imageUrl = rs.getString("image_url");
            return new ProductEntity(productId, name, price, imageUrl);
        });
    }

 

[MemberDao]

    public MemberEntity getMemberById(Long id) {
        String sql = "SELECT * FROM member WHERE id = ?";
        List<MemberEntity> members = jdbcTemplate.query(sql, new Object[]{id}, new MemberRowMapper());
        return members.isEmpty() ? null : members.get(0);
    }

 

 

 

두 방법을 아래와 같이 요약할 수 있을 것 같다. 

 

1. Repository에서 여러 번 쿼리 보내기

2. DAO에서 join을 포함한 쿼리 보내기 

 

두 방법 중 어떤 것이 더 좋은 방법인지에 대한 고민이 생겼다.

 

 

 

 

✔ Repository에서 여러 번 쿼리 보내기 

 

  • 하나의 DAO가 하나의 테이블에만 접근해서 로직이 비교적 간단해진다.
  • 여러 번의 쿼리를 보내야 하기 때문에 Transactional에 대한 고민이 추가되고, 데이터베이스에서 제공하는 최적화에 대한 이용이 어려워진다.

 

 

✔ DAO에서 join을 포함한 쿼리 보내기 

 

  • join과 같이 데이터베이스에서 제공하는 최적화를 사용할 수 있다.
  • DAO에서 도메인 객체를 직접 조립하기 때문에, 도메인과 엔티티가 명확하게 구분되지 않는다. (도메인 객체를 entity처럼 사용하게 된다.)
  • 어떤 DAO에서 다른 테이블을 join 할지에 대한 기준을 명확히 해야 할 것이다. 
    • 현재 코드는 CartItem에 Product와 Member의 정보를 모두 포함하고 있다고 보고 작성된 것 같다. 

 

 

 

✔ 다른 방법: Repository에서 JdbcTemplate을 이용해서 다른 테이블을 조인해서 한 번에 CartItem을 조립한다. 

 

Repository는 도메인 객체를 저장하는 방법이 추상화된 것이라고 생각하는데,

내부에서 SQL문을 작성한다는 것은 구체적인 데이터베이스 종류를 이미 특정해버린 것이기 때문에 좋지 않다고 생각한다!

 

 

 


 

 

💋 도메인 객체와 entity 객체를 분리하는 이유는 뭘까? 

 

 

현재 미션에서는 데이터베이스 설계와 도메인 설계가 동시에 들어가기 때문에 entity와 도메인 구조를 크게 다르지 않게 가져갈 수 있었다.

 

하지만 서비스가 커지고, 새로운 비즈니스 로직이 추가되는 상황을 상상해 본다면, 데이터베이스 entity는 이미 존재하는 데이터로 고정된 형식을 이미 가지고 있을 것이다. 도메인은 완전히 새로운 비즈니스를 창조할 수 있는 영역이므로, 데이터베이스에 저장된 형태와 다르게 될 확률이 아주 높다고 생각한다. 따라서 이런 경우에는 두 가지를 분리하고 싶지 않더라도 분리할 수 밖에 없게 될 것 같다. 

 

 

 

 

반응형
저작자표시 비영리 변경금지 (새창열림)

'Spring' 카테고리의 다른 글

[Spring] 빈 스코프(Scope): 싱글톤, 프로토타입, 웹 관련 스코프  (0) 2023.06.15
[Spring] 빈 생명주기 콜백: 인터페이스(InitializingBean, DisposableBean), 설정 파일(initMethod, destroyMethod), 어노테이션(@PostConstruct, @PreDestroy)  (2) 2023.06.14
[Spring] 로컬 8080 포트 종료하기: Web server failed to start. Port 8080 was already in use.Web server failed to start. Port 8080 was already in use.  (0) 2023.05.28
[Spring] Property File (application.properties), @PropertySource: 자바 설정 파일에 프로퍼티 파일(외부 설정) 가져오기  (0) 2023.05.23
[Spring] 스프링 빈 등록 어노테이션 기반의 자바 코드로 설정하기: Java based Container Configuration  (0) 2023.05.22
  1. 💋 Repository VS DAO
  2. 1. Repository에서, 하나의 테이블에만 접근하는 DAO에, 여러 번의 쿼리를 보내고, 반환된 entity 정보를 바탕으로 도메인 객체로 조립
  3. 2. DAO에서 여러 테이블을 join하는 쿼리를 보내고, DAO에서 도메인 객체를 조립해서 반환
  4. ✔ Repository에서 여러 번 쿼리 보내기 
  5. ✔ DAO에서 join을 포함한 쿼리 보내기 
  6. ✔ 다른 방법: Repository에서 JdbcTemplate을 이용해서 다른 테이블을 조인해서 한 번에 CartItem을 조립한다. 
  7. 💋 도메인 객체와 entity 객체를 분리하는 이유는 뭘까? 
'Spring' 카테고리의 다른 글
  • [Spring] 빈 스코프(Scope): 싱글톤, 프로토타입, 웹 관련 스코프
  • [Spring] 빈 생명주기 콜백: 인터페이스(InitializingBean, DisposableBean), 설정 파일(initMethod, destroyMethod), 어노테이션(@PostConstruct, @PreDestroy)
  • [Spring] 로컬 8080 포트 종료하기: Web server failed to start. Port 8080 was already in use.Web server failed to start. Port 8080 was already in use.
  • [Spring] Property File (application.properties), @PropertySource: 자바 설정 파일에 프로퍼티 파일(외부 설정) 가져오기
깃짱
깃짱
연새데학교 컴퓨터과학과 & 우아한테크코스 5기 백엔드 스타라이토 깃짱
반응형
깃짱
깃짱코딩
깃짱
전체
오늘
어제
  • 분류 전체보기
    • About. 깃짱
    • Weekly Momentum
      • 2024
    • PROJECT
      • AIGOYA LABS
      • Stamp Crush
      • Sunny Braille
    • 우아한테크코스5기
    • 회고+후기
    • Computer Science
      • Operating System
      • Computer Architecture
      • Network
      • Data Structure
      • Database
      • Algorithm
      • Automata
      • Data Privacy
      • Graphics
      • ETC
    • WEB
      • HTTP
      • Application
    • C, C++
    • JAVA
    • Spring
      • JPA
      • MVC
    • AI
    • MySQL
    • PostgreSQL
    • DevOps
      • AWS
      • 대규모 시스템 설계
    • frontend
      • HTML+CSS
    • NextJS
    • TEST
    • Industrial Engineering
    • Soft Skill
    • TIL
      • 2023
      • 2024
    • Linux
    • Git
    • IntelliJ
    • ETC
      • 日本語

블로그 메뉴

  • 홈
  • 깃허브

인기 글

최근 글

태그

  • 함수형프로그래밍
  • 레벨로그
  • Stream
  • lamda
  • 우테코
  • OOP
  • 상속
  • 우아한테크코스5기
  • TDD
  • 우테코5기
  • 스트림
  • 예외
  • Composition
  • 우아한테크코스
  • 람다와스트림
  • 람다
  • 조합
  • 컴포지션
  • Java
  • 상속과조합
hELLO · Designed By 정상우.v4.2.0
깃짱
[Spring] Repository 계층: Repository VS DAO, 도메인 객체와 entity 객체를 분리해야 하는 이유
상단으로

티스토리툴바

개인정보

  • 티스토리 홈
  • 포럼
  • 로그인

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.