17 ๋ถ„ ์†Œ์š”

1. AOP๊ฐ€ ๋ญ”๊ฐ€์š”? ๐Ÿค”

์‹ค์ƒํ™œ ๋น„์œ ๋กœ ์ดํ•ดํ•˜๊ธฐ

์—ฌ๋Ÿฌ๋ถ„์ด ํšŒ์‚ฌ CEO๋ผ๊ณ  ์ƒ๊ฐํ•ด๋ณด์„ธ์š”. ํ•˜๋ฃจ์— ์ˆ˜๋งŽ์€ ์—…๋ฌด๊ฐ€ ์žˆ์ง€๋งŒ, ๋ชจ๋“  ์—…๋ฌด ์ „ํ›„๋กœ ํ•ด์•ผ ํ•  ์ผ๋“ค์ด ์žˆ์Šต๋‹ˆ๋‹ค.

graph TB
    subgraph "CEO์˜ ํ•˜๋ฃจ (AOP ์ ์šฉ ์ „)"
        A1[ํšŒ์˜ ์ค€๋น„] --> A2[๋ณด์•ˆ ์ฒดํฌ] --> A3[์‹œ๊ฐ„ ๊ธฐ๋ก] --> A4[ํšŒ์˜ ์ง„ํ–‰] --> A5[๊ฒฐ๊ณผ ๊ธฐ๋ก] --> A6[์ •๋ฆฌ]
        B1[๊ณ„์•ฝ์„œ ๊ฒ€ํ†  ์ค€๋น„] --> B2[๋ณด์•ˆ ์ฒดํฌ] --> B3[์‹œ๊ฐ„ ๊ธฐ๋ก] --> B4[๊ณ„์•ฝ์„œ ๊ฒ€ํ† ] --> B5[๊ฒฐ๊ณผ ๊ธฐ๋ก] --> B6[์ •๋ฆฌ]
        C1[์˜์‚ฌ๊ฒฐ์ • ์ค€๋น„] --> C2[๋ณด์•ˆ ์ฒดํฌ] --> C3[์‹œ๊ฐ„ ๊ธฐ๋ก] --> C4[์˜์‚ฌ๊ฒฐ์ •] --> C5[๊ฒฐ๊ณผ ๊ธฐ๋ก] --> C6[์ •๋ฆฌ]
    end
    
    style A2 fill:#ffebee
    style A3 fill:#e8f5e8
    style A5 fill:#e3f2fd
    style B2 fill:#ffebee
    style B3 fill:#e8f5e8
    style B5 fill:#e3f2fd
    style C2 fill:#ffebee
    style C3 fill:#e8f5e8
    style C5 fill:#e3f2fd

๋ฌธ์ œ์ : ๋ณด์•ˆ ์ฒดํฌ, ์‹œ๊ฐ„ ๊ธฐ๋ก, ๊ฒฐ๊ณผ ๊ธฐ๋ก ๋“ฑ์ด ๋ชจ๋“  ์—…๋ฌด์— ๋ฐ˜๋ณต๋จ ๐Ÿ˜ต

AOP ์ ์šฉ ํ›„

graph TB
    subgraph "CEO์˜ ํ•˜๋ฃจ (AOP ์ ์šฉ ํ›„)"
        D[๋น„์„œ/์‹œ์Šคํ…œ] -.-> E1[ํšŒ์˜ ์ง„ํ–‰]
        D -.-> E2[๊ณ„์•ฝ์„œ ๊ฒ€ํ† ]
        D -.-> E3[์˜์‚ฌ๊ฒฐ์ •]
        
        F[๋ณด์•ˆ ๋‹ด๋‹น์ž] -.-> D
        G[์‹œ๊ฐ„ ๊ด€๋ฆฌ์ž] -.-> D
        H[๊ธฐ๋ก ๋‹ด๋‹น์ž] -.-> D
    end
    
    style D fill:#e8f5e8
    style F fill:#ffebee
    style G fill:#e3f2fd
    style H fill:#fff3e0

ํ•ด๊ฒฐ์ฑ…: ๋ถ€๊ฐ€ ์—…๋ฌด๋“ค์„ ์ „๋‹ด ์‹œ์Šคํ…œ์ด ์ž๋™์œผ๋กœ ์ฒ˜๋ฆฌ! ๐ŸŽฏ

2. ํ”„๋ก์‹œ ํŒจํ„ด ์ดํ•ดํ•˜๊ธฐ ๐Ÿ•ต๏ธโ€โ™‚๏ธ

ํ”„๋ก์‹œ๋ž€?

ํ”„๋ก์‹œ๋Š” ๋Œ€๋ฆฌ์ธ ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค. ์‹ค์ œ ๊ฐ์ฒด ๋Œ€์‹  ์š”์ฒญ์„ ๋ฐ›์•„์„œ ๋ถ€๊ฐ€ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•œ ํ›„ ์‹ค์ œ ๊ฐ์ฒด์—๊ฒŒ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.

sequenceDiagram
    participant Client as ํด๋ผ์ด์–ธํŠธ
    participant Proxy as ํ”„๋ก์‹œ ๊ฐ์ฒด
    participant Target as ์‹ค์ œ ๊ฐ์ฒด
    
    Client->>Proxy: ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ
    Note over Proxy: ๋ถ€๊ฐ€ ๊ธฐ๋Šฅ ์‹คํ–‰ (๋กœ๊น…, ๋ณด์•ˆ ๋“ฑ)
    Proxy->>Target: ์‹ค์ œ ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ
    Target-->>Proxy: ๊ฒฐ๊ณผ ๋ฐ˜ํ™˜
    Note over Proxy: ๋ถ€๊ฐ€ ๊ธฐ๋Šฅ ์‹คํ–‰ (๊ฒฐ๊ณผ ๋กœ๊น… ๋“ฑ)
    Proxy-->>Client: ์ตœ์ข… ๊ฒฐ๊ณผ ๋ฐ˜ํ™˜

ํ”„๋ก์‹œ๊ฐ€ ์—†๋‹ค๋ฉด? ๐Ÿ˜ฐ

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
// ๐Ÿ˜ฐ ๋ชจ๋“  ๋ฉ”์„œ๋“œ์— ์ค‘๋ณต ์ฝ”๋“œ๊ฐ€ ๋“ค์–ด๊ฐ
@Service
public class UserService {
    
    public User findUser(Long id) {
        // ๋กœ๊น…
        System.out.println("์‚ฌ์šฉ์ž ์กฐํšŒ ์‹œ์ž‘");
        long startTime = System.currentTimeMillis();
        
        // ๋ณด์•ˆ ์ฒดํฌ
        if (!SecurityContext.getCurrentUser().hasPermission("READ_USER")) {
            throw new SecurityException("๊ถŒํ•œ ์—†์Œ");
        }
        
        // ํŠธ๋žœ์žญ์…˜ ์‹œ์ž‘
        TransactionManager.begin();
        
        try {
            // ๐ŸŽฏ ์‹ค์ œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง (์ด๊ฒƒ๋งŒ ํ•˜๊ณ  ์‹ถ์—ˆ๋Š”๋ฐ...)
            User user = userRepository.findById(id);
            
            // ํŠธ๋žœ์žญ์…˜ ์ปค๋ฐ‹
            TransactionManager.commit();
            
            // ์„ฑ๋Šฅ ๋กœ๊น…
            long endTime = System.currentTimeMillis();
            System.out.println("์‹คํ–‰ ์‹œ๊ฐ„: " + (endTime - startTime) + "ms");
            
            return user;
        } catch (Exception e) {
            TransactionManager.rollback();
            throw e;
        }
    }
    
    public void saveUser(User user) {
        // ๐Ÿ˜ฑ ๋˜ ๊ฐ™์€ ์ฝ”๋“œ ๋ฐ˜๋ณต...
        System.out.println("์‚ฌ์šฉ์ž ์ €์žฅ ์‹œ์ž‘");
        // ... ์ค‘๋ณต ์ฝ”๋“œ๋“ค
    }
}

3. JDK Dynamic Proxy ๐Ÿš€

ํŠน์ง•

  • ์ธํ„ฐํŽ˜์ด์Šค ๊ธฐ๋ฐ˜: ๋ฐ˜๋“œ์‹œ ์ธํ„ฐํŽ˜์ด์Šค๊ฐ€ ์žˆ์–ด์•ผ ํ•จ
  • JDK ๋‚ด์žฅ: ์ถ”๊ฐ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋ถˆํ•„์š”
  • ๋ฆฌํ”Œ๋ ‰์…˜ ์‚ฌ์šฉ: InvocationHandler ํ™œ์šฉ

๊ตฌ์กฐ

graph TB
    subgraph "JDK Dynamic Proxy ๊ตฌ์กฐ"
        A[<<interface>> UserService] --> B[UserServiceImpl]
        A --> C[JDK Proxy]
        C -.-> B
        D[InvocationHandler] --> C
    end
    
    style A fill:#e3f2fd
    style C fill:#ffebee
    style D fill:#e8f5e8

์‹ค์Šต ์˜ˆ์ œ

1๋‹จ๊ณ„: ์ธํ„ฐํŽ˜์ด์Šค์™€ ๊ตฌํ˜„์ฒด ๋งŒ๋“ค๊ธฐ

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
// ๐Ÿ“‹ ์ธํ„ฐํŽ˜์ด์Šค (ํ•„์ˆ˜!)
public interface UserService {
    User findUser(Long id);
    void saveUser(User user);
}

// ๐Ÿ—๏ธ ์‹ค์ œ ๊ตฌํ˜„์ฒด
public class UserServiceImpl implements UserService {
    
    @Override
    public User findUser(Long id) {
        // ์ˆœ์ˆ˜ํ•œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง๋งŒ!
        System.out.println("DB์—์„œ ์‚ฌ์šฉ์ž ์กฐํšŒ: " + id);
        return new User(id, "ํ™๊ธธ๋™");
    }
    
    @Override
    public void saveUser(User user) {
        System.out.println("DB์— ์‚ฌ์šฉ์ž ์ €์žฅ: " + user.getName());
    }
}

// ๐Ÿ“ฆ ๊ฐ„๋‹จํ•œ User ํด๋ž˜์Šค
public class User {
    private Long id;
    private String name;
    
    public User(Long id, String name) {
        this.id = id;
        this.name = name;
    }
    
    // getter, setter, toString...
    public Long getId() { return id; }
    public String getName() { return name; }
    public String toString() { return "User{id=" + id + ", name='" + name + "'}"; }
}

2๋‹จ๊ณ„: InvocationHandler ๊ตฌํ˜„

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
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class LoggingInvocationHandler implements InvocationHandler {
    private final Object target; // ์‹ค์ œ ๊ฐ์ฒด
    
    public LoggingInvocationHandler(Object target) {
        this.target = target;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // ๐ŸŽฏ Before: ๋ฉ”์„œ๋“œ ์‹คํ–‰ ์ „
        System.out.println("๐Ÿš€ [JDK Proxy] " + method.getName() + " ๋ฉ”์„œ๋“œ ์‹œ์ž‘");
        long startTime = System.currentTimeMillis();
        
        try {
            // ์‹ค์ œ ๋ฉ”์„œ๋“œ ์‹คํ–‰
            Object result = method.invoke(target, args);
            
            // ๐ŸŽฏ After: ๋ฉ”์„œ๋“œ ์‹คํ–‰ ํ›„ (์„ฑ๊ณต)
            long endTime = System.currentTimeMillis();
            System.out.println("โœ… [JDK Proxy] " + method.getName() + " ์„ฑ๊ณต (์‹คํ–‰์‹œ๊ฐ„: " + (endTime - startTime) + "ms)");
            
            return result;
        } catch (Exception e) {
            // ๐ŸŽฏ After: ๋ฉ”์„œ๋“œ ์‹คํ–‰ ํ›„ (์‹คํŒจ)
            System.out.println("โŒ [JDK Proxy] " + method.getName() + " ์‹คํŒจ: " + e.getMessage());
            throw e;
        }
    }
}

3๋‹จ๊ณ„: ํ”„๋ก์‹œ ์ƒ์„ฑ ๋ฐ ์‹คํ–‰

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
import java.lang.reflect.Proxy;

public class JdkProxyExample {
    public static void main(String[] args) {
        // 1. ์‹ค์ œ ๊ฐ์ฒด ์ƒ์„ฑ
        UserService target = new UserServiceImpl();
        
        // 2. ํ”„๋ก์‹œ ์ƒ์„ฑ
        UserService proxy = (UserService) Proxy.newProxyInstance(
            UserService.class.getClassLoader(),           // ํด๋ž˜์Šค๋กœ๋”
            new Class[]{UserService.class},               // ์ธํ„ฐํŽ˜์ด์Šค๋“ค
            new LoggingInvocationHandler(target)          // ํ•ธ๋“ค๋Ÿฌ
        );
        
        // 3. ํ”„๋ก์‹œ๋ฅผ ํ†ตํ•œ ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ
        System.out.println("=== JDK Dynamic Proxy ํ…Œ์ŠคํŠธ ===");
        
        User user = proxy.findUser(1L);
        System.out.println("์กฐํšŒ ๊ฒฐ๊ณผ: " + user);
        
        System.out.println();
        
        proxy.saveUser(new User(2L, "๊น€์ฒ ์ˆ˜"));
        
        // 4. ํ”„๋ก์‹œ ํƒ€์ž… ํ™•์ธ
        System.out.println("\n=== ํƒ€์ž… ํ™•์ธ ===");
        System.out.println("ํ”„๋ก์‹œ ํด๋ž˜์Šค: " + proxy.getClass().getName());
        System.out.println("UserService ์ธํ„ฐํŽ˜์ด์Šค? " + (proxy instanceof UserService));
        System.out.println("UserServiceImpl ํด๋ž˜์Šค? " + (proxy instanceof UserServiceImpl));
    }
}

์‹คํ–‰ ๊ฒฐ๊ณผ:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
=== JDK Dynamic Proxy ํ…Œ์ŠคํŠธ ===
๐Ÿš€ [JDK Proxy] findUser ๋ฉ”์„œ๋“œ ์‹œ์ž‘
DB์—์„œ ์‚ฌ์šฉ์ž ์กฐํšŒ: 1
โœ… [JDK Proxy] findUser ์„ฑ๊ณต (์‹คํ–‰์‹œ๊ฐ„: 15ms)
์กฐํšŒ ๊ฒฐ๊ณผ: User{id=1, name='ํ™๊ธธ๋™'}

๐Ÿš€ [JDK Proxy] saveUser ๋ฉ”์„œ๋“œ ์‹œ์ž‘
DB์— ์‚ฌ์šฉ์ž ์ €์žฅ: ๊น€์ฒ ์ˆ˜
โœ… [JDK Proxy] saveUser ์„ฑ๊ณต (์‹คํ–‰์‹œ๊ฐ„: 8ms)

=== ํƒ€์ž… ํ™•์ธ ===
ํ”„๋ก์‹œ ํด๋ž˜์Šค: com.sun.proxy.$Proxy0
UserService ์ธํ„ฐํŽ˜์ด์Šค? true
UserServiceImpl ํด๋ž˜์Šค? false

4. CGLIB Proxy โšก

ํŠน์ง•

  • ํด๋ž˜์Šค ๊ธฐ๋ฐ˜: ์ธํ„ฐํŽ˜์ด์Šค ์—†์ด๋„ ํ”„๋ก์‹œ ์ƒ์„ฑ ๊ฐ€๋Šฅ
  • ์ƒ์† ๋ฐฉ์‹: ๋Œ€์ƒ ํด๋ž˜์Šค๋ฅผ ์ƒ์†๋ฐ›์•„ ํ”„๋ก์‹œ ์ƒ์„ฑ
  • ๋ฐ”์ดํŠธ์ฝ”๋“œ ์กฐ์ž‘: ๋” ๋น ๋ฅธ ์„ฑ๋Šฅ

๊ตฌ์กฐ

graph TB
    subgraph "CGLIB Proxy ๊ตฌ์กฐ"
        A[OrderService] --> B[CGLIB Proxy]
        B -.-> A
        C[MethodInterceptor] --> B
    end
    
    style A fill:#e8f5e8
    style B fill:#ffebee
    style C fill:#e3f2fd

์‹ค์Šต ์˜ˆ์ œ

1๋‹จ๊ณ„: ๊ตฌ์ฒด ํด๋ž˜์Šค ๋งŒ๋“ค๊ธฐ (์ธํ„ฐํŽ˜์ด์Šค ์—†์Œ!)

1
2
3
4
5
6
7
8
9
10
11
12
// ๐Ÿ—๏ธ ๊ตฌ์ฒด ํด๋ž˜์Šค๋งŒ ์žˆ์–ด๋„ OK!
public class OrderService {
    
    public void createOrder(String orderInfo) {
        System.out.println("์ฃผ๋ฌธ ์ƒ์„ฑ: " + orderInfo);
    }
    
    public String getOrderStatus(String orderId) {
        System.out.println("์ฃผ๋ฌธ ์ƒํƒœ ์กฐํšŒ: " + orderId);
        return "์ฃผ๋ฌธ ์ƒํƒœ: ์ฒ˜๋ฆฌ์ค‘";
    }
}

2๋‹จ๊ณ„: MethodInterceptor ๊ตฌํ˜„

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
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

public class LoggingMethodInterceptor implements MethodInterceptor {
    
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // ๐ŸŽฏ Before: ๋ฉ”์„œ๋“œ ์‹คํ–‰ ์ „
        System.out.println("โšก [CGLIB] " + method.getName() + " ๋ฉ”์„œ๋“œ ์‹œ์ž‘");
        long startTime = System.currentTimeMillis();
        
        try {
            // ์‹ค์ œ ๋ฉ”์„œ๋“œ ์‹คํ–‰ (๋” ํšจ์œจ์ ์ธ ๋ฐฉ๋ฒ•)
            Object result = proxy.invokeSuper(obj, args);
            
            // ๐ŸŽฏ After: ๋ฉ”์„œ๋“œ ์‹คํ–‰ ํ›„ (์„ฑ๊ณต)
            long endTime = System.currentTimeMillis();
            System.out.println("โœ… [CGLIB] " + method.getName() + " ์„ฑ๊ณต (์‹คํ–‰์‹œ๊ฐ„: " + (endTime - startTime) + "ms)");
            
            return result;
        } catch (Exception e) {
            // ๐ŸŽฏ After: ๋ฉ”์„œ๋“œ ์‹คํ–‰ ํ›„ (์‹คํŒจ)
            System.out.println("โŒ [CGLIB] " + method.getName() + " ์‹คํŒจ: " + e.getMessage());
            throw e;
        }
    }
}

3๋‹จ๊ณ„: ์˜์กด์„ฑ ์ถ”๊ฐ€ (build.gradle)

1
2
3
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter'
}

4๋‹จ๊ณ„: ํ”„๋ก์‹œ ์ƒ์„ฑ ๋ฐ ์‹คํ–‰

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
import org.springframework.cglib.proxy.Enhancer;

public class CglibProxyExample {
    public static void main(String[] args) {
        // 1. CGLIB Enhancer ์ƒ์„ฑ
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(OrderService.class);           // ์ƒ์†ํ•  ํด๋ž˜์Šค
        enhancer.setCallback(new LoggingMethodInterceptor()); // ์ธํ„ฐ์…‰ํ„ฐ
        
        // 2. ํ”„๋ก์‹œ ์ƒ์„ฑ
        OrderService proxy = (OrderService) enhancer.create();
        
        // 3. ํ”„๋ก์‹œ๋ฅผ ํ†ตํ•œ ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ
        System.out.println("=== CGLIB Proxy ํ…Œ์ŠคํŠธ ===");
        
        proxy.createOrder("๋งฅ๋ถ ํ”„๋กœ ๊ตฌ๋งค");
        System.out.println();
        
        String status = proxy.getOrderStatus("ORDER-001");
        System.out.println("๊ฒฐ๊ณผ: " + status);
        
        // 4. ํ”„๋ก์‹œ ํƒ€์ž… ํ™•์ธ
        System.out.println("\n=== ํƒ€์ž… ํ™•์ธ ===");
        System.out.println("ํ”„๋ก์‹œ ํด๋ž˜์Šค: " + proxy.getClass().getName());
        System.out.println("OrderService ํด๋ž˜์Šค? " + (proxy instanceof OrderService));
    }
}

์‹คํ–‰ ๊ฒฐ๊ณผ:

1
2
3
4
5
6
7
8
9
10
11
12
13
=== CGLIB Proxy ํ…Œ์ŠคํŠธ ===
โšก [CGLIB] createOrder ๋ฉ”์„œ๋“œ ์‹œ์ž‘
์ฃผ๋ฌธ ์ƒ์„ฑ: ๋งฅ๋ถ ํ”„๋กœ ๊ตฌ๋งค
โœ… [CGLIB] createOrder ์„ฑ๊ณต (์‹คํ–‰์‹œ๊ฐ„: 12ms)

โšก [CGLIB] getOrderStatus ๋ฉ”์„œ๋“œ ์‹œ์ž‘
์ฃผ๋ฌธ ์ƒํƒœ ์กฐํšŒ: ORDER-001
โœ… [CGLIB] getOrderStatus ์„ฑ๊ณต (์‹คํ–‰์‹œ๊ฐ„: 5ms)
๊ฒฐ๊ณผ: ์ฃผ๋ฌธ ์ƒํƒœ: ์ฒ˜๋ฆฌ์ค‘

=== ํƒ€์ž… ํ™•์ธ ===
ํ”„๋ก์‹œ ํด๋ž˜์Šค: com.example.OrderService$$EnhancerByCGLIB$$12345678
OrderService ํด๋ž˜์Šค? true

5. ๋‘ ๋ฐฉ์‹ ๋น„๊ต โš–๏ธ

๋น„๊ตํ‘œ

ํŠน์ง• JDK Dynamic Proxy CGLIB Proxy
ํ•„์š” ์กฐ๊ฑด ์ธํ„ฐํŽ˜์ด์Šค ํ•„์ˆ˜ โœ… ๊ตฌ์ฒด ํด๋ž˜์Šค๋งŒ ์žˆ์–ด๋„ OK โœ…
์ƒ์„ฑ ๋ฐฉ์‹ ์ธํ„ฐํŽ˜์ด์Šค ๊ตฌํ˜„ ํด๋ž˜์Šค ์ƒ์†
์„ฑ๋Šฅ ๋А๋ฆผ (๋ฆฌํ”Œ๋ ‰์…˜) ๋น ๋ฆ„ (๋ฐ”์ดํŠธ์ฝ”๋“œ)
์˜์กด์„ฑ JDK ๋‚ด์žฅ ์™ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
ํƒ€์ž… ์บ์ŠคํŒ… ์ธํ„ฐํŽ˜์ด์Šค๋งŒ ๊ฐ€๋Šฅ ๊ตฌ์ฒด ํด๋ž˜์Šค ๊ฐ€๋Šฅ
final ์ œํ•œ ์—†์Œ final ํด๋ž˜์Šค/๋ฉ”์„œ๋“œ ๋ถˆ๊ฐ€

์„ ํƒ ๊ธฐ์ค€

flowchart TD
    A[ํ”„๋ก์‹œ ์ƒ์„ฑ ํ•„์š”] --> B{์ธํ„ฐํŽ˜์ด์Šค ์žˆ์Œ?}
    B -->|Yes| C{์„ฑ๋Šฅ์ด ์ค‘์š”?}
    B -->|No| D[CGLIB ์„ ํƒ]
    C -->|Yes| E[CGLIB ๊ถŒ์žฅ]
    C -->|No| F[JDK Proxy ๊ถŒ์žฅ]
    
    G[Spring Boot] --> H[๊ธฐ๋ณธ์ ์œผ๋กœ CGLIB ์‚ฌ์šฉ]
    
    style D fill:#e8f5e8
    style E fill:#e8f5e8
    style F fill:#e3f2fd
    style H fill:#fff3e0

6. Spring AOP ์‹ค์Šต ๐ŸŒฑ

ํ”„๋กœ์ ํŠธ ์„ค์ •

build.gradle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
plugins {
	id 'java'
	id 'org.springframework.boot' version '3.5.4'
	id 'io.spring.dependency-management' version '1.1.7'
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-aop'

    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'

    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

    testCompileOnly 'org.projectlombok:lombok'
    testAnnotationProcessor 'org.projectlombok:lombok'
}

application.yml

1
2
3
4
5
6
7
spring:
  aop:
    proxy-target-class: true  # CGLIB ์‚ฌ์šฉ ๊ฐ•์ œ
    
logging:
  level:
    com.example: DEBUG

๊ธฐ๋ณธ ์„ค์ •

1
2
3
4
5
6
7
8
// ๐Ÿ“ Application.java
@SpringBootApplication
@EnableAspectJAutoProxy  // AOP ํ™œ์„ฑํ™”
public class AopDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(AopDemoApplication.class, args);
    }
}

7. ๊ณตํ†ต ๊ธฐ๋Šฅ ๊ตฌํ˜„ ๐Ÿ”ง

1. ๋กœ๊น… Aspect

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
@Aspect
@Component
@Slf4j
public class LoggingAspect {
    
    // ๐ŸŽฏ ๋ชจ๋“  Service ํด๋ž˜์Šค์˜ public ๋ฉ”์„œ๋“œ์— ์ ์šฉ
    @Pointcut("execution(public * com.example.service.*.*(..))")
    public void serviceLayer() {}
    
    @Before("serviceLayer()")
    public void logBefore(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        String className = joinPoint.getTarget().getClass().getSimpleName();
        Object[] args = joinPoint.getArgs();
        
        log.info("๐Ÿš€ [{}] {} ์‹œ์ž‘ - ํŒŒ๋ผ๋ฏธํ„ฐ: {}", className, methodName, Arrays.toString(args));
    }
    
    @AfterReturning(value = "serviceLayer()", returning = "result")
    public void logAfterReturning(JoinPoint joinPoint, Object result) {
        String methodName = joinPoint.getSignature().getName();
        String className = joinPoint.getTarget().getClass().getSimpleName();
        
        log.info("โœ… [{}] {} ์„ฑ๊ณต - ๊ฒฐ๊ณผ: {}", className, methodName, result);
    }
    
    @AfterThrowing(value = "serviceLayer()", throwing = "ex")
    public void logAfterThrowing(JoinPoint joinPoint, Exception ex) {
        String methodName = joinPoint.getSignature().getName();
        String className = joinPoint.getTarget().getClass().getSimpleName();
        
        log.error("โŒ [{}] {} ์‹คํŒจ - ์—๋Ÿฌ: {}", className, methodName, ex.getMessage());
    }
}

2. ์„ฑ๋Šฅ ์ธก์ • Aspect

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
@Aspect
@Component
@Slf4j
public class PerformanceAspect {
    
    // ๐ŸŽฏ @PerformanceMonitor ์–ด๋…ธํ…Œ์ด์…˜์ด ๋ถ™์€ ๋ฉ”์„œ๋“œ์— ์ ์šฉ
    @Around("@annotation(PerformanceMonitor)")
    public Object measureExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        String methodName = joinPoint.getSignature().getName();
        String className = joinPoint.getTarget().getClass().getSimpleName();
        
        long startTime = System.currentTimeMillis();
        
        try {
            Object result = joinPoint.proceed();
            
            long endTime = System.currentTimeMillis();
            long executionTime = endTime - startTime;
            
            // ์„ฑ๋Šฅ ์ž„๊ณ„์น˜ ์ฒดํฌ (100ms)
            if (executionTime > 100) {
                log.warn("๐ŸŒ [{}] {} ๋А๋ฆฐ ์‹คํ–‰ ๊ฐ์ง€! {}ms", className, methodName, executionTime);
            } else {
                log.info("โšก [{}] {} ์‹คํ–‰ ์™„๋ฃŒ: {}ms", className, methodName, executionTime);
            }
            
            return result;
        } catch (Exception e) {
            long endTime = System.currentTimeMillis();
            log.error("๐Ÿ’ฅ [{}] {} ์‹คํ–‰ ์ค‘ ์˜ค๋ฅ˜ ({}ms): {}", className, methodName, 
                     endTime - startTime, e.getMessage());
            throw e;
        }
    }
}

// ์„ฑ๋Šฅ ๋ชจ๋‹ˆํ„ฐ๋ง ์–ด๋…ธํ…Œ์ด์…˜
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PerformanceMonitor {
}

3. ๋ณด์•ˆ Aspect

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
@Aspect
@Component
@Slf4j
public class SecurityAspect {
    
    @Before("@annotation(RequireAuth)")
    public void checkAuthentication(JoinPoint joinPoint, RequireAuth requireAuth) {
        // ํ˜„์žฌ ์‚ฌ์šฉ์ž ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ (์‹ค์ œ๋กœ๋Š” SecurityContext์—์„œ)
        String currentUser = getCurrentUser();
        
        if (currentUser == null) {
            log.error("๐Ÿ”’ ์ธ์ฆ๋˜์ง€ ์•Š์€ ์‚ฌ์šฉ์ž์˜ ์ ‘๊ทผ ์‹œ๋„: {}", 
                     joinPoint.getSignature().getName());
            throw new SecurityException("๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.");
        }
        
        // ๊ถŒํ•œ ์ฒดํฌ
        String requiredRole = requireAuth.role();
        if (!hasRole(currentUser, requiredRole)) {
            log.error("๐Ÿšซ ๊ถŒํ•œ ์—†๋Š” ์ ‘๊ทผ ์‹œ๋„: ์‚ฌ์šฉ์ž={}, ํ•„์š”๊ถŒํ•œ={}", currentUser, requiredRole);
            throw new SecurityException("๊ถŒํ•œ์ด ๋ถ€์กฑํ•ฉ๋‹ˆ๋‹ค. ํ•„์š” ๊ถŒํ•œ: " + requiredRole);
        }
        
        log.info("๐Ÿ”“ ๋ณด์•ˆ ๊ฒ€์ฆ ํ†ต๊ณผ: ์‚ฌ์šฉ์ž={}, ๋ฉ”์„œ๋“œ={}", currentUser, 
                joinPoint.getSignature().getName());
    }
    
    private String getCurrentUser() {
        // ์‹ค์ œ๋กœ๋Š” SecurityContextHolder.getContext().getAuthentication()
        return "testUser"; // ํ…Œ์ŠคํŠธ์šฉ
    }
    
    private boolean hasRole(String user, String role) {
        // ์‹ค์ œ๋กœ๋Š” DB๋‚˜ ์บ์‹œ์—์„œ ๊ถŒํ•œ ํ™•์ธ
        return "ADMIN".equals(role) && "testUser".equals(user);
    }
}

// ๋ณด์•ˆ ์–ด๋…ธํ…Œ์ด์…˜
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequireAuth {
    String role() default "USER";
}

8. ์ข…ํ•ฉ ์‹ค์Šต ๐ŸŽฏ

๋น„์ฆˆ๋‹ˆ์Šค ์„œ๋น„์Šค ํด๋ž˜์Šค

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
@Service
public class ProductService {
    
    @PerformanceMonitor
    @RequireAuth(role = "USER")
    public Product findProduct(Long productId) {
        log.info("์ƒํ’ˆ ์กฐํšŒ ๋กœ์ง ์‹คํ–‰");
        
        // ์˜๋„์ ์œผ๋กœ ์ง€์—ฐ ์‹œ๋ฎฌ๋ ˆ์ด์…˜
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        
        return new Product(productId, "๋งฅ๋ถ ํ”„๋กœ", new BigDecimal("2500000"));
    }
    
    @PerformanceMonitor
    @RequireAuth(role = "ADMIN")
    public void createProduct(Product product) {
        log.info("์ƒํ’ˆ ์ƒ์„ฑ ๋กœ์ง ์‹คํ–‰");
        
        // ์˜๋„์ ์œผ๋กœ ๋А๋ฆฐ ์ž‘์—… ์‹œ๋ฎฌ๋ ˆ์ด์…˜
        try {
            Thread.sleep(150);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        
        log.info("์ƒํ’ˆ ์ƒ์„ฑ ์™„๋ฃŒ: {}", product.getName());
    }
    
    public void deleteProduct(Long productId) {
        log.info("์ƒํ’ˆ ์‚ญ์ œ ๋กœ์ง ์‹คํ–‰: {}", productId);
        
        // ์˜ˆ์™ธ ๋ฐœ์ƒ ์‹œ๋ฎฌ๋ ˆ์ด์…˜
        if (productId == 999L) {
            throw new IllegalArgumentException("์‚ญ์ œํ•  ์ˆ˜ ์—†๋Š” ์ƒํ’ˆ์ž…๋‹ˆ๋‹ค.");
        }
    }
}

// Product ์—”ํ‹ฐํ‹ฐ
public class Product {
    private Long id;
    private String name;
    private BigDecimal price;
    
    public Product(Long id, String name, BigDecimal price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }
    
    // getter, setter, toString
    public Long getId() { return id; }
    public String getName() { return name; }
    public BigDecimal getPrice() { return price; }
    
    @Override
    public String toString() {
        return "Product{id=" + id + ", name='" + name + "', price=" + price + "}";
    }
}

ํ…Œ์ŠคํŠธ ์ปจํŠธ๋กค๋Ÿฌ

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
@RestController
@RequestMapping("/api")
public class TestController {
    
    @Autowired
    private ProductService productService;
    
    @GetMapping("/products/{id}")
    public ResponseEntity<Product> getProduct(@PathVariable Long id) {
        Product product = productService.findProduct(id);
        return ResponseEntity.ok(product);
    }
    
    @PostMapping("/products")
    public ResponseEntity<String> createProduct(@RequestBody Product product) {
        productService.createProduct(product);
        return ResponseEntity.ok("์ƒํ’ˆ์ด ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.");
    }
    
    @DeleteMapping("/products/{id}")
    public ResponseEntity<String> deleteProduct(@PathVariable Long id) {
        productService.deleteProduct(id);
        return ResponseEntity.ok("์ƒํ’ˆ์ด ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.");
    }
}

์‹คํ–‰ ๊ฒฐ๊ณผ ํ™•์ธ

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
@Component
@Slf4j
public class AopTestRunner implements CommandLineRunner {
    
    @Autowired
    private ProductService productService;
    
    @Override
    public void run(String... args) {
        log.info("=== AOP ์ข…ํ•ฉ ํ…Œ์ŠคํŠธ ์‹œ์ž‘ ===");
        
        try {
            // 1. ์ •์ƒ์ ์ธ ์ƒํ’ˆ ์กฐํšŒ (๋น ๋ฅธ ์‹คํ–‰)
            log.info("\n--- ํ…Œ์ŠคํŠธ 1: ์ƒํ’ˆ ์กฐํšŒ ---");
            Product product = productService.findProduct(1L);
            
            // 2. ๊ด€๋ฆฌ์ž ๊ถŒํ•œ์ด ํ•„์š”ํ•œ ์ƒํ’ˆ ์ƒ์„ฑ (๋А๋ฆฐ ์‹คํ–‰)
            log.info("\n--- ํ…Œ์ŠคํŠธ 2: ์ƒํ’ˆ ์ƒ์„ฑ ---");
            productService.createProduct(new Product(2L, "์•„์ดํŒจ๋“œ", new BigDecimal("800000")));
            
            // 3. ์˜ˆ์™ธ ๋ฐœ์ƒ ์ผ€์ด์Šค
            log.info("\n--- ํ…Œ์ŠคํŠธ 3: ์˜ˆ์™ธ ๋ฐœ์ƒ ---");
            productService.deleteProduct(999L);
            
        } catch (Exception e) {
            log.info("์˜ˆ์ƒ๋œ ์˜ˆ์™ธ ๋ฐœ์ƒ: {}", e.getMessage());
        }
        
        log.info("=== AOP ์ข…ํ•ฉ ํ…Œ์ŠคํŠธ ์™„๋ฃŒ ===");
    }
}

์‹คํ–‰ ๊ฒฐ๊ณผ ์˜ˆ์‹œ:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
=== AOP ์ข…ํ•ฉ ํ…Œ์ŠคํŠธ ์‹œ์ž‘ ===

--- ํ…Œ์ŠคํŠธ 1: ์ƒํ’ˆ ์กฐํšŒ ---
๐Ÿ”“ ๋ณด์•ˆ ๊ฒ€์ฆ ํ†ต๊ณผ: ์‚ฌ์šฉ์ž=testUser, ๋ฉ”์„œ๋“œ=findProduct
๐Ÿš€ [ProductService] findProduct ์‹œ์ž‘ - ํŒŒ๋ผ๋ฏธํ„ฐ: [1]
์ƒํ’ˆ ์กฐํšŒ ๋กœ์ง ์‹คํ–‰
โšก [ProductService] findProduct ์‹คํ–‰ ์™„๋ฃŒ: 52ms
โœ… [ProductService] findProduct ์„ฑ๊ณต - ๊ฒฐ๊ณผ: Product{id=1, name='๋งฅ๋ถ ํ”„๋กœ', price=2500000}

--- ํ…Œ์ŠคํŠธ 2: ์ƒํ’ˆ ์ƒ์„ฑ ---
๐Ÿ”“ ๋ณด์•ˆ ๊ฒ€์ฆ ํ†ต๊ณผ: ์‚ฌ์šฉ์ž=testUser, ๋ฉ”์„œ๋“œ=createProduct
๐Ÿš€ [ProductService] createProduct ์‹œ์ž‘ - ํŒŒ๋ผ๋ฏธํ„ฐ: [Product{id=2, name='์•„์ดํŒจ๋“œ', price=800000}]
์ƒํ’ˆ ์ƒ์„ฑ ๋กœ์ง ์‹คํ–‰
์ƒํ’ˆ ์ƒ์„ฑ ์™„๋ฃŒ: ์•„์ดํŒจ๋“œ
๐ŸŒ [ProductService] createProduct ๋А๋ฆฐ ์‹คํ–‰ ๊ฐ์ง€! 152ms
โœ… [ProductService] createProduct ์„ฑ๊ณต - ๊ฒฐ๊ณผ: null

--- ํ…Œ์ŠคํŠธ 3: ์˜ˆ์™ธ ๋ฐœ์ƒ ---
๐Ÿš€ [ProductService] deleteProduct ์‹œ์ž‘ - ํŒŒ๋ผ๋ฏธํ„ฐ: [999]
์ƒํ’ˆ ์‚ญ์ œ ๋กœ์ง ์‹คํ–‰: 999
โŒ [ProductService] deleteProduct ์‹คํŒจ - ์—๋Ÿฌ: ์‚ญ์ œํ•  ์ˆ˜ ์—†๋Š” ์ƒํ’ˆ์ž…๋‹ˆ๋‹ค.
์˜ˆ์ƒ๋œ ์˜ˆ์™ธ ๋ฐœ์ƒ: ์‚ญ์ œํ•  ์ˆ˜ ์—†๋Š” ์ƒํ’ˆ์ž…๋‹ˆ๋‹ค.

=== AOP ์ข…ํ•ฉ ํ…Œ์ŠคํŠธ ์™„๋ฃŒ ===

๐Ÿ’ธ @Transactional์ด ์•ˆ ๊ฑธ๋ฆฌ๋Š” 4๊ฐ€์ง€ ์ผ€์ด์Šค

๐Ÿค” Spring Boot vs ์ˆ˜๋™ AOP ์„ค์ • ์ฐจ์ด์ 

Spring Boot (์ž๋™ ์„ค์ •)

1
2
3
4
5
6
@SpringBootApplication  // ์ด๊ฒƒ๋งŒ์œผ๋กœ @Transactional ๋™์ž‘!
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

์ˆ˜๋™ AOP ์„ค์ • (Spring Boot ์•„๋‹ ๋•Œ)

1
2
3
4
5
6
@Configuration
@EnableTransactionManagement  // ์ˆ˜๋™์œผ๋กœ ์ถ”๊ฐ€ํ•ด์•ผ ํ•จ
@EnableAspectJAutoProxy
public class Config {
    // ํŠธ๋žœ์žญ์…˜ ๋งค๋‹ˆ์ € ๋“ฑ ์ถ”๊ฐ€ ์„ค์ • ํ•„์š”
}

๐Ÿ› ๏ธ ํ”„๋กœ์ ํŠธ ์„ค์ •

build.gradle

1
2
3
4
5
6
7
8
9
10
11
12
13
plugins {
    id 'java'
    id 'org.springframework.boot' version '3.5.4'
    id 'io.spring.dependency-management' version '1.1.7'
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    runtimeOnly 'com.h2database:h2'
    
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
}

application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
spring:
  datasource:
    url: jdbc:h2:mem:testdb
    driver-class-name: org.h2.Driver
  jpa:
    open-in-view: false
    show-sql: true
    hibernate:
      ddl-auto: create-drop
    
logging:
  level:
    org.springframework.transaction: DEBUG  # ํŠธ๋žœ์žญ์…˜ ๋กœ๊ทธ ํ™•์ธ

๐Ÿ“‹ ๊ธฐ๋ณธ ์ฝ”๋“œ

User ์—”ํ‹ฐํ‹ฐ

1
2
3
4
5
6
7
8
9
10
11
12
13
@Entity
@Getter @Setter @NoArgsConstructor
public class User {
    @Id @GeneratedValue
    private Long id;
    private String name;
    private String email;
    
    public User(String name, String email) {
        this.name = name;
        this.email = email;
    }
}

UserRepository

1
2
3
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}

โŒ ์ผ€์ด์Šค 1: ํ”„๋ก์‹œ ์™ธ๋ถ€ ํ˜ธ์ถœ (Self-Invocation)

๋ฌธ์ œ๊ฐ€ ๋˜๋Š” ์ฝ”๋“œ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Slf4j
@RequiredArgsConstructor
@Service
public class Case1Service {

    private final UserRepository userRepository;

    public void selfInvocationExample() {
        this.saveUserInternal("user1", "user1@test.com");
    }

    @Transactional
    public void saveUserInternal(String name, String email) {
        User user = new User(name, email);
        User savedUser= userRepository.save(user);
        savedUser.setName("Case1Service");
    }
}

โŒ ์ผ€์ด์Šค 2: Private ๋ฉ”์„œ๋“œ

๋ฌธ์ œ๊ฐ€ ๋˜๋Š” ์ฝ”๋“œ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Service
@RequiredArgsConstructor
@Slf4j
public class Case2Service {

    private final UserRepository userRepository;

    public void privateMethodExample() {
        this.updateUserPrivate();
        throw new RuntimeException("private Exception");
    }

    @Transactional  // โŒ private ๋ฉ”์„œ๋“œ๋Š” ํ”„๋ก์‹œ ๋ถˆ๊ฐ€!
    private void updateUserPrivate() {
        User newUser = new User("๋กค๋ฐฑ๋˜์•ผํ•  ์œ ์ €", "temp@test.com");
        User savedUser= userRepository.save(newUser);
        savedUser.setName("Private");
    }
}

๐Ÿคทโ€โ™‚๏ธ ์™œ ์•ˆ๋˜๋‚˜์š”?

  • ํ”„๋ก์‹œ๋Š” public ๋ฉ”์„œ๋“œ๋งŒ ๊ฐ€๋กœ์ฑŒ ์ˆ˜ ์žˆ์Œ
  • private ๋ฉ”์„œ๋“œ๋Š” ์™ธ๋ถ€์—์„œ ์ ‘๊ทผ ๋ถˆ๊ฐ€ํ•˜๋ฏ€๋กœ ํ”„๋ก์‹œ ์ƒ์„ฑ ๋ถˆ๊ฐ€

โŒ ์ผ€์ด์Šค 3: Checked Exception

๋ฌธ์ œ๊ฐ€ ๋˜๋Š” ์ฝ”๋“œ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Slf4j
@RequiredArgsConstructor
@Service
public class Case3Service {

    private final UserRepository userRepository;

    @Transactional
    public void checkedExceptionExample() throws Exception {
        User newUser = new User("๋กค๋ฐฑ๋˜์•ผํ•  ์œ ์ €", "temp@test.com");
        userRepository.save(newUser);
        throw new Exception("์กธ๋ฆฌ๋‹ค.");
    }
}

๐Ÿคทโ€โ™‚๏ธ ์™œ ์•ˆ๋˜๋‚˜์š”?

  • Spring ๊ธฐ๋ณธ ์„ค์ •: RuntimeException๋งŒ ๋กค๋ฐฑ
  • Checked Exception(Exception, IOException ๋“ฑ)์€ ๋กค๋ฐฑ ์•ˆ๋จ
  • ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์—์„œ ๋ณต๊ตฌ ๊ฐ€๋Šฅํ•œ ์˜ˆ์™ธ๋กœ ๊ฐ„์ฃผ

โŒ ์ผ€์ด์Šค 4: ํ”„๋ก์‹œ ๋‚ด๋ถ€์—์„œ ์ž๊ธฐ ์ž์‹  ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ

๋ฌธ์ œ๊ฐ€ ๋˜๋Š” ์ฝ”๋“œ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Service
@RequiredArgsConstructor
@Slf4j
public class Case4Service {

    private final UserRepository userRepository;

    @Transactional
    public void createMultipleUsers() {
        createSingleUser("ํ™๊ธธ๋™", "hong@test.com");
        createSingleUser("๊น€์ฒ ์ˆ˜", "invalid-email");
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void createSingleUser(String name, String email) throws RuntimeException {
        if (!email.contains("@")) {
            throw new RuntimeException("์ž˜๋ชป๋œ ์ด๋ฉ”์ผ");
        }

        User user = new User(name, email);
        userRepository.save(user);
    }
}

๐Ÿคทโ€โ™‚๏ธ ์™œ ์•ˆ๋˜๋‚˜์š”?

  • createSingleUser() ํ˜ธ์ถœ์ด ํ”„๋ก์‹œ๋ฅผ ๊ฑฐ์น˜์ง€ ์•Š์Œ
  • ๋‚ด๋ถ€์ ์œผ๋กœ๋Š” ๋ชจ๋‘ ๊ฐ™์€ ํŠธ๋žœ์žญ์…˜์—์„œ ์‹คํ–‰๋จ
  • ๊ฐœ๋ณ„ ๋ฉ”์„œ๋“œ์˜ @Transactional ์„ค์ •์ด ๋ฌด์‹œ๋จ

โœ… ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

๋ฐฉ๋ฒ• 1: ๋ณ„๋„ ์„œ๋น„์Šค ๋ถ„๋ฆฌ

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
@Service
public class UserService {
    @Autowired
    private UserCreationService userCreationService;
    
    @Transactional
    public void createMultipleUsers() {
        userCreationService.createSingleUser("ํ™๊ธธ๋™", "hong@test.com");
        userCreationService.createSingleUser("๊น€์ฒ ์ˆ˜", "kim@test.com");
    }
}

@Service
public class UserCreationService {
    @Autowired
    private UserRepository userRepository;
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)  // ์ƒˆ ํŠธ๋žœ์žญ์…˜
    public void createSingleUser(String name, String email) {
        if (!email.contains("@")) {
            throw new RuntimeException("์ž˜๋ชป๋œ ์ด๋ฉ”์ผ");
        }
        
        User user = new User(name, email);
        userRepository.save(user);
    }
}

๐Ÿ“Š ์ •๋ฆฌ

์ผ€์ด์Šค ๋ฌธ์ œ์  ํ•ด๊ฒฐ๋ฐฉ๋ฒ•
Self-Invocation ๊ฐ™์€ ํด๋ž˜์Šค ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ๋ณ„๋„ ์„œ๋น„์Šค๋กœ ๋ถ„๋ฆฌ
Private ๋ฉ”์„œ๋“œ ํ”„๋ก์‹œ ์ƒ์„ฑ ๋ถˆ๊ฐ€ public์œผ๋กœ ๋ณ€๊ฒฝ
Checked Exception ๊ธฐ๋ณธ์ ์œผ๋กœ ๋กค๋ฐฑ ์•ˆ๋จ rollbackFor ๋ช…์‹œ
๋‚ด๋ถ€ ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ํ”„๋ก์‹œ ์šฐํšŒ ์„œ๋น„์Šค ๋ถ„๋ฆฌ + ์ „ํŒŒ ์„ค์ •

๐ŸŽฏ ํ•ต์‹ฌ ํฌ์ธํŠธ

  1. Spring Boot๋Š” ์ž๋™์œผ๋กœ @Transactional ์ง€์› - ๋ณ„๋„ AOP ์„ค์ • ๋ถˆํ•„์š”
  2. ํ”„๋ก์‹œ ๊ธฐ๋ฐ˜์ด๋ฏ€๋กœ ์™ธ๋ถ€์—์„œ ํ˜ธ์ถœ๋˜์–ด์•ผ ํ•จ
  3. Public ๋ฉ”์„œ๋“œ๋งŒ ๊ฐ€๋Šฅ
  4. RuntimeException๋งŒ ๊ธฐ๋ณธ ๋กค๋ฐฑ
  5. ๋ณต์žกํ•œ ํŠธ๋žœ์žญ์…˜ ๋กœ์ง์€ ์„œ๋น„์Šค๋ฅผ ๋ถ„๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Œ

ํƒœ๊ทธ:

์นดํ…Œ๊ณ ๋ฆฌ:

์—…๋ฐ์ดํŠธ:

๋Œ“๊ธ€๋‚จ๊ธฐ๊ธฐ