Skip to content

Commit a38ccfc

Browse files
committed
BAEL-8140: add missing files
1 parent bee0e91 commit a38ccfc

File tree

5 files changed

+216
-0
lines changed

5 files changed

+216
-0
lines changed
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: 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: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package com.baeldung.rollbackonly;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
5+
6+
import java.util.Optional;
7+
8+
import org.junit.jupiter.api.BeforeEach;
9+
import org.junit.jupiter.api.Test;
10+
import org.springframework.beans.factory.annotation.Autowired;
11+
import org.springframework.boot.test.context.SpringBootTest;
12+
import org.springframework.transaction.UnexpectedRollbackException;
13+
14+
import com.baeldung.rollbackonly.article.Article;
15+
import com.baeldung.rollbackonly.article.ArticleRepo;
16+
import com.baeldung.rollbackonly.audit.AuditRepo;
17+
18+
@SpringBootTest(classes = { Application.class })
19+
class BlogIntegrationTest {
20+
21+
@Autowired
22+
private Blog articleService;
23+
24+
@Autowired
25+
private ArticleRepo articleRepo;
26+
27+
@Autowired
28+
private AuditRepo auditRepo;
29+
30+
@BeforeEach
31+
void afterEach() {
32+
articleRepo.deleteAll();
33+
auditRepo.deleteAll();
34+
}
35+
36+
@Test
37+
void whenPublishingAnArticle_thenAlsoSaveSuccessAudit() {
38+
articleService.publishArticle(new Article("Test Article", "John Doe"));
39+
40+
assertThat(articleRepo.findAll())
41+
.extracting("title")
42+
.containsExactly("Test Article");
43+
44+
assertThat(auditRepo.findAll())
45+
.extracting("description")
46+
.containsExactly("saved: Test Article");
47+
}
48+
49+
@Test
50+
void whenPublishingAnInvalidArticle_thenThrowsUnexpectedRollbackException() {
51+
assertThatThrownBy(() -> articleService.publishArticle(
52+
new Article("Test Article", null)))
53+
.isInstanceOf(UnexpectedRollbackException.class)
54+
.hasMessageContaining("Transaction silently rolled back because it has been marked as rollback-only");
55+
56+
assertThat(auditRepo.findAll())
57+
.isEmpty();
58+
}
59+
60+
@Test
61+
void whenPublishingAnInvalidArticle_thenSavesFailureToAudit() {
62+
assertThatThrownBy(() -> articleService.publishArticle_v2(
63+
new Article("Test Article", null)))
64+
.isInstanceOf(Exception.class);
65+
66+
assertThat(auditRepo.findAll())
67+
.extracting("description")
68+
.containsExactly("failed to save: Test Article");
69+
}
70+
71+
@Test
72+
void whenPublishingAnInvalidArticle_thenRecoverFromError_andSavesFailureToAudit() {
73+
Optional<Long> id = articleService.publishArticle_v3(
74+
new Article("Test Article", null));
75+
76+
assertThat(id)
77+
.isEmpty();
78+
79+
assertThat(auditRepo.findAll())
80+
.extracting("description")
81+
.containsExactly("failed to save: Test Article");
82+
}
83+
}

0 commit comments

Comments
 (0)