JDBC 기반 데이터 액세스 계층(4) - Spring Data JDBC에서의 엔티티와 레포지토리

Backend kwon·2023년 9월 4일
0

Java에서 테이블의 외래키(Foreign key)를 표현하는 일반적인 방법: 클래스의 객체 참조 리스트(List)

테이블 간의 관계는 외래키라는 연결 요소가 있어서 직관적입니다.
그런데 클래스들 간에는 외래키라는 연결 요소가 없습니다. 대신에 클래스들은 객체 간에 참조가 가능하기 때문에 이 객체 참조를 사용해서 외래키의 기능을 대신합니다

Spring Data JDBC에서의 애그리거트(Aggregate) 객체 매핑

애그리거트 객체 매핑 규칙

(1) 모든 엔티티 객체의 상태는 애그리거트 루트를 통해서만 변경할 수 있다.

Ex) 배달음식의 주소 변경시 음식이 완성이 되서 이미 배달중인 경우의 문제

 
(2) 하나의 동일한 애그리거트 내에서의 엔티티 객체 참조

  • 동일한 하나의 애그리거트 내에서는 엔티티 간에 객체로 참조한다.

 
(3) 애그리거트 루트 대 애그리거트 루트 간의 엔티티 객체 참조

  • 애그리거트 루트 간의 참조는 객체 참조 대신에 ID로 참조한다.

Ex) order 엔티티에서 long memberId로 외래키를 표현할수도 있지만,
Spring Data JDBC에서는 AggregateReference 라는 클래스를 이용해 private AggregateReference<Member, Long> memberId; 와 같이 외래키를 표현할 수도 있습니다.

  • 1대1 또는 1대N 관계일 때 테이블 간의 외래키 방식과 동일하다.

  • N대 N 관계일 때는 외래키 방식인 ID 참조와 객체 참조 방식이 함께 사용된다.

@Getter
@Setter
@Table("ORDERS")
public class Order {
    @Id
    private long orderId;

    // 테이블 외래키처럼 memberId를 추가한다.
    private long memberId;

    // (1)
    @MappedCollection(idColumn = "ORDER_ID")
    private Set<OrderCoffee> orderCoffees = new LinkedHashSet<>();
		
		...
		...
}

@MappedCollection(idColumn = "ORDER_ID", keyColumn = "ORDER_COFFEE_ID") 은 엔티티 클래스 간에 연관 관계를 맺어주는 정보를 의미합니다.

idColumn 애트리뷰트는 자식 테이블에 추가되는 외래키에 해당되는 열명을 지정합니다.

ORDERS 테이블의 자식 테이블은 ORDER_COFFEE 테이블이고 이 ORDER_COFFEE 테이블은 ORDERS 테이블의 기본키인 ORDER_ID 열의 값을 외래키로 가집니다.

keyColumn 애트리뷰트는 외래키를 포함하고 있는 테이블의 기본키 열명을 지정합니다.

ORDERS 테이블의 자식 테이블인 ORDER_COFFEE 테이블의 기본키는 ORDER_COFFEE_ID 이므로, keyColumn의 값이 ‘ORDER_COFFEE_ID’인 것입니다

point!
1. N대 N의 관계를 —> 1대 N, N대 1의 관계로 변경
2. 1대 N, N대 1의 관계를 OrderCoffee 같이 중간에서 ID를 참조하게 해주는 클래스를 통해 다시 1대 N대1의 관계로 변경

 

리포지토리(Repository) 인터페이스 정의

public interface MemberRepository extends CrudRepository<Member, Long> {

      Optional<Member> findByEmail(String email);
}

Spring Data JDBC에서는 CrudRepository라는 인터페이스를 제공해주고 있으며, 이 CrudRepository의 기능을 사용하기 위해서 MemberRepository가 CrudRepository를 상속하고 있습니다.

우리는 이 CrudRepository 인터페이스를 통해서 편리하게 데이터를 데이터베이스의 테이블에 저장, 조회, 수정, 삭제할 수 있습니다.

CrudRepository<Member, Long>에서 Member는 Member 엔티티 클래스를 가리키며, Long은 Member 엔티티 클래스에서 @Id 애너테이션이 붙은 멤버 변수의 타입을 가리킵니다.

Spring Data JDBC에서는 ‘find + By + SQL 쿼리문에서 WHERE 절의 열명 + (WHERE 절 열의 조건이 되는 데이터) ’ 형식으로 쿼리 메서드(Query Method)를 정의하면 조건에 맞는 데이터를 테이블에서 조회합니다.

정의한 쿼리 메서드(Query Method)는 내부적으로 아래의 SQL 쿼리문으로 변환되어 데이터베이스의 MEMBER 테이블에 질의를 보냅니다.

SELECT "MEMBER"."NAME" AS "NAME", "MEMBER"."PHONE" AS "PHONE", "MEMBER"."EMAIL" AS "EMAIL", "MEMBER"."MEMBER_ID" AS "MEMBER_ID" FROM "MEMBER" WHERE "MEMBER"."EMAIL" = ?

쿼리 메서드(Query Method)
: Spring Data JDBC에서는 쿼리 메서드를 이용해서 SQL 쿼리문을 사용하지 않고 데이터베이스에 질의를 할 수 있습니다.
기본적인 사용법은 ‘find + By + SQL 쿼리문에서 WHERE 절의 열명 + (WHERE 절 열의 조건이 되는 데이터) ’ 형식이며, WHERE 절의 조건 열을 여러 개 지정하고 싶다면 ‘And’를 사용하면 됩니다.

만약 복잡한 쿼리문을 작성하고자 한다면 @Query 애너테이션을 사용합니다.
@Query 애너테이션은 쿼리 메서드명을 기준으로 SQL 쿼리문을 생성하는 것이 아니라 개발자가 직접 쿼리문을 작성해서 질의를 할 수 있도록 해줍니다.

그리고 MemberRepository 인터페이스는 정의했지만 인터페이스의 구현 클래스는 별도로 구현을 한 적이 없습니다.

-> Spring Data JDBC에서 내부적으로 Java의 리플렉션 기술과 Proxy 기술을 이용해서 MemberRepository 인터페이스의 구현 클래스 객체를 생성해 줍니다.

 

Optional.ofNullable(…)을 사용하는 이유

파라미터로 전달받은 member 객체는 클라이언트 쪽에서 사용자가 이름 정보나 휴대폰 정보를 선택적으로 수정할 수 있기 때문에 name 멤버 변수가 null일 수 도 있고, phone 멤버 변수가 null일 수도 있습니다.

이처럼 멤버 변수 값이 null일 경우에는 Optional.of()가 아닌 Optional.ofNullable()을 이용해서 null 값을 허용할 수 있습니다.

따라서 값이 null이더라도 NullPointerException이 발생하지 않고, 다음 메서드인 ifPresent() 메서드를 호출할 수 있습니다.

수정할 값이 있다면(name 또는 phone 멤버 변수의 값이 null이 아니라면) ifPresent() 메서드 내의 코드가 실행이 되고, 수정할 값이 없다면 (name 또는 phone 멤버 변수의 값이 null이라면) 아무 동작도 하지 않습니다.

profile
백엔드개발자를 향해서

0개의 댓글