Mam 2 usługi: RecentRecordService
i BookService
.
@Service
public class RecentRecordService {
@Transactional
public List<Book> getRecentReadBooks() {
List<Long> recentReadBookIds = getRecentReadBookIds();
List<Book> recentReadBooks = new ArrayList<>();
for (Long bookId : recentReadBookIds) {
try {
Book book = bookService.getBook(bookId);
recentReadBooks.add(book);
} catch (AccessDeniedException e) {
// skip
}
}
return recentReadBooks;
}
}
@Service
public class BookService {
@Transactional
public Book getBook(Long bookId) {
Book book = bookDao.get(bookId);
if (!hasReadPermission(book)) {
throw new AccessDeniedException(); // spring-security exception
}
return book;
}
}
Załóżmy, że getRecentReadBookIds()
zwraca [1, 2, 3]
.
Gdy użytkownik sesji ma uprawnienia do wszystkich identyfikatorów książek, które zwróciły z getRecentReadBookIds()
, wszystko działa prawidłowo. getRecentReadBooks()
zwróci listę 3 Book
s.
Nagle właściciel książki nr 2 zmienił ustawienie jej uprawnień z „Publicznego” na „Prywatne”. Dlatego użytkownik sesji nie może już czytać książki nr 2.
Spodziewałem się, że getRecentReadBooks()
zwróci listę 2 Book
s, która zawiera informacje o książce nr 1 i książce nr 3. Jednak metoda nie powiodła się z następującym wyjątkiem:
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
Po kilku badaniach stwierdziłem, że ma to coś wspólnego z propagacją transakcji. Transakcja zostanie oznaczona jako „tylko wycofywanie”, nawet jeśli użyję try-catch do obsługi AccessDeniedException
w getRecentReadBooks()
.
Wydaje się, że ten problem można rozwiązać, zmieniając adnotację @Transactional
dla getBook()
na:
@Transactional(propagation = Propagation.NESTED)
public Book getBook(Long bookId) {
// ...
}
Lub
@Transactional(noRollbackFor = AccessDeniedException.class)
public Book getBook(Long bookId) {
// ...
}
Jednak zastanawiałem się, czy mogę rozwiązać problem, modyfikując tylko RecentRecordService
. W końcu RecentRecordService
jest tym, który chce samodzielnie poradzić sobie z AccessDeniedException
.
Każda sugestia pomoże. Dzięki.
2 odpowiedzi
Cytat z dokumentacji
Domyślnie transakcja zostanie wycofana w przypadku wyjątków RuntimeException i Error, ale nie w przypadku zaznaczonych wyjątków (wyjątków biznesowych)
AccessDeniedException jest podklasą RuntimeException
Jeśli w twoim przypadku użyty wyjątek AccessDeniedException to ramy wiosenne i zgodnie z wymaganiem, jeśli wyjątek dotyczący odmowy dostępu może być niestandardowym wyjątkiem biznesowym (a nie podklasą wyjątku środowiska wykonawczego), powinien działać bez adnotacji metody getBook (Long bookId) .
Aktualizuj
OP zdecydował się naprawić główną przyczynę. Ma to na celu uzasadnienie mojej odpowiedzi na wszelkie przyszłe odniesienia
AccessDeniedException: zgłaszany, jeśli obiekt uwierzytelniania nie ma wymaganych uprawnień.
Jak rozumiem, tutaj dostęp do książki opiera się na logice biznesowej, a nie na roli użytkownika. W takim scenariuszu odpowiedni byłby niestandardowy wyjątek biznesowy.
Nie sądzę, aby można było go rozwiązać, modyfikując tylko RecentRecordService
, ponieważ transakcja jest już oznaczona jako wycofanie w BookService
z powodu zdarzenia RuntimeException
w nim. A gdy TransactionStatus
zostanie oznaczony jako wycofywanie, nie ma możliwości przywrócenia go do stanu bez wycofywania.
Musisz więc sprawić, by BookService#getBook()
nie oznaczał transakcji jako wycofania przez:
@Transactional(noRollbackFor = Throwable.class)
public Book getBook(Long bookId) {
}
Co oznacza, że transakcja nie zostanie oznaczona jako wycofanie bez względu na wyjątek. Ma to sens, ponieważ ta metoda ma być tylko do odczytu, więc nie ma sensu wycofywać zmian, gdy metoda nie ma niczego modyfikować.
BookService#getBook()
był niezmodyfikowany, czy ma sens, jeśli napiszę dla niego klasę opakowującą, powiedz BookServiceWrapper#getBook()
i ustawię metodę opakowującą jako @Transactional(noRollbackFor = Throwable.class)
?
BookService#getBook()
, ale zachowaj BookService#getBook()
niezmieniony, ponieważ nadal będzie oznaczać transakcję jako wycofanie, jeśli wystąpi w niej wyjątek.
@Transactional(Propagation.NESTED)
?
Podobne pytania
Powiązane pytania
Nowe pytania
java
Java to język programowania wysokiego poziomu. Użyj tego tagu, jeśli masz problemy z używaniem lub zrozumieniem samego języka. Ten tag jest rzadko używany samodzielnie i jest najczęściej używany w połączeniu z [spring], [spring-boot], [jakarta-ee], [android], [javafx], [hadoop], [gradle] i [maven].