设置为Persist时,将忽略CascadeType Merge

问题描述

大家好

我很难解决下面的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);
    }
 }

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...