해당 게시글에는
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-2/dashboard
스프링 DB 2편 - 데이터 접근 활용 기술 강의 | 김영한 - 인프런
김영한 | 백엔드 개발에 필요한 DB 데이터 접근 기술을 활용하고, 완성할 수 있습니다. 스프링 DB 접근 기술의 원리와 구조를 이해하고, 더 깊이있는 백엔드 개발자로 성장할 수 있습니다., 백엔드
www.inflearn.com
의 섹션 5 데이터 접근 기술 - JPA을 수강하며 알게된 점들을 정리한 내용이 담겨있습니다.
1. JPA가 관리하는 엔티티, @Entity
@Entity를 클래스 레벨에 선언하면 해당 클래스는 JPA가 관리하는 엔티티가 된다. 엔티티란 고유한 식별 값(PK)을 갖는 객체를 의미한다. 따라서 @Entity가 선언된 클래스는 반드시 PK 값이 있어야 한다. (PK는 @Id로 선언)
JPA의 근간인 ORM 기술을 사용하면 객체지향 패러다임과 SQL 패러다임 사이에서 발생하는 차이를 해결할 수 있다.
이와 관련된 내용은 https://www.youtube.com/watch?v=_tMJPysViNU&list=PLumVmq_uRGHgP87Jf1-up391q_es9KiZ9&index=4 에서 좀 더 자세하게 확인할 수 있다.
어쨌든, 스프링 부트에서 선언한 엔티티는 하나의 테이블에 들어가는 인스턴스(1 Row)와 매칭될 수 있는데,
이 때, 클래스의 필드명과 테이블의 컬럼명의 변환관계가 흥미롭다.
클래스는 기본적으로 필드명을 정의할 때, 카멜 케이스를 사용한다. (ex. itemName, carNumber)
테이블은 기본적으로 컬럼명을 정의할 때, 스테이크 케이스를 사용한다. (ex. item_name, car_number)
스프링 JPA 기술을 사용하면 이들의 변환체계 차이를 자동으로 해결할 수 있다. (JPA에게 변환하는 과정을 위임)
@Entity
public class Item {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(length = 10)
private String itemName;
private Integer price;
private Integer quantity;
// JPA는 기본 생성자가 기본적으로 필요함 -> JPA가 프록시 객체를 만들 때, 기본 생성자를 사용하기 때문
public Item() {
}
...
}
- itemName 필드는 실제 DB에 존재하는 item 테이블에는 "item_name"이라고 저장된다.
- price, quantity는 동일하게 저장된다.
만약, 테이블에는 스네이크 케이스가 적용된 이름이 아닌 아예 다른 이름을 적용하고 싶으면 @Column 애노테이션을 사용하면 된다.
- itemName (클래스) <-> classic_item_name (DB)
- 만약, 이런 관계를 만들고 싶다면 itemName 필드 위에 @Column(name = "classic_item_name")이라고 붙인다.
이런 특수한 상황이 아니라면, @Column을 생략했을 때 자동으로 스테이크 케이스로 변환된다. (물론 코드의 가독성을 위해 각 필드명에 @Column을 붙이는 경우도 있다.)
그런데 위처럼 이름의 변환을 지정하기 위한 경우가 아니라 아예 다른 상황에서 @Column을 써야할 수도 있다.
https://github.com/2024-LikeLionHackerton-MoodinG/MoodinG-BE/pull/23/files
fix: 피드백 길이 변경 by whxogus215 · Pull Request #23 · 2024-LikeLionHackerton-MoodinG/MoodinG-BE
🔧연결된 이슈 closed #20 🛠️작업 내용 GPT가 생성한 피드백이 담기는 문자열의 길이를 한글 500자까지 저장할 수 있도록 수정하였습니다. 🤷♂️PR이 필요한 이유 기본이 VARCHAR(255)로 되어 있
github.com
해당 PR을 보면 알 수 있듯이, content라는 String 타입 (DB에서는 VARCHAR)이 기본 크기를 초과할 경우 예외가 발생하는 문제를 해결하였는데, 컬럼에 저장되는 크기를 지정하기 위해 @Column(length = xxx)를 사용하였다.
JPA는 @Entity가 붙은 클래스에 대한 테이블을 만드는 DDL을 작성하는데, 이 때 @Column에 지정한 속성 값을 반영하여 쿼리를 생성한다.
또한, 엔티티 클래스는 기본 생성자(public 또는 protected)가 반드시 필요하다.
JPA는 자바의 리플렉션 기능을 사용해서 객체를 만드는데, 이 때 파라미터가 없는 기본 생성자만 사용할 수 있다.
또한 JPA는 프록시 객체를 생성하여 지연 로딩 기능을 제공하는데, 이 때도 마찬가지로 기본 생성자를 사용한다.
2. JPA의 update 메서드
JPA에서 영속성 컨텍스트를 관리하는 엔티티 매니저를 사용하면 자바에서 컬렉션을 사용하는 것처럼 특정 객체를 쉽게
영속화할 수 있다. 그런데, 이 때 데이터를 update하는 메서드는 존재하지 않는다. 그러면 JPA는 어떻게 특정 객체의 상태를 update 할 수 있는걸까?
public void update(Long itemId, ItemUpdateDto updateParam) {
final Item findItem = em.find(Item.class, itemId);
findItem.setItemName(updateParam.getItemName());
findItem.setPrice(updateParam.getPrice());
findItem.setQuantity(updateParam.getQuantity());
log.info("-------SQL 업데이트 쿼리-------");
}
find 메서드를 통해 특정 객체를 조회한 다음, 상태 값을 변경하는 setter 메서드만 적용하였다.
해당 테스트 메서드를 실행했을 때의 발생하는 쿼리를 확인해보자.
@Test
void updateItem() {
//given
Item item = new Item("item1", 10000, 10);
Item savedItem = itemRepository.save(item);
Long itemId = savedItem.getId();
//when
ItemUpdateDto updateParam = new ItemUpdateDto("item2", 20000, 30);
itemRepository.update(itemId, updateParam);
//then
Item findItem = itemRepository.findById(itemId).get();
assertThat(findItem.getItemName()).isEqualTo(updateParam.getItemName());
assertThat(findItem.getPrice()).isEqualTo(updateParam.getPrice());
assertThat(findItem.getQuantity()).isEqualTo(updateParam.getQuantity());
}

item을 저장하고, 저장된 엔티티의 ID 값으로 다시 조회해서 상태 값을 변경하는 코드와 찍히는 로그다.
현재 Item 엔티티 객체의 PK 생성전략이 IDENTITY(DB에게 생성 방법을 위임 ex. MySQL의 AUTO_INCREMENT)이기 때문에 save를 함과 동시에 바로 insert 쿼리가 나간다. (직접 PK 값을 지정한게 아니기 때문에 먼저 DB에 생성되도록 insert 쿼리를 날려서 자동생성된 ID 값을 가져오기 위함) 원래는 트랜잭션이 커밋되거나 엔티티 매니저에서 강제로 flush를 할 때(JPQL이 수행되면 자동으로 flush가 호출), 쿼리가 나간다 (중요)
어라...? 방금 update 메서드를 호출해서 조회한 엔티티의 상태를 변경했는데 update 쿼리가 나가지 않았다.
그 이유는 현재 테스트 메서드가 있는 테스트 클래스에 @Transactional이 선언되어, 각 테스트 메서드가 커밋되지 않고, 롤백되었기 때문에 영속성 컨텍스트(1차 캐시)의 변경사항에 대한 update 쿼리가 나가지 않은 것이다. 따라서 이러한 상황에서 update 쿼리가 나가는걸 보려면 두 가지 방법을 사용한다.
1. @Commit 어노테이션을 메서드에 선언
@Test
@Commit
void updateItem() {
//given
Item item = new Item("item1", 10000, 10);
Item savedItem = itemRepository.save(item);
Long itemId = savedItem.getId();
//when
ItemUpdateDto updateParam = new ItemUpdateDto("item2", 20000, 30);
itemRepository.update(itemId, updateParam);
//then
Item findItem = itemRepository.findById(itemId).get();
assertThat(findItem.getItemName()).isEqualTo(updateParam.getItemName());
assertThat(findItem.getPrice()).isEqualTo(updateParam.getPrice());
assertThat(findItem.getQuantity()).isEqualTo(updateParam.getQuantity());
}
이렇게 하면, @Transactional이 선언되었지만 해당 메서드만큼은 롤백되지 않고 커밋된다. 따라서 이 때는 변경사항에 대한 update 쿼리가 나간다.
2. EntityManager를 사용해서 강제로 flush를 한다.
@Transactional
@SpringBootTest
class ItemRepositoryTest {
@Autowired
ItemRepository itemRepository;
@Autowired
EntityManager em;
@Test
void updateItem() {
//given
Item item = new Item("item1", 10000, 10);
Item savedItem = itemRepository.save(item);
Long itemId = savedItem.getId();
//when
ItemUpdateDto updateParam = new ItemUpdateDto("item2", 20000, 30);
itemRepository.update(itemId, updateParam);
//then
Item findItem = itemRepository.findById(itemId).get();
assertThat(findItem.getItemName()).isEqualTo(updateParam.getItemName());
assertThat(findItem.getPrice()).isEqualTo(updateParam.getPrice());
assertThat(findItem.getQuantity()).isEqualTo(updateParam.getQuantity());
em.flush();
}
}
엔티티 매니저를 주입받은 뒤, 강제로 flush를 호출하였다. 이러면 트랜잭션을 커밋하지 않고도 update 쿼리를 확인할 수 있다.

다음 글에서 정리할 내용
- JPQL이 SQL과 다른점
- JPA의 예외 변환
- 예외 로그를 확인하는 방법 (예외를 변환할 때, 로그가 어떻게 찍히는지)
- JPA에서 발생하는 예외를 변환해야 하는 이유
- @Repository의 사용에 따라 달라지는 예외 로그 확인
- AOP 프록시 사용
다음 게시글
https://whxogus215.tistory.com/121
[JPA] 스프링 JPA 기술을 공부하며 배운 내용 정리 (2)
해당 게시글에는 https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-2/dashboard 스프링 DB 2편 - 데이터 접근 활용 기술 강의 | 김영한 - 인프런김영한 | 백엔드 개발에 필요한 DB 데이터 접근 기술을 활
whxogus215.tistory.com
'Spring Framework > Spring Boot' 카테고리의 다른 글
[JPA] 스프링 Data JPA 기술을 공부하며 배운 내용 정리 (0) | 2024.08.14 |
---|---|
[JPA] 스프링 JPA 기술을 공부하며 배운 내용 정리 (2) (0) | 2024.08.03 |
멋사 미션을 하며 스프링 JdbcTemplate에 대해 알게 된 내용 (0) | 2024.05.20 |
멋사 미션을 하며 스프링 부트 예외처리에 대해 알게 된 내용들 (0) | 2024.05.14 |
관심사의 분리, 의존관계 주입(DI) (0) | 2022.12.30 |