카테고리 없음
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 쪽에 사용되는 유용한 기능들을 살펴봤다. 조금이라도 도움이 되었으면 좋겠다.
오랜만에 포스팅을 했다. 이런저런 일도 있고 사실 귀찮았던게 더 컷다. 일주일에 한번(적어도 이주일엔)은 꼭 쓰도록 노력해야겠다.