카테고리 없음

[Spring_4기 본캠프] Spring 플러스 - 쿼리 다루기 | Day 64

austindynasty 2025. 1. 23. 18:58

1. 쿼리 파일 만들기 (QueryMapper)

▶ MyBatis

- JDBC Template에 이어 SQL Mapper 두 번째 주자로 탄생

- JDBC 프로그래밍 RowMapper가 가지고 있는 단점인 반복되는 코드를 줄이고자 함

- SQL 쿼리들을 XML 파일에 작성해 코드와 SQL 분리

- jdbc로 처리하는 코드의 설정 부분을 줄이고 실제 sql문에 연결함으로서 빠른 개발

- map 인터페이스(혹은 클래스)와 SQL 쿼리와 ResultSet 매핑을 위한 XML 및 애너테이션을 사용

- 다른 방식에 비해 객체 자체보다 쿼리에 집중할 수 있음

2. 쿼리 코드 만들기 (JpaRepository)

1) QueryMapper의 DB의존성 및 중복 쿼리 문제로 ORM이 탄생

2) ORM은 DAO 혹은 Mapper를 통해 조작하는 것이 아니라 테이블을 아예 하나의 객체(Object)와 대응시켜 버림

3) ORM이 해결해야 하는 문제점과 해결책 (객체와 데이터베이스 테이블의 차이점에 대한 문제)

- 상속의 문제

   - 객체 : 객체간에 멤버변수나 상속관계를 맺을 수 있다.

   - RDB : 테이블들은 상속관계가 없고 모두 독립적으로 존재한다.

   - 해결방법 : 매핑정보에 상속정보를 넣어준다 (@OneToMany, @ManyToOne)

                     RDB에 있는 테이블의 필드가 객체와 매핑이 되고, 어떤 필드가 다른 객체의 외래키를 사용하는데 그 외래키가 의미를 갖도록 하는 것

 

- 관계 문제

   - 객체 : 참조를 통해 관계를 가지며 방향을 가진다. (다대다 관계 포함)

   - RDB : 외래키를 설정해 Join으로 조회 시에만 참조가 가능 (단, 다대다는 매핑 필요)

   - 예시 : User 테이블에 있는 여러 명의 멤버가 여러 개의 동아리를 가입할 수 있고, 동아리는 여러 명의 멤버를 받을 수 있다고 가정 (다대다) 

               → 그러면 가입자 명부를 이용해 중간에 매핑 정보를 가진 테이블을 가지면 다대다 관계를 설정해줄 수 있다.

   - 해결방법 : 매핑정보에 방향정보를 넣어준다. (@JoinColumn, @MappedBy)

 

- 탐색 문제

   - 객체 : 참조를 통해 다른 객체로 순차적 탐색이 가능하며 컬렉션이 순회한다.

   - RDB : 탐색 시 참조하는만큼 추가 쿼리나 Join이 발생하며 비효율적이다.

   - 해결방법 : 매핑/조회 정보로 참조탐색 시점을 관리한다. (@FetchType, fetchJoin())

                     설정해놓은 관계정보(OneToMany...) 방향성정보(JoinColumn...)를 통해 참조탐색을 한 번에 한다는 뜻. 즉, 쿼리를 한 번에 연결해서 

                     여러 개를 한꺼번에 조회하거나 Join 쿼리를 자동으로 생성해주는 애너테이션이나 메서드를 사용한다는 것

 

- 밀도 문제

   - 객체 : 멤버 객체크기가 매우 클 수 있다.

   - RDB : 기본 데이터타입만 존재한다.

   - 해결방법 : 크기가 큰 멤버 객체는 테이블을 분리해 상속으로 처리한다. (@embedded)

 

- 식별성 문제

   - 객체 : 객체의 hashCode 또는 정의한 equals() 메서드를 통해 식별 → 객체가 hashCode를 통해 equals() 메서드를 호출했을 때 같은 객체인지 확

               인하는 것 

   - RDB : 고유식별자(PK)로만 식별

   - 해결방법 : PK를 객체 Id로 설정하고 EntityManager는 해당 값으로 객체를 식별하여 관리한다. (@Id, @GeneratedValue)

 

>>> 그러니까, JPA에서 만든 이런 애너테이션들을 사용해 객체와 테이블간의 간극에서 오는 문제점을 해결할 수 있게된다! 

 

3. 영속성 컨텍스트 

◆ 영속성 컨텍스트(1차 캐시-임시저장소)를 활용한 쓰기 지연

> 영속성 

- 데이터를 생성한 프로그램이 종료되어도 사라지지 않는 데이터의 특성

- 영속성을 갖지 않으면 데이터를 메모리에서만 존재하게 되고 프로그램이 종료되면 해당 데이터는 모두 사라지게 된다.

- 그래서 우리는 데이터를 파일이나 DB에 영구 저장함으로써 데이터에 영속성을 부여한다.

> 영속성의 4가지 상태

- 비영속 : 엔티티 객체가 만들어져 아직 저장되지 않은 상태로, 연속성 컨텍스트와 전혀 관계가 없는 상태

- 영속 : 엔티티가 영속성 컨텍스트에 저장되어, 영속성 컨텍스트가 관리할 수 있는 상태

- 준영속 : 엔티티가 영속성 컨텍스트에 저장되어 있다가 분리된 상태로, 영속성 컨텍스트가 더 이상 관리하지 않는 상태

- 삭제 : 엔티티를 영속성 컨텍스트와 데이터베이스에서 삭제하겠다고 표시한 상태

> 객체의 영속성 상태는 Entity Manager의 메서드를 통해 전환된다.

> Raw JPA 관점에서 순서대로 요약정리 해보자면?

    new => 비영속 상태 => persist(), merge() => 영속성 컨텍스트에 저장된 상태 => flush() => DB에 쿼리가 전송된 상태 

    => commit() => DB에 쿼리가 반영된 상태

Item item = new Item();		// 1
item.setItemNm("테스트 상품");	

EntityManager em = entityManagerFactory.createEntityManager();	// 2
EntityTransaction transaction = em.getTransaction();		// 3
	
transaction.begin();		
em.persist(item);		// 4-1
em.flush(item).     // 4-2 (DB에 SQL 보내기/commit시 자동수행되어 생략 가능함)
transaction.commit();		// 5

em.close();			// 6

1️⃣  영속성 컨텍스트에 담을 상품 엔티티 생성
2️⃣  엔티티 매니저 팩토리로부터 엔티티 매니저를 생성
3️⃣  데이터 변경 시 무결성을 위해 트랜잭션 시작
4️⃣  영속성 컨텍스트에 저장된 상태, 아직 DB에 INSERT SQL 보내기 전
5️⃣  트랜잭션을 DB에 반영, 이 때 실제로 INSERT SQL 커밋 수행
6️⃣  엔티티 매니저와 엔티티 매니저 팩토리 자원을 close() 호출로 반환

 

◆ 쓰기 지연이 발생하는 시점 

- flush() 동작이 발생하기 전까지 최적화 한다.

- flsuh() 동작으로 전송된 쿼리는 더이상 쿼리 최적화는 되지 않고, 이후 commit()으로 반영만 가능하다.

 

◆ 쓰기 지연 효과

- 여러 개의 객체를 생성할 경우 모아서 한 번에 쿼리를 전송한다.

- 영속성 상태의 객체가 생성 및 수정이 여러 번 일어나더라도 해당 트랜잭션 종료 시 쿼리를 한 번만 전송될 수 있다.

- 영속성 상태에서 객체가 생성되었다 삭제된다면 실제 DB에는 아무 동작이 전송되지 않을 수 있다. → 삭제가 될거라면 DB에 보내지 않는 게 당연히 좋다! 그런 관점에서 쓰기 지연이 효과가 있다는 것.

- 즉, 여러 가지 동작이 많이 발생하더라도 쿼리는 트랜잭션 당 최적화되어 최소 쿼리만 날라가게 된다. 

- 예시 : setName()을 해서 여러 개의 변수를 추가하는데 모두 UPDATE 쿼리를 날리는 것은 비효율적 → 그것들을 모두 영속성 컨텍스트에 set 해놓음 → 한 번에 flush()로 영속 객체에 대한 쿼리를 자동으로 Manager가 만들어줌 → 최소한의 쿼리만 날라가게 됨