ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • spring boot 의 spring data nosql (4) example
    카테고리 없음 2023. 4. 22. 14:40
    오늘은 Spring boot에서 지원해주는 spring data nosql의 마지막시간이다. 저번시간까지 Redis, MongoDB, Neo4j 에 대해서 예제를 살펴봤는데 오늘은 Solr, Elasticsearch, Cassandra, Couchbase 예제들을 살펴보도록 하자. 거의 비슷한 맥략이라 손쉽게 따라 할 수 있을 듯하다.

    Solr

    Solr의 경우에도 SolrCrudRepository가 존재한다. 별다른 설정 없이 해당 인터페이스를 이용해서 repositories를 사용하면 된다.
    public interface PersonRepository extends SolrCrudRepository<Person, String> {
    
      Person findByName(String name);
    }
    
    다들 알기 때문에 설명할 내용이 없다. Solr 역시도 메서드 이름 기반의 쿼리를 지원한다. 테스트를 해보자.
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class PersonRepositoryTests {
    
      @Autowired
      private PersonRepository personRepository;
    
      @Test
      public void repository() {
        personRepository.deleteAll();
        personRepository.save(new Person("wonwoo"));
        personRepository.save(new Person("kevin"));
        assertThat(personRepository.findByName("wonwoo").getName()).isEqualTo("wonwoo");
        assertThat(personRepository.findAll()).hasSize(2);
      }
    }
    
    테스트도 잘 통과 하였다. Solr도 SolrTemplate이라는 클래스가 존재하는데 이는 자동 설정에 포함안되어 있는 것 같다. 그래서 따로 설정을 해줘야 하는데 다음과 같이 설정해주면 된다.
    @Bean
    public SolrClient solrClient(SolrProperties solrProperties) {
      return new HttpSolrClient(solrProperties.getHost());
    }
    
    @Bean
    public SolrTemplate solrTemplate() {
      return new SolrTemplate(solrClient(null));
    }
    
    이렇게 위와 같이 SolrTemplate은 SolrClient solr프로젝트의 클라이언트를 디펜더시 받고 있다. SolrTemplate의 사용법은 간단히 살펴보자.
    @Component
    public class PersonTemplate {
    
      private final SolrTemplate solrTemplate;
      private final String collectionName = "collection1";
    
      public PersonTemplate(SolrTemplate solrTemplate) {
        this.solrTemplate = solrTemplate;
      }
    
      protected long count(Query query) {
        return this.solrTemplate.count(collectionName, SimpleQuery.fromQuery(query));
      }
    
      public long count() {
        return count(new SimpleQuery(new Criteria(Criteria.WILDCARD).expression(Criteria.WILDCARD)));
      }
    
    
      public void deleteAll() {
        this.solrTemplate.delete(collectionName, new SimpleFilterQuery(new Criteria(Criteria.WILDCARD).expression(Criteria.WILDCARD)));
        this.solrTemplate.commit(this.collectionName);
      }
    
      public void save(Person person) {
        this.solrTemplate.saveBean(person);
        this.solrTemplate.commit(this.collectionName);
      }
    
      public Page<Person> findAll() {
        Pageable pageable = new SolrPageRequest(0, (int) count());
        return this.solrTemplate.queryForPage(collectionName,
            new SimpleQuery(new Criteria(Criteria.WILDCARD).expression(Criteria.WILDCARD))
                .setPageRequest(pageable),
            Person.class);
      }
    }
    
    페이징 처리를 해서 조금 복잡해 보이는데 페이징 처리를 뺀다면 그나마 덜 복잡하다. 일반적으로 read는 빼고 거의 다른 template과 동일하다. 하지만 solr 역시 검색 스토어이기에 쿼리가 조금 복잡해 보일 수 있다. 한번 테스트를 해보자.
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class PersonTemplateTests {
    
      @Autowired
      private PersonTemplate personTemplate;
    
      @Test
      public void template() {
        personTemplate.deleteAll();
        personTemplate.save(new Person(UUID.randomUUID().toString(), "wonwoo"));
        personTemplate.save(new Person(UUID.randomUUID().toString(),"kevin"));
        assertThat(personTemplate.findAll()).hasSize(2);
      }
    }
    

    Elasticsearch

    Elasticsearch 역시 검색 스토어이다. solr와 마찬가지로 아파치 루씬을 검색엔진으로 삼고 있다. 어떤게 더 좋은지는 모르겠다. 각각이 장단점이 있으니 살펴보고 결정하면 되겠다.
    public interface PersonRepository extends ElasticsearchRepository<Person, String> {
    
      Person findByName(String name);
    }
    
    
    Elasticsearch 도 ElasticsearchRepository 라는 인터페이스를 제공해주고 있다. 일반적으로 spring data들의 Repository 구현체명들은 Simple* 로 시작한다. 예를들어 ElasticsearchRepository의 구현체는 SimpleElasticsearchRepository이다. 테스트를 해보자.
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class PersonRepositoryTests {
    
      @Autowired
      private PersonRepository personRepository;
    
      @Test
      public void repository(){
        personRepository.deleteAll();
        personRepository.save(new Person("wonwoo"));
        personRepository.save(new Person("kevin"));
        assertThat(personRepository.findByName("wonwoo").getName()).isEqualTo("wonwoo");
        assertThat(personRepository.findAll()).hasSize(2);
      }
    }
    
    ElasticsearchTemplate도 spring boot에서 자동설정에 포함되어있다. 그래서 아무 설정하지 않아도 자동으로 ElasticsearchTemplate이 빈으로 등록된다. 그래서 우리는 사용만 하면 된다.
    @Component
    public class PersonTemplate {
      private final ElasticsearchTemplate elasticsearchTemplate;
    
      public PersonTemplate(ElasticsearchTemplate elasticsearchTemplate) {
        this.elasticsearchTemplate = elasticsearchTemplate;
      }
    
      public void deleteAll() {
        DeleteQuery deleteQuery = new DeleteQuery();
        deleteQuery.setQuery(matchAllQuery());
        this.elasticsearchTemplate.delete(deleteQuery, Person.class);
        this.elasticsearchTemplate.refresh(Person.class);
      }
    
      public void save(Person person) {
        IndexQuery indexQuery = new IndexQuery();
        indexQuery.setObject(person);
        this.elasticsearchTemplate.index(indexQuery);
        this.elasticsearchTemplate.refresh(Person.class);
      }
    
      public List<Person> findAll() {
        SearchQuery query = new NativeSearchQueryBuilder().withQuery(matchAllQuery()).build();
        return this.elasticsearchTemplate.queryForList(query, Person.class);
      }
    
    }
    
    
    Elasticsearch도 검색 스토어이기에 검색 관련 API가 많다. 그래서 사용하려면 해당 문서를 잘보면서 테스트를 해봐야 될 듯 싶다. 잘 동작하나 테스트를 해보자.
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class PersonTemplateTests {
    
      @Autowired
      private PersonTemplate personTemplate;
    
      @Test
      public void template() {
        personTemplate.deleteAll();
        personTemplate.save(new Person("wonwoo"));
        personTemplate.save(new Person("kevin"));
            assertThat(personTemplate.findAll()).hasSize(2);
      }
    }
    

    Cassandra

    Cassandra도 CassandraRepository를 지원한다. 별다른 설정 없이도 CassandraRepository 또는 TypedIdCassandraRepository를 이용해도 된다.
    public interface PersonRepository extends CassandraRepository<Person> {
    
      Person findByName(String name);
    
    }
    
    Cassandra도 다른 Repository와 동일하게 메서드 이름 기반의 쿼리를 지원한다. 한번 테스트를 해보자.
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class PersonRepositoryTests {
    
      @Autowired
      private PersonRepository personRepository;
    
      @Test
      public void repository() {
        personRepository.deleteAll();
        personRepository.save(new Person(UUIDs.timeBased(),"wonwoo"));
        personRepository.save(new Person(UUIDs.timeBased(),"kevin"));
        assertThat(personRepository.findByName("wonwoo").getName()).isEqualTo("wonwoo");
        assertThat(personRepository.findAll()).hasSize(2);
      }
    
    }
    
    잘 동작하는 것을 볼 수 있다. 마찬가지로 CassandraTemplate도 자동 설정에 포함 되어있어 별다른 설정 없이도 우리는 그냥 사용해도 된다.
    @Component
    public class PersonTemplate {
    
      private final CassandraTemplate cassandraTemplate;
    
      public PersonTemplate(CassandraTemplate cassandraTemplate) {
        this.cassandraTemplate = cassandraTemplate;
      }
    
      public void deleteAll() {
        cassandraTemplate.deleteAll(Person.class);
      }
    
      public void save(Person person) {
        cassandraTemplate.insert(person);
      }
    
      public List<Person> findAll() {
        return cassandraTemplate.selectAll(Person.class);
      }
    }
    
    
    CassandraTemplate API 역시도 사용하기 쉽고 직관적이다. 무엇을 하는지 메서드명만 봐도 어떤 일을 하는지 알 수 있다. 한번 테스트를 해보자.
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class PersonTemplateTests {
    
      @Autowired
      private PersonTemplate personTemplate;
    
      @Test
      public void template() {
        personTemplate.deleteAll();
        personTemplate.save(new Person(UUIDs.timeBased(), "wonwoo"));
        personTemplate.save(new Person(UUIDs.timeBased(),"kevin"));
        assertThat(personTemplate.findAll()).hasSize(2);
      }
    }
    

    Couchbase

    마지막으로 알아볼 Couchbase도 CouchbaseRepository를 지원한다. 별다른 설정 없이도 CouchbaseRepository를 이용하면 된다.
    @ViewIndexed(designDoc = "person")
    public interface PersonRepository extends CouchbaseRepository<Person, String> {
    
      @View(viewName = "byName")
      List<Person> findByName(String name);
    }
    
    
    Couchbase 는 좀 까탈스럽다. View도 생성해야 하며 자바스크립트로 코딩도 해야 된다. View code에 필자는 다음과 같이 넣어 두었다.
    function (doc, meta) {
      emit(doc.name, null);
    }
    
    byName라는 뷰를 생성한 후에 테스트를 해보자.
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class PersonRepositoryTests {
    
      @Autowired
      private PersonRepository personRepository;
    
      @Test
      public void repository() {
        personRepository.deleteAll();
        personRepository.save(new Person(UUID.randomUUID().toString(),"wonwoo"));
        personRepository.save(new Person(UUID.randomUUID().toString(),"kevin"));
        assertThat(personRepository.findByName("wonwoo").get(0).getName()).isEqualTo("wonwoo");
        assertThat(personRepository.findAll()).hasSize(2);
      }
    }
    
    필자가 잘 몰라서 List 타입으로 반환하였다. 단일 정보를 반환하려면 어떻게 하지? 아직 깊이 있게 보는 중은 아니라 프로덕션이나 필자가 개인적으로 사용한다면 다시 살펴 볼 예정이다. 마찬가지로 CouchbaseTemplate도 별다른 설정을 하지 않아도 자동으로 빈으로 등록 된다.
    @Component
    public class PersonTemplate {
    
      private final CouchbaseTemplate couchbaseTemplate;
    
      public PersonTemplate(CouchbaseTemplate couchbaseTemplate) {
        this.couchbaseTemplate = couchbaseTemplate;
      }
    
      public void deleteAll() {
        ViewQuery query = ViewQuery.from("person", "all");
        query.reduce(false);
        query.stale(couchbaseTemplate.getDefaultConsistency().viewConsistency());
    
        ViewResult response = couchbaseTemplate.queryView(query);
        for (ViewRow row : response) {
          try {
            couchbaseTemplate.remove(row.id());
          } catch (DataRetrievalFailureException e) {
            //ignore
          }
        }
      }
    
      public void save(Person person) {
        couchbaseTemplate.save(person);
      }
    
      public List<Person> findAll() {
        ViewQuery query = ViewQuery.from("person", "all");
        query.reduce(false);
        query.stale(couchbaseTemplate.getDefaultConsistency().viewConsistency());
        return couchbaseTemplate.findByView(query, Person.class);
      }
    
    }
    
    별다른 설정 없이도 CouchbaseTemplate를 사용하였다. 테스트를 해보자.
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class PersonTemplateTests {
    
      @Autowired
      private PersonTemplate personTemplate;
    
      @Test
      public void template() {
        personTemplate.deleteAll();
        personTemplate.save(new Person(UUID.randomUUID().toString(), "wonwoo"));
        personTemplate.save(new Person(UUID.randomUUID().toString(),"kevin"));
        assertThat(personTemplate.findAll()).hasSize(2);
      }
    }
    
    뷰를 잘 설정 했다면 정상적으로 동작할 것으로 보인다. 이렇게 간단하게나마 Spring boot에서 지원해주는 nosql들을 살펴봤다. 물론 필자도 다 사용하지는 않는다. 현재는 개인적으로 레디스와 엘라스틱 서치만 사용하고 있다. 여기 필자가 만든 블로그 중에 여기 있는 검색 시스템이 엘라스틱 서치로 개발되어 있다. 물론 아주 잘 사용한 건 아니지만 한번 해봤다는 것에 의미를 두고 있다. 만약 프로덕션에 사용한다면 좀 더 공부를 하고 사용해야 될 듯 싶다. 그럼 Spring data nosql은 여기서 마치겠다. 지금까지의 소스는 여기에 있다.

    댓글

Designed by Tistory.