본문 바로가기
SPRING

[JAVA] JPA 실습(Create table and PK/FK 설정)

by 킹명주 2022. 7. 19.

https://mjoo1106.tistory.com/entry/JAVA-JPA-%EA%B0%9C%EB%85%90-%EB%B0%8F-%EC%98%88%EC%A0%9C

 

[JAVA] JPA 개념 및 예제

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

mjoo1106.tistory.com

앞서 JPA의 기본 개념과 예제를 통해 간략하게 실습하는 시간을 가져보았다. 이번 시간에는 거기에 조금 더 나아가고자 한다.


데이터베이스 설계

 

국민 예제로 유명한 수강신청 시스템 DB를 간략하게 설계해보자.

진짜 데이터베이스 설계는 아니고 이런 형을 쓸 것이다 그리고 이런 관계가 있다 정도로만 참고하면 될 것이다. 이를 바탕으로 테이블을 jpa를 통해 create해보고 join을 통해 조회까지 해보고자 한다.


코드 작성

 

1. 프로젝트 생성

 

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 파일을 위와 같이 작성한다. 참고로 mysql로 프로젝트를 진행하기 때문에 3 번째 줄은 자신이 사용하는 db 플랫폼에 따라 다르게 설정해야 한다.

 

src > main > 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>jpaenroll.domain.Enroll</class>
        <class>jpaenroll.domain.Student</class>
        <class>jpaenroll.domain.Subject</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="08520130"/>
            <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/jpaschool?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"/>
            <property name="hibernate.hbm2ddl.auto" value="create"/>
        </properties>
    </persistence-unit>
</persistence>

참고로 META-INF와 persistence.xml은 직접 만들어 주어야 한다. 여기서 마지막 줄을 주목해야 한다. hibernate.hbm2ddl.auto value="create"라고 작성되어 있는 부분은 기존의 데이터베이스가 있다면 날려버리고 새로 하나 만드는 옵션이다. 만약 jpa가 종료되는 시점에 만든 db를 없애고 싶다면 create-drop, 새로 추가된 부분만 업데이트 해주는 update 옵션이 있다. 초기에는 table을 만들기 위해 create로 지정했다.

그리고 password 설정 밑에 jpaschool의 경우 db의 스키마로 mysql에서는 create database jpaschool; 로 생성 후 사용 가능하다.

 

3. Domain 

앞서 설계한 db를 참고하여 각각을 객체로 매핑하려고 한다. 그러므로 위 양식에 맞춰 하나씩 만들어주자!

Student

package jpaenroll.domain;

import javax.persistence.*;

@Entity
public class Student {
    @Id
    @Column(name="student_id")
    private Long id;
    private int grade;
    // enum은 기본적으로 숫자인데, 이를 string 그대로 사용한다는 뜻임
    @Enumerated(EnumType.STRING)
    private KindOfMajor major;

    public Long getId() {
        return id;
    }

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

    public int getGrade() {
        return grade;
    }

    public void setGrade(int grade) {
        this.grade = grade;
    }

    public KindOfMajor getMajor() {
        return major;
    }

    public void setMajor(KindOfMajor major) {
        this.major = major;
    }
}

 

enum을 정의하기 위해 KindOfMajor enum을 하나 생성해야 한다. (domain 안에 KindOfMajor class를 생성해주세요!)

package jpaenroll.domain;

public enum KindOfMajor {
    Math, Computer, Science
}

 

Subject

package jpaenroll.domain;

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

@Entity
public class Subject {
    @Id
    @Column(name="subject_id")
    private Long id;
    private String name;
    private int grade;

    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;
    }

    public int getGrade() {
        return grade;
    }

    public void setGrade(int grade) {
        this.grade = grade;
    }
}

 

Enroll

package jpaenroll.domain;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.time.LocalDateTime;

@Entity
public class Enroll {
    @Id @GeneratedValue
    @Column(name="enrollment_id")
    private Long id;
    @Column(name="student_id")
    private Long studentid;
    @Column(name="subject_id")
    private Long subjectid;
    private LocalDateTime enrolldate;

    public Long getId() {
        return id;
    }

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

    public Long getStudentid() {
        return studentid;
    }

    public void setStudentid(Long studentid) {
        this.studentid = studentid;
    }

    public Long getSubjectid() {
        return subjectid;
    }

    public void setSubjectid(Long subjectid) {
        this.subjectid = subjectid;
    }

    public LocalDateTime getEnrolldate() {
        return enrolldate;
    }

    public void setEnrolldate(LocalDateTime enrolldate) {
        this.enrolldate = enrolldate;
    }
}

lombok이 마렵지만 참아야한다. 기초부터 차근차근 가보자. 이렇게 작성해주면 앞서 작성한 db 설계에 딱맞다. 근데 여기서 @Id, @GeneratedValue, @Column 이런 것을 사용했는데 하나씩 간단하게 설명하고자 한다.

 

@Id는 PK를 알려주는 것이다!

 

@GeneratedValue는 값을 자동으로 생성해서 넣어주는 것이다. 위 코드처럼 옵션을 사용하지 않으면 AUTO INCREMENT와 동일한 효과를 얻을 수 있다. 즉, 입력하지 않아도 자동으로 1씩 증가한 숫자가 기본 키에 할당된다.

 

@Column은 실제 테이블의 column name과 내가 사용하고자하는 객체 이름이 다를 때 사용한다. 만약 student_unique_id 라는 column이 있다고 생각하면 이를 매번 자바 코드에 작성하기 힘들다. 그래서 나는 id라고만 쓰고 위에 @Column을 통해 수동으로 매핑해주는 것이다.

 

 

4. 테이블 생성

앞서 create 옵션을 사용했으므로 기본 main 코드만 작성해서 테이블이 생성되는지 테스트 해보자.

package jpaenroll;

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();
        EntityTransaction tx=em.getTransaction();
        tx.begin();
        try {
            tx.commit();
        } catch (Exception e){
            tx.rollback();
        } finally{
            em.close();
        }
        emf.close();
    }
}

성공적으로 create 된 것을 확인할 수 있다. 이제 임의로 값을 insert 후 join 문을 진행해보고자 한다.

 

5. INSERT

INSERT 하기 전에 아까 persistence.xml의 마지막 줄에서 create -> none 으로 반드시 변경해야한다.

        try {
            Subject subject=new Subject();
            subject.setId(1101L);
            subject.setName("JPA_BASIC");
            subject.setGrade(3);

            Student student=new Student();
            student.setId(160164L);
            student.setGrade(4);
            student.setMajor(KindOfMajor.Computer);
            
            em.persist(subject);
            em.persist(student);
            tx.commit();
        }

위 코드는 JpaMain의 try 구문 안에 넣어주면 된다. 우선 subject와 student 정보를 하나씩 입력했다.

       try {
            Enroll enroll = new Enroll();
            enroll.setStudentid(em.find(Student.class, 160164L).getId());
            enroll.setSubjectid(em.find(Subject.class,1101L).getId());
            enroll.setEnrolldate(LocalDateTime.now());
            em.persist(enroll);
            tx.commit();
        }

위 코드 또한 JpaMain의 try 구문 안에 넣어주면 된다. 앞서 삽입된 정보들을 찾아 db에 삽입하도록 설계했다.

이제 조인문을 활용하여 진행해보자.

 

6. JOIN

Q. 수강신청에 성공한 사람의 전공은 무엇일까?

        try {
            Enroll enroll = em.find(Enroll.class, 1L);
            Student student = em.find(Student.class, enroll.getStudentid());
            System.out.println(student.getMajor());
            tx.commit();
        }

우선 find를 사용하여 enroll의 첫 번째 항목을 불러와 이를 활용하여 한 번 더 find 함수를 써서 전공을 찾는다.

그런데, 이는 JPA의 객체 지향 보다는 기존의 RDB 조회와 다를 것이 없다. 객체 그래프를 활용하고 있다는 느낌이 없다. (객체 그래프 : 이어진 객체들 사이의 관계)

 

그럼 어떻게 해결해야할까?

간단하게 Enroll Class에 private Student student; 를 하나 추가해준다. (현재는 table을 새롭게 create하지 않았기 때문에 참고만 해주세요!)

그 후 아래와 같은 코드를 작성한다.

        try {
            Enroll enroll = em.find(Enroll.class, 1L);
            Student student=enroll.getStudent();
            System.out.println(student.getMajor());
            tx.commit();
        }

두 코드를 비교해보면 어떤 것이 조금 더 객체 지향이 가까운지 바로 느껴질 것이다.


JPA는 진짜 신기하다. 하면서도 놀라는 경우가 많다. 앞으로 JPA 전문가가 되는 그 날까지 쭉~~ 진행해야겠다.

 

 

 

 

 

 

 

'SPRING' 카테고리의 다른 글

[Spring] JWT 실습 - 1  (2) 2022.07.27
[JAVA] JPA 연관관계 매핑  (0) 2022.07.25
[JAVA] JPA 개념 및 예제  (0) 2022.07.18
[Spring] JPA를 활용한 로그인 구현  (2) 2022.07.15
[Spring] JPA를 활용한 회원가입 구현  (0) 2022.07.14