오늘은 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은 여기서 마치겠다. 지금까지의 소스는 여기에 있다.