Posting

Machbase의 최신 소식을 지금 만나보세요

[MACHBASE JPA] spring data

Machbase와 Spring Data JPA

1. ORM과 JPA

ORM(Object Relational Mapping)이란 RDB 테이블을 객체지향적으로 사용하기 위한 기술이다. RDB 테이블은 객체지향적 특징(상속, 다형성, 레퍼런스, 오브젝트 등)이 없고 자바와 같은 언어로 접근하기 쉽지 않다. 때문에 ORM이란 객체와 RDB 사이에 존재하는 개념과 접근의 간극을 줄이고 보다 더 객체지향적으로 다루기 위한 기술이다. JPA는 여러 ORM 전문가가 참여한 EJB 3.0 스펙 작업에서 기존 EJB ORM이던 Entity Bean을 JPA라고 바꾸고 JavaSE, JavaEE를 위한 영속성(persistence) 관리와 ORM을 위한 표준 기술이다. 대표적인 ORM 구현체로는 Hibernate, OpenJPA, EclipseLink, TopLink Essentials과 같은 구현체들이 있고 이에 대한 표준 인터페이스가 바로 JPA인 것이다. 오늘 우리는 그 중에서도 Hibernate를 사용한 Spring Data JPA를 사용할 것이다.

2. 프로젝트 생성 및 실행

2.1 준비물

  • 자바 빌드 프로그램(여기에선 Maven을 사용한다)
  • JDK
  • machbase.jar

필요한 준비물들이 다 구비되었다면 이제 프로젝트를 생성할 차례다.
git을 이용해서 https://github.com/MACHBASE/hibernate-example에 있는 hibernate-example 저장소를 clone받는다.

해당 repository에는 Spring Data JPA에서 사용하는 Machbase의 Dialect와 3가지 샘플 프로젝트가 포함되어 있다.
아래 README.md에서 설명되어 있는 것처럼 IDEA를 사용한다면 실행하고자 하는 프로젝트의 pom.xml 파일을 열고 dependency를 설정해 바로 실행해볼 수도 있고,
단순 커맨드로 실행한다면 똑같이 pom.xml이 있는 디렉토리로 이동해 mvn compile 후 mvn spring-boot:run 명령어를 이용해 실행해볼 수 있다.

3. Sample

자세한 샘플코드들은 JPA sample project(Machbase)를 참고해보자

이 페이지에서는 타 DB의 샘플코드들과 가장 유사한 lookup 테이블을 이용해서
간단하게 레코드를 입력하고 조회하는 프로그램을 실행해보고자 한다.

우선 Spring Data JPA를 사용하기 위해서는 반드시 각각의 레코드에 해당하는 Entity 클래스와 그 레코드들의 집합체, 즉 테이블에 해당하는 Repository 인터페이스가 필요하다.
그리고 현재 MachbaseDialect는 DDL을 지원하지 않기 때문에 application을 실행하기 전에 테이블을 먼저 만들어 주어야 한다.

CREATE lookup TABLE lookup (id long primary key, name varchar(32), ts datetime);

3.1 application.properties

resource 디렉토리에 있는 이 파일에는 사용하려는 DB의 Dialect나 Data Source 정보들, 기타 Property들을 설정해줄 수 있다.
혹 log를 남겨서 실행에 대한 자세한 기록들을 같이 보고 싶다면 위의 logging.level을 설정해줄 수 있다. 

logging.level.org.springframework=INFO
logging.level.com.machbase=INFO
logging.level.com.zaxxer=DEBUG
logging.level.root=ERROR

spring.datasource.url=jdbc:machbase://localhost:5656/mhdb
spring.datasource.username=SYS
spring.datasource.password=MANAGER
spring.datasource.driver-class-name=com.machbase.jdbc.MachDriver
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MachbaseDialect

3.2 Entity

Entity 클래스는 테이블에서 하나하나의 레코드라 볼 수 있다. 엔티티는 일반적으로 다른 엔티티들과 관계가 있으며 이러한 관계는 객체/관계형 메타 데이터를 통해 표현된다.
객체/관계형 메타데이터는 어노테이션을 사용하여 엔티티 클래스 파일에 직접 명시하거나 응용프로그램과 함께 배포되는 별도의 XML 설명자 파일에서 지정할 수 있다.
Entity클래스를 사용하기 위해서는 반드시 @id, 즉 기본키를 지정하는 것이 필수적이다. 그렇기 때문에 아래의 기본키 선정시 고려사항들을 반드시 숙지한 뒤에 Entity 클래스를 작성해야 한다.

  • 릴레이션 내 투플을 식별할 수 있는 고유한 값을 가져야 한다.
  • NULL 값은 허용하지 않는다.
  • 키 값의 변동이 일어나지 않아야 한다.
  • 최대한 적은 수의 속성을 가진 것이라야 한다.
  • 향후 키를 사용하는 데 있어서 문제 발생 소지가 없어야 한다.
@Entity
@Table(name = "lookup")
public class Lookup {

    @Id        // 기본키가 없는 log테이블이나 tag테이블의 경우에도 @Id는 필요하니 아래에 주의사항 참고
    private Long id;
    @Column(name = "name", nullable = false)  // 어노테이션으로 명시해준 column의 메타데이터
    private String name;
    private Timestamp ts;

    public Lookup() {}

    public Lookup(Long id, String name, Timestamp ts) {
        this.id = id;
        this.name = name;
        this.ts = ts;
    }
    ...
    // Getter
    // Setter
    ...
}

주의! 위에서 상기한 것처럼 Entity를 구분하기 위해서는 반드시 기본키가 있어야 하지만 마크베이스에서 log테이블과 tag테이블은 기본키 지정이 불가능하다.
이를 해결하기 위해서 log테이블에서는 _arrival_time을 기본키로, 그리고 tag테이블에서는 tag name과 time을 복합키로 사용해 기본키가 있는 것처럼 사용해야 한다.

3.2.1 Log 테이블의 기본키 지정

@Entity
@Table(name = "t1")
public class User {
    @Id
    private Timestamp _arrival_time;    // _arrival_time column을 기본키처럼 사용
    private String name;                // 다만 이와 같이 arrival_time을 @Id로 사용할 경우 select문과 같은
    private int age;                    // repository.findById() 메서드를 사용하는 것보다는 repository.findByName() 메서드를 사용하는 편이 좋다

    public User() {
    }
    public User(Timestamp dt, String name, int age) {
        this._arrival_time = dt;
        this.name = name;
        this.age = age;
    }

    // _arrival_time 입력을 위한 메서드
    public static Timestamp get_arrival_time() throws ParseException, InterruptedException {   
        Thread.sleep(0, 1);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        Calendar cal = Calendar.getInstance();
        String today = null;
        today = sdf.format(cal.getTime());
        Timestamp ts = Timestamp.valueOf(today);

        return ts;
    }
}

3.2.2 Tag 테이블의 기본키 지정

public class Tag {

    @EmbeddedId
    private TagPK pk;

    @Column
    private double value;
    ...
}

@Embeddable
public class TagPK implements Serializable {
    private static final long serialVersionUID = 1L;

    @Column(name = "name", nullable = false)          // name column과 time column을 복합키로 사용
    private String name;

    @Column(name = "time", nullable = false)
    private Timestamp time;
    ...
}

3.3 Repository

Spring Data JPA에서는 JpaRepository 인터페이스를 제공한다.
Repository는 Entity의 집합체이다. 우리는 Repository를 통해 Entity를 관리할 수 있고 간단하게는 CRUD메서드부터 nativeQuery을 이용한 복잡한 쿼리문도 사용할 수 있다.

public interface LookupRepository extends JpaRepository {
    List findByName(String name);
}

3.3.1 NativeQuery

JPA는 SQL이 지원하는 대부분의 문법과 SQL 함수들을 지원하지만 특정 데이터베이스에 종속적인 기능은 잘 지원하지 않는다.
하지만 때론 특정 데이터베이스에 종속적인 기능이 필요할 수도 있다.
다양한 이유로 JPQL을 사용할 수 없을 때, JPA는 SQL을 직접 사용할 수 있는 기능을 제공하는데 이것을 네이티브 SQL(네이티브쿼리)라고 한다.

예를 들어, tag 테이블에서 tag name으로 entity를 조회하고 싶다고 하자.
machbase에서는 ‘select * from tag where name = ‘XOXO”라는 쿼리를 이용해 간단히 수행할 수 있는 쿼리이지만 JPA에서 이를 수행하고자 할 때엔 상당한 애로사항이 꽃 필 수 있다.
이 경우 nativeQuery를 이용하면 기존 machbase에서 사용한 쿼리를 그대로 사용할 수 있다.

public interface TagRepository extends JpaRepository {
    @Query(value = "SELECT * FROM tag WHERE name = :name", nativeQuery=true)  // Tag name으로 조회하는 nativeQuery
    List findTagsByName(@Param("name") String name);
}

3.4 Application

@SpringBootApplication
public class LookupApplication implements CommandLineRunner {
    private static final Logger log = LoggerFactory.getLogger(LookupApplication.class);

    private final LookupRepository repository;

    public LookupApplication(LookupRepository repository) {
        this.repository = repository;
    }

    public static void main(String[] args) {
        SpringApplication.run(LookupApplication.class, args);
    }

    @Override
    public void run(String... args) throws Exception {

        log.info("TestMachbaseApplication...");
        ArrayList lookupList = new ArrayList();

        lookupList.add(new Lookup(1L, "Noah", Lookup.get_arrival_time()));
        lookupList.add(new Lookup(2L, "John", Lookup.get_arrival_time()));
        repository.saveAll(lookupList);                                             // 이렇게 list에 담아서 한꺼번에 insert를 수행할 수도 있고

        repository.save(new Lookup(3L, "Nana", Lookup.get_arrival_time()));         // 혹은 이렇게 하나하나 수행할 수도 있다.

        System.out.println("\nfindAll()");
        repository.findAll().forEach(System.out::println);                          // 전체 Entity를 조회

        System.out.println("\nfindById(1)");
        repository.findById(1L).ifPresent(x -> System.out.println(x.getName()));    // PK로 하나의 Entity를 조회

        System.out.println("\nfindByName(\"Noah\")");
        System.out.println(repository.findByName("Noah").toString());               // Name column으로 조회

        System.gc();
        System.exit(0);
    }
}

3.5 결과

findAll()
Lookup(id=1, name=Noah, ts=2020-04-23 18:55:18.108)
Lookup(id=2, name=John, ts=2020-04-23 18:55:18.11)

Lookup(id=3, name=Nana, ts=2020-04-23 18:55:18.182)

findById(1)
Noah

findByName('Noah')
[Lookup(id=1, name=Noah, ts=2020-04-23 18:55:18.108)]

// 2회차 실행. volatile / lookup table은 update가 가능하다.
findAll()
Lookup(id=1, name=Noah, ts=2020-04-23 19:04:12.641)
Lookup(id=2, name=John, ts=2020-04-23 19:04:12.642)
Lookup(id=3, name=Nana, ts=2020-04-23 19:04:12.714)

findById(1)
Noah

findByName('Noah')
[Lookup(id=1, name=Noah, ts=2020-04-23 19:04:12.641)]

4. 주의사항

  • MachbaseDialect는 DDL을 지원하지 않는다(즉, create table나 index, drop table등의 쿼리는 직접 수행해야 한다)
  • 현재는 insert 구문과 select 구문이 수행 가능하다
  • 사용하려는 Table의 종류에 따라 작성해야 하는 코드의 차이가 있다
  • Machbase는 Lookup/Volatile 테이블을 제외한 나머지 두 테이블에 Update와 Delete를 지원하지 않는다.
    그렇기 때문에 Log/Tag 테이블을 사용할 때엔 set나 delete 메서드를 사용하는 것을 지양해야 한다.

5. 맺음말

분명 JPA는 RDB에 최적화되어서 설계되었기 때문에 Machbase에서 100%의 효율을 바라는 것은 무리가 있다.
하지만 JDBC를 조금 더 객체지향적으로 사용하고 보다 비즈니스 로직을 구현하는데 집중한다는 목표가 있다면 JPA는 좋은 선택이 될 것이다.

연관 포스트

C언어로 Binary data를 Machbase에 넣기

1.개요 Data를 다루다 보면 numeric, varchar 형 데이터뿐만 아니라 JPG, PNG, MP3와 같은 Binary data도 database에 저장해야 할 때가 존재한다. 그러나 일반 data들과는 달리 Binary

[MACHBASE 연동] Android studio에서 JDBC 연결하기

마크베이스 기술지원본부 이현민 1. 개요 수많은 데이터들이 많은 환경에서 생성되고 있는 오늘날, 우리 현대인들의 동반자인 스마트폰 또한 데이터생성의 주체로써 또는 전달자로서 알게 모르게 그 구실을