ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring Data 여러 기능
    카테고리 없음 2023. 4. 23. 14:06
    오늘은 Spring Data의 몇가지 기능들을 살펴 볼 예정이다. 예전에 작성했던 Spring data common 기타 기능과는 별개로 유용하게 사용할 수 있는 것들을 정리해 보도록 하자. 물론 어떤 특정한 버전, 프로젝트들은 따로 명시를 해놓도록 하겠다.

    query method

    Spring data 프로젝트의 특징인 query method는 아주 유용한 기능이다. 물론 복잡한 쿼리에는 사용할 수 없지만 간단한 쿼리를 작성하는데는 더할 나위 없이 유용한 기능이다. 필자도 간단한 쿼리를 작성할 때는 자주 이용하고 있다. 잘 모르고 있을 수도 있는 기능들을 좀 더 살펴보자.
    public interface PersonRepository extends CrudRepository<Person, String> {
    
        List<Person> findByName(String name);
    
    }
    
    만약 이름으로 select을 하고 있다면 대부분 위와 같이 작성을 할 것이다. find란 키워드와 by라는 키워드 spring data에서 정의한 키워드이다. 만약 두개의 키워드가 존재하지 않다면 쿼리메서드는 제대로 동작하지 않을 수 있다. 하지만 select 할때는 find 라는 키워드 말고도 여러 키워드들이 존재한다. 굳이 find 키워드를 사용할 필요 없이 다른 키워드를 사용해도 된다.
    public interface PersonRepository extends CrudRepository<Person, String> {
    
        List<Person> findByName(String name);
    
        List<Person> queryByName(String name);
    
        List<Person> streamByName(String name);
    
        List<Person> getByName(String name);
    
        List<Person> readByName(String name);
    
    }
    
    find 라는 키워드 말고도 query, stream, get, read 등으로 대체 할 수 있다. 이외에도 카운터를 세는 count 존재 여부를 알려주는 exists 등이 있다. 또한 삭제쿼리 메서드도 존재하는데 remove, delete 두가지 키워드를 사용해서 삭제할 수 있다.
    public interface PersonRepository extends CrudRepositor<Person, String> {
    
        void deleteByName(String name);
    
        void removeByName(String name);
    
    }
    
    
    위와 같이 사용해도 동일한 결과를 얻을 수 있다. find 라는 키워드 말고도 여러 키워드가 있으니 다른 키워드도 사용해도 좋다.
    사실 위와 같이 delete method 에는 @Modifying 어노테이션이 필요 없다. @Modifying 어노테이션은 @Query(update or delete) 어노테이션을 사용할 때만 작성하면 된다.
    또한 한글, 중국어, 일어 등도 가능하다. 사실 그럴일은 거의거의 없지만 그냥 가능하다고 만 알고 있자.
    //@Entity, //Document ...
    public class Person {
    
      // ...
    
      private String 이름;
    
      //... getter setter
    
    }
    
    public interface PersonRepository extends CrudRepositor<Person, String> {
    
      List<Book> findBy이름(String name);
    
    }
    
    

    entityName (Data-JPA)

    이것은 Jpa에 특화된 기능이다. 다른 data 하위 프로젝트엔 동작하지 않는다. @Query 어노테이션을 사용할 때 유용하게 사용될 수 있는 기능이다. entityName 이라는 키워드를 통해 해당 엔티티를 조회, 저장, 삭제등을 할 수 있다.
    @MappedSuperclass
    public class Product {
    
        @Id
        @GeneratedValue
        private Long id;
    
        private String name;
    
        ....
    }
    
    // book, disc 
    
    @NoRepositoryBean
    public interface ProductRepository<T extends Product> extends JpaRepository<T, Long> {
    
        @Query("select p from #{#entityName} p where p.name = :name")
        List<T> findName(String name);
    }
    
    
    public interface BookRepository extends ProductRepository<Book> {
    
    }
    
    public interface DiscRepository extends ProductRepository<Disc> {
    
    }
    
    
    주로 사용하고 있는 곳으로는 위와 같이 공통적인 상위 인터페이스를 정의 한 후 공통된 쿼리들을 entityName을 이용하여 처리 할 수 있다. @Query 어노테이션을 자주 사용하는 분들은 아주 유용한 기능인 듯 싶다.

    Mongo (Data-Mongo)

    Spring data mongo를 사용할 때 몇가지 유용한 기능이다. Document(model) 를 작성할 때 굳이 @Document 어노테이션과 @Id를 선언하지 않아도 된다.
    @Value
    public class Person {
    
        ObjectId id;
    
        String name;
    
        int age;
    
    }
    
    public interface PersonRepository extends MongoRepository<Person, ObjectId> {
    
    }
    
    위와 같이 Person 모델에 @Document 를 붙이지 않아도 자동으로 Spring data가 만들어서 넣어 준다. 만약 @Document 없다면 클래스명(첫글자는 소문자)으로 collection이 만들어 진다. @Id도 마찬가지로 id, _id 라는 필드가 존재하면 그것을 키로 잡아 저장을 한다. 만약 어노테이션이 많아 보기가 힘들다면 작성하지 않아도 되며 명시적인 어노테이션을 선호한다면 작성해도 무방하다. 단, 아래코드는 동작하지 않는다.

    @Value public class Person { ObjectId id; String name; int age; } public interface PersonRepository extends CrudRepository<Person, String> { }
    그 이유는 해당 엔티티가 어느 스토어를 사용하지는 알수 없어 빈으로 등록하지 못한다. 위의 경우엔 명시적으로 @Document 어노테이션을 사용하거나 직접적인 MongoRepository를 사용해야 한다.
    다른 spring data 하위 프로젝트(JPA는 해당사항 없다.) 들은 어떻게 되어 있는지 확인해보지 않았다. 만약 다른 프로젝트도 이런 기능이 있을 수도, 없을 수도 있으니 잘 보고 적용해야 된다.
    Spring data 쪽에는 엔티티를 위와 같이 불변으로 만들어도 된다. (사실.. 확인은 mongo, redis 만 했다. 이것 또한 JPA는 해당사항 없다.)
    JPA는 명시된 스펙으로 작성을 해야 되기 때문에 @Entity, @Id, 기본생성자, 정의된 스펙에 맞게 작성해야 한다.
    public class Person {
    
        private final ObjectId id;
    
        private final String name;
    
        private final int age;
    
        public Person(ObjectId id, String name, int age) {
            this.id = id;
            this.name = name;
            this.age = age;
        }
       // getter
    }
    
    or
    
    @Value
    public class Person {
    
        ObjectId id;
    
        String name;
    
        int age;
    
    }
    
    or
    
    @Value(staticConstructor = "of")
    public class Person {
    
        ObjectId id;
    
        String name;
    
        int age;
    
    }
    
    
    @Value 는 롬복에 있는 어노테이션이며 여기를 참고하며 되겠다. 또한 팩토리 메서드를 만들어 사용해도 된다. 다른 더 많은 기능이 있으나 주로 사용될만한 기능은 이정도 일듯 싶다.

    Streamable

    Spring data 2.0 에서 추가된 Streamable 인터페이스를 query method 를 사용할 때 리턴타입으로 사용할 수 있다.
    public interface PersonRepository extends MongoRepository<Person, String> {
    
        Streamable<Person> findByName(String name);
    
    }
    
    해당 인터페이스를 사용하면 바로 map, filter를 사용할 수 있다.
     List<String> names = people.map(Person::getName)
                        .stream()
                        .collect(Collectors.toList());
    
    사실 사용 Api를 보면 List를 사용하는 것과 동일하다. 어차피 Stream으로 다시 만들어 하기에 굳이 사용할 필요가 있나 싶기도 하다. 하지만 spring data 2.2 부터는 (현재기준으로 아직 릴리즈는 되지 않았다) 조금 더 간편하게 사용할 수 있다.
    List<String> names = people.map(Person::getName)
                        .toList()
    
    Set<String> names = people.map(Person::getName)
                        .toSet()
    
    내부적인 코드는 동일하지만 사용하는 Api는 간단하게 사용할 수 있다. 또한 spring data 2.2 부터는 좀 더 커스텀한 Wrapper Type의 Streamable 만들어 사용할 수 있다.
    //@RequiredArgsConstructor(staticName = "of")
    @RequiredArgsConstructor
    public class Persons implements Streamable<Person> {
    
        private final Streamable<Person> people;
    
        public int getAge() {
            return people.stream()
                    .mapToInt(Person::getAge)
                    .sum();
        }
    
        @Override
        public Iterator<Person> iterator() {
            return people.iterator();
        }
    }
    
    
    public interface PersonRepository extends MongoRepository<Person, String> {
    
        Persons readByName(String name);
    
    }
    
    Persons people = personRepository.readByName("wonwoo");
    int totalAge = people.getTotalAge();
    
    
    이렇게 Wrapper Type을 만들어 내부적으로 기능들을 추가할 수 있어 유용한 기능인 것 같다. 오늘은 이렇게 Spring data 쪽에 사용되는 유용한 기능들을 살펴봤다. 조금이라도 도움이 되었으면 좋겠다. 오랜만에 포스팅을 했다. 이런저런 일도 있고 사실 귀찮았던게 더 컷다. 일주일에 한번(적어도 이주일엔)은 꼭 쓰도록 노력해야겠다.

    댓글

Designed by Tistory.