2011년 6월 28일 화요일

Cassandra, JPA, Spring

인터넷 상에서 많은 카산드라용 JPA 라이브러리 내지는 클라이언트를 발견할 수 있었다. 이에 그 내용을 어느 정도 정리하려고 한다. 대부분의 솔루션들이 기존 카산드라에서 단점으로 지적될 만한 사항을 기술하고 이를 해결하려고 본 소스들이 추가되었다고 주장한다.
먼저 살펴볼 것이 Kundera 라는 것이다. Kundera의 아이디어는 카산드라와 함께 간단하게 작동하게 하는것이라고 한다. 어떤 클라이언트 라이브러리를 새로 만든다기 보다 기존에 존재하는 라이브러리, 빌드들을 래핑하고 필요없는 코드들을 쓰지 않게 하여 더 깔끔하고 깨끗한 만들어서 프로그램의 질을 높인다고 한다. 그리고 물론 생산성을 높이는 것이다.
목적을 살펴보자면
 불필요한 디테일을 제거하는데, 그것은 컬럼 리스트, 수퍼 컬럼리스트, 바이트 어레이, 데이터 인코딩 등이라고 한다.
 어노테이션의 도움으로 도메인 모델과 직접적으로 일하게 한다.
 데이터 프로세싱의 흐름을 깨끗하고 분명하게 하기위해 “code plumbing”을 제거한다.
 카산드라와 애플리케이션 레벨의 로직을 분리하여 애플리케이션 개발을 편하게한다.
 비즈니스 레이어에서 기존의 것을 망치지 않은채 카산드라의 가장 최신 개발 사항을 포함시키려고 한다.

- 카산드라 데이터 모델
가장 기본적인 사항으로 카산드라는 당신의 데이터를 저장하기 위해 컬럼과 수퍼 컬럼을 가지고 있다. 컬럼은 이름들 그리고 타임스탬프의 값이다. 수퍼컬럼은 컬럼의 컬럼이다. 컬럼은 컬럼군에 저장되며, 수퍼 컬럼은 수퍼컬럼군에 저장된다. 가장 중요한 점은 카산드라는 기존의 관계형 데이터베이스가 아니어서 평평한 시스템이다. Join, 외래키가 없다. 당신이 저장하는 모든 것은 100% 비정규화되어있다.

- Kundera 사용하기
Kundera는 현재 JPA 1.0 과 호환된다. 다양한 어노테이션을 JPA 어노테이션위에 빌드하고 그것의 필요성을 만족시킨다.

- 기본 법칙
엔터티 클래스는 디폴트로 인자없는 생성자를 가진다.
엔터티 클래스는 @Entity 어노테이션을 보여야한다.
컬럼군의 엔터티 클래스는 @ColumnFamily 어노테이션이다.
수퍼컬럼군의 엔터티 클래스는 @SuperColumnFamily 이다.
각각 엔터티는 @ld 라는 필드 어노테이션을 가져야한다. @ld 필드는 스트링 타입이다. 엔터티당 한 개의 @ld 만 있어야 한다.
Kundera는 현재 프라퍼티 레벨에서 작동하므로 모든 메서드 레벨 어노테이션은 무시된다.

- 컬럼군의 법칙
1. @ColumnFamily 에서 컬럼군의 이름을 정의해야 한다. 예를 들면 @ColumnFamily(“Authors”) . Kundera는 이 엔터티 클래스를 “Authors” 컬럼군과 링크한다.
2. @ColumnFamily 로 어노테이션된 엔터티는 프라퍼티를 위해 스캔되며 @Column 어노테이션 때문이다.
3. 각 필드는 카산드라 컬럼이 되기 위해 분별된다.
4. 컬럼의 이름은 디폴트로 프로퍼티의 이름이다. 그러나 이름을 바꿀때는 오버라이드 할 수 있다.
5. Integer, String, Long, Date 타입의 프로퍼티는 지원되며, 나머지는 저장되기 전에 직렬화되고 읽히기 전에 비직렬화된다. 직렬화는 제한사항이 있는데 Kundera가 당신이 카산드라 컬럼 프로퍼티로 커스텀 오브젝트를 쓰는 것을 막는 이유이다. 그러나 당신은 당신이 원하는 대로 할 수는 있다.
6. Kundera 는 Collection과 Map 프로퍼티를 지원한다. 몇 가지 주의해야할 것이 있는데
* Collection과 Map 프로퍼티를 다음과 같이 초기화 해주어야한다.
List list = new ArrayList();
Set set = new HashSet();
Map map = new HashMap ();
* 타입 파라터는 5번에서 설명한 규칙을 지켜야한다.
* 타입 파라미터를 명시적으로 정의하지 않으면 엘레먼트는 직렬화되며 저장 및 복구되기전에 비직렬화된다.
* Collection 엘레먼트 순서가 유지된다는 보장은 없다.
* Collection과 Map은 그가 가진 엘레먼트의 숫자만큼 컬럼을 생성한다.
* Collection은 Name~0은 인덱스 0의 엘레먼트 Name~1은 인덱스1의 엘레먼트 이런 식으로 된다.
* Map 은 Name~key1 : key1의 엘레먼트 Name~key2: key2의 엘레먼트 등이다.

- SuperColumnFamily 의 법칙
1. @SuperColumnFamily에 수퍼컬럼군의 이름을 정의한다.
2. @SuperColumnFamily로 어노테이션된 엔터티는 @Column, @SuperColumn 의 두개 어노테이션을 위해 스캔된다.
3. 양쪽 어노테이션에 의해 어노테이션된 것만 선별되어 Column과 SuperColumn이 되도록 검사된다.
4. Column군에서 했던것처럼 이름을 정의할 수 있다.
5. 그러나 SuperColumn의 이름을 지정해야한다.
6. 나머지는 위와 같다.

- 5분내에 작동시키기
예제로서 배워보자. 간단한 Blog 애플리케이션을 만들어본다. 여기에는 Post, Tag, Author가 있다.
“Author”를 위한 카산드라 데이터모델은 아래와 같다.
01 ColumnFamily: Authors = {
02 “Eric Long”:{ // row 1
03 “email”:{
04 name:“email”,
05 value:“eric (at) long.com”
06 },
07 “country”:{
08 name:“country”,
09 value:“United Kingdom”
10 },
11 “registeredSince”:{
12 name:“registeredSince”,
13 value:“01/01/2002”
14 }
15 },
16 ...
17 }
“Posts”를 위한 데이터 모델은 다음과 같다.
01 SuperColumnFamily: Posts = {
02 “cats-are-funny-animals”:{ // row 1
03 “post” :{ // super-column
04 “title”:{
05 “Cats are funny animals”
06 },
07 “body”:{
08 “Bla bla bla… long story…”
09 }
10 “author”:{
11 “Ronald Mathies”
12 }
13 “created”:{
14 “01/02/2010"
15 }
16 },
17 “tags” :{
18 “0”:{
19 “cats”
20 }
21 “1”:{
22 “animals”
23 }
24 }
25 },
26 // row 2
27 }

카산드라 키스페이스 “Blog” 만들기
1
2
3
4
5 org.apache.cassandra.locator.RackUnawareStrategy
6 1
7 org.apache.cassandra.locator.EndPointSnitch
8


“Posts”를 위한 SuperColumnFamily 와 “Authors”를 위한 ColumnFamily 만들기
01
02
03
04
05
06
07 org.apache.cassandra.locator.RackUnawareStrategy
08 1
09 org.apache.cassandra.locator.EndPointSnitch
10




엔터티 클래스 만들기
Author.java
01 @Entity // makes it an entity class
02 @ColumnFamily ("Authors") // assign ColumnFamily type and name
03 public class Author {
04
05 @Id // row identifier
06 String username;
07
08 @Column (name="email") // override column-name
09 String emailAddress;
10
11 @Column
12 String country;
13
14 @Column (name="registeredSince")
15 Date registered;
16
17 String name;
18
19 public Author () { // must have a default constructor
20 }
21
22 ... // getters/setters etc.
23 }

Post.java
01 @Entity // makes it an entity class
02 @SuperColumnFamily("Posts") // assign column-family type and name
03 public class Post {
04
05 @Id // row identifier
06 String permalink;
07
08 @Column
09 @SuperColumn(column = "post") // column 'title' will be stored under super-column 'post'
10 String title;
11
12 @Column
13 @SuperColumn(column = "post")
14 String body;
15
16 @Column
17 @SuperColumn(column = "post")
18 String author;
19
20 @Column
21 @SuperColumn(column = "post")
22 Date created;
23
24 @Column
25 @SuperColumn(column = "tags") // column 'tag' will be stored under super-column 'tags'
26 List tags = new ArrayList();
27
28 public Post () { // must have a default constructor
29 }
30
31 ... // getters/setters etc.
32 }

“tags” 프로퍼티가 어떻게 초기화 되었는지 보자. 이것은 매우 중요한데 Kundera가 Java Reflection을 사용하여 읽고 엔터티 클래스를 만든다.

- 엔터티매니저 초기화하기
Kundera 는 이제 JPA 프로바이더처럼 작동하고 엔터티메니저를 초기화하는 방법을 보자.
http://anismiles.wordpress.com/2010/07/14/kundera-now-jpa-1-0-compatible/#entity-manager
- 지원되는 동작
Kundera는 JPA 엔터티메니저 동작을 지원한다. JPA 쿼리를 비롯해서 아래를 보자.
http://anismiles.wordpress.com/2010/07/14/kundera-now-jpa-1-0-compatible/#entity-operations

- Kundera 와 Spring을 사용하기
우리는 여기서 Kundera와 Spring을 이용하는 법을 본다. Spring과 Hibernate는 적은 자바 코드로서도 데이터베이스 코드를 접근하는 아주 강력한 프레임워크이다. Spring을 사용하는것은 또한 당신의 데이터베이스 동작을 위해서 간단히 unit test 를 쓸 수 있게 해준다.

- 컴포넌트들
1. Spring 3.0 호환 : applicationContext.xml
우리는 Kundera와 동작하기 위해서 최소한의 FactoryBean을 정의해야 한다. JPA interface와 호환하기 때문에 Kundera는 LocalContainerEntityManagerFactoryBean과 직접 사용할 수 있다.
<빈의 정의>



제공하여야할 유일한 프로퍼티는 퍼시스턴스 유닛 이름이다. 이것은 persistence.xml에 정의되어 있다. Hibernate와 작동하는 것처럼 Spring 컨테이너는 Kundera 엔터티 매니저를 LocalContainerEntityManagerFactoryBean 을 통해서 생성, 관리할 것이다.
1. JPA 설정 : persistence.xml
Kundera에는 퍼시스턴스 설정을 쓰는 몇 가지 방법이 있다. 여기서 JPA 퍼시스턴스 선언을 META-INF에 있는 persistence.xml을 통해서 해본다. 가장 장점은 투명한 설정을 가지고 있다는 것이다.
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
version="1.0">

com.impetus.kundera.ejb.KunderaPersistence











여기서 중요한 것은 제공자 클래스를 com.impetus.kundera.ejb.KunderaPersistence에서 준다는 것이다. 다른 프로퍼티 값은 카산드라에게 연결 파라미터를 준다. Kundera.annotations.scan.package 라는 프로퍼티는 JPA 어노테이션을 찾기위해 어느 패키지를 스캔해야 하는지 보여준다.
1. 엔터티 자바 클래스
여기서 우리는 우리의 엔터티를 JPA에서 데이터베이스를 위해 했듯이 정의해야 한다. 그러나 Kundera를 사용하는 것은 카산드라에게 더 많은 부가 정보를 준다.
package com.wix.model;

import com.impetus.kundera.api.ColumnFamily;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.xml.bind.annotation.XmlRootElement;

@Entity
@ColumnFamily(keyspace = "Keyspace1", family = "SimpleComment")
@XmlRootElement(name = "SimpleComment")
public class SimpleComment {

@Id
private String id;

@Column(name = "userId")
private String userId;

@Column(name = "comment")
private String commentText;

public SimpleComment() {
}

......
}
1. Kundera를 사용한 DAO 서비스
여기서 우리는 우리의 엔터티를 정의한다. @PersistenceContext라는 스프링 어노테이션을 간단히 사용할수 있으며 퍼시스턴스 엔터티를 삽입한다.
package com.wix.cassandra;

import com.wix.model.SimpleComment;
import org.springframework.stereotype.Service;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import java.util.List;

@Service
public class KuneraService {

@PersistenceContext(type = PersistenceContextType.EXTENDED)
private EntityManager entityManager;

public SimpleComment addComment(String id, String userId, String commentText) {
SimpleComment simpleComment = new SimpleComment();
simpleComment.setId(id);
simpleComment.setUserId(userId);
simpleComment.setCommentText(commentText);

entityManager.persist(simpleComment);
return simpleComment;
}

public SimpleComment getCommentById(String Id) {
SimpleComment simpleComment = entityManager.find(SimpleComment.class, Id);
return simpleComment;
}

public List getAllComments() {
Query query = entityManager.createQuery("SELECT c from SimpleComment c");
List list = query.getResultList();

return list;
}

}

@PersistenceContext를 선언하는 것은 매우 중요하며 이것은 스프링이 카산드라 클라이언트를 각 트랜잭션마다 닫는 것을 막는다. 이제 클라이언트를 직접 닫고 매니지하는 것이 필요하다.

댓글 없음:

댓글 쓰기