То, что вы пытаетесь достичь, должно работать из коробки. Проверьте конфигурацию пружины.
Убедитесь, что вы создали TransactionManager
боб и убедитесь, что вы поместили аннотацию @EnableTransactionManagement
на некоторые из ваших пружин @Configuration
s. Эта аннотация отвечает за регистрацию необходимых компонентов Spring, которые обеспечивают управление транзакциями на основе аннотаций, таких как TransactionInterceptor
и рекомендации на основе прокси или AspectJ, которые вплетают перехватчик в стек вызовов при вызове методов @Transactional
.
См. Связанную документацию.
Если вы используете spring-boot
, он должен автоматически добавить эту аннотацию для вас, если у вас есть класс PlatformTransactionManager
на пути к классам.
Также обратите внимание, что отмеченные исключения не вызывают откат транзакции . Только исключения и ошибки времени выполнения вызывают откат. Разумеется, вы можете настроить это поведение с помощью параметров аннотаций rollbackFor
и noRollbackFor
.
Редактировать
Поскольку вы пояснили, что используете spring-boot, ответ таков: все должно работать без какой-либо конфигурации.
Вот минимальный 100% рабочий пример для весенней загрузки 2.1.3.RELEASE
(но должен работать с любой версией ofc):
Зависимость:
compile('org.springframework.boot:spring-boot-starter-data-jpa')
runtimeOnly('com.h2database:h2') // or any other SQL DB supported by Hibernate
compileOnly('org.projectlombok:lombok') // for getters, setters, toString
Пользователь:
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
@Getter
@Setter
@ToString
public class User {
@Id
@GeneratedValue
private Integer id;
private String name;
}
Книжная сущность:
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
@Entity
@Getter
@Setter
@ToString
public class Book {
@Id
@GeneratedValue
private Integer id;
@ManyToOne
private User author;
private String title;
}
Репозиторий пользователя:
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Integer> {
}
Хранилище книг:
import org.springframework.data.jpa.repository.JpaRepository;
public interface BookRepository extends JpaRepository<Book, Integer> {
}
Сервис пользователя:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Transactional
@Component
public class UserService {
@Autowired
private UserRepository userRepository;
public User saveUser(User user) {
// return userRepository.save(user);
userRepository.save(user);
throw new RuntimeException("User not saved");
}
public List<User> findAll() {
return userRepository.findAll();
}
}
Служба бронирования:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Transactional
@Component
public class BookService {
@Autowired
private BookRepository bookRepository;
public Book saveBook(Book book) {
return bookRepository.save(book);
}
public List<Book> findAll() {
return bookRepository.findAll();
}
}
Композитный сервис:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
@Transactional
@Component
public class CompositeService {
@Autowired
private UserService userService;
@Autowired
private BookService bookService;
public void saveUserAndBook() {
User user = new User();
user.setName("John Smith");
user = userService.saveUser(user);
Book book = new Book();
book.setAuthor(user);
book.setTitle("Mr Robot");
bookService.saveBook(book);
}
}
Main:
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class JpaMain {
public static void main(String[] args) {
new SpringApplicationBuilder(JpaMain.class)
.web(WebApplicationType.NONE)
.properties("logging.level.org.springframework.transaction=TRACE")
.run(args);
}
@Bean
public CommandLineRunner run(CompositeService compositeService, UserService userService, BookService bookService) {
return args -> {
try {
compositeService.saveUserAndBook();
} catch (RuntimeException e) {
System.err.println("Exception: " + e);
}
System.out.println("All users: " + userService.findAll());
System.out.println("All books: " + bookService.findAll());
};
}
}
Если вы запустите основной метод, вы должны увидеть, что в БД нет книг или пользователей. Сделка откатывается. Если вы удалите строку throw new RuntimeException("User not saved")
из UserService
, оба объекта будут сохранены в порядке.
Также вы должны увидеть журналы пакета org.springframework.transaction
на уровне TRACE
, где, например, вы увидите:
Getting transaction for [demo.jpa.CompositeService.saveUserAndBook]
А потом после исключения выдается:
Completing transaction for [demo.jpa.CompositeService.saveUserAndBook] after exception: java.lang.RuntimeException: User not saved
Applying rules to determine whether transaction should rollback on java.lang.RuntimeException: User not saved
Winning rollback rule is: null
No relevant rollback rule found: applying default rules
Clearing transaction synchronization
Здесь No relevant rollback rule found: applying default rules
означает, что правила, определенные DefaultTransactionAttribute
, будут применяться для определения необходимости отката транзакции. И эти правила:
Откат во время выполнения, но не проверено, исключения по умолчанию.
RuntimeException
- исключение времени выполнения, поэтому транзакция будет откатана.
В строке Clearing transaction synchronization
фактически применяется откат. Вы увидите некоторые другие Applying rules to determine whether transaction should rollback
сообщения, потому что здесь вложены @Transactional
методы (UserService.saveUser
вызывается из CompositeService.saveUserAndBook
и оба метода @Transactional
), но все, что они делают, это определяют правила для будущих действий (в момент синхронизация транзакций). Фактический откат будет выполнен только один раз, при выходе из метода @Transactional
.