본문 바로가기

개발/Backend

[SpringBoot] 예제로 배우는 스프링부트 입문 | JPA 기초

JPA란? 

SQL Mapper

  • SQL <- mapping -> object 필드
  • SQL문으로 직접 데이터베이스를 조작
  • jdbc Template, Mybatis 

 

ORM

  • DB 데이터 <- mapping -> Object 필드
  • 객체를 통해 간접적으로 데이터베이스의 데이터를 다룸
    • 객체와 데이터베이스의 데이터를 자동으로 매핑
    • SQL쿼리가 아니라 메서드로 데이터를 조작
    • 객체간 관계를 바탕으로 sql을 자동으로 생성
  • JPA, Hibemate
ORM은 RDB의 관계를 Object에 반영하는 것이 목적이라면, Mapper는 단순히 필드를 매핑시키는 것이 목적이라는 점에서 지향점의 차이가 있음 

 

JPA

  • JPA는 자바 ORM기술에 대한 표준 명세로, JAVA에서 제공하는 API이지 스프링에서 제공하는 것이 아님
  • Spring-Data-JPA는 JPA를 쉽게 사용하기 위해 스프링에서 제공하고 있는 프레임워크
  • 추상화 정도 (= 손쉽게 기술 사용할 수 있게 많은 api 개발되어 있음)
    • Spring-Data-JPA <- Hibernate <- JPA
    • Hiberate를 쓰는 것과 Spring Data JPA를 쓰는 것 사이에는 큰 차이가 없음
    • 다만 Spring-Data-JPA, Spring Data MongoDB, Spring Data Redis 등 Spring Data의 하위 프로젝트들은 findAll(), save()등을 동일한 인터페이스로 가지고 사용하기 때문에 저장소를 교체하도 기본적인 기능이 변하지 않음
    • 이러한 이유에서 Spring Data JPA를 사용하는 것이 좋음 

 

JPA 동작 과정

  • 개발자가 JPA를 사용하면 JPA 내부에서 JDBC API를 사용하여 SQL을 호출하여 DB와 통신

  • insert

  • find: 개발자는 JPA에서 데이터 꺼내오는 것이지 DB에서 꺼내는 것이 아님 

 

예시 | insert

Member1.java

package exam1;

import java.time.LocalDate;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

@Entity
@Table(name="JpaMember1")
public class Member1 
{
	@Id
	@GeneratedValue
	private Long id;
	private String username;
	@Column(name="create_date")
//	@Temporal(TemporalType.TIMESTAMP)
//	private Date createDate;
	private LocalDate createDate;

	protected Member1() {}
	
	public Member1(String username, LocalDate createDate) 
	{
		this.username = username;
		this.createDate = createDate;
	}
}

 

  • @Entity : 해당 클래스가 JPA의 엔티티임을 의미 = 이 클래스를 @Table 이름으로 만들겠다
  • @Id : 엔티티 클래스의 식별자 - DB 테이블의 primary 키
  • @GeneratedValue: 자동 생성된 값 
  • @Column : 매핑할 테이블의 컬럼 이름을 지정, 만약 필드에 어노테이션이 없으면, 필드 이름과 동일한 이름의
      테이블 컬럼에 매핑
  • @Timestamp : java.util.Date 타입을 매핑 => Java 8에 LocalDateTime 추가되고 (거의) 사용하지 않음
    • TemporalType.? 열거 타입
      - DATE : java.sql.Date 에 해당
      - TIME : java.sql.Time 에 해당
      - TIMESTAMP : java.sql.Timestamp 에 해당
  • @GeneratedValue(strategy = GenerationType.IDENTITY) : 기본키 생성을 데이터베이스에게 위임하는 방식으로 id값을 따로 할당하지 않아도 데이터베이스가 자동으로 AUTO_INCREMENT를 하여 기본키 생성

  • @GeneratedValue(strategy = GenerationType.SEQUNCE)

BasicUse.java

package exam1;

import java.time.LocalDate;

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

public class BasicUse 
{
	public static void main(String[] args) 
	{istence.createEntityManagerFactory("JpaEx01");
        EntityManager entityManager = emf.createEntityManager();
        EntityTransaction transaction = entityManager.getTransaction(); //3줄 필수 
        
        try {
        EntityManagerFactory emf =
                Pers
            transaction.begin();
            
            Member1 user = new Member1("홍길동1", LocalDate.now());
            entityManager.persist(user); // 메모리 안에 영속성으로 insert
            
            transaction.commit(); //실제 db로 insert(db와 영속성 동기화)
        } catch (Exception e) {
            e.printStackTrace();
            transaction.rollback(); //오류나면 rollback 
        } finally {
        	entityManager.close();
        }

        emf.close();
	}
}
  • persist: 메모리 안에 영속성으로 insert
  •  commit: 실제 db로 insert
  • rollback: 예외처리 
JPL을 사용하게 되면 테이블을 직접 관리하지 않아도 된다고 하는 장점은 서비스를 개발할 때 용이하지만, 대규모 SI작업을 할 때는 위험함

 

 

예제2 | insert 

Member2.java

package exam2;

import jakarta.persistence.Access;
import jakarta.persistence.AccessType;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.Table;
import jakarta.persistence.Transient;

@Entity
@Table(name="JpaMember2")
public class Member2 
{
	@Id
	@SequenceGenerator (
			name = "mySequence01",
			sequenceName = "JpaMember2_SEQ",
			initialValue = 1,
			allocationSize = 1
	)
	@GeneratedValue (generator = "mySequence01")
	private Long id;
	@Access(AccessType.FIELD)
	private String username;	// 필드를 통해서 데이터 접근
	@Access(AccessType.PROPERTY)
	private String password;	// 프로퍼티(get/set)를 통해서 데이터 접근
	@Transient
	private long timestamp1;			// 영속 대상에서 제외 어노테이션
	transient private long timestamp2;	// 영속 대상에서 제외 키워드

	protected Member2() {}
	
	public Member2(String username, String password) 
	{
		this.username = username;
		this.password = password;
	}

	public String getPassword()
	{
		return password;
	}

	public void setPassword(String password)
	{
		this.password = password;
	}
}
  • @AccessType.FIELD : member변수를 통해서 값에 접근(default)
  • @AccessType.PROPERTY : member메소드를 통해서 값에 접근 => getter, setter 만들어줘야함 
  • @Transient/ teansient: 테이블과 매핑되지 않음

 

예제3 

Member3.java

package exam3;

import java.time.LocalDate;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

@Entity
@Table(name = "JpaMember3")
public class Member3 
{
    @Id
    private String email;
    private String name;
    @Column(name = "create_date")
    private LocalDate createDate;

    protected Member3() { }

    public Member3(String email, String name, LocalDate createDate) 
    {
        this.email = email;
        this.name = name;
        this.createDate = createDate;
    }

    public String getEmail()
	{
		return email;
	}

	public String getName()
	{
		return name;
	}

	public LocalDate getCreateDate()
	{
		return createDate;
	}

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

 

insert | persist()이용 

Test01_insert.java

package exam3;

import java.time.LocalDate;

import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.Persistence;

public class Test01_Insert
{
	public static void main(String[] args)
	{
        EntityManagerFactory emf =
                Persistence.createEntityManagerFactory("JpaEx01");
        EntityManager em = emf.createEntityManager();

        try 
        {
            em.getTransaction().begin();

            Member3 user = new Member3("test@test.com", "홍길동", LocalDate.now());
            System.out.println(111);		// SQL문 어디서 출력되는지 확인
            em.persist(user);				// 영속 컨텍스트에 반영
            System.out.println(222);
            
            em.getTransaction().commit();	// 실제 sql문 처리
            System.out.println(333);
            System.out.println("가입 요청을 처리했습니다.");
        } catch (Exception e) {
            em.getTransaction().rollback();
            throw e;
        }

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

 

select | find()이용 

Test02_Select.java

package exam3;

import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.Persistence;

public class Test02_Select
{
	public static void main(String[] args)
	{
        EntityManagerFactory emf =
                Persistence.createEntityManagerFactory("JpaEx01");
        EntityManager em = emf.createEntityManager();

        Member3 user = em.find(Member3.class, "test@test.com");//id로 지정된 이메일 컬럼을 통해 검색 
        System.out.println("["+user+"]");
        
        if (user != null) 
        {
            System.out.println("이름 : " + user.getName());
            System.out.printf("생성 : %tY-%<tm-%<td\n", user.getCreateDate());
        } else {
            System.out.println("존재하지 않습니다.");
        }

        em.close();
        emf.close();
	}
}
  • find: id로 지정된 이메일 컬럼을 통해 검색
  • 멤버변수를 통해 접근하지 않고 getName(), getCreateDate() 등 getter를 통해 값을 가져옴
Update

영속성 개체의 값을 바꿔주기만 하면 됨 

Test03_Update.java

package exam3;

import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.Persistence;

public class Test03_Update
{
	public static void main(String[] args)
	{
        EntityManagerFactory emf =
                Persistence.createEntityManagerFactory("JpaEx01");
        EntityManager em = emf.createEntityManager();

        try 
        {
            em.getTransaction().begin();
            
            Member3 user = em.find(Member3.class, "test@test.com");
            if (user == null) 
            {
            	System.out.println("존재하지 않습니다.");
                em.getTransaction().rollback();
            	return;
            }
            user.changeName("전우치");	// 자바 객체를 통해 영속 컨텍스트의 값을 변경 
            
            em.getTransaction().commit(); 	// sql문을 실행해 db에 값의 변경 반영
            System.out.println("이름을 변경했습니다.");
        } catch(Exception e) {
            em.getTransaction().rollback();
            throw e;
        }

        em.close();
        emf.close();
	}
}
  • find()로 값을 찾아온 후 user라는 변수에 넣어준후 
  • user.changeName으로 값을 바꿈 
  • commit을 하면 db의 값까지 동기화 ( = db 업데이트)
Delete

Test04_Delete.java

package exam3;

import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.Persistence;

public class Test04_Delete
{
	public static void main(String[] args)
	{
        EntityManagerFactory emf =
                Persistence.createEntityManagerFactory("JpaEx01");
        EntityManager em = emf.createEntityManager();

        try 
        {
            em.getTransaction().begin();

            Member3 user = em.find(Member3.class, "test@test.com");
            if (user == null) {
                System.out.println("존재하지 않습니다.");
                em.getTransaction().rollback();
                return;
            }
            em.remove(user);
        
            em.getTransaction().commit();
            System.out.println("탈퇴처리 했습니다.");
        } catch (Exception e) {
            em.getTransaction().rollback();
            throw e;
        }

        em.close();
        emf.close();
	}
}
  • 영속성 객체에서 find로 객체를 찾음
  • remove()로 영속성 객체에서 지움
  • commit으로 동기화하면 db에서도 delete

예제4

Member4.java는 Member3.java와 동일

 

Test01_Insert.java

package exam4;

import java.time.LocalDate;

import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.Persistence;

public class Test01_Insert
{
	public static void main(String[] args)
	{
		// hibernate.hbm2ddl.auto의 value : create
        EntityManagerFactory emf =
                Persistence.createEntityManagerFactory("JpaEx01");
        EntityManager em = emf.createEntityManager();

        try 
        {
            em.getTransaction().begin();

            Member4 user;
            user = new Member4("test1@test.com", "이순신", LocalDate.now());
            em.persist(user);
            user = new Member4("test2@test.com", "강감찬", LocalDate.now());
            em.persist(user);
            user = new Member4("test3@test.com", "을지문덕", LocalDate.now());
            em.persist(user);
            user = new Member4("test4@test.com", "계백", LocalDate.now());
            em.persist(user);
            user = new Member4("test5@test.com", "김유신", LocalDate.now());
            em.persist(user);
            user = new Member4("test6@test.com", "연개소문", LocalDate.now());
            em.persist(user);
            user = new Member4("test7@test.com", "양만춘", LocalDate.now());
            em.persist(user);
            user = new Member4("test8@test.com", "김종서", LocalDate.now());
            em.persist(user);
            user = new Member4("test9@test.com", "최영", LocalDate.now());
            em.persist(user);

            em.getTransaction().commit();
            System.out.println("가입 요청을 처리했습니다.");
        } catch (Exception e) {
            em.getTransaction().rollback();
            throw e;
        }

        em.close();
        emf.close();
	}
}
  • 데이터 한번에 추가 

Test02_TypedQuery.java

package exam4;

import java.util.List;

import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.Persistence;
import jakarta.persistence.TypedQuery;

public class Test02_TypedQuery
{
	public static void main(String[] args)
	{
		// hibernate.hbm2ddl.auto의 value : none, create
        EntityManagerFactory emf =
                Persistence.createEntityManagerFactory("JpaEx01");
        EntityManager em = emf.createEntityManager();

        try 
        {
            em.getTransaction().begin();
            
            TypedQuery<Member4> query =
                    em.createQuery(
                            "select m from Member4 m order by m.name",
                            Member4.class);
            List<Member4> result = query.getResultList();//영속성 객체에서 select해서 Member4.class타입으로 가져오겠다. 
            
            em.getTransaction().commit();

            if (result.isEmpty()) 
            {
                System.out.println("사용자가 없습니다.");
            }
            else 
            {
            	result.forEach(user ->
                        System.out.printf(
                                "| %s | %s | %tY-%<tm-%<td |\n",
                                user.getEmail(), user.getName(), user.getCreateDate()));
            }
        
        } catch(Exception e) {
            em.getTransaction().rollback();
            throw e;
        }

        em.close();
        emf.close();
	}
}
  • 세밀한 조정을 위해 sql문과 유사한 TypedQuery문( jpa에서 사용하는 문법) 사용
    • 영속성 객체에서 select해서 Member4.class타입으로 가져오겠다

Test03_Parameter.java

- TypedQuery문에 파라미터 넣어서 사용해보기 

package exam4;

import java.util.List;

import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.Persistence;
import jakarta.persistence.TypedQuery;

public class Test03_Parameter
{
	public static void main(String[] args)
	{
		// hibernate.hbm2ddl.auto의 value : none, validate
        EntityManagerFactory emf =
                Persistence.createEntityManagerFactory("JpaEx01");
        EntityManager em = emf.createEntityManager();

        try 
        {
            em.getTransaction().begin();
            
            TypedQuery<Member4> query =
                    em.createQuery(
                            "select m from Member4 m "
                          + " where m.name = :name "
                          + " order by m.name",
                            Member4.class)
                    .setParameter("name", "양만춘");
            List<Member4> result = query.getResultList();
            
            em.getTransaction().commit();

            if (result.isEmpty()) 
            {
                System.out.println("사용자가 없습니다.");
            }
            else 
            {
            	result.forEach(user ->
                        System.out.printf(
                                "| %s | %s | %tY-%<tm-%<td |\n",
                                user.getEmail(), user.getName(), user.getCreateDate()));
            }
        
        } catch(Exception e) {
            em.getTransaction().rollback();
            throw e;
        }

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

 

  • setParameter로 :name에 들어갈 (키값, 검색값) 줌

Test04_ParamLike.java

package exam4;

import java.util.List;

import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.Persistence;
import jakarta.persistence.TypedQuery;

public class Test04_ParamLike
{
	public static void main(String[] args)
	{
		// hibernate.hbm2ddl.auto의 value : none, validate
        EntityManagerFactory emf =
                Persistence.createEntityManagerFactory("JpaEx01");
        EntityManager em = emf.createEntityManager();

        try 
        {
            em.getTransaction().begin();
            
            TypedQuery<Member4> query =
                    em.createQuery(
                            "select m from Member4 m "
                          + " where m.email like :email "
                          + " order by m.name",
                            Member4.class)
                    .setParameter("email", "%test.com%");
            List<Member4> result = query.getResultList();
            
            em.getTransaction().commit();

            if (result.isEmpty()) 
            {
                System.out.println("사용자가 없습니다.");
            }
            else 
            {
            	result.forEach(user ->
                        System.out.printf(
                                "| %s | %s | %tY-%<tm-%<td |\n",
                                user.getEmail(), user.getName(), user.getCreateDate()));
            }
        
        } catch(Exception e) {
            em.getTransaction().rollback();
            throw e;
        }

        em.close();
        emf.close();
	}
}
  • =이 아니라 like사용 => test.com으로 들어가있는 것 전부 다 검색