[Java] JPA

JPA ?

Java Persistence API

자바 진영의 ORM 기술 표준. 자바 어플리케이션에서 관계형 데이터베이스를 사용하는 방식을 정의한 인터페이스이다.

💡 ORM(Object Relational Mapping)

  • SQL을 직접 다루지 않고, 객체와 테이블을 매핑하여 간접적으로 DB데이터 관리
  • 객체 간 관계를 바탕으로 SQL 문장 자동 생성
  • SQL이 아닌 메소드로 데이터 조작

자바 DB연동 기술의 변천사

  1. EJB의 Entity빈 : 구현 및 배포 어려움 등의 여러가지 이슈 있었음
  2. Hibernate : 자바 표준은 아니지만 가장 많은 개발자들이 사용하는 ORM 기술
  3. JPA : 하이버네이트 개발자들이 만든 자바 표준 ORM 기술

JPA 실행 구조

애플리케이션과 JDBC 사이에서 동작.

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

jpa1

JPA를 사용하는 이유

(1) 생산성 증가 및 유지보수의 용이

  • 간단한 메소드로 CRUD가 가능하다.
  • 필드 변경시 직접 모든 SQL을 수정해야 할 필요 없이 JPA가 알아서 처리해준다.

(2) 데이터 접근 추상화, 벤더 독립적 개발

  • 💎 Dialect (방언) 설정

    • JPA에 Dialect를 설정할 수 있는 추상화 방언 클래스를 제공하여, 설정된 방언으로 각 DBMS에 맞게 구현
    • 각 DBMS들만의 독자적인 기능/문법 고려할 필요 없이 사용 가능
jpa2

JPA Application 개발환경 구축

  1. JPA 프로젝트로 변환

    : 프로젝트 우클릭 - Configure - Convert to JPA Project 클릭하여 JPA 프로젝트로 변환한다 - 마지막에 Disable Library Configuration을 설정해 줄 것이다(메이븐으로 사용할 것임)

jpa3

​ 아래와 같이 persistence.xml파일이 생겼으면 정상적으로 변환 완료

jpa4
  1. Maven 프로젝트로 변환

    : 프로젝트 우클릭 - Configure - Convert to Maven project

  2. Maven 설정파일 pom.xml에 JPA 사용을 위한 dependencies 추가
...
</build>
	<dependencies>
		<dependency>
			<groupId>javax.persistence</groupId>
			<artifactId>javax.persistence-api</artifactId>
			<version>2.2</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-entitymanager</artifactId>
			<version>5.4.2.Final</version>
		</dependency>

		<!-- lombok 사용 -->
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<version>1.18.20</version>
		</dependency>
		
		<!-- oracle driver의 설정 -->
		<dependency>
			<groupId>com.jslsolucoes</groupId>
			<artifactId>ojdbc6</artifactId>
			<version>11.2.0.1.0</version>
		</dependency>
	</dependencies>
		
	<!-- oracle driver의 추가 설정 정보 -->
	<repositories>
		<repository>
			<id>oracle</id>
			<name>ORACLE JDBC Repository</name>
			<url>https://maven.atlassian.com/3rdparty/</url>
		</repository>
	</repositories>
</project>
    	

JPA 설정파일 (persistence.xml) 이해하기

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" 
             xmlns="http://xmlns.jcp.org/xml/ns/persistence" 
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
             xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
	
    <persistence-unit name="oracleDB">	
        <class>model.entity.Team</class>
        <class>model.entity.Member</class>

        <properties>
            <property name="javax.persistence.jdbc.driver" 
                      value="oracle.jdbc.OracleDriver" />
            <property name="javax.persistence.jdbc.url" 
                      value="jdbc:oracle:thin:@127.0.0.1:1521:xe" />
            <property name="javax.persistence.jdbc.user" 
                      value="SCOTT" />
            <property name="javax.persistence.jdbc.password" 
                      value="TIGER" />        
            
            <property name="hibernate.dialect" 
                      value="org.hibernate.dialect.OracleDialect" />	
            <property name="hibernate.show_sql" value="true" />
            <property name="hibernate.id.new_generator_mappings" value="true" />
            <property name="hibernate.hbm2ddl.auto" value="create" />
           
        </properties>
	</persistence-unit>	
</persistence>
  • <persistence-unit> : JPA가 연동할 DataBase에 대한 정보 보유 (연동하고자 하는 DB가 여러개인 경우 여러개의 persistence-unit 설정하면 됨)

    • JDBC Driver 종류 / url / user /password 정보 가지고 있음
  • <class>model.entity.Team</class> : Entity 클래스를 등록
  • <property name="hibernate.dialect" value="org.hibernate.dialect.OracleDialect" /> : 방언처리기 지정 (오라클 Dialect 사용할 것이다)
  • <property name="hibernate.show_sql" value="true" /> : 콘솔창에 생성된 sql문 출력하겠다
  • <property name="hibernate.id.new_generator_mappings" value="true" /> : 테이블의 PK컬럼을 자동 생성하겠다(value=“true” : SequenceStyleGenerator를 사용하겠다)
  • <property name="hibernate.hbm2ddl.auto" value="create" /> : DDL(테이블 생성, 삭제, 변경)을 자동으로 실행하겠다. (실행하지 않으려는 경우 value=“none”)

연관관계 매핑

객체의 참조 관계와 테이블의 외래키를 매핑하는 것을 의미

⚡ Member 객체가 Team 객체를 참조하는 경우 jpa5

  • JDBC : 연관 관계이 있는 상대 테이블의 PK를 멤버변수로 가진다
  • JPA : 엔티티 객체 자체를 통채로 참조한다
// JDBC에서의 설계
class Member{
    private long memberId;
    private String name;
    private long teamId;
}

// JPA에서의 설계
class Member{
    private long memberId;
    private String name;
    private Team team;  // 엔티티 객체를 참조
}

연관관계 매핑시 고려해야할 요소

(1) 다중성

해당 엔티티를 중심으로 상대 엔티티를 바라봤을때

  • OneToOne : 일대일 관계
  • OneToMany : 일대다 관계
  • ManyToOne : 다대일 관계
  • ManyToMany : 다대다 관계

DB 설계상 ‘다’인 쪽이 외래 키를 갖는다. 😊무.조.건.


(2) 방향

데이터베이스 테이블은 외래키 하나로 양쪽 테이블을 조인하므로 단방향이니 양방향이니 나눌 필요가 없다.

하지만 객체는 참조용 필드가 있는 객체만 다른 객체를 참조하는 것이 가능하므로, JPA를 사용하여 데이터베이스와 패러다임을 맞추기 위해서는 연관관계의 방향을 선택해야 한다.

  • 단방향 관계 : 한쪽의 엔티티만 참조용 필드를 갖고 다른 객체를 참조하고 있는 것
  • 양방향 관계 : 양쪽 엔티티가 각각 참조용 필드를 갖고 서로 참조하고 있는 것

🤷‍♀️ 단방향.. 양방향…? 어떨때 써야할까???

  • 다대일 단방향 관계 : member1.getTeam() 처럼 멤버가 팀을 참조 가능
  • 양방향 관계 : team1.getMembers()처럼 팀이 멤버를 참조도 가능하게 하려면 양방향으로 설정하면 됨

    => 역방향으로 객체 탐색이 꼭 필요할때 양방향 관계를 설정하자


(3) 연관관계의 주인(Owner)

  • 두 객체가 양방향 관계를 가질 때, 연관관계의 주인을 지정해야 한다.
  • 외래키를 갖는 테이블이 연관관계의 주인이 된다. 😊무.조.건.

    • 주인 쪽이 외래키를 관리(등록, 수정, 삭제)할 수 있고, 다른 쪽은 읽기만 가능하다.
  • 주인이 아닌 객체 쪽에서 mappedBy 속성을 사용하면 된다.

<Team.java> - 부모 엔티티

@AllArgsConstructor
@NoArgsConstructor
@Setter
@Getter
@Entity
class Team{	
	@Id	
	@Column(name="team_id")
	private Long teamId;

	@Column(length=20, name="team_name")
	private String teamName;
	
    /* 해당 엔티티(Team)가 상대 엔티티(Member)의 여러 객체를 가질 수 있음 
     * `일`인 쪽에 @OneToMany를 추가하여 양방향 관계 만듬 
     * 	- 팀에서 여러 멤버들을 참조 가능하게 ArrayList 만들어준다
     * mappedBy 사용하여 연관관계의 주인을 설정해줌 
     * 	- mappedBy의 값은 대상이 되는 변수명(Member 테이블의 teamId 변수)
     */
	@OneToMany(mappedBy="teamId")       
	List<Member> members = new ArrayList<Member>();
	

<Member.java> - 자식 엔티티, 외래키를 가짐(Team), 연관관계의 주인

@AllArgsConstructor
@NoArgsConstructor
@Setter
@Getter
@Entity
class Member{
    @Id
    @Column(name="member_id")
    private long memberId;
    
    @Column(length=20)
    private String name;
    
    // JoinColumn : 외래키를 매핑하는 애노테이션. 
    // 해당 엔티티(Member)의 여러 객체가 상대 엔티티(Team)에 대응 - 다대일 관계(@ManyToOne)
    @JoinColumn(name="team_id")  
    @ManyToOne
    private Team teamId;
}

JPA Application 개발 Proccess

  1. EntityManagerFactory 생성
  2. EntityManager 생성
  3. EntityTransaction 생성 및 적용
  4. 비즈니스 로직(CRUD)
  5. 자원반환

JPA 주요 API

API 역할
Persistence EntityManagerFactory 인스턴스를 얻기 위한 메소드를 가짐 = createEntityManagerFactory()
EntityManagerFactory 클라이언트 요청시마다(=각 스레드마다) EntityManager 객체 생성, 관리
EntityManager 엔티티에 대한 모든 작업 (CRUD) 처리
EntityTransaction EntityManager와 1:1 관계. 각 EntityManager의 작업은 EntityTransaction 클래스에 의해 유지 관리.

EntityManager 의 CRUD 수행 메소드

메소드 기능
persist(Object entity) entity를 영속화 (INSERT)
merge(Object entity) 준영속 상태의 entity를 영속화 (UPDATE)
remove(Object entity) 영속 상태의 entity를 제거 (DELETE)
find(Class entity, Object pk) 하나의 entity 검색 (SELECT one)
createQuery(String jpql, Class resultClass) JPQL에 해당하는 entity 목록 검색 (SELECT list)
public class RunningTest {
	
	void test() {
		EntityManagerFactory emf = Persistence.createEntityManagerFactory("oracleDB");
		EntityManager em = emf.createEntityManager();
		EntityTransaction tx = em.getTransaction();
		
        tx.begin();
		
        try{
        	// 엔티티 클래스의 setter 메소드 - tx.commit() 시에 UPDATE문 수행 
            Team t1 = new Team();
            t1.setTeamName("우리팀");

            Member m1 = new Member();
            m1.setName("장보리");
            m1.setTeamId(t1);

            Member m2 = new Member();
            m2.setName("서리태");
            m2.setTeamId(t1);

            t1.getMembers().add(m1);
            t1.getMembers().add(m2);
            
            // INSERT
            em.persist(t1);
            em.persist(m1);		
            em.persist(m2);
           
            // SELECT one
            Member oneMember = em.find(Member.class, 1L);  // pk값(1)으로 조회
            System.out.println("조회한 멤버 : " + oneMember);
            
            // SELECT list
            List<Member> allMember = em.createQuery(
                "select m from Member m", Member.class).getResultList();
            System.out.println("멤버 수 : " + allMember.size());
			
            // DELETE
            em.remove(oneMember);  // 객체를 파라미터로 받아서 pk값을 통해 해당 데이터 삭제

            tx.commit();
            
        }catch(Exception e) {
            // 에러 발생시 CRUD 결과 저장하지 않고 rollback
            tx.rollback();
            e.printStacktrace();
        }finally {
            // 자원반환
            em.close();
            emf.close();
        }
    }
	
}



[참고한 사이트]

https://jeong-pro.tistory.com/231


Written by@[hyem]
Hyem's Dev Note

GitHub