[๐Spring] ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๋
๐ Spring Framework ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๋ ์ ๋ฆฌ
JDBC
๐ JDBC(Java Database Connectivity): ์๋ฐ ์ ํ๋ฆฌ์ผ์ด์ ์์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ๊ทผํ๊ธฐ ์ํ ์๋ฐ ํ์ค API
์คํ๋ง์ ์ฌ์ฉํ ๋ DB์ ์ฐ๊ฒฐ, SQL ์ฟผ๋ฆฌ ์ ์ก ๋ฑ์ ๋ชฉ์ ์ผ๋ก ํ์์ ์ผ๋ก ์ฌ์ฉํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ด๋ค.
๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public abstract class ConnectionConst {
public static final String URL = "jdbc:h2:tcp://localhost/~/test";
public static final String USERNAME = "sa";
public static final String PASSWORD = "";
}
public class DBConnectionUtil {
public static Connection getConnection() {
try {
Connection connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);
return connection;
} catch (SQLException e) {
throw new IllegalStateException(e);
}
}
}
JDBC DriverManager
- DriverManager: JDBC ๋๋ผ์ด๋ฒ๋ฅผ ๊ด๋ฆฌํ๊ณ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ์ ์์ฑํ๋ ์ญํ
- ์ ํ๋ฆฌ์ผ์ด์ ๊ณผ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฌ์ด์ ์ฐ๊ฒฐ์ ์ค๊ฐ
JDBC๋ก ์ฟผ๋ฆฌ ์ ์กํ๊ธฐ
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class MemberRepositoryV0 {
public Member save(Member member) throws SQLException {
String sql = "insert into member(member_id, money) values(?, ?)";
Connection con = null;
PreparedStatement pstmt = null;
try {
con = getConnection();
pstmt = con.prepareStatement(sql);
pstmt.setString(1, member.getMemberId());
pstmt.setInt(2, member.getMoney());
pstmt.executeUpdate();
return member;
} catch (SQLException e) {
log.error("db error", e);
throw e;
} finally {
close(con, pstmt, null);
}
}
private void close(Connection con, Statement stmt, ResultSet rs) {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
log.info("error", e);
}
}
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
log.info("error", e);
}
}
if (con != null) {
try {
con.close();
} catch (SQLException e) {
log.info("error", e);
}
}
}
private Connection getConnection() {
return DBConnectionUtil.getConnection();
}
}
JDBC๋ฅผ ์ฌ์ฉํ๋ฉด DB ์ฐ๊ฒฐ, ์ฟผ๋ฆฌ ์์ฑ, ์ฐ๊ฒฐ ํด์ ๋ฑ ๋ฐ๋ณต์ ์ธ ๋ณด์ผ๋ฌํ๋ ์ดํธ ์ฝ๋๊ฐ ๋๋ฌด ๋ง์ด ํ์ํ๋ค.
๊ทธ๋์ ์ค๋ฌด์์๋ JDBC๋ฅผ ์ง์ ์ฌ์ฉํ๊ธฐ๋ณด๋ค JPA์ ํจ๊ป ์ฌ์ฉํ์ฌ ์ด๋ฌํ ๋ณต์ก์ฑ์ ์ค์ธ๋ค.
JPA
๐ JPA(Java Persistence API): ์๋ฐ ๊ฐ์ฒด์ ๊ด๊ณํ DB ํ ์ด๋ธ์ ๋งคํํด์ฃผ๋ ORM(Object-Relational Mapping) ๊ธฐ์
JPA ์์ด ๊ด๊ณํ DB์ ์๋ฐ๊ฐ ์ํธ์์ฉํ๋ ค๋ฉด ๋ค์๊ณผ ๊ฐ์ ๋ถํธํจ์ด ์๋ค:
- ์ฐ๊ด ๊ด๊ณ๋ฅผ ์ง์ SQL๋ก ๊ตฌํํด์ผ ํจ
- ์๋ฐ์ ์์ ๊ตฌ์กฐ๋ฅผ DB์ ํํํ๊ธฐ ์ด๋ ค์
- ๋ฐ๋ณต์ ์ธ CRUD SQL ์์ฑ
JPA๋ฅผ ์ฌ์ฉํ๋ฉด ์๋ฐ ๊ฐ์ฒด๋ฅผ ๋ค๋ฃจ๋ฏ์ด ์ฝ๋๋ฅผ ์์ฑํ๋ฉด ์๋์ผ๋ก SQL ์ฟผ๋ฆฌ๊ฐ ์์ฑ๋์ด DB๋ก ์ ์ก๋๋ค.
JPA ๋์ ๋ฐฉ์
- JPA๋ JDBC๋ฅผ ๋ด๋ถ์ ์ผ๋ก ์ฌ์ฉํ์ง๋ง ๊ฐ๋ฐ์๊ฐ ์ง์ JDBC ์ฝ๋๋ฅผ ์์ฑํ ํ์๊ฐ ์์
- ๊ฐ์ฒด ์งํฅ์ ์ธ ๋ฐฉ์์ผ๋ก ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ๋ค๋ฃฐ ์ ์๊ฒ ํด์ค
๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ ์ค์ (MySQL + JPA)
์์กด์ฑ ์ถ๊ฐ (build.gradle)
1
2
3
4
dependencies {
runtimeOnly 'com.mysql:mysql-connector-j'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
}
์คํ๋ง ์ค์ ํ์ผ (application.yml)
1
2
3
4
5
6
7
8
9
10
11
12
13
spring:
datasource:
url: jdbc:mysql://localhost:3306/haein
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
properties:
hibernate:
format_sql: true
show_sql: true
ddl-auto: update: ์ํฐํฐ ๋ณ๊ฒฝ์ฌํญ์ ์๋์ผ๋ก DB ์คํค๋ง์ ๋ฐ์show_sql: true: ์คํ๋๋ SQL ์ฟผ๋ฆฌ๋ฅผ ์ฝ์์ ์ถ๋ ฅ
์ํฐํฐ๋ก DB ํ ์ด๋ธ ๊ตฌํํ๊ธฐ (N:1 ์ฐ๊ด๊ด๊ณ)
DB ์คํค๋ง ์ค๊ณ
Member์ Post ์ํฐํฐ๊ฐ ์ผ๋๋ค(1:N) ๊ด๊ณ๋ฅผ ๊ฐ์ง ์คํค๋ง ์ค๊ณ ์์
ERD ์ค๊ณ ๋๊ตฌ: https://www.erdcloud.com/
Member ์ํฐํฐ ๊ตฌํ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Entity // DB ํ
์ด๋ธ๊ณผ ๋งคํ๋๋ ์ํฐํฐ์์ ๋ช
์
@Getter
public class Member {
@Id // Primary Key
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "member_id")
private Long id;
private String name;
private String email;
private String password;
// ์ผ๋๋ค ๊ด๊ณ์ '1' ์ชฝ ์ค์
// Member ํ๋๊ฐ ์ฌ๋ฌ Post๋ฅผ ๊ฐ์ง ์ ์์
// mappedBy: ์ฐ๊ด๊ด๊ณ์ ์ฃผ์ธ์ด ์๋์ ๋ช
์ (Post์ writer ํ๋์์ ๊ด๋ฆฌ)
@OneToMany(mappedBy = "writer")
private List<Post> posts = new ArrayList<>();
}
@Entity: ์ด ํด๋์ค๊ฐ JPA ์ํฐํฐ์์ ์ ์ธ@Id: Primary Key ํ๋ ์ง์ @GeneratedValue: PK ๊ฐ ์๋ ์์ฑ ์ ๋ต (IDENTITY: DB์ AUTO_INCREMENT ์ฌ์ฉ)@OneToMany(mappedBy = "writer"): ์ผ๋๋ค ๊ด๊ณ ์ค์ .mappedBy๋ ์ฐ๊ด๊ด๊ณ์ ์ฃผ์ธ์ด ์๋์ ํ์
@OneToMany ํ๋๋ฅผ ๋๋ ์ด์
1
List<Post> posts = member.getPosts(); // ๋งค์ฐ ๊ฐ๋จํ ์กฐํ
JDBC๋ฅผ ์ฌ์ฉํ๋ฉด member์ ๋ชจ๋ post๋ฅผ ์กฐํํ๊ธฐ ์ํด ์ง์ SQL ์ฟผ๋ฆฌ๋ฅผ ์์ฑํด์ผ ํ์ง๋ง, JPA๋ฅผ ์ฌ์ฉํ๋ฉด getter ํ๋๋ก ๊ฐ์ฒด์งํฅ์ ์ผ๋ก ์กฐํํ ์ ์๋ค.
Post ์ํฐํฐ ๊ตฌํ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Entity
@Getter
public class Post {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "post_id")
private Long id;
private String title;
private String content;
// ๋ค๋์ผ ๊ด๊ณ์ 'N' ์ชฝ ์ค์
// ์ฌ๋ฌ Post๊ฐ ํ๋์ Member๋ฅผ ์ฐธ์กฐ
@ManyToOne
@JoinColumn(name = "member_id") // FK ์ปฌ๋ผ ์ด๋ฆ ์ง์
private Member writer; // ์ฐ๊ด๊ด๊ณ์ ์ฃผ์ธ
}
@ManyToOne: ๋ค๋์ผ ๊ด๊ณ ์ค์ @JoinColumn(name = "member_id"):- Post ํ
์ด๋ธ์
member_id๋ผ๋ FK ์ปฌ๋ผ ์์ฑ - Member ์ํฐํฐ์ PK ๊ฐ์ ์ ์ฅ
- Post ํ
์ด๋ธ์
@ManyToOne์ ๋ถ์ธ ํ๋๋ ์ค์ DB ์ปฌ๋ผ์ ์ ์ฅ๋จ (์ฐ๊ด๊ด๊ณ์ ์ฃผ์ธ)
์ฐ๊ด๊ด๊ณ ๋งคํ ์ ๊ณ ๋ ค์ฌํญ
1. ์ง์ฐ ๋ก๋ฉ (Lazy Loading)
๋ฌธ์ ์ํฉ: ๊ฒ์๋ฌผ 100๊ฐ๋ฅผ ์กฐํํ ๋ ์ฐ๊ด๋ Member ์ ๋ณด 100๊ฐ๋ ํจ๊ป ๋ถ๋ฌ์จ๋ค๋ฉด? ๊ฒ์๋ฌผ ์ ๋ชฉ๋ง ํ์ํ ๊ฒฝ์ฐ, ๋ฉค๋ฒ ์ ๋ณด ์กฐํ๋ ๋ถํ์ํ ์ฑ๋ฅ ๋ญ๋น๋ค.
1
2
3
@ManyToOne(fetch = FetchType.LAZY) // ์ง์ฐ ๋ก๋ฉ ์ค์
@JoinColumn(name = "member_id")
private Member writer;
- ์ง์ฐ ๋ก๋ฉ: ์ฐ๊ด๋ ์ํฐํฐ๋ฅผ ์ค์ ๋ก ์ฌ์ฉํ๋ ์์ ์ ์กฐํ
postRepository.findAll()์คํ ์writerํ๋์๋ ํ๋ก์(๊ฐ์ง) ๊ฐ์ฒด๊ฐ ๋ค์ด๊ฐpost.getWriter().getName()๋ฑ์ผ๋ก ์ค์ ์ ๊ทผํ ๋ DB ์ฟผ๋ฆฌ ์คํ
ํ๋ก์ ๊ฐ์ฒด
- ์ง์ง ์ํฐํฐ ํด๋์ค๋ฅผ ์์๋ฐ์ JPA๊ฐ ์๋์ผ๋ก ์์ฑ
- ์ค์ ์ฌ์ฉ ์์ ๊น์ง DB ์กฐํ๋ฅผ ๋ฏธ๋ฃธ
์ฑ๋ฅ ์ต์ ํ: ์ฐ๊ด๊ด๊ณ๋ ํญ์
LAZY๋ก ์ค์ ํ๋ ๊ฒ์ ์ต๊ดํํ์!
์ฆ์ ๋ก๋ฉ(EAGER): ์ฐ๊ด๋ ์ํฐํฐ๋ฅผ ์ฆ์ ์กฐํ (์ฑ๋ฅ ๋ฌธ์ ๋ก ๊ฑฐ์ ์ฌ์ฉ ์ ํจ)
2. ์์์ฑ ์ ์ด (Cascade)
1
2
3
// Member ์ญ์ ์ ์ฐ๊ด๋ Post๋ค๋ ํจ๊ป ์ญ์
@OneToMany(mappedBy = "writer", cascade = CascadeType.REMOVE)
private List<Post> posts = new ArrayList<>();
CascadeType.REMOVE: ๋ถ๋ชจ ์ํฐํฐ ์ญ์ ์ ์์ ์ํฐํฐ๋ ํจ๊ป ์ญ์ CascadeType.PERSIST: ๋ถ๋ชจ ์ํฐํฐ ์ ์ฅ ์ ์์ ์ํฐํฐ๋ ํจ๊ป ์ ์ฅ
๋ฉค๋ฒ ํํด ์ ๊ฒ์๊ธ์ ์ผ์ผ์ด ์ฐพ์ ์ญ์ ํ ํ์ ์์ด JPA๊ฐ ์๋์ผ๋ก ์ฒ๋ฆฌํ๋ค.
๊ณ์ธตํ ์ํคํ ์ฒ: Repository-Service-Controller
์คํ๋ง์์๋ ์ญํ ์ ๊ณ์ธต๋ณ๋ก ๋ถ๋ฆฌํ์ฌ ์ฝ๋๋ฅผ ๊ด๋ฆฌํ๋ค.
Repository
๐ Repository: ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ง์ ํต์ ํ๋ ๊ณ์ธต (Data Access Layer)
1
2
3
4
public interface UserRepository extends JpaRepository<User, Long> {
// JPA๊ฐ ๋ฉ์๋ ์ด๋ฆ์ ๋ถ์ํ์ฌ ์๋์ผ๋ก ์ฟผ๋ฆฌ ์์ฑ
Optional<Member> findByName(String name);
}
JpaRepository<์ํฐํฐ ํ์ , PK ํ์ >์์- ๊ธฐ๋ณธ CRUD ๋ฉ์๋ ์๋ ์ ๊ณต:
save(),findById(),findAll(),deleteById()๋ฑ - ๋ฉ์๋ ์ด๋ฆ ๊ท์น์ผ๋ก ์ปค์คํ
์ฟผ๋ฆฌ ์์ฑ:
findBy + ํ๋๋ช - ๊ตฌํ ํด๋์ค๋ฅผ ์์ฑํ ํ์ ์์ (์คํ๋ง์ด ๋ฐํ์์ ์๋ ์์ฑ)
Service
๐ Service: ๋น์ฆ๋์ค ๋ก์ง์ ์ฒ๋ฆฌํ๋ ๊ณ์ธต
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
@Service
@RequiredArgsConstructor // final ํ๋ ์์ฑ์ ์๋ ์์ฑ (์์กด์ฑ ์ฃผ์
)
public class UserService {
private final UserRepository userRepository;
// ์ฌ์ฉ์ ์ ์ฅ
public User createUser(User user) {
return userRepository.save(user);
}
// ์ฌ์ฉ์ ์ญ์
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
// ID๋ก ์ฌ์ฉ์ ์กฐํ (Optional ํ์ฉ)
public Member findOne(Long memberId) {
return memberRepository.findById(memberId)
.orElseThrow(() -> new IllegalArgumentException("์กด์ฌํ์ง ์๋ ํ์์
๋๋ค."));
}
// ์ด๋ฆ์ผ๋ก ์ฌ์ฉ์ ์กฐํ
public User getUserByUsername(String username) {
return userRepository.findByUsername(username);
}
// ์ ์ฒด ์ฌ์ฉ์ ์กฐํ
public List<User> getAllUsers() {
return userRepository.findAll();
}
}
- Repository์ ๋ฉ์๋๋ฅผ ์กฐํฉํ์ฌ ๋น์ฆ๋์ค ๋ก์ง ๊ตฌํ
- ํธ๋์ญ์ ๊ด๋ฆฌ
Controller
1
2
3
4
5
6
7
8
9
10
@Controller
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@GetMapping("/users/{username}")
public User getUser(@PathVariable String username) {
return userService.getUserByUsername(username);
}
}
- HTTP ์์ฒญ์ ๋ฐ์ Service๋ฅผ ํธ์ถํ๊ณ ์๋ต ๋ฐํ
ํธ๋์ญ์ ๊ด๋ฆฌ
๐ ํธ๋์ญ์ : ์ฌ๋ฌ DB ์์ ์ ํ๋์ ๋ ผ๋ฆฌ์ ๋จ์๋ก ๋ฌถ์ด ๋ชจ๋ ์ฑ๊ณตํ๊ฑฐ๋ ๋ชจ๋ ์คํจํ๋๋ก ๋ณด์ฅ
@Transactional ์ด๋ ธํ ์ด์
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
@Service
@RequiredArgsConstructor
public class AccountService {
private final AccountRepository accountRepository;
@Transactional // ํธ๋์ญ์
์ผ๋ก ๋ฌถ์
public void transferMoney(Long fromAccountId, Long toAccountId, int amount) {
// 1. ๋ณด๋ด๋ ๊ณ์ข ์กฐํ
Account fromAccount = accountRepository.findById(fromAccountId)
.orElseThrow(() -> new IllegalArgumentException("๋ณด๋ด๋ ๊ณ์ข๊ฐ ์์ต๋๋ค."));
// 2. ๋ฐ๋ ๊ณ์ข ์กฐํ
Account toAccount = accountRepository.findById(toAccountId)
.orElseThrow(() -> new IllegalArgumentException("๋ฐ๋ ๊ณ์ข๊ฐ ์์ต๋๋ค."));
// 3. ์ถ๊ธ
fromAccount.withdraw(amount);
// 4. ์์ธ ๋ฐ์ ์ ์ ์ฒด ๋กค๋ฐฑ
if (toAccount.isInactive()) {
throw new IllegalStateException("๋ฐ๋ ๊ณ์ข๊ฐ ๋นํ์ฑํ ์ํ์
๋๋ค.");
}
// 5. ์
๊ธ
toAccount.deposit(amount);
// JPA๊ฐ ํธ๋์ญ์
์ปค๋ฐ ์ ์๋์ผ๋ก UPDATE ์ฟผ๋ฆฌ ์คํ
}
}
- ๋ฉ์๋ ์ฑ๊ณต: ์๋ Commit
- ์์ธ ๋ฐ์: ์๋ Rollback (๋ชจ๋ ๋ณ๊ฒฝ์ฌํญ ์ทจ์)
- ์ฃผ๋ก Service ๊ณ์ธต์์ ์ฌ์ฉ
DTO๋ก ๋ฐ์ดํฐ ์ ์กํ๊ธฐ
๐ DTO(Data Transfer Object): ๊ณ์ธต ๊ฐ ๋ฐ์ดํฐ ์ ์ก์ ์ํ ๊ฐ์ฒด
์ผ๋ฐ์ ์ธ ๋ฐฉ์์ ๋นํจ์จ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Service
public class WaitingService {
@Autowired
private WaitingRepository waitingRepository;
// โ ๋นํจ์จ: ์ํฐํฐ ์ ์ฒด ์กฐํ ํ DTO ๋ณํ
public List<WaitingListDTO> getWaitingList(Long pubId) {
// 1๋จ๊ณ: ๋ถํ์ํ ์ปฌ๋ผ๊น์ง ๋ชจ๋ ์กฐํ
List<Waiting> waitings = waitingRepository.findByPubId(pubId);
// 2๋จ๊ณ: Stream์ผ๋ก DTO ๋ณํ (๋ฉ๋ชจ๋ฆฌ 2๋ฐฐ ์ฌ์ฉ)
return waitings.stream()
.map(w -> new WaitingListDTO(
w.getId(),
w.getCustomerName(),
w.getCreatedAt()))
.collect(Collectors.toList());
}
}
๊ฐ์ ๋ ๋ฐฉ์: Repository์์ ๋ฐ๋ก DTO ๋ฐํ
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
// DTO ํด๋์ค
@Getter
@AllArgsConstructor
public class WaitingListDTO {
private Long id;
private String customerName;
private LocalDateTime createdAt;
}
// Repository: JPQL๋ก ํ์ํ ์ปฌ๋ผ๋ง SELECTํ์ฌ DTO ์์ฑ
@Repository
public interface WaitingRepository extends JpaRepository<Waiting, Long> {
@Query("SELECT new com.example.dto.WaitingListDTO(w.id, w.customerName, w.createdAt) " +
"FROM Waiting w WHERE w.pubId = :pubId")
List<WaitingListDTO> findWaitingDTOByPubId(@Param("pubId") Long pubId);
}
// Service: ๋ณํ ๊ณผ์ ์์ด ๋ฐ๋ก ๋ฐํ
@Service
public class WaitingService {
@Autowired
private WaitingRepository waitingRepository;
public List<WaitingListDTO> getWaitingList(Long pubId) {
return waitingRepository.findWaitingDTOByPubId(pubId);
}
}
๊ฐ์ ํจ๊ณผ:
- ํ์ํ ์ปฌ๋ผ๋ง SELECT (๋คํธ์ํฌ ๋ถํ ๊ฐ์)
- ์ํฐํฐโDTO ๋ณํ ๊ณผ์ ์๋ต (๋ฉ๋ชจ๋ฆฌ ์ ์ฝ)
- ์ฝ๋ ๊ฐ๊ฒฐํ
Fetch Join์ผ๋ก N+1 ๋ฌธ์ ํด๊ฒฐ
N+1 ๋ฌธ์ ๋?
1
2
3
4
5
6
7
8
9
10
11
12
13
// โ N+1 ๋ฌธ์ ๋ฐ์
public void printTeamsAndMembers() {
List<Team> teams = teamRepository.findAll(); // 1๋ฒ ์ฟผ๋ฆฌ
for (Team team : teams) {
System.out.println("ํ: " + team.getName());
// ๊ฐ ํ๋ง๋ค members ์กฐํ โ N๋ฒ ์ฟผ๋ฆฌ ๋ฐ์!
for (Member member : team.getMembers()) {
System.out.println(" - ๋ฉค๋ฒ: " + member.getName());
}
}
// ์ด 1 + N๋ฒ์ ์ฟผ๋ฆฌ ์คํ (๋นํจ์จ)
}
Fetch Join์ผ๋ก ํด๊ฒฐ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Repository: Fetch Join ์ฌ์ฉ
@Query("SELECT DISTINCT t FROM Team t JOIN FETCH t.members")
List<Team> findAllWithMembers();
// Service: 1๋ฒ์ ์ฟผ๋ฆฌ๋ก ํด๊ฒฐ
public void printTeamsAndMembers() {
List<Team> teams = teamRepository.findAllWithMembers(); // 1๋ฒ ์ฟผ๋ฆฌ๋ก Team + Member ๋ชจ๋ ์กฐํ
for (Team team : teams) {
System.out.println("ํ: " + team.getName());
for (Member member : team.getMembers()) {
System.out.println(" - ๋ฉค๋ฒ: " + member.getName());
}
}
}
- Fetch Join: ์ฐ๊ด๋ ์ํฐํฐ๋ฅผ ํ ๋ฒ์ ์ฟผ๋ฆฌ๋ก ํจ๊ป ์กฐํ
- N+1 ๋ฌธ์ ํด๊ฒฐ: 1 + N๋ฒ โ 1๋ฒ์ผ๋ก ์ฟผ๋ฆฌ ์ ๊ฐ์
์คํ๋ง์ ๋ฉํฐ์ค๋ ๋์ ๋๊ธฐ I/O
์คํ๋ง์ ์ค๋ ๋ ํ
- ๋ฉํฐ์ค๋ ๋ ๋ฐฉ์: ์ฌ๋ฌ ์ฌ์ฉ์ ์์ฒญ์ ๋์ ์ฒ๋ฆฌ
- ์ค๋ ๋ ํ: ๋ฏธ๋ฆฌ ์์ฑ๋ ์์ปค ์ค๋ ๋๋ฅผ ์ฌ์ฌ์ฉ
- ์์ฒญ ์ฒ๋ฆฌ ์๋ฃ ํ ์ค๋ ๋๋ ๋ค์ ํ๋ก ๋ฐํ
Blocking I/O ๋ฐฉ์
- ๋๊ธฐ I/O: I/O ์์ ์๋ฃ๊น์ง ์ค๋ ๋ ๋๊ธฐ (Block)
- DB ์กฐํ์ 1์ด ๊ฑธ๋ ค๋ ๋ค๋ฅธ ์ค๋ ๋๊ฐ ์ฒ๋ฆฌ๋๋ฏ๋ก ์ ์ฒด ์๋ฒ๊ฐ ๋ฉ์ถ์ง๋ ์์
- ๋จ, CPU๊ฐ I/O ๋๊ธฐ ์ค์ธ ์ค๋ ๋๋ฅผ WAIT ์ํ๋ก ์ ํํ๊ณ ๋ค๋ฅธ ์ค๋ ๋๋ฅผ ์คํ
์ค๋ ๋ ํ ๊ณ ๊ฐ ๋ฌธ์ (Thread Pool Exhaustion)
- ์์ฒญ์ด ํญ์ฆํ๊ณ ๋ง์ ์ค๋ ๋๊ฐ I/O ๋๊ธฐ ์ํ๋ฉด?
- ์์ปค ์ค๋ ๋๊ฐ ๋ชจ๋ WAIT ์ํ โ ์ ์์ฒญ ์ฒ๋ฆฌ ๋ถ๊ฐ
- ๋ณ๋ชฉ ํ์ ๋ฐ์
Non-blocking I/O: ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ๋น๋๊ธฐ ๋ฐฉ์๋ ์๋ค ์ฐธ๊ณ : https://velog.io/@yjl8628/Blocking-Non-blocking-IO








