Skip to content

Commit cfd57df

Browse files
authored
Merge pull request #18874 from etrandafir93/BAEL-8140_tx_rollback
BAEL-8140: rollback-only transactions
2 parents caadbbc + 34f3cc1 commit cfd57df

File tree

11 files changed

+374
-0
lines changed

11 files changed

+374
-0
lines changed

persistence-modules/spring-boot-persistence-5/pom.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@
8383
<instancio.version>5.2.1</instancio.version>
8484
<spring-boot.version>3.4.1</spring-boot.version>
8585
<db2.version>12.1.0.0</db2.version>
86+
<java.version>17</java.version>
87+
<maven.compiler.source>17</maven.compiler.source>
88+
<maven.compiler.target>17</maven.compiler.target>
8689
</properties>
8790

8891
</project>
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
@startuml Nested Transactions in publishArticle_v2
2+
3+
participant "Client" as C
4+
participant "Blog" as B
5+
participant "ArticleRepo" as AR
6+
participant "AuditService" as AS
7+
database "Database" as DB
8+
9+
C -> B: publishArticle_v2()
10+
activate B
11+
note right of B: @Transactional
12+
B -> DB: Begin TX1
13+
activate DB #LightBlue
14+
15+
B -> AR: save()
16+
activate AR
17+
AR -> DB: INSERT article
18+
AR --> B:
19+
deactivate AR
20+
21+
B -> AS: saveAudit()
22+
activate AS
23+
note right of AS: @Transactional\n(REQUIRES_NEW)
24+
AS -> DB: Begin TX2
25+
activate DB #LightGreen
26+
27+
AS -> DB: INSERT audit
28+
29+
AS -> DB: Commit TX2
30+
deactivate DB
31+
AS --> B:
32+
deactivate AS
33+
34+
B -> DB: Commit / Rollback TX1
35+
deactivate DB
36+
B --> C:
37+
deactivate B
38+
39+
note over DB
40+
TX1: Article save
41+
TX2: Audit save (independent, always commits)
42+
end note
43+
44+
@enduml
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
@startuml Sequential Transactions in publishArticle_v3
2+
3+
participant "Client" as C
4+
participant "Blog" as B
5+
participant "TransactionTemplate" as TT
6+
participant "ArticleRepo" as AR
7+
participant "AuditRepo" as AuR
8+
database "Database" as DB
9+
10+
C -> B: publishArticle_v3()
11+
activate B
12+
13+
B -> TT: execute()
14+
activate TT
15+
TT -> DB: Begin TX1
16+
activate DB #LightBlue
17+
18+
TT -> AR: save()
19+
activate AR
20+
AR -> DB: INSERT article
21+
AR --> TT
22+
deactivate AR
23+
24+
TT -> DB: Commit / Rollback TX1
25+
deactivate DB
26+
TT --> B
27+
deactivate TT
28+
29+
30+
B -> DB: Begin TX2
31+
activate DB #LightGreen
32+
33+
B -> AuR: save()
34+
activate AuR
35+
AuR -> DB: INSERT audit (FAILURE)
36+
AuR --> B:
37+
deactivate AuR
38+
39+
B -> DB: Commit TX2
40+
deactivate DB
41+
42+
B --> C:
43+
deactivate B
44+
45+
note over DB
46+
TX1 completes (commit or rollback) BEFORE TX2 begins
47+
TX2: Independent transaction for FAILURE audit
48+
end note
49+
50+
@enduml
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.baeldung.rollbackonly;
2+
3+
import org.springframework.boot.SpringApplication;
4+
import org.springframework.boot.autoconfigure.SpringBootApplication;
5+
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
6+
7+
@SpringBootApplication
8+
@EnableJpaRepositories
9+
public class Application {
10+
11+
public static void main(String[] args) {
12+
SpringApplication.run(Application.class, args);
13+
}
14+
15+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package com.baeldung.rollbackonly;
2+
3+
import java.util.Optional;
4+
5+
import org.springframework.stereotype.Component;
6+
import org.springframework.transaction.annotation.Transactional;
7+
import org.springframework.transaction.support.TransactionTemplate;
8+
9+
import com.baeldung.rollbackonly.article.Article;
10+
import com.baeldung.rollbackonly.article.ArticleRepo;
11+
import com.baeldung.rollbackonly.audit.Audit;
12+
import com.baeldung.rollbackonly.audit.AuditRepo;
13+
import com.baeldung.rollbackonly.audit.AuditService;
14+
15+
@Component
16+
public class Blog {
17+
18+
private final ArticleRepo articleRepo;
19+
private final AuditRepo auditRepo;
20+
private final AuditService auditService;
21+
private final TransactionTemplate transactionTemplate;
22+
23+
Blog(ArticleRepo articleRepo, AuditRepo auditRepo, AuditService auditService, TransactionTemplate transactionTemplate) {
24+
this.articleRepo = articleRepo;
25+
this.auditRepo = auditRepo;
26+
this.auditService = auditService;
27+
this.transactionTemplate = transactionTemplate;
28+
}
29+
30+
@Transactional
31+
public Optional<Long> publishArticle(Article article) {
32+
try {
33+
article = articleRepo.save(article);
34+
auditRepo.save(new Audit("SAVE_ARTICLE", "SUCCESS", "saved: " + article.getTitle()));
35+
return Optional.of(article.getId());
36+
37+
} catch (Exception e) {
38+
String errMsg = "failed to save: %s, err: %s".formatted(article.getTitle(), e.getMessage());
39+
auditRepo.save(new Audit("SAVE_ARTICLE", "FAILURE", errMsg));
40+
return Optional.empty();
41+
}
42+
}
43+
44+
@Transactional
45+
public Optional<Long> publishArticle_v2(Article article) {
46+
try {
47+
article = articleRepo.save(article);
48+
auditService.saveAudit("SAVE_ARTICLE", "SUCCESS", "saved: " + article.getTitle());
49+
return Optional.of(article.getId());
50+
51+
} catch (Exception e) {
52+
auditService.saveAudit("SAVE_ARTICLE", "FAILURE", "failed to save: " + article.getTitle());
53+
return Optional.empty();
54+
}
55+
}
56+
57+
public Optional<Long> publishArticle_v3(final Article article) {
58+
try {
59+
Article savedArticle = transactionTemplate.execute(txStatus -> {
60+
Article saved = articleRepo.save(article);
61+
auditRepo.save(new Audit("SAVE_ARTICLE", "SUCCESS", "saved: " + article.getTitle()));
62+
return saved;
63+
});
64+
return Optional.of(savedArticle.getId());
65+
66+
} catch (Exception e) {
67+
auditRepo.save(
68+
new Audit("SAVE_ARTICLE", "FAILURE", "failed to save: " + article.getTitle()));
69+
return Optional.empty();
70+
}
71+
}
72+
73+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.baeldung.rollbackonly.article;
2+
3+
import jakarta.persistence.Column;
4+
import jakarta.persistence.Entity;
5+
import jakarta.persistence.GeneratedValue;
6+
import jakarta.persistence.GenerationType;
7+
import jakarta.persistence.Id;
8+
9+
import lombok.Data;
10+
import lombok.NoArgsConstructor;
11+
12+
@Data
13+
@Entity
14+
@NoArgsConstructor
15+
public class Article {
16+
17+
@Id
18+
@GeneratedValue(strategy = GenerationType.SEQUENCE)
19+
private Long id;
20+
21+
@Column(nullable = false)
22+
private String title;
23+
24+
@Column(nullable = false)
25+
private String author;
26+
27+
public Article(String title, String author) {
28+
this.title = title;
29+
this.author = author;
30+
}
31+
32+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.baeldung.rollbackonly.article;
2+
3+
import org.springframework.data.jpa.repository.JpaRepository;
4+
import org.springframework.stereotype.Repository;
5+
6+
7+
@Repository
8+
public interface ArticleRepo extends JpaRepository<Article, Long> {
9+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.baeldung.rollbackonly.audit;
2+
3+
import jakarta.persistence.Entity;
4+
import jakarta.persistence.GeneratedValue;
5+
import jakarta.persistence.GenerationType;
6+
import jakarta.persistence.Id;
7+
8+
import lombok.Data;
9+
import lombok.NoArgsConstructor;
10+
11+
import java.time.LocalDateTime;
12+
13+
@Data
14+
@NoArgsConstructor
15+
@Entity
16+
public class Audit {
17+
18+
@Id
19+
@GeneratedValue(strategy = GenerationType.SEQUENCE)
20+
private Long id;
21+
22+
private String operation;
23+
24+
private String status;
25+
26+
private String description;
27+
28+
private LocalDateTime timestamp;
29+
30+
public Audit(String operation, String status, String description) {
31+
this.operation = operation;
32+
this.status = status;
33+
this.description = description;
34+
this.timestamp = LocalDateTime.now();
35+
}
36+
37+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.baeldung.rollbackonly.audit;
2+
3+
import org.springframework.data.jpa.repository.JpaRepository;
4+
import org.springframework.stereotype.Repository;
5+
6+
@Repository
7+
public interface AuditRepo extends JpaRepository<Audit, Long> {
8+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.baeldung.rollbackonly.audit;
2+
3+
import org.springframework.stereotype.Component;
4+
import org.springframework.transaction.annotation.Propagation;
5+
import org.springframework.transaction.annotation.Transactional;
6+
7+
@Component
8+
public class AuditService {
9+
10+
private final AuditRepo auditRepo;
11+
12+
@Transactional(propagation = Propagation.REQUIRES_NEW)
13+
public void saveAudit(String action, String status, String message) {
14+
auditRepo.save(new Audit(action, status, message));
15+
}
16+
17+
public AuditService(AuditRepo auditRepo) {
18+
this.auditRepo = auditRepo;
19+
}
20+
}

0 commit comments

Comments
 (0)