| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | ||||
| 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 11 | 12 | 13 | 14 | 15 | 16 | 17 |
| 18 | 19 | 20 | 21 | 22 | 23 | 24 |
| 25 | 26 | 27 | 28 | 29 | 30 | 31 |
- Linux
- 제이쿼리
- Dunk Low
- Nike
- 발매예정
- jQuery
- dunk high
- 리눅스
- stockx.com
- 주식공부
- 자바스크립트
- 덩크로우
- 나이키
- 덩크 하이
- dunklow
- 드로우
- sacai
- GIT
- draw
- 덩크 로우
- 오라클
- 리액트
- 코로나19
- 파이썬
- 주식
- Python
- react
- oracle
- Java
- JavaScript
- Today
- Total
Life goes slowly...
[JPA]JPA 로 개발하면서 자주 하는 실수 Top 5 본문

JPA 초보자가 자주 하는 실수 Top 5
JPA(Java Persistence API)는 자바 개발자에게 ORM(Object-Relational Mapping)의 강력함을 제공하여 객체 지향적으로 데이터베이스와 상호작용할 수 있게 해줍니다. 하지만 강력한 만큼 제대로 사용하지 않으면 예상치 못한 문제에 부딪힐 수 있습니다. JPA를 처음 접하는 개발자들이 자주 하는 실수 5가지와 이를 피하는 방법을 알아보겠습니다.
1. Entity에 비즈니스 로직 넣기 → Service로 빼기
문제점: Entity는 데이터베이스 테이블과 매핑되는 순수한 객체여야 합니다. 여기에 비즈니스 로직이 들어가면 Entity의 역할이 모호해지고, 재사용성이 떨어지며, 테스트하기 어려워집니다. Entity는 데이터의 상태를 표현하는 데 집중해야 합니다.
해결 방안: 비즈니스 로직은 Service 계층으로 분리해야 합니다. Service 계층은 여러 Entity나 Repository를 조합하여 복잡한 비즈니스 규칙을 처리하는 역할을 합니다.
예시:
// bad: Entity에 비즈니스 로직 포함
@Entity
public class Order {
private int quantity;
private int price;
public void calculateTotalPrice() {
// 비즈니스 로직이 Entity 내부에 존재
return quantity * price;
}
}
// good: Service 계층에서 비즈니스 로직 처리
@Service
public class OrderService {
public int calculateTotalPrice(Order order) {
return order.getQuantity() * order.getPrice();
}
}

2. Controller에서 Repository 직접 호출 → Service 거치기
문제점: Controller는 웹 요청을 받고 응답을 반환하는 역할을 합니다. Controller에서 직접 Repository를 호출하면 비즈니스 로직이 Controller에 노출되거나, Controller가 너무 많은 책임을 지게 됩니다. 이는 계층 간의 의존성을 높이고 코드의 응집도를 떨어뜨립니다.
해결 방안: Controller는 Service 계층을 통해 비즈니스 로직을 위임해야 합니다. Service 계층은 트랜잭션 관리와 비즈니스 로직 처리를 담당하여 Controller의 역할을 가볍게 만듭니다.
예시:
// bad: Controller에서 Repository 직접 호출
@RestController
public class OrderController {
@Autowired
private OrderRepository orderRepository;
@GetMapping("/orders/{id}")
public Order getOrder(@PathVariable Long id) {
return orderRepository.findById(id).orElse(null);
}
}
// good: Controller에서 Service 호출
@RestController
public class OrderController {
@Autowired
private OrderService orderService;
@GetMapping("/orders/{id}")
public OrderDto getOrder(@PathVariable Long id) {
return orderService.findOrderById(id);
}
}

3. @Transactional 안 쓰기 → 데이터 깨짐 위험
문제점: 데이터베이스의 무결성을 유지하기 위해서는 여러 작업이 하나의 논리적인 단위로 묶여 처리되어야 합니다. @Transactional 어노테이션을 사용하지 않으면 각 데이터베이스 작업이 독립적으로 실행되어 데이터 일관성이 깨지거나, 예상치 못한 오류 발생 시 롤백이 되지 않아 데이터가 손상될 수 있습니다.
해결 방안: 데이터 변경이 일어나는 모든 Service 메서드에는 @Transactional을 적용해야 합니다. 이를 통해 메서드 내의 모든 데이터베이스 작업이 하나의 트랜잭션으로 묶여 원자성(Atomicity)을 보장받을 수 있습니다.
예시:
// bad: @Transactional 없이 데이터 변경
@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
public void updateProductPrice(Long productId, int newPrice) {
Product product = productRepository.findById(productId).orElseThrow();
product.setPrice(newPrice);
// save를 호출해도 트랜잭션 범위가 아니므로 예외 발생 시 롤백 안됨
}
}
// good: @Transactional 적용
@Service
@Transactional
public class ProductService {
@Autowired
private ProductRepository productRepository;
public void updateProductPrice(Long productId, int newPrice) {
Product product = productRepository.findById(productId).orElseThrow();
product.setPrice(newPrice);
// 트랜잭션 내에서 처리되므로 안정성 보장
}
}

4. Entity를 그대로 Response로 주기 → DTO로 변환 필수
문제점: Entity는 애플리케이션의 핵심 비즈니스 로직과 밀접하게 연관되어 있으며, 데이터베이스의 구조를 반영합니다. Entity를 HTTP 응답으로 직접 반환하면 다음과 같은 문제가 발생할 수 있습니다.
- 보안 문제: 민감한 정보(예: 비밀번호, 내부 ID)가 외부에 노출될 수 있습니다.
- 유연성 부족: Entity의 필드가 변경될 때마다 API 스펙도 변경되어야 합니다.
- 불필요한 데이터 전송: 클라이언트에게 필요 없는 필드까지 함께 전송되어 네트워크 오버헤드가 발생합니다.
- 무한 재귀 참조: 양방향 연관관계가 있는 Entity의 경우 JSON 직렬화 시 무한 루프가 발생할 수 있습니다.
해결 방안: 클라이언트에게 데이터를 반환할 때는 항상 DTO(Data Transfer Object)로 변환하여 사용해야 합니다. DTO는 클라이언트에게 필요한 데이터만 포함하며, API 스펙을 Entity와 분리하여 관리할 수 있게 해줍니다.
예시:
// bad: Entity를 직접 반환
@RestController
public class UserController {
@Autowired
private UserRepository userRepository;
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
return userRepository.findById(id).orElse(null); // User Entity 반환
}
}
// good: DTO로 변환하여 반환
public class UserDto {
private String name;
private String email;
// ... 필요한 필드만 포함
}
@RestController
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/users/{id}")
public UserDto getUser(@PathVariable Long id) {
return userService.findUserDtoById(id); // UserDto 반환
}
}

5. 모든 메서드에 @Transactionsal(readOnly = true) 를 쓰지 않는것
문제점: 조회 전용 메서드임에도 불구하고 불필요한 스냅샷 생성하여 Dirty Checking 비용이 발생한다
해결 방안: 조회용 메서드는 반드시 readOnly를 붙이는 습관을 갖고 개발을 진행하는것이 좋다. 이로써 성능의 차이가 20~50% 이상 나는 경우도 흔하다.
예시:
// 읽기 메서드인데 readOnly = true 없음 → 불필요한 dirty checking, 2nd level cache 활용 못 함
public List<Order> findRecentOrders() { ... }
// 반대로 모든 메서드에 readOnly = true 붙여놓고 중요한 쓰기 메서드도 그대로 → 데이터 안 저장됨
//추천하는 설정방법
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class OrderQueryService {
// 대부분의 메서드는 readOnly 그대로 사용
@Transactional
public void someModifyMethod() { ... } // 필요한 곳에만 override
}
'프로그래밍 > Java' 카테고리의 다른 글
| [JPA] JPA 핵심 개념 5가지!!! (0) | 2026.01.26 |
|---|---|
| [JPA] 초보자가 쉽게 이해하는 JPA 동작구조 (0) | 2026.01.25 |
| [JPA] 초보자가 쉽게 이해하는 Spring Data JPA 설명 (3) | 2026.01.24 |
| [Java] 자바에서 객체 정렬하기 - Comparable, Comparator (0) | 2020.10.17 |
| [Java] 자바의 폴더 생성 함수 - mkdir(), mkdirs() (2) | 2020.10.16 |
| [Java] java.lang.NullPointerException 원인 그리고 해결방법 (0) | 2020.10.14 |
| [Java] 자바 로직 분리 - DAO / DTO / VO (0) | 2020.10.14 |
| [Java] 자바 Log 유틸 - Apache Log4j (0) | 2020.10.13 |