如果在插入数据时发生任何错误,我想使用 JPA 在一个事务中保存两个实体,应该回滚更改
I want to save two entities in one transaction with JPA if any error occure while inserting the data the changes should be rolled back
例如我有两个实体:第一个是学生,另一个是地址。我想在两个具有外键关系的表上插入数据,但它们是一个条件:如果在任何表中插入数据时发生任何错误,所有更改都应回滚。
我该怎么做请帮忙
class Student{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String firstName;
@OneToOne(fetch = FetchType.EAGER)
@JoinColumn(name="address_id",referencedColumnName = "id")
private Address address;
}
class Address {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String city;
}
@Service
public class StudentService {
@Autowired
private StudentRepo studentRepo;
@Autowired
private AddressRepo addressRepo;
public Student createStudent(StudentRequest studentRequest) {
Student student=new Student(studentRequest);
Address address=new Address();
address.setCity(studentRequest.getCity());
address.setStreet(studentRequest.getStreet());
address=addressRepo.save(address);
student.setAddress(address);
student=studentRepo.save(student);
return student;
}
这是一个非常笼统的问题,因此我会尽力为您指出正确的方向。为此,我假设:
- 您正在使用
spring-data-jpa
- 您已经配置了一个数据源
- 您的应用程序正确启动
- 您已经配置了
@Repository
或者您知道如何通过 @PersistenceContext
使用 EntityManager
- 我将采用
Repository
解决方案
好消息是:使用 OneToOne
、OneToMany
、ManyToOne
和 ManyToMany
标记的持久对象是一项核心功能,因此其中大部分已经由 JPA/Hibernate 处理。您只需确保阅读文档并遵循文档。
第一步是理解Cascading
。默认情况下,级联 未 打开。网络上有大量资源可以帮助您理解这一点,我将向您指出 this one。这意味着您需要在 OneToOne
注释上设置 cascade
属性(参见参考教程)。
Student.java
class Student{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String firstName;
@OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@JoinColumn(name="address_id",referencedColumnName = "id")
private Address address;
}
然后,使用正常的 @Repository
:
public interface StudentRepository extends JpaRepository<Student, Long> {
}
现在您已经拥有正确工作的映射和存储库。
完成此操作后,您应该准备好开始了,前提是上述假设成立。您现在可以为学生编写一个小测试用例,例如
@DataJpaTest
class StudentRepositoryTest {
@Autowired
private StudentRepository studentRepository;
@Autowired
private AddressRepository addressRepository;
@BeforeEach
void setup() {
assumeThat(studentRepository).isNotNull();
assumeThat(addressRepository).isNotNull();
}
@Test
void testSaveStudent {
assumeThat(studentRepository.count()).isZero();
assumeThat(addressRepository.count()).isZero();
var student = new Student();
var address = new Address();
address.setCity("New York");
student.setFirstName("Max");
student.setAddress(address);
studentRepository.save(student);
assertThat(studentRepository.count()).isOne();
assertThat(addressRepository.count()).isOne();
}
第二:关于您的交易问题。默认情况下,当您调用存储库的 save
方法时,会启动一个事务。因此,在最基本的情况下,您不一定需要交易。不过,一旦您处理了更复杂的业务案例,就有必要了解 JPA 的事务管理器。事务通常发生在服务层。有关交易的更多信息,请参阅下面的链接。
@Service
@Transactional(readOnly = true)
public class StudentService {
@Autowired
private StudentRepository studentRepository;
@Transactional
public Student assignStudentToAddress(Student s, Address a) {
s.setAddress(a);
return studentRepository.save(s);
}
}
一些想法:
- 你确定一个学生只能住在一个地址吗?这意味着生活大爆炸(合租公寓)中的生活安排将行不通。出于测试目的,这当然有效。
- 考虑相反的情况:如果我们有一个地址屏幕并且我们想要将学生添加到其中怎么办?
更多资源:
- 映射新手指南by VladMihalcea
- 映射
OneToOne
初学者友好:by Baeldung and slightly more advanced by VladMihalcea
- 映射
OneToMany
初学者友好:by baeldung and slightly more advanced: by VladMihalcea
- 使用
Transactional
by VladMihalcea and by MarcoBehler
使用无状态 EJB:
@Stateless
public StudentDAO {
@PersistenceContext
private EntityManager em;
public Student createStudent(StudentRequest studentRequest) {
Student student=new Student(studentRequest);
Address address=new Address();
address.setCity(studentRequest.getCity());
address.setStreet(studentRequest.getStreet());
em.persist(address);
student.setAddress(address);
em.persist(student);
return student;
}
}
例如我有两个实体:第一个是学生,另一个是地址。我想在两个具有外键关系的表上插入数据,但它们是一个条件:如果在任何表中插入数据时发生任何错误,所有更改都应回滚。
我该怎么做请帮忙
class Student{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String firstName;
@OneToOne(fetch = FetchType.EAGER)
@JoinColumn(name="address_id",referencedColumnName = "id")
private Address address;
}
class Address {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String city;
}
@Service
public class StudentService {
@Autowired
private StudentRepo studentRepo;
@Autowired
private AddressRepo addressRepo;
public Student createStudent(StudentRequest studentRequest) {
Student student=new Student(studentRequest);
Address address=new Address();
address.setCity(studentRequest.getCity());
address.setStreet(studentRequest.getStreet());
address=addressRepo.save(address);
student.setAddress(address);
student=studentRepo.save(student);
return student;
}
这是一个非常笼统的问题,因此我会尽力为您指出正确的方向。为此,我假设:
- 您正在使用
spring-data-jpa
- 您已经配置了一个数据源
- 您的应用程序正确启动
- 您已经配置了
@Repository
或者您知道如何通过@PersistenceContext
使用 - 我将采用
Repository
解决方案
EntityManager
好消息是:使用 OneToOne
、OneToMany
、ManyToOne
和 ManyToMany
标记的持久对象是一项核心功能,因此其中大部分已经由 JPA/Hibernate 处理。您只需确保阅读文档并遵循文档。
第一步是理解Cascading
。默认情况下,级联 未 打开。网络上有大量资源可以帮助您理解这一点,我将向您指出 this one。这意味着您需要在 OneToOne
注释上设置 cascade
属性(参见参考教程)。
Student.java
class Student{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String firstName;
@OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@JoinColumn(name="address_id",referencedColumnName = "id")
private Address address;
}
然后,使用正常的 @Repository
:
public interface StudentRepository extends JpaRepository<Student, Long> {
}
现在您已经拥有正确工作的映射和存储库。
完成此操作后,您应该准备好开始了,前提是上述假设成立。您现在可以为学生编写一个小测试用例,例如
@DataJpaTest
class StudentRepositoryTest {
@Autowired
private StudentRepository studentRepository;
@Autowired
private AddressRepository addressRepository;
@BeforeEach
void setup() {
assumeThat(studentRepository).isNotNull();
assumeThat(addressRepository).isNotNull();
}
@Test
void testSaveStudent {
assumeThat(studentRepository.count()).isZero();
assumeThat(addressRepository.count()).isZero();
var student = new Student();
var address = new Address();
address.setCity("New York");
student.setFirstName("Max");
student.setAddress(address);
studentRepository.save(student);
assertThat(studentRepository.count()).isOne();
assertThat(addressRepository.count()).isOne();
}
第二:关于您的交易问题。默认情况下,当您调用存储库的 save
方法时,会启动一个事务。因此,在最基本的情况下,您不一定需要交易。不过,一旦您处理了更复杂的业务案例,就有必要了解 JPA 的事务管理器。事务通常发生在服务层。有关交易的更多信息,请参阅下面的链接。
@Service
@Transactional(readOnly = true)
public class StudentService {
@Autowired
private StudentRepository studentRepository;
@Transactional
public Student assignStudentToAddress(Student s, Address a) {
s.setAddress(a);
return studentRepository.save(s);
}
}
一些想法:
- 你确定一个学生只能住在一个地址吗?这意味着生活大爆炸(合租公寓)中的生活安排将行不通。出于测试目的,这当然有效。
- 考虑相反的情况:如果我们有一个地址屏幕并且我们想要将学生添加到其中怎么办?
更多资源:
- 映射新手指南by VladMihalcea
- 映射
OneToOne
初学者友好:by Baeldung and slightly more advanced by VladMihalcea - 映射
OneToMany
初学者友好:by baeldung and slightly more advanced: by VladMihalcea - 使用
Transactional
by VladMihalcea and by MarcoBehler
使用无状态 EJB:
@Stateless
public StudentDAO {
@PersistenceContext
private EntityManager em;
public Student createStudent(StudentRequest studentRequest) {
Student student=new Student(studentRequest);
Address address=new Address();
address.setCity(studentRequest.getCity());
address.setStreet(studentRequest.getStreet());
em.persist(address);
student.setAddress(address);
em.persist(student);
return student;
}
}