์น ์๋น์ค๋ฅผ ๋ง๋ค๋ค ๋ณด๋ฉด ์ฌ์ฉ์์ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๊ฑฐ๋ ๋ถ๋ฌ์ค๋ ์ผ์ด ์ ๋ง ๋ง์ต๋๋ค.
ํ์๊ฐ์ ์ ์ ๋ ฅํ ์ ๋ณด, ๊ฒ์ํ์ ๊ธ๊ณผ ๋๊ธ, ์ฃผ๋ฌธ ๋ด์ญ ๋ฑ ์๋ง์ ๋ฐ์ดํฐ๊ฐ ์ค๊ฐ์ฃ .
๊ทธ๋ ๋ค๋ฉด ์ด๋ฐ ๋ฐ์ดํฐ๋ ์ด๋์, ์ด๋ป๊ฒ ์ ์ฅ๋๊ณ ๊ด๋ฆฌ๋ ๊น์?
Spring Boot๋ก API๋ฅผ ๋ง๋ค์๋ค๋ฉด , ์ด์ ๋ค์ ๋จ๊ณ๋ ๋ฐ๋ก ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์ฐ๋์ ๋๋ค.
๋จ์ํ save()๋ง ํ๋ค๊ณ ๋๋๋ ๊ฒ์ด ์๋๋ผ, JPA์ ๋์ ์๋ฆฌ, Entity์ Repository ์ค๊ณ, ์ฐ๊ด๊ด๊ณ ๋งคํ, ํธ๋์ญ์ ๊ด๋ฆฌ, ๊ทธ๋ฆฌ๊ณ ์ฑ๋ฅ ์ต์ ํ ์ ๋ต๊น์ง ํจ๊ป ์ดํดํด์ผ ์์ ์ ์ด๊ณ ํจ์จ์ ์ธ ๋ฐฑ์๋ ์์คํ ์ ๋ง๋ค ์ ์์ต๋๋ค.
์ด๋ฒ ๊ธ์์๋ ์ค๋ฌด์์ ์์ฃผ ๋ง์ฃผ์น๋ ๋ฐ์ดํฐ ์ฒ๋ฆฌ์ ํต์ฌ ๊ฐ๋ ๋ค์ ์ค์ฌ์ผ๋ก, JPA์ ํจ๊ป ๋ฐฑ์๋ ๊ฐ๋ฐ์๊ฐ ๋ฐ๋์ ์์์ผ ํ ๋ด์ฉ๋ค์ ์ ๋ฆฌํด ๋ณด๊ฒ ์ต๋๋ค.
๐ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ์ ๊ธฐ๋ณธ๊ธฐ: JDBC์ ConnectionPool
JDBC๋?
JDBC(Java Database Connectivity)๋ ์๋ฐ์์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ๊ทผํ๊ธฐ ์ํ ํ์ค API์ ๋๋ค.
DB์ ์ฐ๊ฒฐํ๊ณ , SQL์ ์คํํ๋ฉฐ, ๊ฒฐ๊ณผ๋ฅผ ์ฒ๋ฆฌํ๋ ์ญํ ์ ํ์ฃ .
ํ์ง๋ง ์์ JDBC ๋ฐฉ์์ ์ค๋ฌด์์ ์ฌ๋ฌ ํ๊ณ๋ฅผ ๋๋ฌ๋ ๋๋ค. ์์ฒญ์ด ๋ค์ด์ฌ ๋๋ง๋ค ๋งค๋ฒ DB ์ฐ๊ฒฐ์ ์๋ก ์์ฑํด์ผ ํ๊ธฐ ๋๋ฌธ์ ์ฑ๋ฅ์ ๋ถ๋ด์ด ํฌ๊ณ , try-catch-finally ๊ตฌ์กฐ๊ฐ ๋ฐ๋ณต๋ผ ์ฝ๋๊ฐ ์ง์ ๋ถํด์ง๊ธฐ ์ฝ์ต๋๋ค. ๊ฒ๋ค๊ฐ ์๋ฐ ์ฝ๋ ์์ SQL์ด ์์ด๋ค ๋ณด๋ ์ ์ง๋ณด์๋ ์ด๋ ต์ต๋๋ค.
์ด๋ฌํ ์ด์ ๋ก ์ฐ๋ฆฌ๋ ์ปค๋ฅ์ ํ๊ณผ ๊ฐ์ ๊ธฐ์ ์ ํจ๊ป ์ฌ์ฉํ๋ฉฐ, ๋์๊ฐ JPA ๊ฐ์ ORM ๋๊ตฌ๋ก ๋ ํจ์จ์ ์ธ ๋ฐ์ดํฐ ์ฒ๋ฆฌ๋ฅผ ํ๊ฒ ๋ฉ๋๋ค.
Connection Pool ์ด๋?
์ปค๋ฅ์ ํ์ ๋ฏธ๋ฆฌ ์ฌ๋ฌ ๊ฐ์ DB ์ฐ๊ฒฐ์ ์์ฑํด ๋๊ณ , ์์ฒญ์ด ๋ค์ด์ฌ ๋๋ง๋ค ํ๋์ฉ ๋น๋ ค์ฃผ๋ ๊ตฌ์กฐ์ ๋๋ค.
Spring Boot์์๋ ๊ธฐ๋ณธ์ ์ผ๋ก HikariCP๋ฅผ ์ฌ์ฉํด ํจ์จ์ ์ธ ์ฐ๊ฒฐ์ ์ ๊ณตํฉ๋๋ค.
# application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/app
username: root
password: pass
driver-class-name: com.mysql.cj.jdbc.Driver
# HikariCP ์ค์
hikari:
maximum-pool-size: 20 # ์ต๋ ์ฐ๊ฒฐ ์
minimum-idle: 5 # ์ต์ ์ ์ง ์ฐ๊ฒฐ ์
connection-timeout: 3000 # ์ฐ๊ฒฐ ๋๊ธฐ ์๊ฐ (ms)
idle-timeout: 600000 # ์ ํด ์ฐ๊ฒฐ ์ ๊ฑฐ ์๊ฐ (ms)
max-lifetime: 1800000 # ์ฐ๊ฒฐ ์ต๋ ์๋ช
(ms)
leak-detection-threshold: 60000 # ์ฐ๊ฒฐ ๋์ ๊ฐ์ง ์๊ฐ
๐ก ์ค๋ฌด ํ: ConnectionPool ํฌ๊ธฐ๋ CPU ์ฝ์ด ์์ 2-3๋ฐฐ ์ ๋๋ก ์ค์ ํ๋ ๊ฒ์ด ์ผ๋ฐ์ ์ ๋๋ค.
๐ ORM๊ณผ JPA ์ํ๊ณ ์ดํดํ๊ธฐ
ORM์ด๋?
ORM(Object-Relational Mapping)์ ๊ฐ์ฒด์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ํ ์ด๋ธ์ ๋งคํํด ์ฃผ๋ ๊ธฐ์ ์ ๋๋ค.
์ฐ๋ฆฌ๋ ์๋ฐ ๊ฐ์ฒด๋ฅผ ๋ค๋ฃจ์ง๋ง, ORM์ด ์ด๋ฅผ SQL๋ก ๋ณํํ์ฌ DB์ ๋ฐ์ํด ์ค๋๋ค.
ORM์ด ๋์ ๋๋ฉด ๋ค์๊ณผ ๊ฐ์ ์ฅ์ ์ด ์์ต๋๋ค:
- SQL ์์ด ๊ฐ์ฒด ์ค์ฌ ๊ฐ๋ฐ ๊ฐ๋ฅ
- DB ๋ ๋ฆฝ์ ์ธ ์ฝ๋ ์์ฑ
- ํธ๋์ญ์ ๊ณผ ์บ์๋ฅผ ํตํ ์ฑ๋ฅ ์ต์ ํ
JPA, Hibernate, Spring Data JPA ๊ด๊ณ
๊ธฐ์ | ์ญํ | ์ค๋ช |
JPA | Java ORM ํ์ค ์คํ | ์ธํฐํ์ด์ค/์ด๋ ธํ ์ด์ ์ ์ |
Hibernate | JPA ๊ตฌํ์ฒด | ์ค์ ORM ๊ธฐ๋ฅ ์ ๊ณต |
Spring Data JPA | Spring์ JPA ์ถ์ํ | Repository ํจํด์ผ๋ก ํธ์ ๊ธฐ๋ฅ ์ ๊ณต |
- JPA๋ ํ์ค ์ธํฐํ์ด์ค,
- Hibernate๋ ์ค์ ๋์์ ์ํํ๋ ๊ตฌํ์ฒด,
- Spring Data JPA๋ Repository ๊ธฐ๋ฐ ์ถ์ํ๋ฅผ ์ ๊ณตํฉ๋๋ค.
๐งฉ Entity์ Repository ์ค๊ณ
Entity๋?
Entity๋ DB ํ ์ด๋ธ์ ๋งคํ๋๋ ํด๋์ค์ ๋๋ค.
๊ฐ Entity๋ ๊ณ ์ ํ ID์ ์ปฌ๋ผ์ ๊ฐ์ง๊ณ , JPA ์ด๋ ธํ ์ด์ ์ผ๋ก ์ ์ํฉ๋๋ค
@Entity
public class User {
@Id @GeneratedValue
private Long id;
private String name;
private String email;
// Getter, Setter, Constructor
}
Repository๋?
Repository๋ Entity์ ๋ํ CRUD ์์ ์ ์ถ์ํํ ์ธํฐํ์ด์ค์ ๋๋ค.
Spring Data JPA์์๋ JpaRepository๋ฅผ ์์๋ฐ์ ์ฌ์ฉํฉ๋๋ค.
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
}
๐ก ๋ณต์กํ SQL ์์ด๋ ๋ฐ์ดํฐ ์กฐ์์ด ๊ฐ๋ฅํ๊ณ , ์ปค์คํ ์ฟผ๋ฆฌ๋ ๋ฉ์๋๋ก ์ ์ํ ์ ์์ด์.
๐ ์ฐ๊ด๊ด๊ณ ๋งคํ (1:1, 1:N, N:M)
JPA์์๋ ์ํฐํฐ ๊ฐ ๊ด๊ณ๋ ๊ฐ์ฒด์ฒ๋ผ ํํํ ์ ์์ต๋๋ค.
- 1:1 ๊ด๊ณ → @OneToOne
- 1:N / N:1 ๊ด๊ณ → @OneToMany, @ManyToOne
- N:M ๊ด๊ณ → @ManyToMany (๋์ ์ค๊ฐ ์ํฐํฐ ๊ถ์ฅ)
์: ์ฌ์ฉ์์ ๊ฒ์๊ธ (1:N)
@Entity
public class User {
@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Post> posts = new ArrayList<>();
}
@Entity
public class Post {
@ManyToOne(fetch = FetchType.LAZY)
private User user;
}
์ง์ฐ๋ก๋ฉ(LAZY) vs ์ฆ์๋ก๋ฉ(EAGER)์ ์ฑ๋ฅ์ ํฐ ์ํฅ์ ์ฃผ๋ฏ๋ก ์ ์คํ ์ ํํด์ผ ํฉ๋๋ค.
๐ก ์ค๋ฌด ํ:
- ๋๋ถ๋ถ์ ์ฐ๊ด๊ด๊ณ๋ LAZY๋ก ์ค์ ํ์ธ์
- N:M ๊ด๊ณ๋ ์ค๊ฐ ํ ์ด๋ธ์ ๋ณ๋ ์ํฐํฐ๋ก ํ์ด ๊ด๋ฆฌํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
โก ํธ๋์ญ์ ๊ณผ ์์์ฑ ์ปจํ ์คํธ
์์์ฑ ์ปจํ ์คํธ๋?
์์์ฑ ์ปจํ ์คํธ๋ JPA์์ ์ํฐํฐ๋ฅผ 1์ฐจ ์บ์์ ์ ์ฅํ์ฌ ๊ด๋ฆฌํ๋ ๊ณต๊ฐ์ ๋๋ค.
์ฌ๊ธฐ์ ๋์ผ ๊ฐ์ฒด๋ฅผ ์ฌ์ฌ์ฉํ๊ณ , ๋ณ๊ฒฝ ๊ฐ์ง๋ฅผ ํตํด ํจ์จ์ ์ธ UPDATE๊ฐ ๊ฐ๋ฅํฉ๋๋ค.
ํธ๋์ญ์ ๊ด๋ฆฌ
Spring์์๋ @Transactional ์ด๋ ธํ ์ด์ ์ผ๋ก ํธ๋์ญ์ ์ ๊ด๋ฆฌํฉ๋๋ค.
@Service
@Transactional
public class UserService {
public void register(User user) {
userRepository.save(user);
// ํธ๋์ญ์
์ปค๋ฐ ์์ ์ DB ๋ฐ์๋จ
}
}
์์์ฑ ์ปจํ ์คํธ์ ํต์ฌ ์ญํ
- 1์ฐจ ์บ์: ๋์ผํ ๊ฐ์ฒด ์ฌ์ฌ์ฉ (์กฐํ ์ฑ๋ฅ ํฅ์)
- Dirty Checking: ๋ณ๊ฒฝ ๊ฐ์ง ํ ์๋ ์ ๋ฐ์ดํธ
- ์ง์ฐ ์ฐ๊ธฐ: ํธ๋์ญ์ ์ปค๋ฐ ์ ์ผ๊ด ๋ฐ์
๐ N+1 ๋ฌธ์ ์ ์ฑ๋ฅ ์ต์ ํ ์ ๋ต
N+1 ๋ฌธ์ ๋?
N+1 ๋ฌธ์ ๋ JPA์์ ๊ฐ์ฅ ํํ๊ฒ ๋ฐ์ํ๋ ์ฑ๋ฅ ์ด์์ ๋๋ค.
์ฐ๊ด๋ ์ํฐํฐ๋ฅผ ์กฐํํ ๋ ์ถ๊ฐ ์ฟผ๋ฆฌ๊ฐ N๋ฒ ์คํ๋๋ ๋ฌธ์ ์์.
๊ฒ์๊ธ 1๋ฒ ์กฐํ + ์์ฑ์ N๋ฒ ์กฐํ = ์ด N+1๋ฒ ์ฟผ๋ฆฌ ์คํ๋๋ ๋ฌธ์ ์ ๋๋ค.
ํด๊ฒฐ ๋ฐฉ๋ฒ
1. JPQL + Fetch Join
@Query("SELECT p FROM Post p JOIN FETCH p.user")
List<Post> findAllWithUser();
2. @EntityGraph
@EntityGraph(attributePaths = {"user"})
List<Post> findAll();
3. Batch Size ์ค์
spring:
jpa:
properties:
hibernate:
default_batch_fetch_size: 100
๐ก ๋ฐ์ดํฐ๊ฐ ๋ง์์๋ก ํ์ด์ง + fetch join์ ์ ์ ํ ์กฐํฉํ๋ ์ ๋ต์ด ์ค์ํฉ๋๋ค.
๐ ๏ธ ์ค๋ฌด์์ ์์ฃผ ์ฌ์ฉํ๋ JPA ํจํด๋ค
JPA๋ ๋จ์ํ CRUD๋ฅผ ๋์ด ์ค๋ฌด์์ ์ ์ฉํ๊ฒ ํ์ฉํ ์ ์๋ ๋ค์ํ ํจํด๋ค์ ์ ๊ณตํฉ๋๋ค.
์๋๋ ๊ทธ์ค ์์ฃผ ์ฌ์ฉ๋๋ ํต์ฌ ํจํด๋ค์ ๋๋ค.
1. Soft Delete (๋ ผ๋ฆฌ์ ์ญ์ )
DB์์ ๋ฐ์ดํฐ๋ฅผ ๋ฌผ๋ฆฌ์ ์ผ๋ก ์ญ์ ํ์ง ์๊ณ , deleted๋ผ๋ ํ๋๋ฅผ ์ด์ฉํด ๋ ผ๋ฆฌ์ ์ผ๋ก ์ญ์ ๋ ๊ฒ์ฒ๋ผ ์ฒ๋ฆฌํ๋ ๋ฐฉ์์ ๋๋ค.
์ค๋ฌด์์ ๋ฐ์ดํฐ ๋ณต๊ตฌ๋ ์ด๋ ฅ ๊ด๋ฆฌ๋ฅผ ์ํด ์์ฃผ ์ฌ์ฉ๋ฉ๋๋ค.
@Entity
@SQLDelete(sql = "UPDATE users SET deleted = true WHERE id = ?")
@Where(clause = "deleted = false")
public class User extends BaseEntity {
@Column(name = "deleted")
private boolean deleted = false;
public void delete() {
this.deleted = true;
}
}
๐ก @SQLDelete๋ ์ญ์ ์ฟผ๋ฆฌ๋ฅผ ์ปค์คํฐ๋ง์ด์งํ๊ณ , @Where๋ ์กฐํ ์ ์กฐ๊ฑด์ ์๋์ผ๋ก ์ถ๊ฐํด ์ค๋๋ค.
2. ๋ฒ์ ๊ด๋ฆฌ (Optimistic Lock)
๋๊ด์ ๋ฝ์ ๋์์ ์ฌ๋ฌ ํธ๋์ญ์ ์ด ๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ์์ ํ๋ ค ํ ๋ ์ถฉ๋์ ๋ฐฉ์งํ๊ธฐ ์ํ ์ ๋ต์ ๋๋ค.
@Version ํ๋๋ฅผ ์ฌ์ฉํ๋ฉด ์๋์ผ๋ก ๋ฒ์ ์ด ์ฆ๊ฐํ๋ฉฐ ์ถฉ๋ ์ฌ๋ถ๋ฅผ ์ฒดํฌํ ์ ์์ต๋๋ค.
@Entity
public class Product {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private BigDecimal price;
@Version
private Long version; // JPA๊ฐ ์๋์ผ๋ก ์ฆ๊ฐ์ํด
public void updatePrice(BigDecimal newPrice) {
this.price = newPrice;
}
}
๐ก ๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ๋์์ ์์ ํ๋ ค๋ ์๋๊ฐ ๊ฐ์ง๋๋ฉด OptimisticLockException์ด ๋ฐ์ํฉ๋๋ค.
3. ๊ฐ์ฌ(Audit) ์ ๋ณด ์๋ ๊ด๋ฆฌ
์์ฑ์, ์์ ์, ์์ฑ์ผ, ์์ ์ผ๊ณผ ๊ฐ์ ์ ๋ณด๋ฅผ ์๋์ผ๋ก ๊ด๋ฆฌํ๊ณ ์ถ์ ๋ JPA Auditing ๊ธฐ๋ฅ์ ํ์ฉํ ์ ์์ต๋๋ค.
์ด๋ฅผ ์ํด ์ค์ ํด๋์ค์ @CreatedDate, @LastModifiedDate ๋ฑ์ ์ฌ์ฉํฉ๋๋ค.
@Configuration
@EnableJpaAuditing
public class JpaAuditingConfig {
@Bean
public AuditorAware<String> auditorAware() {
return () -> {
Authentication authentication = SecurityContextHolder
.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated()) {
return Optional.of("system");
}
return Optional.of(authentication.getName());
};
}
}
๊ทธ๋ฆฌ๊ณ BaseEntity ๊ฐ์ ์ถ์ ํด๋์ค์ ๊ณตํต ํ๋๋ฅผ ์ ์ธํด ๋๊ณ ์์๋ฐ์ ์ฌ์ฉํ๋ฉด ๊ด๋ฆฌ๊ฐ ํธ๋ฆฌํด์ง๋๋ค.
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@Getter
public abstract class BaseEntity {
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
@CreatedBy
@Column(updatable = false)
private String createdBy;
@LastModifiedBy
private String updatedBy;
}
๐ก ์ํฐํฐ์ @CreatedBy, @LastModifiedBy๋ฅผ ์ค์ ํ๋ฉด ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ์๋ ์ฃผ์ ๋ฐ์ ์ ์์ต๋๋ค.
๐งก ๋ง๋ฌด๋ฆฌํ๋ฉฐ
์ด๋ฒ ๊ธ์์๋ ๋ฐฑ์๋ ๊ฐ๋ฐ์์ ํต์ฌ์ธ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๋๋ถํฐ JPA ํ์ฉ, ์ฑ๋ฅ ์ต์ ํ๊น์ง ์ค๋ฌด์์ ๊ผญ ํ์ํ ๋ด์ฉ๋ค์ ์ ๋ฆฌํด ๋ดค์ต๋๋ค.
๐ ํต์ฌ ์์ฝ
- ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ: JDBC์ ConnectionPool์ ์ค์์ฑ, HikariCP ์ค์
- ORM๊ณผ JPA: ๊ฐ์ฒด-๊ด๊ณ ๋งคํ์ ์ฅ์ ๊ณผ JPA ์ํ๊ณ ์ดํด
- Entity ์ค๊ณ: BaseEntity, Repository ํจํด, ์ปค์คํ ์ฟผ๋ฆฌ ํ์ฉ
- ์ฐ๊ด๊ด๊ณ ๋งคํ: 1:1, 1:N, N:M ๊ด๊ณ์ ์ฌ๋ฐ๋ฅธ ๋งคํ ๋ฐฉ๋ฒ
- ํธ๋์ญ์ ๊ด๋ฆฌ: ์์์ฑ ์ปจํ ์คํธ์ ๋ค์ํ ํธ๋์ญ์ ์ ๋ต
- ์ฑ๋ฅ ์ต์ ํ: N+1 ๋ฌธ์ ํด๊ฒฐ๊ณผ ์ฟผ๋ฆฌ ์ต์ ํ ๊ธฐ๋ฒ
ํนํ ์ค๋ฌด์์๋ N+1 ๋ฌธ์ ํด๊ฒฐ๊ณผ ์ ์ ํ ํ์ด์ง ์ ๋ต์ด ์๋น์ค ์ฑ๋ฅ์ ํฐ ์ํฅ์ ๋ฏธ์นฉ๋๋ค.
Fetch Join, @EntityGraph, Batch Size ๋ฑ์ ์ํฉ์ ๋ง๊ฒ ์ ์ ํ ํ์ฉํ๋ ๊ฒ์ด ์ค์ํด์.
๋ค์ ๊ธ์์๋ Spring Boot ๊ธฐ๋ฐ ๊ฐ๋จํ API ์ค์ต์ ๋ค๋ค๋ณด๊ฒ ์ต๋๋ค.
๊ถ๊ธํ ์ ์ด๋ ๋ ์์ธํ ์๊ณ ์ถ์ JPA ํ์ฉ๋ฒ์ด ์๋ค๋ฉด ๋๊ธ๋ก ๋จ๊ฒจ์ฃผ์ธ์! ๐
'๐ฅ๏ธ Backend' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
๐ฑ Spring ํต์ฌ ๊ฐ๋ ๋ง์คํฐํ๊ธฐ (0) | 2025.05.29 |
---|---|
๐ ๏ธ ๋ฐฑ์๋ ๊ฐ๋ฐ ํ๊ฒฝ ์ค์ A to Z (0) | 2025.05.28 |
๐ ์น์ ๊ธฐ๋ณธ ๊ฐ๋ ๊ณผ HTTP ์์ ์ ๋ณต (0) | 2025.05.27 |
๐ป ๋ฐฑ์๋๋ ๋ฌด์์ธ๊ฐ? (0) | 2025.05.20 |