저번에 spring에서 jpa를 활용하여 로그인 및 회원가입을 구현했다. 근데 jpa를 잘 모르고 쓰다보니 이게 맞는건지.. mybatis와 별 차이가 없는데? 이런 생각이 들어 강의를 찾아보았고 이에 대한 정보를 정리하고자 한다.
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&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에 관련해서 진짜 기초만 알아보았다. 이렇게 블로그에 정리해보니 조금 감이 오는 것 같다. 확실히 개념을 적립해서 로그인, 회원가입 예제를 수정해보겠다!
'SPRING' 카테고리의 다른 글
[JAVA] JPA 연관관계 매핑 (0) | 2022.07.25 |
---|---|
[JAVA] JPA 실습(Create table and PK/FK 설정) (0) | 2022.07.19 |
[Spring] JPA를 활용한 로그인 구현 (2) | 2022.07.15 |
[Spring] JPA를 활용한 회원가입 구현 (0) | 2022.07.14 |
[Spring] MyBatis를 활용한 Login 구현 - 2 (0) | 2022.07.06 |