Spring CRUD 기본

데이터 생성(Create), 검색(Read), 갱신(Update), 삭제(Delete)의 4 가지 데이터베이스 액세스의 기본이라고 할 수 있다. 이러한 기본 작업에 대해 설명한다.

Query로 데이터의 목록 조회

데이터베이스 조작의 기본은 “CRUD"라고 한다. 이전에 더미 데이터 몇개를 저장하여 데이터 목록을 표시하는 예제를 만들었다.

(참고 : Spring Data JPA 이용 - 데이터베이스를 이용해 보자.

예제를 보면 ‘Create’와 ‘Read’의 기본은 이미 만들어져 있을 것이다. 그럼 어떻게 했는지 살펴 보자.

package com.devkuma.spring.db;
 
import java.util.List;
 
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Query;
 
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 
public class App {
 
    public static void main(String[] args) {
        ApplicationContext app = new
            AnnotationConfigApplicationContext(SampleEntityConfig.class);
 
        EntityManagerFactory factory = app.getBean(EntityManagerFactory.class);
        EntityManager manager = factory.createEntityManager();
 
        makeDummyData(manager);
 
        // get list
        Query query = manager.createQuery("from SampleEntity");
        List list = query.getResultList();
        printList(list);
 
        System.out.println("...ok.");
    }
 
    public static void makeDummyData(EntityManager manager) { ...생략... }
 
    public static void printList(List list) { ...생략... }
}

먼저 “데이터 목록 가져 오기"이다. 여기에는 “Query"라는 클래스를 이용하고 있다.

Query query = manager.createQuery("from SampleEntity");

이것은 “Query"라는 클래스의 인스턴스를 만들고, 해당 메서드를 호출하여 전체 엔터티 목록을 검색한다. Query라는 것은 SQL 쿼리 문장의 단순화 된 버전과 같은 문장을 사용하여 데이터베이스에 액세스하기 위한 것이다.

인스턴스 생성은 new 대신 “createQuery"라는 메소드를 이용한다. 인수는 “from SampleEntity” 텍스트을 넣었다. 이것이 SampleEntity 엔티티를 모두 취득하는 것을 나타내는 구문이다.

List list = query.getResultList();

이렇게 작성한 Query의 “getResultList"을 호출하면, 가져온 엔티티를 목록으로 얻을 수 있다. 그 다음, l결과 목록(list)에서 부터 차례로 엔티티를 꺼내 처리하면 된다.

createQuery 인수로 어떤 텍스트를 지정하면 어떤 결과를 얻을 수 있을까? 라는 것이 제일 문제이다. 우선 여기에서는 “from 엔티티"라는 형태로 쓰면 그 엔터티 목록을 얻을 수 있다는 사실만 기억하자.

새로운 엔티티 추가

이어서 신규 엔터티를 저장한다. 이는 생각하기에는 매우 간단하다. 엔티티 클래스의 인스턴스를 만들고 EntityManager에 있는 저장용의 메소드를 호출하기만 하면 된다.

다만! 데이터베이스 조작은 단순히 데이터를 읽기만 한다면 간단하지만 데이터를 갱신하는 경우에는 주의가 필요하다. 데이터베이스는 동시에 여러 곳에서 액세스되는 경우도 있다. 따라서 다른 데이터가 액세스되는 동안에 데이터가 갱신이 되거나 한다면 문제가 일어날 수 있다.

따라서 데이터의 갱신이 필요한 경우에는 “트랜잭션"이라는 것을 사용해야 한다. 트랜잭션은 여러 처리를모와서 실행 할 수 있도록하는 기능이다.

트랜잭션을 지정하는 동안에는 그 처리를 실시하고 있는 해당 데이터에 대해서, 외부에서 액세스를 할 수 없게 된다. 트랜잭션 처리가 완료된 후에는 외부에서 부터 데이터 액세스 가능해 진다.

package com.devkuma.spring.db;
 
import java.util.List;
 
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Query;
 
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 
public class App {
 
    public static void main(String[] args) {
        ApplicationContext app = new
            AnnotationConfigApplicationContext(SampleEntityConfig.class);
 
        EntityManagerFactory factory = app.getBean(EntityManagerFactory.class);
        EntityManager manager = factory.createEntityManager();
 
        makeDummyData(manager);
 
        // get list
        Query query = manager.createQuery("from SampleEntity");
        List list = query.getResultList();
        printList(list);
 
        System.out.println("...ok.");
    }
 
    // create dummy entity data.
    public static void makeDummyData(EntityManager manager) {
        EntityTransaction transaction = manager.getTransaction();
        transaction.begin();
        manager.persist(new SampleEntity("tuyano", "syoda@tuyano.com"));
        manager.persist(new SampleEntity("hanako", "hanako@flower"));
        manager.persist(new SampleEntity("taro", "taro@yamada"));
        manager.persist(new SampleEntity("sachiko", "sachico@happy"));
        manager.flush();
        transaction.commit();
    }
 
    public static void printList(List list) { ...생략... }
}

트랜잭션에 의해서 특정 작업을 수행하는 동안, 외부에서 액세스 할 수 없게 하여 안전하게 데이터베이스를 업데이트 할 수 있다는 것이다. 그럼, 처리의 흐름을 살펴 보자.

EntityTransaction transaction = manager.getTransaction ();

EntityManager의 “getTransaction"은 EntityTransaction라는 클래스의 인스턴스를 가져온다. 이것이 트랜잭션 클래스이다.

transaction.begin ();

“begin"메소드를 실행하여 트랜잭션 처리를 시작한다.

manager.persist(new SampleEntity ( "tuyano", "syoda@tuyano.com"));
manager.persist(new SampleEntity ( "hanako", "hanako @ flower"));
manager.persist(new SampleEntity ( "taro", "taro @ yamada"));
manager.persist(new SampleEntity ( "sachiko", "sachico @ happy"));

엔티티의 신규 추가는 EntityManager의 “persist"라는 메서드를 사용한다. 인수에 엔티티 클래스의 인스턴스 (여기에서는 SampleEntity)을 지정하여 호출하는 것으로, 그 엔티티를 데이터베이스에 저장한다.

manager.flush ();
transaction.commit ();

마지막으로 EntityManager의 ‘flush’을 호출해서 버퍼를 비우고, EntityTransaction의 “commit"메서드를 호출하여 커밋하면 persist한 엔티티가 전부 데이터베이스에 저장된다. 커밋을 하면 바로 트랜잭션 처리는 종료되고, 데이터베이스는 개방된다.

데이터 추가는 트랜잭션에 대한 사용법을 모르면 안된다. 이것은 데이터베이스 갱신의 기본이므로 여기에서 기본적인 사용법을 꼭 기억해 두도록 하자.

엔티티 수정

이어 엔터티의 변경이다. 이미 데이터베이스에 저장되어 있는 데이터를 업데이트하려면 해당 데이터 엔티티를 가져와서, 그 값을 변경해서 저장하는 작업을 해야한다.

아래와 같이 간단한 예제를 작성해 놨다.

package com.devkuam.spring.db;
 
import java.util.List;
 
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Query;
 
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 
public class App {
    private static EntityManager manager;
 
    public static void main(String[] args) {
        ApplicationContext app = new
            AnnotationConfigApplicationContext(SampleEntityConfig.class);
 
        EntityManagerFactory factory = app.getBean(EntityManagerFactory.class);
        manager = factory.createEntityManager();
 
        // make dummy
        makeDummyData();
         
        // update entity
        updateEntity(1L);
        updateEntity(3L);
 
        // get list
        Query query = manager.createQuery("from SampleEntity");
        List list = query.getResultList();
        printList(list);
 
        System.out.println("...ok.");
    }
 
    public static void makeDummyData() { ...생략... }
    public static void printList(List list) { ...생략... }
     
    // update entity
    public static void updateEntity(long id) {
        SampleEntity entity = manager.find(SampleEntity.class, id);
        entity.setName("**update name**");
        entity.setMail("**update@mail**");
        EntityTransaction transaction = manager.getTransaction();
        transaction.begin();
        manager.merge(entity);
        manager.flush();
        transaction.commit();
    }
}

실행을 하면 4개의 더미 데이터 중에 2개의 내용을 변경해서 출력하고 있다.

변경 처리는 updateEntity 메서드로 정리하고 있다. 이는 데이터를 변경하는 ID 번호를 인수에 전달할 수 있게끔 되어 있다.

그럼 처리의 흐름을 살펴보자.

EntityManagerFactory factory = app.getBean(EntityManagerFactory.class);
manager = factory.createEntityManager();

먼저, EntityManager 인스턴스를 얻어오고, 그것을 사용하여 처리를 하고 있다.

SampleEntity entity = manager.find(SampleEntity.class, id);

SampleEntity 인스턴스를 얻어온다. 이것은 new로 새롭게 만드는 것이 아니라 EntityManger의 “find"메소드를 호출한다. find는 특정 ID의 엔티티를 검색하는 것이다. 이것은 인수에 엔티티 클래스의 class 값과 검색 엔티티의 ID 값을 각각 지정한다.

먼저 목록 리스트를 얻어오는 경우에는 다음 처리 등을 생각하면 귀찮긴 하지만, 이렇게 EntityManager 의 메소드를 사용하면 ID를 지정하는 것만으로 엔티티를 얻을 수 있다. (다만, ID를 모르는 상태에서는 이를 사용할 수 없다.)

다음에 얻어온 엔티티의 메소드를 호출하여 내용을 업데이트한다.

entity.setName("** update name **");
entity.setMail("** update @ mail **");

편집이 끝나면 신규 작성과 같이 트랜잭션을 사용하여 업데이트 처리를 한다. 이것은 기본적으로 신규 작성을 하는 흐름과 같지만, 갱신하는 부분만 다르다.

EntityTransaction transaction = manager.getTransaction ();
transaction.begin ();
manager.merge (entity);
manager.flush ();
transaction.commit ();

이미 저장되어 있는 엔터티의 변경은 “merge"라는 메소드를 사용해 실시한다. 인수에 업데이트하는 엔터티 클래스의 인스턴스를 지정하면 해당 엔티티의 값을 최신 상태로 업데이트한다.

그 후에는 EntityManager를 flush하고 EntityTransaction을 commit하면 작업 완료이다. 업데이트는 신규 작성과 달리, find에서 특정 ID의 엔티티를 얻어서, merge 업데이트 처리를 하면 된다.

엔티티 삭제

남은 것은 엔티티의 삭제이다.

삭제도 기본적으로는 엔티티의 업데이트와 동일하다. ID를 지정하여 엔티티의 인스턴스를 얻어와서, 삭제하고 트랜잭션을 커밋하는 방식을 한다.

아래와 같이 간단한 예제를 작성해 놨다.

package com.devkuma.spring.db;
 
import java.util.List;
 
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Query;
 
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 
public class App {
    private static EntityManager manager;
 
    public static void main(String[] args) {
        ApplicationContext app = new
            AnnotationConfigApplicationContext(SampleEntityConfig.class);
 
        EntityManagerFactory factory = app.getBean(EntityManagerFactory.class);
        manager = factory.createEntityManager();
 
        // make dummy
        makeDummyData();
         
        // delete entity
        deleteEntity(1L);
        deleteEntity(3L);
 
        // get list
        Query query = manager.createQuery("from SampleEntity");
        List list = query.getResultList();
        printList(list);
 
        System.out.println("...ok.");
    }
 
    public static void makeDummyData() {  }
    public static void printList(List list) {  }
     
    // delete entity
    public static void deleteEntity(long id) {
        SampleEntity entity = manager.find(SampleEntity.class, id);
        EntityTransaction transaction = manager.getTransaction();
        transaction.begin();
        manager.remove(entity);
        manager.flush();
        transaction.commit();
    }
}

실행하면 ID=1, 3의 요소를 제거하고 남은 2 개를 표시한다. 그럼 이것도 흐름을 정리해 보자.

SampleEntity entity = manager.find (SampleEntity.class, id);

먼저 EntityManager의 “find"를 사용하여 지정된 ID의 엔티티 (여기에서는 SampleEntity)를 얻어온다.

EntityTransaction transaction = manager.getTransaction ();
transaction.begin ();

이어서 EntityTransaction 인스턴스를 얻어와서 트랜잭션 처리를 시작한다.

manager.remove(entity);

엔티티를 삭제한다. 이는 EntityManager의 “remove"메서드를 사용한다. 인수에 데이터베이스에서 검색한 엔티티의 인스턴스를 지정하면, 데이터베이스에서 해당 엔티티를 제거한다.

manager.flush();
transaction.commit();

마지막으로 EntityManager를 flush하고 EntityTransaction을 commit하면 데이터베이스 갱신이 완료 데이터가 삭제된다. 엔터티의 변경을 알면 제거도 거의 같은 느낌으로 실행할 수 있다.

JPQL 쿼리 문장의 기본

이것으로 CRUD 기본 조작은 할 수 있게 되었다. 여기서 조금 더 알아 두지 않으면 안되는 것은 기본 중에 기본인 “Read"부분이다.

앞의 예제에서는 모든 엔티티를 얻어오는 간단한 샘플이 있었다. 이보다 실제 처리는 더 조건을 세밀하게 지정하여 엔티티를 검색할 필요가 있을 것이다.

엔티티를 얻어올 때, Query라는 클래스를 했었다. 이 Query 인스턴스를 만들 때 “from SampleEntity"라는 문장을 인수로 넣었다. 이 문장은 SQL쿼리 문장과 같은 기능을 하는 것으로, “JPQL"이라고 한다. 다만, SQL과 약간 작성이 다르다. 우선, 엔티티 검색의 기본적인 작성법에 대해 설명을 하겠다.

JPQL의 검색의 기본

from 엔티티 where 조건식

SQL에서도 친숙한 where 절을 사용하면 기본적인 검색은 거의 할 수 있다. 조건이 되는 수식은 등호 등의 비교 연산자 ( =<> )를 사용하여 작성한다.

ID가 1 인 엔티티를 반환

from SampleEntity where id = 1

ID가 2보다 큰 엔터티를 반환

from SampleEntity where id > 2

또한, 텍스트를 검색하려면 퍼지(fuzzy) 검색 “like"를 사용할 수 있다. %기호를 사용하여 와일드 카드의 지정도 가능하다.

name이 ‘K’로 시작하는 엔터티 반환

from SampleEntity where name like 'K %'

여러 조건식을 설정하려면, “and”, “or"를 사용할 수 있다. where는 “조건식 and 조건식’와 같은 형식으로 여러 조건식을 and/or로 이어 간다.

id가 10이상 20이하의 엔티티를 취득

from SampleEntity where id> = 10 and id <= 20

우선, 이러한 기본적인 검색 방법을 알면 초보적인 데이터 검색의 처리는 대체로 수 있게 될 것이다. 실제로 JPQL 쿼리 문장을 만들어서 실행해 보면 재미있다.




최종 수정 : 2017-12-22