본문 바로가기
SPRING

[JAVA] JPA 개념 및 예제

by 킹명주 2022. 7. 18.

저번에 spring에서 jpa를 활용하여 로그인 및 회원가입을 구현했다. 근데 jpa를 잘 모르고 쓰다보니 이게 맞는건지.. mybatis와 별 차이가 없는데? 이런 생각이 들어 강의를 찾아보았고 이에 대한 정보를 정리하고자 한다.

https://mjoo1106.tistory.com/entry/JPA%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B5%AC%ED%98%84

 

[Spring] JPA를 활용한 로그인 구현

https://mjoo1106.tistory.com/entry/Spring-JPA%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%ED%9A%8C%EC%9B%90%EA%B0%80%EC%9E%85-%EA%B5%AC%ED%98%84 [Spring] JPA를 활용한 회원가입 구현 앞서 MyBatis를 활용해서..

mjoo1106.tistory.com


 

JPA란?

JPA는 JAVA ORM 기술 표준으로 사용되는 인터페이스 모음이다. 대표적인 오픈소스로는 Hibernate가 있다. 말이 너무 어렵다 그래서 ORM은 무엇일까..?

ORM(Object-Relational Mapping)

아마 이 때까지는 RDB(Relational DataBase)의 테이블에 직접 연결(매핑)해서 사용했을 것이다. 그런데 ORM은 객체와 RDB 데이터를 자동으로 연결(매핑)해준다. 객체 지향 프로그래밍은 클래스를 사용하고, RDB는 테이블을 사용한다.

 

그래서 왜 JPA를 사용해야할까?

2016년 6월부터 현재까지의 트랜드를 조사해보았다. 과거에는 mybatis를 많이 사용했지만 jpa관심이 계속 증가하더니 2021년 부터는 mybatis를 이겼다.  사실 이 이유 하나로만으로도 jpa를 쓸 이유는 충~~분하다. 왜? 취미로 프로그래밍을 배우는 사람은 해당되지 않지만 그 외는 취업을 목표로 두고 있기 때문이다. 

 

두 번째 이유는 설계에 있다고 생각한다. 기존에는 RDB에 있는 물리적 테이블을 대상으로 쿼리를 날렸다. 이 경우 해당 데이터베이스에 종속적으로 설계되는 경우가 빈번하다. 반면 JPA는 엔티티 객체를 대상으로 쿼리를 작성하므로 이와 반대되는 성격을 지녔다. 그 외에도 JPA를 사용하는 이점이 많지만, 가장 중요한 이유 두 가지만 나타내보았다.


C(Create) R(Read) U(Update) D(Delete)

 

이번 실습에서는 완전 기초부터 할 예정이므로 SPRING을 사용하지 않고 진행해보겠다. 우선 실험 환경은 MySQL, Intellij, JDK 11, gradle을 사용했다. (DB는 H2, Oracle 등 다양한 것을 사용해도 된다. 실제 코드는 한 줄만 다르다.)

 

1. Project Create

2. build.gradle

dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
    implementation 'mysql:mysql-connector-java:8.0.28'
    implementation 'org.hibernate:hibernate-entitymanager:5.3.10.Final'
    implementation 'javax.xml.bind:jaxb-api:2.3.0'
}

dependencies를 위와 같이 수정한다. hibernate는 jpa의 대표적인 오픈소스이다. 그리고 mysql을 사용하지 않는 분들은 3 번째 줄만 변경하면 된다.

 

3. persistence.xml

Spring으로 진행했을 때는 application.properties 에서 슉슉 진행했지만, 이번에는 JAVA 환경에서만 진행하기 때문에 persistence.xml 을 통해 DB와 매핑한다. 이 때 경로는 main > resources > META-INF > persistence.xml이다.

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" version="2.1">
    <persistence-unit name="hello">
        <class>hellojpa.Member</class>
        <properties>
            <!-- 필수 속성 -->
            <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
            <property name="javax.persistence.jdbc.user" value="root"/>
            <property name="javax.persistence.jdbc.password" value="your_password"/>
            <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/{schema}?serverTimezone=UTC&amp;characterEncoding=UTF-8"/>
            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect"/>
            <!-- 쿼리 보여줘 -->
            <property name="hibernate.show_sql" value="true"/>
            <!-- 쿼리 예쁘게 포맷해줘 -->
            <property name="hibernate.format_sql" value="true"/>
            <!-- insert 등으로 왜 쿼리가 발생했는지 -->
            <property name="hibernate.use_sql_comments" value="true"/>
        </properties>
    </persistence-unit>
</persistence>

필요한 부분만 설명하자면 첫 번째로 persistence-unit name을 반드시 지정해주어야 한다. (이름은 아무거나) 그 후 <class>hellojpa.Member</class>를 지정한 이유는 Member라는 클래스를 나중에 만들 것인데, 인식을 하지 못해서 추가해줬다. (Maven에서는 문제가 발생하지 않는 것으로 파악) 그 후 mysql 설정을 해주는 코드이다. h2, oracle 등을 사용하는 경우에는 검색 후 properties의 첫 번째 줄과 다섯 번째 줄만 변경해주면 된다. 그리고 4 번째 줄의 schema는 mysql의 경우 데이터베이스 이름이다.

 

3. Main

세팅은 끝났으므로 main 함수를 만들어준다. 그 전에 패키지를 아무 이름이나 하나 생성한다.  경로는 main > java > (생성한 패키지 이름) > (생성한 클래스 이름) 

JpaMain

package hellojpa;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.util.List;

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

        tx.begin();
        try {
        // 나중에 CRUD code 입력
            tx.commit();
        } catch (Exception e){
            tx.rollback();
        } finally{
            em.close();
        }
        emf.close();
    }
}

main 함수 안의 요소들을 하나씩 살펴보자

> emf

emf의 경우 앞서 persistence.xml을 만들었을 때 지정해준 이름이 있을 것이다. 이를 활용하여 mysql과 연결하는 것으로 애플리케이션에서 딱 한 번만 사용한다.

 

> em

entity를 관리하는 역할을 수행한다. 

 

> tx

transaction 또한 반드시 지정해야 한다. commit, rollback 옵션이 있는데 두 가지 요소에 대해 왜 사용하는지 간략하게 설명하겠다.

commit은 간단하게 항공사 예제로 설명하겠다. A라는 사용자가 43D 자리를 예약하고 있다. 이 때 B라는 사용자가 동시에 43D 자리를 예약한다면 어떤 상황이 발생할까? 특정 예외처리가 없다면 동시에 예약될 수 있다. 즉, 이런 동시성 문제를 해결하기 위해 commit을 사용하는데 commit은 query를 처리하는 동안 lock을 수행하여 다른 작업이 들어오지 못하고 작업이 끝나면 다음 작업을 진행할 수 있다. 이를 통해 항공사 문제도 해결할 수 있을 것이다. rollback은 말 그대로 오류가 났을 때 rollback하는 것이라 넘어가겠다.

 

코드는 try 구문 안에서 작성할 것이다. 이제 CRUD 각각의 요소 하나씩 작성할 것인데, 이 전에 현재 데이터베이스가 비어있으므로 하나 만들어주고 이를 매핑할 Class를 하나 만들자!

 

4. MySQL

mysql -u root -p
Enter password: 

mysql> create database jpatest;
mysql> use jpatest;
mysql> create table Member( id bigint not null primary key, name varchar(255));

select * from member; 를 입력했을 때 Empty set 이라고 나오면 성공적으로 생성된 것이다.

 

5. Member

아까 만든 main 함수와 동일한 위치에 Member Class를 하나 만들어준다.

package hellojpa;

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

@Entity
/*
@Table(name="") 으로 수동으로 매핑 가능하다.
@Column(name="")으로 거기서 지정한 이름이랑 다르게 지정했을 때 매핑해서 사용함
 */
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를 통해 PK라는 것을 알려준다. Table과 Column의 이름을 다르게 지정하고 싶은 경우에는 @Table, @Column을 통해 수동으로 매핑해주면 사용 가능하다.

 

6. Create

    try {
            Member member = new Member();
            member.setId(1L);
            member.setName("HelloA");

            em.persist(member);
            tx.commit();
        }

삽입은 위 코드가 끝이다. 실제로 실행시켜보면 빨간 줄이 많이 뜰 것인데 그럼 성공한 것이다.

7. Read

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

em.find(Member.class, 1L)에서 1L은 Id가 1인 것을 조회하라는 뜻이다.

8. Update

    try {
            Member findMember=em.find(Member.class,1L);
            findMember.setName("HelloGTA");
            tx.commit();
        }

 

9. Delete

    try {
            Member findMember=em.find(Member.class, 1L);
            em.remove(findMember);
            tx.commit();
        }

10. JPQL

아마 앞의 CRUD를 보면서 이렇게 간단할 수가 있나? 라는 생각이 든다.. 점점 JPA 빠져들고 있다...

그런데 복잡한 조회는 어떤 식으로 접근할까? 라는 고민이 들 수도 있다. 이 때 사용하는 것이 JPQL이다. JPQL은 SQL을 추상화한 객체 지향 쿼리 언어이다. SQL 문법과 유사해서 기존에 SQL 문을 사용해 본 사람이라면 쉽게 적응할 수 있다. 즉, SQL은 DB TABLE을 대상으로 쿼리문을 날리고 JPQL은 엔티티 객체를 대상으로 쿼리를 날린다는 차이점이 있다. 둘 다 사용할 수 있다. 앞선 로그인 예제에서는 SQL 문을 사용하고 있다. 그러나 JPA를 쓰는 것으로 결정했다면 당장 JPQL을 연습해야 한다.

 

그래서 조금 더 복잡하게 짜보겠다. 첫 번째부터 다섯번째까지 이름을 조회하라는 쿼리문을 어떻게 작성할까?

    try {
            List<Member> result=em.createQuery("select m from Member as m", Member.class)
                    .setFirstResult(1)
                    .setMaxResults(5)
                    .getResultList();
            for(Member member : result){
                System.out.println("member.name = "+member.getName());
            }
        }

JPA에 관련해서 진짜 기초만 알아보았다. 이렇게 블로그에 정리해보니 조금 감이 오는 것 같다. 확실히 개념을 적립해서 로그인, 회원가입 예제를 수정해보겠다!