JPA/JPA 기본

5. 개발

sdafdq 2023. 10. 19. 12:21

JPA 구동 방식

 

JPA는 Persistence라는 클래스가 있는데,

저게 persistence.xml을 읽어서

EntityManagerFactory라는 클래스를 만듦.

 

그 후, 필요할 때 마다 EntityManager라는 것을 생성해서 그걸 사용하면 됨.

 

자 여튼 본격적으로 실습해 보자면,

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

이렇게 팩토리 생성해서 "hello"해서 저기에 관한 정보들로 구성된 팩토리를 생성해 주고

한번 실행해봤는데

 

막 빨간줄로 쫘라락 뜸.

근데 이렇게 된거면 잘한거라고 함..

"C:\Program Files\Java\jdk-17\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2023.2.2\lib\idea_rt.jar=63057:C:\Program Files\JetBrains\IntelliJ IDEA 2023.2.2\bin" -Dfile.encoding=UTF-8 -classpath E:\spring\ex-hello-jpa\target\classes;C:\Users\세잔실습실13\.m2\repository\org\hibernate\orm\hibernate-core\6.2.9.Final\hibernate-core-6.2.9.Final.jar;C:\Users\세잔실습실13\.m2\repository\jakarta\persistence\jakarta.persistence-api\3.1.0\jakarta.persistence-api-3.1.0.jar;C:\Users\세잔실습실13\.m2\repository\jakarta\transaction\jakarta.transaction-api\2.0.1\jakarta.transaction-api-2.0.1.jar;C:\Users\세잔실습실13\.m2\repository\org\jboss\logging\jboss-logging\3.5.0.Final\jboss-logging-3.5.0.Final.jar;C:\Users\세잔실습실13\.m2\repository\org\hibernate\common\hibernate-commons-annotations\6.0.6.Final\hibernate-commons-annotations-6.0.6.Final.jar;C:\Users\세잔실습실13\.m2\repository\io\smallrye\jandex\3.0.5\jandex-3.0.5.jar;C:\Users\세잔실습실13\.m2\repository\com\fasterxml\classmate\1.5.1\classmate-1.5.1.jar;C:\Users\세잔실습실13\.m2\repository\net\bytebuddy\byte-buddy\1.14.7\byte-buddy-1.14.7.jar;C:\Users\세잔실습실13\.m2\repository\jakarta\xml\bind\jakarta.xml.bind-api\4.0.0\jakarta.xml.bind-api-4.0.0.jar;C:\Users\세잔실습실13\.m2\repository\jakarta\activation\jakarta.activation-api\2.1.0\jakarta.activation-api-2.1.0.jar;C:\Users\세잔실습실13\.m2\repository\org\glassfish\jaxb\jaxb-runtime\4.0.2\jaxb-runtime-4.0.2.jar;C:\Users\세잔실습실13\.m2\repository\org\glassfish\jaxb\jaxb-core\4.0.2\jaxb-core-4.0.2.jar;C:\Users\세잔실습실13\.m2\repository\org\eclipse\angus\angus-activation\2.0.0\angus-activation-2.0.0.jar;C:\Users\세잔실습실13\.m2\repository\org\glassfish\jaxb\txw2\4.0.2\txw2-4.0.2.jar;C:\Users\세잔실습실13\.m2\repository\com\sun\istack\istack-commons-runtime\4.1.1\istack-commons-runtime-4.1.1.jar;C:\Users\세잔실습실13\.m2\repository\jakarta\inject\jakarta.inject-api\2.0.1\jakarta.inject-api-2.0.1.jar;C:\Users\세잔실습실13\.m2\repository\org\antlr\antlr4-runtime\4.10.1\antlr4-runtime-4.10.1.jar;C:\Users\세잔실습실13\.m2\repository\com\h2database\h2\2.1.214\h2-2.1.214.jar hellojpa.JpaMain
10월 19, 2023 11:32:58 오전 org.hibernate.jpa.internal.util.LogHelper logPersistenceUnitInformation
INFO: HHH000204: Processing PersistenceUnitInfo [name: hello]
10월 19, 2023 11:32:58 오전 org.hibernate.Version logVersion
INFO: HHH000412: Hibernate ORM core version 6.2.9.Final
10월 19, 2023 11:32:58 오전 org.hibernate.cfg.Environment <clinit>
INFO: HHH000406: Using bytecode reflection optimizer
10월 19, 2023 11:32:59 오전 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl configure
WARN: HHH10001002: Using built-in connection pool (not intended for production use)
10월 19, 2023 11:32:59 오전 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001005: Loaded JDBC driver class: org.h2.Driver
10월 19, 2023 11:32:59 오전 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001012: Connecting with JDBC URL [jdbc:h2:tcp://localhost/./test]
10월 19, 2023 11:32:59 오전 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001001: Connection properties: {password=****, user=sa}
10월 19, 2023 11:32:59 오전 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl buildCreator
INFO: HHH10001003: Autocommit mode: false
10월 19, 2023 11:32:59 오전 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl$PooledConnections <init>
INFO: HHH10001115: Connection pool size: 20 (min=1)
10월 19, 2023 11:32:59 오전 org.hibernate.bytecode.internal.BytecodeProviderInitiator buildBytecodeProvider
INFO: HHH000021: Bytecode provider name : bytebuddy
10월 19, 2023 11:33:00 오전 org.hibernate.engine.transaction.jta.platform.internal.JtaPlatformInitiator initiateService
INFO: HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]

뭐 빨간 줄 이라서 그렇지 INFO : 이렇게 되어 있는거 보니 그냥 로그인가 봄

 

public class JpaMain {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();
        //여기에 작업
        em.close();
        emf.close();
    }
}

우리가 em까지는 앎. 

emFactory 까지는 처음보내. 뭐 근데 대충 em을 생성해주는.. em과 관련된.. 저 팩토리는 로딩 시점에 딱 하나만 만들어 줘야 함. 그리고 한 트랜잭션의 단위? 그 때마다 em을 새로 생성해서 그걸로 DB와 관련된 작업을 하고, 닫아주면 됨.

 

close() 이거 안쓴지 하도 오래 되어서

저게 커넥션 close 해주는 거였었나?

 

여튼 저렇게 em 생성하고, em을 통해 작업하고, 

 

나중엔 em과 emf를 닫아줘야 함.

그럼 emf를 닫는 것도 서버 완전히 닫을 때 일듯?

em은 클라이언트에서 요청이 올때마다 새로 생성되고, 클라이언트가 요청한 작업 다 끝나면 닫아서 버려주고.

그래서 em은 다른 쓰레드간에 공유하면 안됨.

얘가 커넥트랑도 묶여있어서 다른 쓰레드랑 공유되면 의도하지 않게 동작할 수도 있음.

그냥 요청들어오면 쓰고 닫아서 버리고.

 

여튼 대충 확인은 해 봤고, 이제 본격적으로 DB 테이블 생성하고 또 도메인 만들어서 그거랑 연결할 건데,

 

@Entity
public class Member {
    @Id
    private Long id;
    private String name;
}

도메인에 엔티티까지 만들어 줌.

getter, setter 우선은 다 만들어 줌.

 

자 이제 슬 해보자.

Create

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("hello");

        em.persist(member);

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

팩토리에서 em 얻어오고,

Member 생성해서 값 넣어주고, 

em.persist(member) 하면 저장해 주는거임.

close도 잘 했고

그런데 안됨.

 

이유는, JPA는 꼭 트랜잭션 단위에서 해야 함. 왜냐하면 RDB 자체가 쿼리 하나날려도 DB 안에서는 결국 하나의 트랜잭션 안에서 끝내는 거임. AutoCommit모드가 그냥 자동으로 commit 해 주는 거니까.

 

이제 트랜잭션 얻어서 진행해 보겠음.

 

public static void main(String[] args) {
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
    EntityManager em = emf.createEntityManager();
    EntityTransaction tx = em.getTransaction();

    tx.begin();

    Member member = new Member();
    member.setId(1L);
    member.setName("hello");

    em.persist(member);

    tx.commit();

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

팩토리에서 em 얻어오고,

em에서 트랜잭션 얻어서,

트랜잭션 시작해 주고,

 

작업.

작업된 것을 

em.persist(작업된것);

해서 넣음.

 

그리고 트랜잭션 커밋.

 

제대로 됨.

실제로 DB에 들어갔음.

 

 

로그에,

우리가 hibernate.show_sql 해서 옵션을 설정해 줘서 로그에 쿼리 찍히도록 해 놨음.

 

 

여튼, 잘 된다.

그리고 우리가 딱히 맵핑해 준적도 없는데 잘 되는 이유는, JPA가 알아서 클래스 이름이나 필드 이름보고 맵핑 해 줌.

만약 좀 이 엔티티 필드명이나 클래스명이 열명이나 테이블명과 다르다,

@Entity
@Table(name="USER")
public class Member {
    @Id
    private Long id;
    
    @Column(name="username")
    private String name;
}

이런 식으로

@Table(name="테이블명")

@Column(name="열명")

해서 명시해 주면 됨.

 

물론 다시 말하지만,

기본적으로 클래스명을 테이블명으로 인식하고,

필드명을 열명으로 인식함.

 

 

다시 main으로 돌아와서, 저 코드 문제없이 저장이 되지만, 에러상황 처리를 안했음.

public static void main(String[] args) {
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
    EntityManager em = emf.createEntityManager();
    EntityTransaction tx = em.getTransaction();

    tx.begin();
    try{
        Member member = new Member();
        member.setId(2L);
        member.setName("helloB");

        em.persist(member);
        tx.commit();
    } catch (Exception e){
        tx.rollback();
    } finally {
        em.close();
    }

    emf.close();
}

em이 커넥션을 물고 동작하기에, 작업 다 끝났으면 꼭 커넥션 닫아줘야 함.

 

 

대충 틀은 완성되었으니, 조회도 해보자.

public static void main(String[] args) {
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
    EntityManager em = emf.createEntityManager();
    EntityTransaction tx = em.getTransaction();

    tx.begin();

    try{
        Member findedMember = em.find(Member.class, 1L);
        System.out.println("id = " + findedMember.getId());
        System.out.println("name = " + findedMember.getName());
        tx.commit();

    } catch (Exception e){
        tx.rollback();
    } finally {
        em.close();

    }

    emf.close();
}

콘솔로 찍어본거 말고, 

그냥

em.find(찾아올것의클래스, id)

em.find() 이 메소드가 

아마 저 찾아올것의 클래스로 찾아온 뒤 binding 해주는 듯.

기본으로 id로 찾고.

id 이외의 것으로 찾으려면 따로 작업 해줘야 하는 걸로 앎.

 

 

 

 

변경

public static void main(String[] args) {
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
    EntityManager em = emf.createEntityManager();
    EntityTransaction tx = em.getTransaction();

    tx.begin();

    try{
        Member findedMember = em.find(Member.class, 1L);
        findedMember.setName("change");

        tx.commit();

    } catch (Exception e){
        tx.rollback();
    } finally {
        em.close();

    }

    emf.close();
}

변경도 신기한 게,

사실 조회해서 찾아올 때, 그 상태를 캡쳐해놓음.

그 다음 그냥 위처럼 그 찾아온 것의 값을 변경해주면,

놀랍게도 실제로 변경이 됨.

 

이게 왜 이러냐면, 조회해서 처음 찾아왔을 때 그 상태를 캡쳐해 놓는다고 했는데,

 

나중에 commit 시점에 그때 캡쳐해 놓은거랑 지금 상태랑 바뀐점 찾은 다음에,

바뀐 점 있으면 알아서 쿼리 만들어서 날려 줌. 그 후 commit을 실행함.

 

em을 통해서 뭔갈 가져오면, 그 가져온 건 em도 관리를 함.

 

 

 

삭제

public static void main(String[] args) {
    EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
    EntityManager em = emf.createEntityManager();
    EntityTransaction tx = em.getTransaction();

    tx.begin();

    try{
        Member findedMember = em.find(Member.class, 1L);
        em.remove(findedMember);

        tx.commit();
    } catch (Exception e){
        tx.rollback();
    } finally {
        em.close();
    }

    emf.close();
}

그냥 찾아온 다음에,

em.remove(찾아온거)

해주면 삭제됨.

실제 DB에서 삭제 됨.

 

 

 

 

근데, 예를들어 select 같은 건 id만 기준으로 가지고 올 수 있다.

좀더 세세한 조건을 주고 싶다면? 예를 들어 이름에 a가 들어간다거나.

 

JPA도 결국은, 우리가 호출하는 메소드에 따라, 엔티티의 상태에 따라 그 상태를 읽고 쿼리를 만들어서 RDB에 날려 주는 것이다.

그렇기 때문에, 그런 세세한 조건들을 맞추려면, 쿼리를 작성할 수 밖에 없다.

JPA는 JPQL이라는 쿼리를 따로 지원한다.

 

 

 

 

 

JPQL 전체 조회

List<Member> result =  em.createQuery("select m from Member as m", Member.class)
                .getResultList();

em.createQuery(JPQL문, 만들엔티티타입)

.getResultList();

 

일단 쿼리를 만들고, (아마 JPQL -> SQL, 방언 등의 여부도 따지고)

날린 것을 리스트 형태로 받아온 다고 해서 getResultList()

 

JPQL을 살펴보면

select m from Member as m

보통 SQL과는 다른 느낌인데, 이건 엔티티, 즉 객체를 대상으로 하는 SQL문이라고 보면 된다.

왜냐하면 DB 테이블 대상으로 쿼리를 날려버리면 그 DB에 종속적인 설계가 되기 때문에 객체를 대상으로 작성하는 SQL문임.

객체지향 쿼리 언어임.

 

 

JPQL은 나중에 자세히 다루긴 할 텐데,

일단 지금은 

조회한다 m 로부터 멤버객체를 m이란 별명을짓고

즉 select m 이니 m의 모든 것을 가져온다.

 

또,

List<Member> result =  em.createQuery("select m from Member as m", Member.class)
                    .setFirstResult(5)
                    .setMaxResults(8)
                    .getResultList();

이런 페이징 작업도 함수로 할 수 있다.

 

페이징이란 한번에 조회할 데이터를 결정하는 방법이다.

페이징은 특히 DB마다 쿼리가 다른데, 방언( dialect )을 h2로 설정해놔서 쿼리 나갈 때 H2 껄로 나간다.

그래서, 방언만 누군지 명시를 해 주면 JPQL을 안바꿔도 된다.

 

 

 

 

 

 

'JPA > JPA 기본' 카테고리의 다른 글

7. 영속성 컨텍스트 2  (0) 2023.10.20
6. 영속성 컨텍스트  (0) 2023.10.20
4. 프로젝트 생성  (0) 2023.10.19
3. JPA  (0) 2023.10.19
2. 객체지향과 관계형DB  (0) 2023.10.18