为什么 PostPersist 需要设置 oneToOne 子实体的 id?
Why is PostPersist required to set the id of oneToOne child entity?
我正在尝试实现双向 OneToOne 映射并从父实体 (Project.java) 插入子实体 (ProjectDetails.java)。但是,实体管理器试图插入 null 作为子实体 (ProjectDetails) 的 id。
错误日志:
[EL Fine]: sql: 2019-08-19 01:16:50.969--ClientSession(1320691525)--Connection(926343068)--INSERT INTO project (name) VALUES (?)
bind => [Project]
[EL Fine]: sql: 2019-08-19 01:16:50.973--ClientSession(1320691525)--Connection(926343068)--SELECT @@IDENTITY
[EL Fine]: sql: 2019-08-19 01:16:50.983--ClientSession(1320691525)--Connection(926343068)--INSERT INTO project_details (project_id, details) VALUES (?, ?)
bind => [null, Details]
[EL Fine]: sql: 2019-08-19 01:16:50.986--ClientSession(1320691525)--SELECT 1
[EL Warning]: 2019-08-19 01:16:50.991--UnitOfWork(1746098804)--Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.7.4.v20190115-ad5b7c6b2a): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: java.sql.SQLIntegrityConstraintViolationException: Column 'project_id' cannot be null
Error Code: 1048
我尝试从@OneToOne 中删除 insertable=false 和 updatable=false,但它给了我错误,同一列不能被引用两次。
我有以下实体 classes。
Class:项目
package com.example.playground.domain.dbo;
import com.example.playground.jsonviews.BasicView;
import com.example.playground.jsonviews.ProjectView;
import com.fasterxml.jackson.annotation.JsonView;
import lombok.Data;
import lombok.ToString;
import org.eclipse.persistence.annotations.JoinFetch;
import org.eclipse.persistence.annotations.JoinFetchType;
import javax.persistence.*;
@JsonView(BasicView.class)
@Data
@Entity
@Table(name = "project")
public class Project {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="project_id")
private Integer projectId;
@Column(name="name")
private String name;
@ToString.Exclude
@JsonView(ProjectView.class)
@OneToOne(mappedBy = "project", cascade = CascadeType.ALL, optional = false)
private ProjectDetails projectDetails;
}
Class:项目详情
package com.example.playground.domain.dbo;
import com.example.playground.jsonviews.BasicView;
import com.example.playground.jsonviews.ProjectDetailsView;
import com.fasterxml.jackson.annotation.JsonView;
import lombok.Data;
import lombok.ToString;
import javax.persistence.*;
@JsonView(BasicView.class)
@Data
@Entity
@Table(name = "project_details")
public class ProjectDetails {
@Id
@Column(name = "project_id")
private Integer projectId;
@ToString.Exclude
@JsonView(ProjectDetailsView.class)
@OneToOne
@JoinColumn(name = "project_id", nullable = false, insertable = false, updatable = false)
private Project project;
@Column(name = "details")
private String details;
}
class:项目控制器
package com.example.playground.web;
import com.example.playground.domain.dbo.Project;
import com.example.playground.jsonviews.ProjectView;
import com.example.playground.service.ProjectService;
import com.fasterxml.jackson.annotation.JsonView;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/projects")
public class ProjectController {
@Autowired
private ProjectService projectService;
@GetMapping("/{projectId}")
@JsonView(ProjectView.class)
public ResponseEntity<Project> getProject(@PathVariable Integer projectId){
Project project = projectService.getProject(projectId);
return ResponseEntity.ok(project);
}
@PostMapping
@JsonView(ProjectView.class)
public ResponseEntity<Project> createProject(@RequestBody Project projectDTO){
Project project = projectService.createProject(projectDTO);
return ResponseEntity.ok(project);
}
}
class 项目服务
package com.example.playground.service;
import com.example.playground.domain.dbo.Project;
public interface ProjectService {
Project createProject(Project projectDTO);
Project getProject(Integer projectId);
}
class ProjectServiceImpl
package com.example.playground.impl.service;
import com.example.playground.domain.dbo.Project;
import com.example.playground.repository.ProjectRepository;
import com.example.playground.service.ProjectService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class ProjectServiceImpl implements ProjectService {
@Autowired
private ProjectRepository projectRepository;
@Transactional
@Override
public Project createProject(Project projectDTO) {
projectDTO.getProjectDetails().setProject(projectDTO);
return projectRepository.saveAndFlush(projectDTO);
}
@Override
public Project getProject(Integer projectId) {
return projectRepository.findById(projectId).get();
}
}
JPAConfig
package com.example.playground.config;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Configuration
@EnableTransactionManagement(mode= AdviceMode.ASPECTJ)
public class JPAConfig{
@Bean("dataSource")
@ConfigurationProperties(prefix = "db1")
public DataSource getDataSource(){
return DataSourceBuilder.create().build();
}
@Bean("entityManagerFactory")
public LocalContainerEntityManagerFactoryBean getEntityManager(@Qualifier("dataSource") DataSource dataSource){
EclipseLinkJpaVendorAdapter adapter = new EclipseLinkJpaVendorAdapter();
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setPackagesToScan("com.example.playground.domain.dbo");
em.setDataSource(dataSource);
em.setJpaVendorAdapter(adapter);
em.setPersistenceUnitName("persistenceUnit");
em.setJpaPropertyMap(getVendorProperties());
return em;
}
@Bean(name = "transactionManager")
public JpaTransactionManager
transactionManager(@Qualifier("entityManagerFactory") EntityManagerFactory entityManagerFactory)
{
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}
protected Map<String, Object> getVendorProperties()
{
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("eclipselink.ddl-generation", "none");
map.put("eclipselink.ddl-generation.output-mode", "database");
map.put("eclipselink.weaving", "static");
map.put("eclipselink.logging.level.sql", "FINE");
map.put("eclipselink.logging.parameters", "true");
map.put(
"eclipselink.target-database",
"org.eclipse.persistence.platform.database.SQLServerPlatform");
return map;
}
}
和pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>playground</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Java-Spring-Boot-Playground</name>
<description>Java playground.</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-devtools -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.data/spring-data-jpa -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>2.1.9.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>org.eclipse.persistence.jpa</artifactId>
<version>2.7.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.tomcat/tomcat-jdbc -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
<version>9.0.21</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
编辑:可以正常使用,但仍在寻找解释
如果我将以下代码添加到我的项目 class ,它会按预期工作。
@PostPersist
public void fillIds(){
projectDetails.setProjectId(this.projectId);
}
为什么需要 postPersist?鉴于关系被标记为 oneToOne,JPA 是否应该自动填充这些值?有没有更好的方法?
JPA 按照您的指示执行:您有两个映射到 "project_id",一个有值,OneToOne 是只读的。这意味着 JPA 必须从您保留为 null 的基本 ID 映射 'projectId' 中提取值,导致它向字段中插入 null。
这是一个常见问题,JPA 中有许多解决方案。首先是将 @ID 映射标记为只读 (insertable/updatable=false) 并让关系映射控制该值。
JPA 2.0 引入了其他解决方案。
对于相同的设置,您可以使用 @MapsId 注释标记关系。这告诉 JPA 关系外键值将在指定的 ID 映射中使用,并且会在没有 postPersist 方法的情况下完全按照您预期的那样为您设置它。
JPA 2.0 中的另一种选择是您可以只将 OneToOne 标记为 ID 映射,并从 class 中删除 projectId 属性。显示了一个更复杂的示例 here
我正在尝试实现双向 OneToOne 映射并从父实体 (Project.java) 插入子实体 (ProjectDetails.java)。但是,实体管理器试图插入 null 作为子实体 (ProjectDetails) 的 id。
错误日志:
[EL Fine]: sql: 2019-08-19 01:16:50.969--ClientSession(1320691525)--Connection(926343068)--INSERT INTO project (name) VALUES (?)
bind => [Project]
[EL Fine]: sql: 2019-08-19 01:16:50.973--ClientSession(1320691525)--Connection(926343068)--SELECT @@IDENTITY
[EL Fine]: sql: 2019-08-19 01:16:50.983--ClientSession(1320691525)--Connection(926343068)--INSERT INTO project_details (project_id, details) VALUES (?, ?)
bind => [null, Details]
[EL Fine]: sql: 2019-08-19 01:16:50.986--ClientSession(1320691525)--SELECT 1
[EL Warning]: 2019-08-19 01:16:50.991--UnitOfWork(1746098804)--Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.7.4.v20190115-ad5b7c6b2a): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: java.sql.SQLIntegrityConstraintViolationException: Column 'project_id' cannot be null
Error Code: 1048
我尝试从@OneToOne 中删除 insertable=false 和 updatable=false,但它给了我错误,同一列不能被引用两次。
我有以下实体 classes。
Class:项目
package com.example.playground.domain.dbo;
import com.example.playground.jsonviews.BasicView;
import com.example.playground.jsonviews.ProjectView;
import com.fasterxml.jackson.annotation.JsonView;
import lombok.Data;
import lombok.ToString;
import org.eclipse.persistence.annotations.JoinFetch;
import org.eclipse.persistence.annotations.JoinFetchType;
import javax.persistence.*;
@JsonView(BasicView.class)
@Data
@Entity
@Table(name = "project")
public class Project {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="project_id")
private Integer projectId;
@Column(name="name")
private String name;
@ToString.Exclude
@JsonView(ProjectView.class)
@OneToOne(mappedBy = "project", cascade = CascadeType.ALL, optional = false)
private ProjectDetails projectDetails;
}
Class:项目详情
package com.example.playground.domain.dbo;
import com.example.playground.jsonviews.BasicView;
import com.example.playground.jsonviews.ProjectDetailsView;
import com.fasterxml.jackson.annotation.JsonView;
import lombok.Data;
import lombok.ToString;
import javax.persistence.*;
@JsonView(BasicView.class)
@Data
@Entity
@Table(name = "project_details")
public class ProjectDetails {
@Id
@Column(name = "project_id")
private Integer projectId;
@ToString.Exclude
@JsonView(ProjectDetailsView.class)
@OneToOne
@JoinColumn(name = "project_id", nullable = false, insertable = false, updatable = false)
private Project project;
@Column(name = "details")
private String details;
}
class:项目控制器
package com.example.playground.web;
import com.example.playground.domain.dbo.Project;
import com.example.playground.jsonviews.ProjectView;
import com.example.playground.service.ProjectService;
import com.fasterxml.jackson.annotation.JsonView;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/projects")
public class ProjectController {
@Autowired
private ProjectService projectService;
@GetMapping("/{projectId}")
@JsonView(ProjectView.class)
public ResponseEntity<Project> getProject(@PathVariable Integer projectId){
Project project = projectService.getProject(projectId);
return ResponseEntity.ok(project);
}
@PostMapping
@JsonView(ProjectView.class)
public ResponseEntity<Project> createProject(@RequestBody Project projectDTO){
Project project = projectService.createProject(projectDTO);
return ResponseEntity.ok(project);
}
}
class 项目服务
package com.example.playground.service;
import com.example.playground.domain.dbo.Project;
public interface ProjectService {
Project createProject(Project projectDTO);
Project getProject(Integer projectId);
}
class ProjectServiceImpl
package com.example.playground.impl.service;
import com.example.playground.domain.dbo.Project;
import com.example.playground.repository.ProjectRepository;
import com.example.playground.service.ProjectService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class ProjectServiceImpl implements ProjectService {
@Autowired
private ProjectRepository projectRepository;
@Transactional
@Override
public Project createProject(Project projectDTO) {
projectDTO.getProjectDetails().setProject(projectDTO);
return projectRepository.saveAndFlush(projectDTO);
}
@Override
public Project getProject(Integer projectId) {
return projectRepository.findById(projectId).get();
}
}
JPAConfig
package com.example.playground.config;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Configuration
@EnableTransactionManagement(mode= AdviceMode.ASPECTJ)
public class JPAConfig{
@Bean("dataSource")
@ConfigurationProperties(prefix = "db1")
public DataSource getDataSource(){
return DataSourceBuilder.create().build();
}
@Bean("entityManagerFactory")
public LocalContainerEntityManagerFactoryBean getEntityManager(@Qualifier("dataSource") DataSource dataSource){
EclipseLinkJpaVendorAdapter adapter = new EclipseLinkJpaVendorAdapter();
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setPackagesToScan("com.example.playground.domain.dbo");
em.setDataSource(dataSource);
em.setJpaVendorAdapter(adapter);
em.setPersistenceUnitName("persistenceUnit");
em.setJpaPropertyMap(getVendorProperties());
return em;
}
@Bean(name = "transactionManager")
public JpaTransactionManager
transactionManager(@Qualifier("entityManagerFactory") EntityManagerFactory entityManagerFactory)
{
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}
protected Map<String, Object> getVendorProperties()
{
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("eclipselink.ddl-generation", "none");
map.put("eclipselink.ddl-generation.output-mode", "database");
map.put("eclipselink.weaving", "static");
map.put("eclipselink.logging.level.sql", "FINE");
map.put("eclipselink.logging.parameters", "true");
map.put(
"eclipselink.target-database",
"org.eclipse.persistence.platform.database.SQLServerPlatform");
return map;
}
}
和pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>playground</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Java-Spring-Boot-Playground</name>
<description>Java playground.</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-devtools -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.data/spring-data-jpa -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>2.1.9.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>org.eclipse.persistence.jpa</artifactId>
<version>2.7.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.tomcat/tomcat-jdbc -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
<version>9.0.21</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
编辑:可以正常使用,但仍在寻找解释
如果我将以下代码添加到我的项目 class ,它会按预期工作。
@PostPersist
public void fillIds(){
projectDetails.setProjectId(this.projectId);
}
为什么需要 postPersist?鉴于关系被标记为 oneToOne,JPA 是否应该自动填充这些值?有没有更好的方法?
JPA 按照您的指示执行:您有两个映射到 "project_id",一个有值,OneToOne 是只读的。这意味着 JPA 必须从您保留为 null 的基本 ID 映射 'projectId' 中提取值,导致它向字段中插入 null。
这是一个常见问题,JPA 中有许多解决方案。首先是将 @ID 映射标记为只读 (insertable/updatable=false) 并让关系映射控制该值。
JPA 2.0 引入了其他解决方案。 对于相同的设置,您可以使用 @MapsId 注释标记关系。这告诉 JPA 关系外键值将在指定的 ID 映射中使用,并且会在没有 postPersist 方法的情况下完全按照您预期的那样为您设置它。
JPA 2.0 中的另一种选择是您可以只将 OneToOne 标记为 ID 映射,并从 class 中删除 projectId 属性。显示了一个更复杂的示例 here