오늘은 저번시간에 이어서 Spring boot 에서 지원해주는 nosql의 예제를 간단하게 살펴보자. 깊이 있는 내용은 아니고 아주 간단한 예제이므로 더 상세한걸 원하면 해당되는 문서를 찾아서 보길 권장한다.
디펜더시 부분은 저번시간에 했기에 생략하도록 하자.

Redis

Redis 같은 경우에는 딱히 RedisRepository라는 것이 존재하지 않는다. KeyValueRepository라는 것이 존재하는데 아마도 key value 스토어들을 추상화해서 만든 Repository라고 생각된다. redis 말고도 찾아보니까 Riak 라는 것도 있는데 이건잘..
아무튼 KeyValueRepository를 사용해도 되고 CrudRepository를 사용해도 되고 PagingAndSortingRepository를 사용해도 상관없다. 하지만 이 예제에서는 CrudRepository를 사용했다. 딱히 paging 처리 예제가 없기에..

public interface PersonRepository extends CrudRepository<Person, String> {

  Person findByName(String name);
}

아주 심플하게 구현이 완료 되었다. 이 역시 메서드 이름 기반의 쿼리를 지원한다. 잘 동작을 하는지 테스트를 해보자.


@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); } }

별다른 내용을 작성하지 않았지만 테스트를 통과하였다.
이번에는 Repository 말고 RedisTemplate을 살펴보자. Spring boot는 자동으로 RedisTemplate을 빈으로 등록시킨다. 자동으로 등록시키는 빈은 두가지가 있는데 한가지는 RedisTemplate<Object, Object> 타입이고 다른 한가지는 StringRedisTemplate (실제론 RedisTemplate<String, String>와 동일)이다. RedisTemplate 같은 경우에는 타입파라미터 2개를 선언해줘야 한다. 첫번째 타입 파라미터는 Key에 대한 타입이고 두번째 타입파라미터는 Value의 대한 타입이다. 별다른 설정을 하기 귀찮다면 그냥 StringRedisTemplate 을 사용해도 된다. 그럼 Key Value 모두 String 타입이어야만 한다. 하지만 필자는 특정한 도메인을 넣기 위해 추가적인 작업을 했다.

@Bean
public RedisTemplate<String, Person> personRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
  RedisTemplate<String,Person> template = new RedisTemplate<>();
  template.setValueSerializer(new Jackson2JsonRedisSerializer<>(Person.class));
  template.setConnectionFactory(redisConnectionFactory);
  return template;
}

Spring 에서 지원해주는 Jackson2JsonRedisSerializer를 사용해서 컨버팅을 하였다. 한번 사용해보도록 하자.

@Component
public class PersonTemplate {

  private final RedisTemplate<String,Person> personRedisTemplate;

  public PersonTemplate(RedisTemplate<String, Person> personRedisTemplate) {
    this.personRedisTemplate = personRedisTemplate;
  }

  public void deleteAll() {
    personRedisTemplate.delete("persons");
  }

  public void save(Person person) {
    personRedisTemplate.opsForList().leftPush("persons", person);
  }

  public List<Person> findAll() {
    return personRedisTemplate.opsForList().range("persons", 0, -1);
  }
}

간단하게 사용할 수 있는 delete, save, findAll를 만들었다. 이 역시 테스트를 해보자.

@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);
  }
}

Redis에는 다양한 자료구조를 지원하니 좀 더 복잡하게 저장과 조회를 해야 된다면 문서를 찾아보는 것이 좋을 듯하다. 필자도 겉핥기만 하는식이라..

MongoDB

몽고 디비 경우에는 Spring Data 에서 지원해주는 MongoRepository를 사용하면 된다. 이 역시 메서드 이름 기반의 쿼리를 지원한다.

public interface PersonRepository extends MongoRepository<Person, String>{

  Person findByName(String name);
}

MongoRepository 인터페이스 경우에도 PagingAndSortingRepository를 상속하고 있기에 페이징처리를 손쉽게 할 수 있다. redis와 동일하게 테스트를 해보자.

@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);
  }
}

테스트를 통과 하는 것을 볼 수 있다. 다음으로는 MongoTemplate을 살펴보자. 이 역시 딱히 별다른 커스텀하게 설정을 변경하지 않는다면 Spring boot에서 자동으로 지원해주는 설정만으로도 충분히 개발할 수 있다고 생각 된다. 호스트 정보, 아이디, 패스워드 정보 기타 몽고디비에서 사용하는 정보들도 프로퍼티로 지정할 수 있으니 참고하면 되겠다.

@Component
public class PersonTemplate {

  private final MongoTemplate mongoTemplate;

  public PersonTemplate(MongoTemplate mongoTemplate) {
    this.mongoTemplate = mongoTemplate;
  }

  public void deleteAll() {
    mongoTemplate.dropCollection(Person.class);
  }

  public void save(Person person) {
    mongoTemplate.save(person);
  }

  public List<Person> findAll() {
    return mongoTemplate.findAll(Person.class);
  }
}

redis보다는 좀 더 쉽게 느껴지는 듯하다. 약간 Repository 와 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);
  }
}

이 역시 특별한 에러 없이 잘 동작한다.

Neo4j

Neo4j도 Spring data에서 지원해주는 GraphRepository 혹은 Neo4jRepository를 사용하면 된다. 여기 예제에서는 GraphRepository을 사용하였다.

public interface PersonRepository extends GraphRepository<Person> {

  Person findByName(String name);
}

자주 봐서 알겠지만 이 역시도 메서드 이름기반의 쿼리를 지원한다. 아마도 Spring data에 있는 것들은 모두 메서드 기반의 쿼리를 지원하는 듯 하다. 동일하게 테스트를 해보자. 가만보니까 다 똑같은 소스들이네.. 이름이라도 좀 바꿀껄 그랬나?

@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);
  }
}

다음으로는 Session을 알아볼텐데 저번에 이야기 했듯이 현재 Neo4jTemplate은 Deprecated 되어있어 굳이 하지 않고 Spring에서 권장하는 Session을 이용해보자. org.neo4j.ogm.session.Session은 Neo4j에서 지원해주는 인터페이스이며 그 구현체로는 Neo4jSession가 존재한다. 이 역시 Spring boot에서는 Session을 자동으로 빈으로 등록시켜주어서 따로 설정할 필요는 없다.

@Component
@Transactional
public class PersonTemplate {

  private final Session session;

  public PersonTemplate(Session session) {
    this.session = session;
  }

  public void deleteAll() {
    session.deleteAll(Person.class);
  }

  public void save(Person person) {
    session.save(person);
  }

  public Collection<Person> findAll() {
    return session.loadAll(Person.class);
  }
}

나름 괜찮은 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);
  }
}

무사히 테스트 코드가 잘 동작한다.

이렇게 오늘 Spring boot에서 지원해주는 Spring data nosql 예제를 몇가지 살펴봤다. 다음시간에는 Solr, Elasticsearch, Cassandra, couchbase를 살펴보도록 하자. 원래 gemfire도 해야 되지만 현재 필자 관심밖이라 해보지 않았고 딱히 Spring boot에서도 현재 그닥 관심이 없어보여 하지 않았다.

이렇게 보면 동일한 코드들만 보여 잘 이해가 안될 수도 있으니 실제로 한번씩 돌려보도록 하자. 현재까지의 소스는 github 에 올라가 있으니 한번씩 살펴보고 실행시켜보도록 하자. 각 데이터 스토어들을 docker-compose로 실행시키면 되니 저걸 언제 설치하고 있어 고민하지 말고 docker-compose를 설치해서 손쉽게 실행시켜보자.