Java 升级后,@MapsId在保存现有实体时抛出错误,但在其他方面可以正常工作
我正在将SpringBoot1.5.21项目(Java8U221)升级到SpringBoot2.1.9(Java11.0.2-open)。在这两种情况下,我们都使用带有spring启动程序和依赖项解析器的gradle构建,因此底层spring、JPA和Hibernate库的版本都是spring管理的 该项目有一个可选的一对一映射,其中子实体从父实体生成的ID获取其ID。在该项目的Spring Boot 1版本中,关系的配置如下:Java 升级后,@MapsId在保存现有实体时抛出错误,但在其他方面可以正常工作,java,hibernate,spring-boot,jpa,one-to-one,Java,Hibernate,Spring Boot,Jpa,One To One,我正在将SpringBoot1.5.21项目(Java8U221)升级到SpringBoot2.1.9(Java11.0.2-open)。在这两种情况下,我们都使用带有spring启动程序和依赖项解析器的gradle构建,因此底层spring、JPA和Hibernate库的版本都是spring管理的 该项目有一个可选的一对一映射,其中子实体从父实体生成的ID获取其ID。在该项目的Spring Boot 1版本中,关系的配置如下: @Entity @Table(name = "PARENT_OBJ
@Entity
@Table(name = "PARENT_OBJECT", schema = "MYSCHEMA")
public class ParentObject implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "PARENT_OBJECT_ID_SEQ")
@SequenceGenerator(name = "PARENT_OBJECT_ID_SEQ", sequenceName = "MYSCHEMA.PARENT_OBJECT_ID_SEQ")
protected Long id;
@OneToOne(optional = true, mappedBy = "parentObject", cascade = CascadeType.ALL, orphanRemoval = true)
@Valid
protected ChildObject childObject;
// Other fields and methods
}
@Entity
@Table(name = "CHILD_OBJECT", schema = "MYSCHEMA")
public class ChildObject implements Serializable {
@Id
@Column(name = "parent_object_id", unique = true, nullable = false, insertable = true, updatable = false)
private Long parentObjectId;
@OneToOne
@MapsId
@PrimaryKeyJoinColumn
@JsonIgnore
private ParentObject parentObject;
// Other fields and methods
}
当我更新到Spring Boot 2时,我不得不更新很多JPA设置,有一件特别令人抱怨的事情是,生成的查询查找的是“PARENTOBJECT\u ID”而不是“PARENT\u OBJECT\u ID”,所以我仔细查看了一下,找到一篇文章,解释如何使用@JoinColumn
修复一对一映射的列名,并将parentObject字段的子对象注释更新为以下内容:
@OneToOne
@MapsId
@JoinColumn(name = "parent_object_id")
@JsonIgnore
private ParentObject parentObject;
我完全删除了@PrimaryKeyJoinColumn
,因为文档似乎建议您应该使用@MapsId
,如果您希望Hibernate来处理ID的分配,并且如果您要自己管理它们,那么您应该使用@PrimaryKeyJoinColumn
此配置对于我的所有测试都很有效,除了2:控制器的更新集成测试(当第一次使用关联的ChildObject创建ParentObject时,后期测试效果很好)这两个测试的错误是:
org.springframework.orm.jpa.JpaSystemException:尝试从空的一对一属性[org.mycompany.myproject.mymodule.mysubmodule.ChildObject.parentObject]分配id;嵌套异常为org.hibernate.id.IdentifierGenerationException:尝试从空的一对一属性[org.mycompany.myproject.mymodule.mysubmodule.ChildObject.parentObject]分配id
奇怪的是,这些实体及其关联的所有存储库集成测试以及所有其他控制器集成测试都通过了,包括一个新的ParentObject
与其新的ChildObject
一起发布的测试。我到处寻找可能的解决方案,但我读到的每一篇文章似乎都表明我使用的配置应该可以工作。我还尝试了其他配置,建议使用@PrimaryKeyJoinColumn
和自己设置ID字段,在ChildObject
的ID上使用ID生成器(即使@MapsId
的目的是告诉系统使用ParentObject
的ID),通常,我最终会遇到更多失败的测试,并出现以下错误:
org.springframework.orm.jpa.JpaSystemException:调用save()之前必须手动分配此类的ID:org.mycompany.myproject.mymodule.mysubmodule.ChildObject;嵌套异常为org.hibernate.id.IdentifierGenerationException:调用save()之前,必须手动分配此类的id:org.mycompany.myproject.mymodule.mysubmodule.ChildObject
虽然有时我会遇到无法“从null分配id”的原始错误。在这一点上,我不知道这是如何配置不正确的,或者是什么其他因素在搞砸事情。在这一点上,我愿意尝试所有的建议
为了完整起见,下面是应用程序中的一组代码片段堆栈跟踪中的相关行号用注释加以说明。我省略了控制器代码,因为它没有什么值得注意的地方;POST和PUT方法都调用相同的服务方法;它们只是位于不同的端点,PUT首先检查对象是否存在于数据库中,然后再调用服务保存方法。下面是调用父对象的JPA repo的服务方法:
@Service
@Transactional(readOnly = true)
public class ParentObjectServiceImpl implements ParentObjectService {
// other fields and other methods
@Autowired
private ParentObjectRepository parentObjectRepository;
@Transactional(readOnly = false)
@Override
public ParentObject saveParentObject(final ParentObject parentObject) {
parentObject.prepForPersistence();
return parentObjectRepository.save(parentObject); //Line 93
}
}
public void prepForPersistence() {
if(childObject != null) {
childObject.setParentObject(this);
// In some iterations of the code in trying to solve this, I also had the following line
//childObject.setParentObjectId(this.id);
}
}
这是我的JPA存储库:
public interface ParentObjectRepository extends CrudRepository<ParentObject, Long> {
// custom methods, no override for save though
}
以下是两个测试(一个通过,一个失败):
以下是完整的堆栈跟踪:
由于以下错误,使用HTTPStatus=内部服务器错误进行响应:
org.springframework.orm.jpa.JpaSystemException:尝试从空的一对一属性[org.mycompany.myproject.mymodule.mysubmodule.ChildObject.parentObject]分配id;嵌套异常为org.hibernate.id.IdentifierGenerationException:尝试从空的一对一属性[org.mycompany.myproject.mymodule.mysubmodule.ChildObject.parentObject]分配id
位于org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:352)
如果可能,请访问org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateException(HibernateJpaDialect.java:254)
位于org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.TranslateExceptionIffailable(AbstractEntityManagerFactoryBean.java:528)
在org.springframework.dao.support.ChainedPersistenceExceptionTranslator.TranslateExceptionIfEnabled(ChainedPersistenceExceptionTranslator.java:61)
位于org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
位于org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:153)
位于org.springframework.aop.framework.ReflectiveMethodInvocation.procedue(ReflectiveMethodInvocation.java:186)
位于org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:144)
位于org.springframework.aop.framework.ReflectiveMethodInvocation.procedue(ReflectiveMethodInvocation.java:186)
位于org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$ExposerePositionInvocationInterceptor
@RunWith(SpringRunner.class)
@SpringBootTest(classes = MyApplication.class)
@WebAppConfiguration
// This profile disabled csrf for testing, and sets some env variables
@ActiveProfiles("integration-test")
@Transactional
public class ParentObjectControllerTest {
@Autowired
private WebApplicationContext context;
private MockMvc mockMvc;
@Before
public void setUp() throws Exception {
mockMvc = MockMvcBuilders.webAppContextSetup(context)
.apply(springSecurity())
.build();
}
// This test PASSES
@Test
@WithMockUser(roles = {"MY_APP_ADMIN"}, username = TEST_USER)
@Sql(scripts = "/db-scripts/bootstrap.sql")
public void testPostParentObjectWithChildObject() {
final ChildObject childObject = new ChildObject();
// set some properties on childObject that don't relate to ParentObject
final ParentObject parentObject = new ParentObject();
// set some properties on parentObject that don't relate to ChildObject
parentObject.setChildObject(childObject);
final ParentObject result = given().mockMvc(mockMvc).contentType(ContentType.JSON)
.and().body(item).log().all()
.when().post("/parent-objects")
.then().log().all().statusCode(201).contentType(ContentType.JSON)
.and().body(matchesJsonSchemaInClasspath("json-schemas/parent-object.json"))
.and().body("username", equalTo(TEST_USER))
.extract().as(ParentObject.class);
assertThat(result.getId(), is(notNullValue()));
assertThat(result.getChildObject().getParentObjectId(), is(result.getId()));
}
// This test FAILS with the 'attempted to assign id from null one-to-one property' error
@Test
@WithMockUser(roles = {"MY_APP_ADMIN"}, username = TEST_USER)
// Inserts a ParentObject record with ID -1
@Sql(scripts = "/db-scripts/bootstrap.sql")
public void testPutParentObjectWithChildObject() {
final ChildObject childObject = new ChildObject();
// set some properties on childObject that don't relate to ParentObject
final Long EXISTING_ID = -1L;
final ParentObject parentObject = new ParentObject();
parentObject.setId(EXISTING_ID);
parentObject.setChildObject(childObject);
final ParentObject result = given().mockMvc(mockMvc).contentType(ContentType.JSON)
.and().body(item).log().all()
.when().put("/parent-objects/{parentObjectId}", parentObject.getId()) //line 260
.then().log().all().statusCode(200)
.extract().as(ParentObject.class);
assertThat(result.getId(), is(EXISTING_ID));
assertThat(result.getChildObject().getParentObjectId(), is(EXISTING_ID));
}