Post

[๐ŸƒSpring] ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๋™

[๐Ÿƒ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

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 ๋™์ž‘ ๋ฐฉ์‹

  • 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 ์Šคํ‚ค๋งˆ ์„ค๊ณ„

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 ๊ฐ’์„ ์ €์žฅ
  • @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): ๊ณ„์ธต ๊ฐ„ ๋ฐ์ดํ„ฐ ์ „์†ก์„ ์œ„ํ•œ ๊ฐ์ฒด

DTO ๊ฐœ๋…

DTO ๋น„์œ 

์ผ๋ฐ˜์ ์ธ ๋ฐฉ์‹์˜ ๋น„ํšจ์œจ

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 ๋ฐฉ์‹

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


This post is licensed under CC BY 4.0 by the author.