Day_36 ( 스프링 - 3 / JPA )

HD.Y·2023년 12월 18일
0

한화시스템 BEYOND SW

목록 보기
31/58
post-thumbnail

오늘 학습한 내용

  • ORM( Object Relational Mapping ) 이란 ❓
    ORM은 객체와 DB의 테이블이 매핑을 이루는 것을 말한다. 다시 말해, 객체가 관계형 데이터 베이스의 테이블이 되도록 매핑 시켜주는 것이다. ORM을 사용하면 SQL 쿼리가 아닌 직관적인 코드(메서드)로서 DB의 데이터를 조작하는것이 가능하다.
  • 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:11: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의 원래 원리에 대한 이해가 있다면 오류를 해결할 수 있지만 아무것도 모른다면 어려울거라고 말씀하셨다.

  • 그만큼 중요하기에 수업을 오랫동안 진행한다고 생각하고, 내일도 열심히 들어서 개념을 완벽히 파악 할 예정이다.

profile
Backend Developer

0개의 댓글