오늘 학습한 내용 ✍
JPA( Java Persistence API ) 란 ❓
JPA는 자바 ORM 기술에 대한 API 표준이다. 즉, 자바를 이용하여 ORM을 구현한 것으로, 방식은 JPA를 구현한 회사마다 다르기때문에 각 구현체마다 특정 회사의 이름으로 불린다. 대표적인 JPA로는 Hibernate, TopLink, EclipseLink 등이 있다.
JPA는 영속성 컨텍스트인 EntityManager를 통해 Entity를 관리하고 이러한 Entity가 DB와 매핑되어 사용자가 Entity에 대한 CRUD를 실행 했을 때 Entity와 관련된 테이블에 대한 적절한 SQL 쿼리문을 생성하고 이를 관리하였다가 필요 시 JDBC API를 통해 DB에 날리게 된다.
영속성 컨텍스트란 ❓
영속성 컨텐스트란 Entity를 영구 저장하는 환경이라는 뜻이다. 어플리케이션과 DB 사이에서 객체를 보관하는 가상의 데이터베이스 같은 역할을 한다. EntityManager를 통해 Entity를 저장하거나 조회하면 EntityManager는 영속성 컨텍스트에 Entity를 보관하고 관리한다.
Entity 생명주기
1) 비영속(new/transient) : 영속성 컨텍스트와 전혀 관계가 없는 상태, 객체를
생성까지만 한 상태
Product product = new Product();
product.setName("test01");
2) 영속(managed) : 영속성 컨텍스트에 저장된 상태, Entity가 영속성 컨텍스트에 의해
관리되는 상태
em.persist(product);
➡ 하지만, 영속 상태가 된다고 바로 DB에 SQL 쿼리문이 날라가는 것은 아니다.
SQL 쿼리문이 날라가는 것은 트랜잭션의 커밋(COMMIT) 시점에 영속성
컨텍스트에 있는 정보들이 DB로 날라간다.
3) 준영속(detached) : 영속성 컨텍스트에 저장되었다가 분리된 상태, 영속성
컨텍스트에서 지운 상태
em.detach(product); 또는 em.clear();
4) 삭제(removed) : DB에서 삭제된 상태
em.remove(product);
1차 캐시 : 영속성 컨텍스트 내부에는 캐시가 있는데 이를 1차 캐시라고 한다. 영속
상태의 Entity를 이곳에 저장한다. 1차 캐시의 키는 식별자 값이고 값은 Entity
인스턴스이다. 1차 캐시를 조회하는 방법은 다음과 같다.
Product product = em.find(Product.class, 1)
쓰기 지연이란 ❓
em.find(product)
를 사용해 product를 저장해도 바로 INSERT/UPDATE/DELETE
SQL 쿼리문이 DB에 보내지는 것이 아니다. EntityManager는 트랜잭션을 커밋(COMMIT)하기 직전까지 내부 쿼리 저장소에 SQL 쿼리문을 모아둔다. 그리고 트랜잭션을 커밋(COMMIT)할 때 모아둔 쿼리를 DB에 보낸다. 이것을 쓰기 지연이라 한다.
Spring Data JPA 란 ❓
Spring Data JPA는 JPA를 편리하게 사용할 수 있도록 지원하는 프로젝트(모듈)이다.
즉, JPA를 사용할 때 필수적으로 생성해야 하지만, 예상가능하고 반복적인 코드들을 대신 작성해줘서 코드를 줄여주는데, 이것은 JPA를 한 단계 더 추상화시킨 Repository 인터페이스를 제공함으로써 이루어진다. 또한, JPA 중에서 Hibernate 구현체를 사용한다.
🐼 Hibernate를 이용한 JSP 실습하기
Hibernate 구현하는 방법
1) 인텔리제이 무료버전에서 새로운 프로젝트 생성(maven 선택)
2) pom.xml
에 "hibernate"와 "mysql-connector" 라이브러리 추가
```java
<dependencies>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>5.4.3.Final</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.2.0</version>
</dependency>
</dependencies>
```
3) resources 폴더 밑에 META-INF
디렉토리 생성
4) 생성한 디렉토리에 persistence.xml
파일 생성
5) 생성한 파일 안에 hibernate 구현을 위한 코드 추가( hibernate 메뉴얼을 보면 있음 )
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0">
<persistence-unit name="hibernateproject">
<properties>
// DB연결을 위한 JDBC Driver를 사용
<property name="javax.persistence.jdbc.driver" value="com.mysql.cj.jdbc.Driver"/>
// DB 사용자 이름
<property name="javax.persistence.jdbc.user" value="test"/>
// DB 사용자 비밀번호
<property name="javax.persistence.jdbc.password" value="qwer1234"/>
// DB IP 주소 및 이름
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://77.77.77.110/hibernate"/>
// SQL 쿼리문을 출력해서 보기위한 코드
<property name="hibernate.show_sql" value="true"/>
// SQL 쿼리문을 실행할때 어떤 방식으로 할 것인지 정함
// update : 기존의 것을 그대로 두고 추가로 실시
// create : 기존의 것을 완전히 없애버리고 새롭게 추가
<property name="hibernate.hbm2ddl.auto" value="update"/>
// SQL 쿼리문이 출력될때 형식을 가꿔서(보기 좋게) 출력되도록 하는 것
<property name="hibernate.format_sql" value="true"/>
</properties>
</persistence-unit>
</persistence>
model
이라는 패키지를 만들고 그곳에 Product
클래스를 생성한다.
@Entity
public class Product {
@Id // 기본 키 지정해주는 어노테이션
@GeneratedValue(strategy = GenerationType.IDENTITY) // Auto-Increment 설정 어노테이션
private Integer id;
private String name;
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
HibernateMain
클래스를 만들고 CRUD 를 실습해본다.
public class HibernateMain {
public static void main(String[] args) {
// 이것을 실행될때 DB랑 연결이 시작됨
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hibernateproject");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
// ✅ Create : DB에 Product 테이블이 생성되고, 1번 id에 test01 데이터가 삽입된것을 확인가능
tx.begin();
Product product = new Product();
product.setName("test01");
em.persist(product); // 여기서 SQL 쿼리가 생성되고
tx.commit(); // 여기서 SQL 쿼리가 실행되며, DB에 적용된다.
// 즉, 이전까지 여러개의 SQL 쿼리문을 생성한뒤 커밋해주는 시점에 생성한 모든 SQL이 적용된다.
// ✅ Read
tx.begin();
Product product = em.find(Product.class, 1);
tx.commit();
System.out.println(product.getId());
System.out.println(product.getName());
// ✅ Update : id가 1인 데이터의 이름이 test02로 바뀐것을 확인 가능
tx.begin();
Product product = em.find(Product.class, 1);
product.setName("test02");
tx.commit();
// ✅ Delete
tx.begin();
Product product = em.find(Product.class,2);
em.remove(product);
tx.commit();
}
}
🦁 위에서 실습한 CRUD의 작동원리를 그림으로 표현해 봤다.
1) CREATE
2) READ
✅ 1차 캐시에 찾는 Entity가 없을 때
✅ 1차 캐시에 찾는 Entity가 있을 때
3) UPDATE
4) DELETE
🐶 연관 관계 매핑 실습하기
연관 관계 매핑 : 객체의 변수와 테이블의 외래 키를 매핑하는 것
일반 테이블과 객체의 연관 관계의 차이점
1) 방향 : 단방향, 양방향(단방향 + 반대 단방향) 이 존재
2) 다중성 : N:1
과 1:N
은 서로 다름
3) 연관 관계의 주인 : 객체를 양방향 연관관계로 만들면 주인을 정해줘야 함.
실습을 위한 가정 : 카테고리 테이블과 상품 테이블은 1:N
관계를 가지고 있다.
기존의 Product 클래스에 카테고리 테이블과의 관계를 만들어 주기 위해 아래와 같이 추가한다.
// 관계를 나타내는 어노테이션 : @ManyToMany / @ManyToOne / @OneToMany / @OneToOne
@ManyToOne // ✅ 상품과 카테고리가 N:1 관계이기 때문
@JoinColumn(name="Category_id") // ✅ 규칙 : @JoinColumn(name="테이블이름_기본키컬럼이름")
private Category category;
public void setCategory(Category category) {
this.category = category;
}
public Category getCategory() {
return category;
}
Category 클래스를 새로 생성해준다.
@Entity
public class Category {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String type;
// 양방향 연결 시 주인을 정해줘야되는데 그 주인을 적을 때 category_id가 아닌
// category_id의 변수 이름 즉 외래키의 변수 이름인 ❗category❗를 적어줘야된다.
@OneToMany(mappedBy = "category")
// 카테고리와 상품은 1:N 의 관계기 때문에 하나의 카테고리에 상품 여러개가 저장되어있다.
// 따라서, 리스트를 만들어줘야 하는데 JPA에서 리스트까지 자동으로 생성해주지 않기 때문에
// 리스트는 우리가 만들어줘야된다.
private List<Product> productList = new ArrayList<>();
public List<Product> getProductList() {
return productList;
}
public Integer getId() {
return id;
}
public String getType() {
return type;
}
public void setId(Integer id) {
this.id = id;
}
public void setType(String type) {
this.type = type;
}
}
HibernateMain 클래스에서 Product 테이블과 Category 테이블에 데이터를 넣어줘보자.
이때 Product 테이블에는 category_id
라는 외래키가 속성에 추가된점을 생각한다.
tx.begin();
for(int i=0; i<10; i++) {
Category category = new Category();
category.setType("카테고리"+(i+1));
for (int j=1; j<=10; j++) {
Product product = new Product();
product.setName("상품"+(i*10 + j));
product.setCategory(category);
em.persist(product);
}
em.persist(category);
}
tx.commit();
이렇게 하면 Product 테이블에는 category_id가 1부터 10인 상품이 각각 10개 씩 생겨서 총 100개의 데이터가 삽입된다. 그리고 Category 테이블에는 id가 1부터 10까지 총 10개의 데이터가 삽입된다.
이때 category_id가 1인 상품의 목록을 출력해보고 싶을때 아래와 같이 작성하여 실행해보면 정상적으로 10개의 데이터가 출력되는 것을 볼 수 있다.
Category category = em.find(Category.class, 1);
for (Product product1 : category.getProductList()) {
System.out.println(category.getType() + " " + product1.getName());
}
오늘의 느낀점 👀
오늘은 Spring Data JPA 를 배우기전에 기존의 JPA의 구현체 중 하나인 Hibernate에 대해 실습해보며 JPA에 대해 알아본 시간이었다. 일단 관계를 1:N
관계만 했는데 내일은 다른 관계들까지 다 하나씩 해본다고 한다.
대부분의 사람들이 Spring Data JPA 를 사용하다보니 기존의 JPA에 대한 지식은 기억을 못한다고 한다. 하지만 강사님께서 이것을 알고 모르고는 중요하다고 말씀해주셨다. 나중에 오류가 발생했을때도, JPA의 원래 원리에 대한 이해가 있다면 오류를 해결할 수 있지만 아무것도 모른다면 어려울거라고 말씀하셨다.
그만큼 중요하기에 수업을 오랫동안 진행한다고 생각하고, 내일도 열심히 들어서 개념을 완벽히 파악 할 예정이다.