220714_TIL : ORM / JPA

백승한·2022년 7월 14일
0

ORM

ORM이란?

💡 ORM: Object-Relational Mapping
Object: "객체"지향 언어 (자바, 파이썬)
Relational: "관계형" 데이터베이스 (H2, MySQL)

  • 중간에서 서로 다른 언어를 통역 해주는 역할

  • 백엔드 개발자(Backend Developer): 웹 서버를 개발하는 개발자
  • DBA (Database Administration): 데이터베이스 관리자. 데이터베이스를 설치, 구성, 관리 등의 일을 맡은 사람

ORM 만든 이유?

  • ORM 이 없는 환경에서는 백엔드 개발자가 비즈니스 로직 개발보다 SQL 작성에 더 많은 노력을 들여야 하더라..

  • SQL 작성이 단순하고 반복적인데, 실수하기는 쉬움

  • 스키마가 변경되었을 경우 Mybatis에서는 관련 DAO의 파라미터, 결과, SQL 등을 모두 확인하여 수정해야 하지만 JPA를 사용하면 이런 일들을 대신해준다.

단점

  • 직접 SQL을 작정하는 것보다 성능이 떨어진다. (물론 해결법 또한 존재한다.)

  • 메서드 호출로 DB데이터를 조작하기 때문에 세밀함이 떨어진다. 복잡한 통계 분석 쿼리를 메서드만으로 해결하는 것을 힘든 일이다.

  • 러닝커브가 있다.

JPA

JPA

💡 JPA: Java Persistence API
자바 ORM 기술에 대한 표준 명세 (Java의 ORM)

하이버네이트 (Hibernate)?

  • JPA 는 표준 명세이고, 이를 실제 구현한 프레임워크 중 사실상 표준

    • JPA는 '어떤 식으로 구현이 되야한다'라고 하는 설명을 해주는 스펙 문서이다. 하이버네이트는 그 설명서를 가지고 만들어내는 구현체
  • 스프링 부트에서 기본적으로 "하이버네이트" 사용 중

  • 사실상 표준 (de facto, 디팩토)
    보통 기업간 치열한 경쟁을 통해 시장에서 결정되는 비 공식적 표준이다
    출처: 위키백과

JPA 영속성 컨텍스트?

  • JPA

    • 객체 - ORM - DB
    • 객체 - 영속성 컨텍스트 매니져 (entity context manager) - DB
  • 영속성 컨텍스트 매니져

    • 객체 ↔ DB 의 소통을 효율적으로 관리

JPA 영속성 컨텍스트 1차 캐시 이해

영속성 컨텍스트 1차 캐시

  • Entity 저장 시

  • Entity 조회 시
  1. 1차 캐시에 조회하는 Id 가 존재하는 경우

  1. 1차 캐시에 조회하는 Id 가 존재하지 않은 경우
    - JPA가 쿼리를 날려서 찾아온 다음에, 1차 캐시 Entity를 만들어주고, 만들어진 Entity를 반환해준다.

'1차 캐시' 사용의 장점

  1. DB 조회 횟수를 줄임 ( 현업에서는 DB가 다른 서버에 존재할 가능성이 높기 때문에 DB를 호출하는 횟수를 줄이는게 중요하다.)

  2. '1차 캐시'를 사용해 DB row 1개 당 객체 1개가 사용되는 것을 보장 (객체 동일성 보장)

    • 비교) '1차 캐시'가 작동하지 않을 때
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class UserRepositoryTest {
@Autowired
UserRepository userRepository;

@Order(1)
@Test
public void create() {
    // 회원 "user1" 객체 생성
    User instance1 = new User("user1", "정국", "불족발");
    // 회원 "user1" 객체 또 생성
    User instance2 = new User("user1", "정국", "불족발");
    assert(instance2 != instance1);
    // 회원 "user1" 객체 또또 생성
    User instance3 = new User("user1", "정국", "불족발");
    assert(instance3 != instance2);

    // 회원 "user1" 객체 추가
    User user1 = new User("user1", "정국", "불족발");

    // 회원 "user1" 객체의 ID 값이 없다가..
    userRepository.save(user1);

    // 테스트 회원 데이터 삭제
    userRepository.delete(user1);
  }
            
@Order(2)
@Test
public void findUser() {
    // ------------------------------------
    // 회원 "user1" 객체 추가
    User beforeSavedUser = new User("user1", "정국", "불족발");

    // 회원 "user1" 객체를 영속화
    User savedUser = userRepository.save(beforeSavedUser);

    // 회원 "user1" 을 조회
    User foundUser1 = userRepository.findById("user1").orElse(null);
    assert(foundUser1 != savedUser);
            
    // 회원 "user1" 을 또 조회
    User foundUser2 = userRepository.findById("user1").orElse(null);
    assert(foundUser2 != savedUser);

    // 회원 "user1" 을 또또 조회
    User foundUser3 = userRepository.findById("user1").orElse(null);
    assert(foundUser3 != savedUser);
            
    // ------------------------------------
    // 테스트 회원 데이터 삭제
    userRepository.delete(beforeSavedUser);
  }
}
- 만약 DB 에서는 'user1' 회원 1명, 자바 객체로는 'user1' 회원 3명이라면??
- 그리고, 각 객체 회원별로 수정한다면.. 어떤 'user1' 이 맞는걸까??

Entity 업데이트 실패

  • Entity 객체를 수정해도 DB 에는 update 가 되지 않음


  • 1차 캐시 Entity 객체에만 업데이트 반영됨
  • User DB 에는 반영되지 않음
public User updateUserFail() {
        // 회원 "user1" 객체 추가
        User user = new User("user1", "뷔", "콜라");
        // 회원 "user1" 객체를 영속화
        User savedUser = userRepository.save(user);

        // 회원의 nickname 변경
        savedUser.setNickname("얼굴천재");
        // 회원의 favoriteFood 변경
        savedUser.setFavoriteFood("버거킹");

        // 회원 "user1" 을 조회
        User foundUser = userRepository.findById("user1").orElse(null);
        // 중요!) foundUser 는 DB 값이 아닌 1차 캐시에서 가져오는 값
        
        assert(foundUser == savedUser);
        assert(foundUser.getUsername().equals(savedUser.getUsername()));
        assert(foundUser.getNickname().equals(savedUser.getNickname()));
        assert(foundUser.getFavoriteFood().equals(savedUser.getFavoriteFood()));

        return foundUser;
        // 결과 : 객체와 DB 값 불일치 확인
    }

Entity 업데이트 방법(1)

저장할 때와 똑같이 .save() 메서드를 사용한다.
처음 .save는 insert 쿼리 / 다음 .save는 update 쿼리

Entity 업데이트 실패(2)

  • @Transactional 을 추가

  • 굳이 userRepository.save() 함수를 호출하지 않아도, 함수가 끝나는 시점에 변경된 부분을 알아서 업데이트 해 줌 (이를 "Dirty check" 라고 함)

  • 함수가 종료되는 시점에 각 Entity 에 save() 가 호출된다라고 이해

@Transactional
public User updateUser2() {

        // 테스트 회원 "user1" 생성
        // 회원 "user1" 객체 추가
        User user = new User("user1", "진", "꽃등심");
        // 회원 "user1" 객체를 영속화
        User savedUser = userRepository.save(user);
        // 중요! 쿼리를 commit하지않고 한번에 날리기 때문에 이 시점에서는 DB에 저장되어있지 않다.

        // 회원의 nickname 변경
        savedUser.setNickname("월드와이드핸섬 진");
        // 회원의 favoriteFood 변경
        savedUser.setFavoriteFood("까르보나라");
		
        return savedUser;
    }

JPA 연관관계

JPA 연관관계 설정방법

profile
방문해주셔서 감사합니다🙂

0개의 댓글