[JPA] 객체와 테이블 매핑 CRUD

Woo Yong·2024년 1월 21일
1

JPA

목록 보기
2/7
post-thumbnail

이전 글에서는 객체 매핑을 해보는 코드에 대해 알아보았다.
이번 글에서는 객체 매핑 완료 후, EntityManagerFactory와 EntityManager 객체를 통해 매핑한 객체를 통해 개발하는 코드를 작성해보자.

그리고 매핑 객체를 통해 개발하기 위해서는 크게 세가지( 엔티티 매니저 설정, 트랜잭션 관리, 비즈니스 로직 )를 나누어서 코드를 작성할 수 있다.

전체 코드

public class JpaMain {

    public static void main(String[] args) {

        //엔티티 매니저 팩토리 생성
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabook");
        EntityManager em = emf.createEntityManager(); //엔티티 매니저 생성

        EntityTransaction tx = em.getTransaction(); //트랜잭션 기능 획득

        try {


            tx.begin(); //트랜잭션 시작
            logic(em);  //비즈니스 로직
            tx.commit();//트랜잭션 커밋

        } catch (Exception e) {
            e.printStackTrace();
            tx.rollback(); //트랜잭션 롤백
        } finally {
            em.close(); //엔티티 매니저 종료
        }

        emf.close(); //엔티티 매니저 팩토리 종료
    }

    public static void logic(EntityManager em) {

        String id = "id1";
        Member member = new Member();
        member.setId(id);
        member.setUsername("지한");
        member.setAge(2);

        //등록
        em.persist(member);

        //수정
        member.setAge(20);

        //한 건 조회
        Member findMember = em.find(Member.class, id);
        System.out.println("findMember=" + findMember.getUsername() + ", age=" + findMember.getAge());

        //목록 조회
        List<Member> members = em.createQuery("select m from Member m", Member.class).getResultList();
        System.out.println("members.size=" + members.size());

        //삭제
        em.remove(member);

    }
}

엔티티 매니저 설정

우선적으로 엔티티 매니저가 생성되는 과정에 대해 알아보자.

위 사진은 엔티티 매니저가 생성되는 과정을 보여준다.

사진을 분석해보면 첫 번째 순서가 설정 정보 조회이다. 즉, 이전 글에서 작성한 persistence.xml를 참고한다는 것이다. 그리고 xml을 파일을 통해 엔티티매니저 팩토리를 생성하고 팩토리 객체를 통해서 엔티티매니저를 생성한다.

엔티티 매니저 팩토리 생성

//엔티티 매니저 팩토리 생성
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabook");

위 코드가 엔티티 매니저 팩토리를 생성하는 코드이다. 우리가 persistence.xml에서 작성한 <persistence-unit>태그 중 jpabook의 영속성 유닛을 찾는 것이다.

이러한 엔티티 매니저 팩토리는 단순히 엔티티 매니저만 생성하는 것이 아니라 데이터베이스와 연결하기 위한 커넥션 풀도 생성하는 역할을 가지고 있어서 생성하는 비용이 매우 크다.
그렇기 때문에 엔티티 매니저 팩토리 객체는 애플리케이션 전역에 있어서 딱 한 번만 생성하고 공유해서 사용하는 것이 좋다.

엔티티 매니저 생성

EntityManager em = emf.createEntityManager(); //엔티티 매니저 생성

코드를 살펴보면 엔티티 매니저는 엔티티 매니저 팩토리에 의해서 만들어지는 것을 볼 수 있다.
그리고 이러한 엔티티 매니저가 실질적인 JPA의 기능을 담당하고 있다.
JPA의 기능이라는 것은 엔티티를 데이터베이스의 등록, 수정, 삭제, 조회를 말한다.

따라서 엔티티 매니저 팩토리로부터 생성된 엔티티 매니저는 JPA 기능을 수행하기 위해 내부적으로 데이터베이스와 커넥션을 유지하고 있어 다른 엔티티매니저와 독립적이어야한다. 또한 개발자의 입장에서는 엔티티 매니저를 통해 데이터베이스와 통신하기 때문에 가상의 데이터베이스라고 할 수도 있다.

종료

em.close();  // 엔티티 매니저 종료
emf.close();  // 엔티티 매니저 팩토리 종료

위에서 말했듯이 하나의 엔티티 매니저는 내부적으로 데이터베이스와 연결을 유지하고 있다. 즉, 연결을 해제하지 않으면 메모리를 계속적으로 사용하고 있다는 것이다.
따라서 사용이 끝나면 명시적으로 리소스를 해제하고 메모리를 반환해야한다.

트랜잭션 관리

트랜잭션은 일련의 연산을 원자적으로 수행하는 논리적인 단위로, 한번 이상의 DB조작이 모두 성공, 실패로 수행되야한다. 이를 통해 데이터베이스와 애플리케이션의 일관성을 유지할 수 있다.

트랜잭션 API

EntityTransaction tx = em.getTransaction();

엔티티 매니저는 JPA기능을 수행하기 위해 데이터베이스와 커넥션을 유지하고 있기 때문에 엔티티매니저로부터 트랜잭션 API를 얻을 수 있다.

시작 및 커밋

try {
	tx.begin(); //트랜잭션 시작
	logic(em);  //비즈니스 로직
	tx.commit();//트랜잭션 커밋
} catch (Exception e) {
	e.printStackTrace();
	tx.rollback(); //트랜잭션 롤백
} finally {
	em.close(); //엔티티 매니저 종료
}

비즈니스 로직

이번 예제에서는 엔티티를 생성 후, 엔티티 매니저를 통해 데이터베이스 등록,수정,삭제,조회하는 코드를 작성해보자.

Entity 등록

String id = "id1";
Member member = new Member();
member.setId(id);
member.setUsername("정우용");
member.setAge(26);

// 등록
em.persist(member);

위 코드는 우리가 @Entity 어노테이션을 이용하여 정의한 Member 객체를 이용하여 Member테이블에 저장하는 코드이다.
즉, Member("id1", "정우용", 26)의 정보를 em(엔티티 매니저)의 persist()를 통해 저장한 것이다.

조금 더 자세히 알아보자.
em.persist(member)를 통해서 데이터베이스 테이블에 데이터를 저장했다. 즉, persist()메서드를 호출하면 INSERT문의 SQL 쿼리가 작동한다는 것이다.

따라서 JPA는 우리가 정의한 @Entity객체를 통해 아래와 같은 SQL문을 만들어서 데이터베이스에 전달한다는 것이다.

INSERT INTO MEMBER (ID, NAME, AGE) VALUES ('id1', '정우용', 26)

직접 디버깅을 통해 확인해보자

persist() 실행 전

persist() 실행 후

이처럼 엔티티 매니저 내부의 persistenceContext 영역에 우리가 생성한 Member객체가 추가 된 것을 확인 할 수 있다.

Entity 수정

member.setAge(20);

수정의 경우는 생각보다 단순한 걸 볼 수 있다.
persist()를 호출하여 엔티티를 등록했다면 변경된 Member객체를 다시 등록해주거나 update()라는 메서드를 호출해야하지 않을까? 라는 생각을 가지고 있었다.

하지만 JPA는 어떤 엔티티가 변경되었는지 추적하는 기능을 갖추고 있다.
스스로 생각해보았을 때는 persist()를 호출하여 등록할 때 객체의 주소값을 참조하고 있기 때문에 객체를 변경하면 자동으로 주소값에 변경된 값을 참조하기 때문에 추가적인 update와 관련된 메서드가 없다고 생각이 든다.. (이 부분은 확실하지 않은 부분이다)

그리고 setter메서드를 통해 아래와 같은 SQL 쿼리를 생성하여 데이터베이스에 전달한다.

UPDATE MEMBER SET AGE=20, NAME='정우용' WHERE ID='id1'

디버깅을 통해 확인해보자

setAge 실행 전

setAge 실행 후

Entity 조회

// 한 건 조회
Member finMember = em.find(Member.class, id);
System.out.println("findMember=" + findMember.getUsername() + ", age=" + findMember.getAge());

find() 메서드는 매개변수로 조회할 엔티티 타입과 @Id(필드식별자)로 테이블 기본와 매핑한 식별자 값으로 한 개의 엔티티를 조회하는 단순한 메서드이다.

이 메서드를 호출하면 SELECT SQL을 생성하여 데이터베이스에 전달하고, 결과 값으로 엔티티를 생성해서 반환해준다.

SELECT * FROM MEMBEr WHERE ID='id1'

디버깅


디버깅을 통해 확인 해보면 위에 등록, 수정, 삭제을 수행했을 때와 동일한 주소를 가진 객체를 찾아서 출력해주는 것을 볼 수 있다.

여러 Entity 조회 / JPQL

//목록 조회
List<Member> members = em.createQuery("select m from Member m", Member.class).getResultList();
System.out.println("members.size=" + members.size());

한 개 데이터를 조회할 때와 달리 한 개 이상의 데이터를 조회할 때는 SQL문을 사용하는 것을 볼 수 있다.
그리고 등록, 수정, 삭제 예시를 살펴보면 엔티티매니저를 통해 SQL문 없이 데이터를 조작할 수 있었다.

하지만 특정 데이터를 조회하기 위해서는 해당 데이터 모두를 가져와서 엔티티 객체로 변경 후, 검색해야한다.
왜냐하면, JPA는 테이블과 매핑한 식별자 값을 통해 데이터베이스에 접근하기 때문이다.
이러한 문제를 해겨랗기 위해서는 결국 검색 조건이 포함된 SQL문을 실행해야한다.

이처럼 JPA에서 검색 조건을 포함한 쿼리 언어인 JPQL을 이용하여 한계점을 극복했다.

JPQL은 SQL과 문법과 거의 유사하다. 하지만 JPQL은 엔티티 객체를 대상으로 쿼리를 실행하고, SQL은 데이터베이스 테이블을 대상으로 쿼리를 실행한다.

즉, JPQL은 클래스와 필드를 대상으로 쿼리를 실행한다는 것이다.

이처럼 SQL문을 사용하지 않고 객체 중심으로 개발하기 위해 JPA를 사용한 것이다.

em.createQuery("select m from Member m", Member.class).getResultList();

실제로 우리가 데이터를 조회하기 위해 작성한 쿼리문(selct m from Member)은 SQL문이 아니라, JPQL이다.

작성된 코드에서 Member는 데이터베이스에 존재하는 테이블의 Member가 아니라, 엔티티를 나타내기 위한 Java Class를 의미하는것이다.

왜냐하면 JPQ은 데이터베이스 테이블을 전혀 알지 못하기 때문이다.

‼️ JPQL과 SQL 차이점
JPQL은 대소문자를 명확하게 구분하지만 SQ은 관례상 대소문자를 구분하지 않는 경우가 많다.

Entity 삭제

em.remove(member);

Entity 삭제의 경우 삭제와 관련한 단어의 메서드를 호출하여 삭제할 수 있다.
그리고 아래와 같은 DELETE SQL을 생성하여 데이터베이스에 전달한다.

DELETE FROM MEMBER WHERE ID = 'id1
profile
Back-End Developer

0개의 댓글

관련 채용 정보