问题描述
大家好
我很难解决下面的spring jpa问题。 假设我有以下简单的数据模型(两个实体,两者之间具有一个方向关系)
@Accessors(chain = true) @Getter @Setter @NoArgsConstructor @AllArgsConstructor
@MappedSuperclass
public class AbstractEntity {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Version
private Long version;
}
@Accessors(chain = true) @Getter @Setter @NoArgsConstructor @AllArgsConstructor
@Entity
public class Entity1 extends AbstractEntity {
private String name;
}
@Accessors(chain = true) @Getter @Setter @NoArgsConstructor @AllArgsConstructor
@Entity
public class Entity2 extends AbstractEntity {
private String name;
@ManyToOne(cascade={ALL})
private Entity1 entity1;
}
和以下用于存储它们的管道
public interface Entity1Dao extends JpaRepository< Entity1,Long >,JpaSpecificationExecutor< Entity1 > {
Entity1 findByName(String name);
}
public interface Entity2Dao extends JpaRepository< Entity2,JpaSpecificationExecutor< Entity2 > {
Entity2 findByName(String name);
}
@Service
public class StoreService {
@Autowired
Entity1Dao dao1;
@Autowired
Entity2Dao dao2;
@Transactional
public Entity1 saveEntity1(Entity1 e) {
return dao1.save(e);
}
@Transactional
public Entity2 saveEntity2(Entity2 e) {
return dao2.save(e);
}
public Entity1 loadEntity1ByName(String name) {
return dao1.findByName(name);
}
}
@SpringBootApplication
public class JpaDemoApplication {
public static void main(String[] args) {
SpringApplication.run(JpaDemoApplication.class,args);
}
}
以及以下测试
@SpringBootTest
@TestMethodOrder(value = MethodOrderer.OrderAnnotation.class)
class JpaDemoApplicationTests {
@Autowired
StoreService store;
@Test
@Order(1)
void contextLoads() {
assertThat(store).isNotNull();
}
@Test
@Order(2)
void insertEntity1() {
store.saveEntity1(new Entity1("test entity1"));
Entity1 saved = store.loadEntity1ByName("test entity1");
assertThat(saved).isNotNull().hasNoNullFieldsOrProperties();
}
@Test
@Order(4)
void insertEntity2WithNewEntity1() {
store.saveEntity2(new Entity2("with new entity1",new Entity1("new entity1")));
}
@Test
@Order(5)
void insertEntity2WithExistingEntity1() {
store.saveEntity2(new Entity2("with saved entity1",store.loadEntity1ByName("test entity1")));
}
}
最后一个测试(即insertEntity2WithExistingEntity1)由于以下异常而失败
org.hibernate.PersistentObjectException:分离的实体传递给 坚持:com.example.jpaDemo.Entity1
如果我将Entity2中的CascadeType更改为MERGE,则该测试通过了,但insertEntity2WithNewEntity1失败,但出现以下异常
org.hibernate.TransientPropertyValueException:对象引用了 未保存的临时实例-在保存临时实例之前 冲洗:com.example.jpaDemo.Entity2.entity1-> com.example.jpaDemo.Entity1
我尝试了级联类型的多种组合,但是似乎一旦使用PERSIST,最后一次测试就会失败(并且所有测试都包括PERSIST)。
我本来希望,如果设置了MERGE 和 PERSIST,它们都将处于活动状态,但是从测试的角度来看,设置了PERSIST时,似乎MERGE被忽略了。 任何提示,技巧和暗示都表明我在做什么错,以便两个测试都能正常运行?
编辑
这些测试假设是模仿REST服务端点接收和保存Entity1的json表示的行为。
第三个测试的json是
{ name: "with new entity1",entity1: { name: "new entity1"}}
第四个的json是
{ name: "with new entity1",entity1: { id: 1,version: 0,name: "test entity1"}}
JPA应该在第三项测试中坚持,因为它的id为null,但应该合并在第四项测试中,因为它的id为不是 null。
但是我不能两者兼而有之。
编辑2
我对Entity1进行了一些修改,以引用与其关联的Entity2列表,并使用@OneToMany和与Entity2中相同的级联类型对其进行注释,并且具有相同的行为。
当我将级联类型设置为MERGE且只有Merge时,我可以保存一个具有引用的现有新实体,但无法保存具有引用的新实体。
当我将级联类型设置为PERSIST(即PERSIST本身,PERSIST和MERGE或ALL)时,它是相反的;我可以保存引用另一个新实体的新实体,但不能保存引用已经存在的新实体。
因此,似乎在设置PERSIST时,它会覆盖MERGE的行为。对我而言,这是一个错误。不是吗?
如果您想尝试或自己查看一下,我已经将源代码上传到了github。 https://github.com/willix71/persistVsMerge.git
解决方法
您需要在上一次测试中添加@Transactional。加载的实体是分离的,因为没有外部事务,您无法持久化它。
@Test
@Order(5)
@Transactional
void insertEntity2WithExistingEntity1() {
store.saveEntity2(new Entity2("with saved entity1",store.loadEntity1ByName("test entity1")));
}
,
我不确定这是否不再相关,但下面的代码按我的预期工作。删除“cascade = CascadeType.PERSIST”将使持久测试失败,并显示“对象引用未保存的瞬态实例”。
我还在您的 github 存储库中注意到您正在尝试从父级到子级和子级到父级进行级联。我认为这是您问题的根本原因。
实体:
@Entity
@Table(name = "users")
@Getter
@Setter
@NoArgsConstructor
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
UUID id;
@ManyToOne(cascade = CascadeType.PERSIST)
Address address;
}
@Entity
@Getter
@Setter
@NoArgsConstructor
public class Address {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
Long id;
@OneToMany(mappedBy = "address")
List<User> user;
}
存储库:
public interface UserRepository extends JpaRepository<User,UUID> {
}
public interface AddressRepository extends JpaRepository<Address,UUID> {
}
测试:
@DataJpaTest
@Import(DataSourceConfig.class)
class UserRepositoryTest {
@Autowired
private UserRepository userRepository;
@Autowired
private AddressRepository addressRepository;
@Test
void testMerge() {
var address = new Address();
addressRepository.save(address);
var user = new User();
user.setAddress(address);
userRepository.save(user);
assertThat(userRepository.findAll()).contains(user);
assertThat(addressRepository.findAll()).contains(address);
}
@Test
void testPersist() {
var address = new Address();
var user = new User();
user.setAddress(address);
userRepository.save(user);
assertThat(userRepository.findAll()).contains(user);
assertThat(addressRepository.findAll()).contains(address);
}
}