기본 콘텐츠로 건너뛰기

개발 공부 - @Transactional, @Propagation

 @Transactional 어노테이션을 붙이면, 해당 메소드/클래스의 메소드가 트랜잭션 처리를 받을 수 있게 해준다.
일반적으로 @Transactional 어노테이션을 붙이지 않으면, 각각 메소드 호출이 자신만의 트랜잭션을 가지게 되어, 메소드 호출이 별도 트랜잭션에서 동작하게 된다.
따라서 만약에 여러 개 메소드를 호출하는 중에 오류가 발생하면, 발생한 지점 이후 작업은 롤백되지 않고, 이전에 정상 완료 된 작업들은 영향을 받지 않는다.

그런데 @Transactional 어노테이션을 붙이면, 해당 메소드나 클래스들의 메소드가 하나의 트랜잭션 내에서 동작하게 된다.
따라서, 여러 개의 메소드를 호출 하는 도중에 오류가 발생하면 해당 트랜잭션 전체가 롤백되어 모든 작업이 이전 상태로 복원된다.

동작 예시를 몇 개 들면 아래와 같다.

test1() -> testRepo1()
@Transactional 이 2개 다 적용 됨
test1()이 끝날 때 testRepo1()에 대한 것이 같이 동작함
만약 testRepo1()에 오류가 나면 test1()도 롤백됨

test2() -> testRepo2()
@Transactional이 testRepo2()에만 적용 됨
test2() 메소드에서 testRepo2()를 호출해도 testRepo2() 메소드가 별도 트랜잭션임
testRepo2()에서 예외가 발생해도 test2() 메소드는 롤백되지 않음

 test3() -> testRepo3()
@Transactional이 test3()에만 적용 됨
test3()에서 testRepo3()을 호출해도, testRepo3() 메소드는 새로운 트랜잭션 없이 실행됨
testRepo3() 을 호출한 뒤에 오류가 발생하면, test3() 의 트랜잭션 안에서 testRepo3()이 호출되었기 때문에 롤백됨.


test4() -> testRepo4()
@Transactional 이 2개 다 없음
두 메소드 서로 트랜잭션 없이 실행됨
예외가 발생 시에는 해당 메소드 내에서만 롤백됨



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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
 
@Service
public class TestService {
 
    @Autowired
    private TestRepository testRepository;
 
    @Transactional
    // test1 메소드는 트랜잭션 내에서 testRepo1을 호출한다.
    public boolean test1() {
        testRepo1(); // 트랜잭션 내에서 메소드 호출한다.
        return true// 메소드 종료
    }
 
    @Transactional
    // testRepo1 메소드는 트랜잭션 내에서 데이터베이스 작업을 수행한다.
    public boolean testRepo1() {
        TestEntity entity = new TestEntity();
        entity.setData("Data for testRepo1");
        testRepository.save(entity); // 데이터베이스에 저장
 
        // 오류가 발생하지 않으면 롤백 없이 메소드가 종료된다.

        // 트랜잭션 내에서 발생한 오류는 롤백된다
        if (true) {
            throw new RuntimeException("Error occurred in testRepo1"); // 예외 발생
        }
 
        return true// 메소드 종료 (실행되지 않음)
    }
 
    // test2 메소드는 트랜잭션 없이 testRepo2를 호출한다.
    public boolean test2() {
        testRepo2(); // 트랜잭션이 없으므로 즉시 메소드 호출
        return true// 메소드 종료
    }
 
    @Transactional
    // testRepo2 메소드는 트랜잭션 내에서 데이터베이스 작업을 수행한다.
    public boolean testRepo2() {
        TestEntity entity = new TestEntity();
        entity.setData("Data for testRepo2");
        testRepository.save(entity); // 데이터베이스에 저장
        return true// 메소드 종료
    }
 
    @Transactional
    // test3 메소드는 트랜잭션 내에서 testRepo3을 호출한다.
    public boolean test3() {
        testRepo3(); // 트랜잭션 내에서 메소드 호출
        return true// 메소드 종료
    }
 
    // testRepo3 메소드는 트랜잭션 내에서 데이터베이스 작업을 수행한다.
    public boolean testRepo3() {
        TestEntity entity = new TestEntity();
        entity.setData("Data for testRepo3");
        testRepository.save(entity); // 데이터베이스에 저장
        return true// 메소드 종료
    }
 
    // test4 메소드는 트랜잭션 없이 testRepo4를 호출한다.
    public boolean test4() {
        testRepo4(); // 트랜잭션이 없으므로 즉시 메소드 호출
        return true// 메소드 종료
    }
 
    // testRepo4 메소드는 트랜잭션 밖에서 데이터베이스 작업을 수행한다.
    public boolean testRepo4() {
        TestEntity entity = new TestEntity();
        entity.setData("Data for testRepo4");
        testRepository.save(entity); // 데이터베이스에 저장
        return true// 메소드 종료
    }
}
 
cs



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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
 
@Service
public class TestService {
 
    @Autowired
    private TestRepository testRepository;
 
    // 예외 발생 여부에 따라 트랜잭션 롤백 후 결과 반환
    @Transactional
    public boolean test1() throws Exception {
        try {
            testRepo1();
            // 예외가 발생하지 않으면 성공적으로 실행됨
            return true;
        } catch (Exception e) {
            // 예외 발생 시 트랜잭션 롤백 후 실패 결과 반환
            return false;
        }
    }
 
    // 트랜잭션 내에서 데이터베이스 작업을 수행하고 예외 발생
    @Transactional
    public void testRepo1() throws Exception {
        TestEntity entity = new TestEntity();
        entity.setData("Data for testRepo1");
        testRepository.save(entity);
 
        // 특정 조건에서 의도적으로 예외 발생
        if (true) {
            throw new RuntimeException("Error occurred in testRepo1");
        }
    }
 
    // 예외 발생 여부에 따라 결과 반환
    public boolean test2() throws Exception {
        try {
            testRepo2();
            // 예외가 발생하지 않으면 성공적으로 실행됨
            return true;
        } catch (Exception e) {
            // 예외 발생 시 실패 결과 반환
            return false;
        }
    }
 
    // 트랜잭션 내에서 데이터베이스 작업을 수행하고 예외 발생하지 않음
    @Transactional
    public void testRepo2() throws Exception {
        TestEntity entity = new TestEntity();
        entity.setData("Data for testRepo2");
        testRepository.save(entity);
    }
 
    // 예외 발생 여부에 따라 결과 반환
    public boolean test3() throws Exception {
        try {
            testRepo3();
            // 예외가 발생하지 않으면 성공적으로 실행됨
            return true;
        } catch (Exception e) {
            // 예외 발생 시 실패 결과 반환
            return false;
        }
    }
 
    // 트랜잭션 외부에서 데이터베이스 작업을 수행하고 예외 발생하지 않음
    public void testRepo3() throws Exception {
        TestEntity entity = new TestEntity();
        entity.setData("Data for testRepo3");
        testRepository.save(entity);
    }
 
    // 예외 발생 여부에 따라 결과 반환
    public boolean test4() throws Exception {
        try {
            testRepo4();
            // 예외가 발생하지 않으면 성공적으로 실행됨
            return true;
        } catch (Exception e) {
            // 예외 발생 시 실패 결과 반환
            return false;
        }
    }
 
    // 트랜잭션 외부에서 데이터베이스 작업을 수행하고 예외 발생하지 않음
    public void testRepo4() throws Exception {
        TestEntity entity = new TestEntity();
        entity.setData("Data for testRepo4");
        testRepository.save(entity);
    }
}

cs



이럴 때, @Propagation 개념도 필요한데, 이것은 메소드가 이미 실행 중인 트랜잭션 내에서 실행되어야 하는지, 아니면 새로운 트랜잭션 내에서 실행되어야 하는지를 결정할 때 사용한다.

아래와 같은 것을 주로 사용하는데, 

1. REQUIRED: 현재 실행 중인 트랜잭션이 있으면 해당 트랜잭션에 참여하고, 없으면 새로운 트랜잭션을 시작한다. 이는 가장 일반적인 옵션으로, 메소드가 항상 트랜잭션 내에서 실행되도록 보장한다.

2. REQUIRES_NEW: 항상 새로운 트랜잭션을 시작한다. 현재 실행 중인 트랜잭션이 있으면 일시 중단되고, 새로운 트랜잭션으로 대체한다. 이 옵션은 메소드 실행을 완전히 독립적인 트랜잭션으로 만들어 준다.

3. SUPPORTS: 현재 실행 중인 트랜잭션이 있으면 해당 트랜잭션에 참여하고, 없으면 트랜잭션 없이 실행된다(!). 따라서 메소드가 트랜잭션 내에서 실행되어도 되지만, 트랜잭션 없이 실행되어도 괜찮은 경우에 사용한다.

4.MANDATORY: 현재 실행 중인 트랜잭션이 있어야 한다. 이 옵션을 사용하는 메소드는 반드시 이미 실행 중인 트랜잭션 내에서 호출되어야 한다. 그렇지 않으면 예외가 발생합니다.

5. NESTED: 현재 실행 중인 트랜잭션이 있으면 중첩된 트랜잭션 내에서 실행되고, 없으면 새로운 트랜잭션을 시작한다. 중첩된 트랜잭션은 외부 트랜잭션과 별개로 커밋 또는 롤백될 수 있다. 이것은 주로 JDBC 드라이버가 지원하는 트랜잭션 관리 방식에 사용한다.

6. NOT_SUPPORTED: 현재 실행 중인 트랜잭션이 있으면 일시 중단되고, 트랜잭션 없이 실행된다. 즉, 메소드가 언제나 트랜잭션 밖에서 실행된다.

7. NEVER: 현재 실행 중인 트랜잭션이 있으면 안된다. 만약 트랜잭션이 이미 실행 중이라면 예외가 발생한다. 이것은 메소드가 절대로 트랜잭션 내에서 실행되지 않아야 하는 경우에 사용된다.



1. REQUIRED vs REQUIRES_NEW:
   - REQUIRED: 이미 실행 중인 트랜잭션이 있으면 해당 트랜잭션에 참여하고, 없으면 새로운 트랜잭션을 시작한다. 따라서 메소드 호출이 항상 트랜잭션 내에서 실행된다. 이는 메소드가 항상 트랜잭션 내에서 실행되도록 보장한다.
   - REQUIRES_NEW: 항상 새로운 트랜잭션을 시작한다. 현재 실행 중인 트랜잭션이 있으면 일시 중단되고, 새로운 트랜잭션으로 대체된다. 이 옵션은 메소드 실행을 완전히 독립적인 트랜잭션으로 만들어 준다.

2. MANDATORY vs NEVER:
   - MANDATORY: 현재 실행 중인 트랜잭션이 있어야 한다. 이 옵션을 사용하는 메소드는 반드시 이미 실행 중인 트랜잭션 내에서 호출되어야 한다. 그렇지 않으면 예외가 발생한다.
   - NEVER: 현재 실행 중인 트랜잭션이 있으면 안 된다. 만약 트랜잭션이 이미 실행 중이라면 예외가 발생한다. 이것은 메소드가 절대로 트랜잭션 내에서 실행되지 않아야 하는 경우에 사용된다.

3. SUPPORTS vs NOT_SUPPORTED:
   - SUPPORTS: 현재 실행 중인 트랜잭션이 있으면 해당 트랜잭션에 참여하고, 없으면 트랜잭션 없이 실행된다. 따라서 메소드가 트랜잭션 내에서 실행되어도 되지만, 트랜잭션 없이 실행되어도 괜찮은 경우에 사용된다.
   - NOT_SUPPORTED: 현재 실행 중인 트랜잭션이 있으면 일시 중단되고, 트랜잭션 없이 실행된다. 즉, 메소드가 언제나 트랜잭션 밖에서 실행된다.

4. NESTED:
   - NESTED: 현재 실행 중인 트랜잭션이 있으면 중첩된 트랜잭션 내에서 실행되고, 없으면 새로운 트랜잭션을 시작한다. 중첩된 트랜잭션은 외부 트랜잭션과 별개로 커밋 또는 롤백될 수 있습니다. 이것은 주로 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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
 
@Service
public class TransactionExampleService {
 
    @Autowired
    private TestRepository testRepository;
 
    @Transactional(propagation = Propagation.REQUIRED)
    public void methodA() {
        System.out.println("Inside methodA");
        testRepo1();
    }
 
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void methodB() {
        System.out.println("Inside methodB");
        testRepo1();
    }
 
    @Transactional(propagation = Propagation.SUPPORTS)
    public void methodC() {
        System.out.println("Inside methodC");
        testRepo1();
    }
 
    @Transactional(propagation = Propagation.MANDATORY)
    public void methodD() {
        System.out.println("Inside methodD");
        testRepo1();
    }
 
    @Transactional(propagation = Propagation.NESTED)
    public void methodE() {
        System.out.println("Inside methodE");
        testRepo1();
    }
 
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void methodF() {
        System.out.println("Inside methodF");
        testRepo1();
    }
 
    @Transactional(propagation = Propagation.NEVER)
    public void methodG() {
        System.out.println("Inside methodG");
        testRepo1();
    }
 
    @Transactional(propagation = Propagation.REQUIRED)
    public void testRepo1() {
        System.out.println("Inside testRepo1");
        TestEntity entity = new TestEntity();
        entity.setData("Data for testRepo1");
        testRepository.save(entity);
        if (true) {
            throw new RuntimeException("Error occurred in testRepo1");
        }
    }
}
 
@SpringBootApplication
public class DemoApplication implements CommandLineRunner {
 
    @Autowired
    private TransactionExampleService transactionExampleService;
 
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
 
    @Override
    public void run(String... args) throws Exception {
        try {
            transactionExampleService.methodA();
        } catch (Exception e) {
            System.out.println("Exception caught in methodA: " + e.getMessage());
        }
        try {
            transactionExampleService.methodB();
        } catch (Exception e) {
            System.out.println("Exception caught in methodB: " + e.getMessage());
        }
        try {
            transactionExampleService.methodC();
        } catch (Exception e) {
            System.out.println("Exception caught in methodC: " + e.getMessage());
        }
        try {
            transactionExampleService.methodD();
        } catch (Exception e) {
            System.out.println("Exception caught in methodD: " + e.getMessage());
        }
        try {
            transactionExampleService.methodE();
        } catch (Exception e) {
            System.out.println("Exception caught in methodE: " + e.getMessage());
        }
        try {
            transactionExampleService.methodF();
        } catch (Exception e) {
            System.out.println("Exception caught in methodF: " + e.getMessage());
        }
        try {
            transactionExampleService.methodG();
        } catch (Exception e) {
            System.out.println("Exception caught in methodG: " + e.getMessage());
        }
    }
}
 
 
cs


1. methodA() - @Transactional(propagation = Propagation.REQUIRED):
   - 메소드 A 내에서 testRepo1()이 호출된다. 메소드 A는 트랜잭션을 시작한다. testRepo1()에서 예외가 발생하면, 메소드 A의 트랜잭션이 롤백된다. 따라서 methodA() 전체가 롤백되며, 데이터베이스에 저장된 내용은 영향을 받지 않는다.
: 항상 트랜잭션 내에서 실행해야 한다.

2. methodB() - @Transactional(propagation = Propagation.REQUIRES_NEW):
   - 메소드 B 내에서 testRepo1()이 호출된다. 메소드 B는 새로운 트랜잭션을 시작한다. testRepo1()에서 예외가 발생하면, 메소드 B의 트랜잭션만 롤백된다. 따라서 methodB()의 호출은 롤백되고, 이전에 저장된 데이터는 영향을 받지 않는다.
: 실행 중인 것이 있으면 새 것으로 대체한다.

3. methodC() - @Transactional(propagation = Propagation.SUPPORTS):
   - 메소드 C 내에서 testRepo1()이 호출된다. 메소드 C는 트랜잭션을 시작하지 않고, 현재 트랜잭션이 존재하면 그 트랜잭션에 참여한다. testRepo1()에서 예외가 발생하면, 현재 트랜잭션이 없으므로 예외가 그대로 전파된다.
: 있으면 참여하고 없으면 없는대로 실행한다.

4. methodD() - @Transactional(propagation = Propagation.MANDATORY):
   - 메소드 D 내에서 testRepo1()이 호출된다. 메소드 D는 현재 트랜잭션이 존재하는지 확인하고, 없으면 예외를 던집니다. testRepo1()에서 예외가 발생하면, 현재 트랜잭션이 없으므로 예외가 그대로 전파된다.
: 반드시 실행중인 트랜잭션이 있어야 한다.

5. methodE() - @Transactional(propagation = Propagation.NESTED):
   - 메소드 E 내에서 testRepo1()이 호출된다. 메소드 E는 현재 트랜잭션이 존재하면 중첩 트랜잭션을 시작한다. testRepo1()에서 예외가 발생하면, 메소드 E의 중첩 트랜잭션이 롤백되고, 메소드 E의 부모 트랜잭션은 영향을 받지 않는다.
: 있으면 중첩된     트랜잭션 내에서 실행하고, 없으면 새로운 것으로 실행한다.

6. methodF() - @Transactional(propagation = Propagation.NOT_SUPPORTED):
   - 메소드 F 내에서 testRepo1()이 호출된다. 메소드 F는 트랜잭션을 지원하지 않으므로, 현재 실행 중인 트랜잭션이 일시 중단된다. testRepo1()에서 예외가 발생하면, 현재 트랜잭션이 없으므로 예외가 그대로 전파된다.
: 있으면 중단하고, 없이 실행한다.

7. methodG() - @Transactional(propagation = Propagation.NEVER):
   - 메소드 G 내에서 testRepo1()이 호출된다. 메소드 G는 트랜잭션을 허용하지 않는다. 만약 현재 실행 중인 트랜잭션이 있다면, 예외가 발생한다. testRepo1()에서 예외가 발생하면, 현재 트랜잭션이 없으므로 예외가 그대로 전파된다.
: 있으면 안되니까 없을 때만 실행한다.










참고 : 
https://n1tjrgns.tistory.com/266
https://oingdaddy.tistory.com/28
https://mangkyu.tistory.com/269






댓글

이 블로그의 인기 게시물

Ebook - 전자책 drm 상관 없이 pdf로 만들기

yes24와 교보문고에서 ebook을 구매 해야 했는데 너무 불편하고, 필기가 매우 화날 정도로 안 좋아서 원시적으로 사용하기로 했다. 1. 목적 : ebook에서 필기 및 사용이 불편하여 pdf로 변환  2. 용도 : 개인 사용 목적이며 화질이 다소 저하되어도 필기만 용이하면 상관 없음 3. 방법 1) 휴대폰 및 카메라로 동영상을 촬영했다. DRM 때문에 프로그램으로는 촬영이 안 되는 것을 확인했다. (사실 개인 사용 목적이면 기본 화면 캡쳐를 사용해도 된다...) 2) 마우스 클릭 해주는 매크로를 사용했다. (1) key_macro.exe > https://blog.daum.net/pg365/250 듀얼 모니터에서 위치 이탈 현상이 있긴 해도 괜찮았다. (2) AutoClick.exe > http://bestsoftwarecenter.blogspot.com/2011/02/autoclick-22.html 이 걸로 잘 사용했다. 3초마다 한 번 클릭하도록 사용했다. 3) 동영상을 이미지로 변경해주는 프로그램을 사용했다. Free Video to JPG Converter > https://www.dvdvideosoft.com/products/dvd/Free-Video-to-JPG-Converter.htm (240826: 다운로드 시 정상적으로 되지 않아서 URL 수정) 일 하면서 듀얼 모니터에 켜 놨는데 속도가 괜찮았다. * Every frame 으로 사용해야 한다. 4) 중복 사진 제거해주는 프로그램을 사용했다. VlsiPics  > http://www.visipics.info/index.php?title=Main_Page 생각보다 느리니 퇴근시에 걸어놓고 가면 된다. 한번 play가 끝나면 Auto-select 하고 Delete 하면 된다. 5) 이미지를 일괄 Crop 작업 해주는 프로그램을 사용했다. JPEGCrops > https://jpegcrops.softonic.kr/ * https://joker1209.tistory.co

개발 공부 - json JSONObject 사용 시 백슬래시(\), 원화 표시(\) 제거 및 치환

import org.json.simple.JSONObject; String dataString = new String(authData.toJSONString()); dataString = dataString.replaceAll("\\\\", ""); String 으로 안 바뀌는 가 싶어서 String 으로 변환 해 주고 작업 하였다. 사실 toJSONString 해도 정상 동작 해야 하는데 이유를 잘 모르겠음. 그리고 나서 다시 이클립스 구동 하니 toString 도 먹은 걸로 봐서 이상하다고 생각! String dataString = authData.toString(); dataString = dataString.replaceAll("\\\\", ""); 어쨌든 백 슬래시 제거를 해줘야 하는데 \\ 도 아니고 \\\\를 해야 변환이 가능했다는 결말이었습니다. 참고 : https://stackoverflow.com/questions/15450519/why-does-string-replace-not-work/15450539 test =test.replace("KP", "");  replace 후에 담아 주지 않으면 적용이 안 됩니다!

개발 공부 - OracleXETNSListener 서비스가 로컬 컴퓨터에서 시작했다가 중지되었습니다.

여러 가지 요인이 있지만 PC 이름 변경시 OracleXETNSListener 서비스 시작이 불가능합니다. 고치는 법은 C:\oraclexe\app\oracle\product\11.2.0\server\network\ADMIN 와 같은 설치 경로에서 listener.ora와 tnsnames.ora 의 pc명을 바꾼 PC명으로 바꿔주면 됩니다. 그래도 안 된다면 cmd 창에서 services.msc 를 입력 후 OracleXETNSListener 서비스를 시작 시키면 됩니다. 오류명: OracleXETNSListener 서비스가 로컬 컴퓨터에서 시작했다가 중지되었습니다. 일부 서비스는 다른 서비스 또는 프로그램에서 사용되지 않으면 자동으로 중지됩니다. 참고한 사이트들 1. http://blog.naver.com/visioner7/120165951652 2. http://database.sarang.net/?inc=read&aid=6819&criteria=oracle&subcrit=&id=&limit=20&keyword=ora-12560&page=5 이런 걸 보면 오라클은 앙칼진 시골 아가씨야