[JPA] JPA 프로젝트 생성2 + 등록, 조회, 수정, 삭제

Benjamin·2023년 3월 26일
0

JPA

목록 보기
2/4

이 게시글은 인프런 김영한님의'자바 ORM 표준 JPA 프로그래밍 - 기본편'을 보며 공부하고 작성한 게시글임을 밝힙니다.
강의 내용 외에도 공부한 내용과 본인생각이 함께 있습니다

JPA 구동방식

JPA 구동방식은 우선 Persistence라는 클래스가 있어야한다.
이 Persistence가 META-INF/persistence.xml에서 설정 정보를 조회한 후 EntityManageFactory 클래스를 생성한다.
그리고 이 EntityManageFactory는 EntityManager를 필요할 때마다 생성해서 돌리면된다.

JPA 프로젝트

우선 Persistence를 사용하자.


Persistence.createEntityManagerFactory(); 을 작성하면 아래와 같이 파라미터로 persistenceUnitName을 넘기라는 안내가 뜬다.
(생성된것은 EntityManagerFactory타입의 변수로 받아야한다.)

이 unitName에는 META-INF/persistence.xml에 작성해둔 persistence-unit name="hello"의 값을 적는다.
나는 "hello"이다.

EntityManagerFactory를 만들었으면, 데이터베이스 연결도되고 웬만한것은 다 된다.

기본적인 메인 코드는 다음과 같다.

public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");

        EntityManager em = emf.createEntityManager();
        
        //code(데이터 저장, 조회 등 실제 코드 작성)

        em.close();
        
        //app이 완전히 끝나면 emf를 닫아줘야한다. 
        emf.close();

    }
  • EntityManagerFactory는 애플리케이션 로딩시점에 딱 하나만 만들어놔야한다.
    -> 데이터베이스가 하나씩 묶여서 돌아가는 것

  • 그리고 실제 디비에 저장하거나 그런 디비 커넥션 얻어서 쿼리 날리고 종료되는 한 일관적인 단위(트랜잭션)를 할 때마다 EntityManager를 꼭 만들어줘야한다.
    -> ex) 고객 요청이올때마다 작업한다면 각 요청마다 EntityManager를 만들어줘야한다.

이제 데이터들을 직접 생성한 후 적용해보자
인터넷 localhost:8082 에 접속해서 H2를 사용할 수 있다. 데이터들을 생성할 수 있다.

여기서 중요한게 있다!

아래 쿼리문을 실행해서 테이블을 생성하자.

CREATE TABLE MEMBER (
    ID BIGINT NOT NULL,   
    NAME VARCHAR(255),               
    PRIMARY KEY (ID)
)

db에 테이블을 생성했으면 다음 코드를 작성하자.

테이블의 컬럼과 동일한 구조의 클래스를 먼저 만든다.
main - java - hellojpa아래에 Member클래스를 선언한다.
코드는 다음과 같다.

package hellojpa;

import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class Member {

    @Id
    private Long id;
    private String name;


    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

id, name 필드값을 선언한 이유는 db에도 그렇게 있기때문이다.

  • @Entity
    꼭 작성해야 JPA가 처음 로딩될 때 JPA를 사용하는 애라고 인식하고 관리한다.
  • @Id
    이 필드가 PK라는 정보를 줘야한다.
    (annotation 작업할때에는 꼭 javax.persistence를 사용한다.)

회원등록

그 다음 main-java-hellojpa -JpaMain 파일의 main메소드를 작성해보자.

public class JpaMain {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        
        Member member = new Member();
        member.setId(1L);
        member.setName("HelloA");

        em.persist(member); //저장

        em.close();
        emf.close();
    }
}

위처럼만 작성하면 에러가난다. 왜일까?

에러가 발생했다.
의도한 에러가 아니긴하지만, 그래도 살펴보자.

  • Database may be already in use: ~~

https://www.inflearn.com/questions/211951/database-may-be-already-in-use-null-에러-발생-ㅠㅠ

포트가 중복으로 사용되고있는것같다.
위 링크를 참고해서 jdbc:h2:tcp://localhost/~/test로 h2연결 및 설정파일 코드의 url을 수정했다.

에러는 해결했지만, persist()를 썼음에도 쿼리가 안날라가는걸보니 뭔가 실행되고있는것같지않다.

public class JpaMain {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");

        EntityManager em = emf.createEntityManager(); //db 커넥션 하나 받았다고 생각
        EntityTransaction tx = em.getTransaction();
        tx.begin(); //db trasaction 시작

        //code(데이터 저장, 조회 등 실제 코드 작성)
        Member member = new Member();
        member.setId(1L);
        member.setName("HelloA");

        em.persist(member);

        tx.commit();
		
        em.close();
        
        //전체 app이 완전히 끝나면 EntityManagerFactory를 닫아줘야한다.
        emf.close();

    }
}

JPA는 트랜잭션이 엄청 중요하다!
데이터를 변경하는 모든 작업은 꼭 트랜잭션 안에서 해야한다!
마지막에 commit을 꼭 해줘야 반영된다.

EntityManager를 닫는게 굉장히 중요하다.
EntityManager는 내부적으로 db connection을 물고 동작한다.그렇기때문에 사용을 다 하고나면 꼭 닫아줘야한다.

실행하면 아래와 같이 결과가 뜬다.
(아래 결과말고도 많이 뜨는데 신경쓰지 말자)

  • /* */ : 이 쿼리가 왜 나온건지 알려주는 것 (위의 경우에는 사용자(JPA)가 Member를 insert했다는 뜻)
  • /* */ 이후 insert ~ 끝 : 실제 쿼리

실행 후 db로 가보면 데이터가 잘 들어온것을 볼 수 있다.

여기서 의문이 들 것이다.

어 이상하다? 나는 멤버가 어느 테이블에 저장되라고 한 적 없는데?
사실 관례를 따른것이다.

만약에 테이블 이름이 member가 아니라 user로 되어있다면 Member 파일에서 @Table를 사용해 설정해주면된다.

import javax.persistence.Table;

@Entity
@Table(name = "USER")  //실제 db의 table명과 매치!
public class Member {

마찬가지로 코드의 필드명과 db의 컬럼명이 맞지않으면 @Column 을 사용해서 설정할 수 있다.

@Column(name = "username") //실제 db의 컬럼명과 일치! 
private String name;

이렇게 annotation에 필요한 맵핑을 다 하면된다.

여기서 JpaMain의 코드가 조금 이상한 부분이 있다!
저렇게 작성하고 중간에 문제가 생기면 .close() 부분이 호출이 안된다.
안좋은 코드이다.
아래는 정석 코드이다.

회원등록

try-catch를 이용

package hellojpa;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

public class JpaMain {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");

        EntityManager em = emf.createEntityManager(); //db 커넥션 하나 받았다고 생각
        EntityTransaction tx = em.getTransaction();
        tx.begin(); //db trasaction 시작
        
        try {
            //code(데이터 저장, 조회 등 실제 코드 작성)
            Member member = new Member();
            member.setId(1L);
            member.setName("HelloA");
            
            em.persist(member);

            tx.commit();
        }catch (Exception e) {
            tx.rollback();
        }finally {
            em.close(); 
        }
        //전체 app이 완전히 끝나면 emf를 닫아줘야한다.
        emf.close();
    }
}
  • em.close();가 매우중요!
    엔티티매니저가 결국 내부적으로 데이터베이스 커넥션을 물고 동작하기때문에 사용을 다하면 꼭 닫아줘야한다.
    그래야 내부적으로 데이터 베이스 커넥션 반환됨

실제 spring에서 사용하면 위처럼 코드가 많지는 않다. (다 해주기때문,, persist()정도만 호출해주면 됨)

  • emf.close();
    ex) 웹 어플리케이션이라면 WAS가 내려갈때 닫아줘야한다.
    그래야 커넥션 풀이나 그런 리소스가 릴리즈되기때문이다.

회원조회

EntityManager를 자바 컬렉션처럼 이해하면된다.
내 객체를 대신 저장해주는 것.

그래서 아래코드로 id가 1L에 해당하는 멤버를 찾을 수 있다.
Member findMember = em.find(Member.class, 1L);
Member.class에서 PK인 id값이 1L인 멤버를 찾아오는 코드

회원삭제

try {
    //code(데이터 저장, 조회 등 실제 코드 작성)
    Member findMember = em.find(Member.class, 1L); //조회 
    em.remove(findMember);// 삭제 

    tx.commit();
}

회원수정

try {
    //code(데이터 저장, 조회 등 실제 코드 작성)
    Member findMember = em.find(Member.class, 1L); //조회
    findMember.setName("HelloJPA");
            
    //em.persist(findMember); //수정 후 안해도됨

    tx.commit();
}

수정 후 .persist()로 저장해주지않아도된다.
지바 컬렉션 다루는것처럼 다루도록 설계되어서 그렇다.

어떻게 되는걸까?
JPA를 통해서 Entity를 가져오면 이 객체는 이제 JPA를 통해 관리됨
그리고 트랜잭션 커밋 시점에 JPA가 변경여부를 다 체크해서, 변경되었으면 트랜잭션 커밋직전에 update쿼리 만들어서 날림


update 쿼리가 나간다.

rdb는 데이터변경자체를 트랜잭션안에서 실행하도록 다 설계가 되어있다.
-> Q : 나는 jpa에서 트랜잭션 안걸어도 디비 변경되던데?
-> A : 우리가 트랜잭션 안걸어도 db는 단건 쿼리가 올때마다 내부적으로 다 처리하는 것.

+단순한 조회시 find()를 이용하는 것 말고, 특정 조건이 걸린 다양한 데이터를 한 번에 가져오려면 어떻게 해야할까?
-> JPQL사용!!

JPQL

객체를 대상으로하는 객체지향 쿼리

EntityManager의 createQuery("쿼리", 타입)를 사용해 파라미터에 쿼리를 작성해서 넣어준다.

(SQL에서 아용하는 기본적인 쿼리는 다 됨)

//JPQL로 다양한 데이터 한 번에 조회
List<Member> result = em.createQuery("select m from Member as m", Member.class)
            .getResultList();

for (Member member : result) {
    System.out.println("member.getName() = " + member.getName());
}

JPA입장에서는 테이블을 대상으로 코드를 절대 짜지 않음.
(테이블에서 가져오면 JPA의 사상이 깨짐)

멤버 객체 대상으로 쿼리를 날린다고 생각
"select m from Member as m" -> 멤버 객체 다 가져와!( select m : Member 엔티티 선택한 것) : 대상이 객체임


결과를 보면, select에서 Member의 필드를 모두 가져옴

어떤 이점이 있을까?

  • 페이지네이션 가능!
 List<Member> result = em.createQuery("select m from Member as m", Member.class)
        .setFirstResult(5) //5번부터 
        .setMaxResults(8) //8개 가져와 
        .getResultList();
  • 방언을 바꾸어도, jpql을 수정할 필요는 없음

0개의 댓글