[스프링 인 액션] 3장 JPA : 데이터로 작업하기

    3장 데이터로 작업하기 - JPA

    💻 실습 : https://github.com/cusbert/spring-in-action-5th

    🎯 이 장에서 배우는 내용

    • 스프링 데이터 (Spring Data) 를 사용해서 JPA 선언하고 사용하기

    3.2 JPA 를 사용해서 데이터 저장하고 사용하기

    대표적인 스프링 데이터 프로젝트

    • 스프링 데이터 JPA : 관계형 데이터베이스 JPA 퍼시스턴스
    • 스프링 데이터 MongoDB: 몽고 문서형 데이터베이스의 퍼시스턴스
    • 스프링 데이터 Neo4: Neo4j 그래프 데이터베이스의 퍼시스턴스
    • 스프링 데이터 Redis: 레디스 Key-value 스토어 퍼시스턴스
    • 스프링 데이터 cassandra: 카산드라 퍼시스턴스

    3.2.1 스프링 데이터 JPA 프로젝트에 추가하기

    • JPA 스타터는 JPA 및 JPA 를 구현한 Hibernate 지원한다.
        <!-- jpa -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

    3.2.2 도메인 객체에 어노테이션 추가하기

    • 도메인 객체에 @Entity를 추가하여 JPA 개체(Entity) 로 선언한다.
    • @Id : 이 속성이 데이터베이스의 개체를 고유하게 식별하게 한다.
    • @GeneratedValue(strategy= GenerationType.AUTO) : id 는 디비가 자동으로 생성해주는 ID 값이 사용된다
      • 'hibernate_sequence'라는 키 생성 전용 테이블이 있다.
    • @NoArgsConstructor : 인자가 없는 생성자를 가지게 한다.
      • AccessLevel.PRIVATE : 클래스 외부에서는 사용 불가하다
      • force = true: 초기화 필요한 final 속성을 가지고 있으므로 설정 추가했다
    • @Data: @RequiredArgsConstructor 를 생성하지만 @NoArgsConstructor 때문에 인자가 있는 생성자는 제거 된다
      • 단, @RequiredArgsConstructor 를 따로 선언 하였으므로 인자 있는 생성자를 가질 수 있다.
    • @PrePersist : 엔티티 속성을 현재일자와 시간으로 설정한다.
    • @Table : 테이블 명을 지정한다.
        @Data
        @RequiredArgsConstructor
        @NoArgsConstructor(access = AccessLevel.PRIVATE, force = true)
        @Entity
        @Table(name = "ingredient")
        public class Ingredient {
    
            @Id
            private final String id;
            private final String name;
            private final Type type;
    
            public static enum Type {
                WRAP, PROTEIN, VEGGIES, CHEESE, SAUCE
            }
        }
        @Data
        @Entity
        @Table(name = "TACO")
        public class Taco {
    
            @Id
            @GeneratedValue(strategy= GenerationType.AUTO)
            private Long id;
    
            private Date createdAt;
    
            @NotNull
            @Size(min=5, message = "Name must be at least 5 characters long")
            private String name;
    
            @ManyToMany(targetEntity=Ingredient.class)
            @Size(min=1, message = "You must choose at least 1 ingredient")
            private List<Ingredient> ingredients;
    
            @PrePersist
            void createdAt() {
                this.createdAt = new Date();
            }
        }

    3.2.3 JPA Repository 선언하기

    • JDBC 버전의 리퍼지토리에서는 리퍼지터리가 제공하는 메서드를 우리가 명시적으로 선언했다.
    • 스프링 데이터에서는 CrudRepository 인터페이스를 확장할 수 있다
    • CrudRepository 인터페이스는 CRUD 를 위한 많은 메서드가 선언되어 있다
    • CrudRepository 인터페이스를 따로 구현해줄 필요가 없다 (커스터마이징 추가할 것이 아니라면)
    • 애플리케이션이 시작될 때 스프링 데이터 JPA가 각 인터페이스 구현체들을 자동으로 생성해준다.
    • CrudRepository는 매개변수화 타입이다.
    • CrudRepository<리퍼지토리에 저장되는 개체 타입, 개체id속성>
    public interface IngredientRepository extends CrudRepository<Ingredient, String> {
    }
    public interface TacoRepository extends CrudRepository<Taco, Long> {
    }

    초기 데이터로드를 위한 부트스트랩 클래스 변경하기

    • 초기데이터 로드가 필요하다면 아래와 같이 부트스트랩 클래스를 변경한다.
    • 애플리케이션이 시작되면서 호출되는 dataLoader()에서 식자재 데이터를 미리 저장하게 한다.
        @SpringBootApplication
        public class TacoCloudApplication {
    
            public static void main(String[] args) {
                SpringApplication.run(TacoCloudApplication.class, args);
            }
    
            @Bean
            public CommandLineRunner dataLoader(IngredientRepository repo) {
                return new CommandLineRunner() {
                    @Override
                    public void run(String... args) throws Exception {
                        repo.save(new Ingredient("FLTO", "Flour Tortilla", Type.WRAP));
                        repo.save(new Ingredient("COTO", "Corn Tortilla", Type.WRAP));
                    }
                };
            }
        }

    컨버터 변경하기

    • null 반환 될 때를 대비하여 Optional로 변경한다
        @Override
        public Ingredient convert(String id) {
            Optional<Ingredient> optionalIngredient = ingredientRepository.findById(id);
            return optionalIngredient.isPresent() ? optionalIngredient.get() : null;
        }

    3.2.4 JPA Repository 커스터마이징하기

    • 본질적으로 스프링 데이터는 일종의 DSL(Domain Specific Language) 로 정의하고 있어 퍼시스트에 관한 내용이 메서드의 시그니처에 표현된다
        public interface OrderRepository extends CrudRepository<Order, Long> {
            List<Order> findByDeliveryZip(String deliveryZip);
            List<Order> readOrdersByDeliveryZipAndPlacedAtBetween(
                    String deliveryZip, Date StartDate, Date endDate
            );
        }
    • 간단한 쿼리는 이름 규칙이 유용하지만 더 복잡해질 경우에는 메소드에 이름만으로는 감당하기 힘들다
    • 이때는 어떤 이름이든 원하는 메소드 이름을 지정한 후 쿼리에 @Query 어노테이션을 지정한다.
        @Query("Order o where o.deliveryCity='Seattle'")
        List<Order> readOrdersDeliverydInSeattle();

    📌 요약

    • 스프링 데이터 JPA는 리퍼지터리 인터페이스를 작성하듯이 JPA 퍼시스턴스를 쉽게 해준다

    참고

    반응형

    댓글

    Designed by JB FACTORY