💡 ORM: Object-Relational Mapping
Object: "객체"지향 언어 (자바, 파이썬)
Relational: "관계형" 데이터베이스 (H2, MySQL)
- 중간에서 서로 다른 언어를 통역 해주는 역할
ORM 이 없는 환경에서는 백엔드 개발자가 비즈니스 로직 개발보다 SQL 작성에 더 많은 노력을 들여야 하더라..
SQL 작성이 단순하고 반복적인데, 실수하기는 쉬움
스키마가 변경되었을 경우 Mybatis에서는 관련 DAO의 파라미터, 결과, SQL 등을 모두 확인하여 수정해야 하지만 JPA를 사용하면 이런 일들을 대신해준다.
직접 SQL을 작정하는 것보다 성능이 떨어진다. (물론 해결법 또한 존재한다.)
메서드 호출로 DB데이터를 조작하기 때문에 세밀함이 떨어진다. 복잡한 통계 분석 쿼리를 메서드만으로 해결하는 것을 힘든 일이다.
러닝커브가 있다.
💡 JPA: Java Persistence API
자바 ORM 기술에 대한 표준 명세 (Java의 ORM)
JPA 는 표준 명세이고, 이를 실제 구현한 프레임워크 중 사실상 표준
스프링 부트에서 기본적으로 "하이버네이트" 사용 중
사실상 표준 (de facto, 디팩토)
보통 기업간 치열한 경쟁을 통해 시장에서 결정되는 비 공식적 표준이다
출처: 위키백과
JPA
영속성 컨텍스트 매니져
'1차 캐시' 사용의 장점
DB 조회 횟수를 줄임 ( 현업에서는 DB가 다른 서버에 존재할 가능성이 높기 때문에 DB를 호출하는 횟수를 줄이는게 중요하다.)
'1차 캐시'를 사용해 DB row 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' 이 맞는걸까??
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 값 불일치 확인
}
저장할 때와 똑같이 .save() 메서드를 사용한다.
처음 .save는 insert 쿼리 / 다음 .save는 update 쿼리
@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;
}