我有一个RESTful终结点,它根据记录是否已存在,将使用JPA的.save()
方法插入新记录或更新数据库中的现有记录。
我遇到的问题是,当尝试更新现有记录时,JPA将尝试访问不存在的表employee_department
。我猜这是因为在我的Employee
实体上,我与@ManyToOne
实体有一个Department
关系。
仅当我尝试将所有从POST请求发送的Employee model
数据映射到Employee entity
时,才会出现此问题。我将收到一个错误,指出数据库中没有名为employee_department
的表。如果相反,我做一个findByEmail(employee.getEmail())
,这是在EmployeeRepository
上创建的方法,该方法扩展了JPA,并尝试将数据直接保存回数据库中,那么就没有错误。
我的问题是,与仅从数据库返回记录并保存记录相比,在尝试保存之前映射model -> entity
可能会丢失什么?
这是从client -> controller -> service -> mapper
然后到客户端的数据流。
{
"id": 109,
"isActive": true,
"manager": null,
"firstName": "string",
"middleInitial": null,
"lastName": "string",
"department": {
"id": 101
},
"jobTitle": {
"id": 1001
},
"email": "g",
"skypeName": "g",
"isManager": false
}
@RestController
@RequestMapping("/emps")
public class EmployeeController {
@Autowired
EmployeeService employeeService;
@RequestMapping(method = {RequestMethod.POST, RequestMethod.PUT})
public Employee createOrUpdateEmployee(@RequestBody Employee employee) {
return employeeService.storeOrUpdate(employee);
}
@Service
public class EmployeeService {
@Autowired
private EmployeeRepository employeeRepository;
@Autowired
EmployeeMapper employeeMapper;
public Employee storeOrUpdate(Employee employee) {
EmployeeEntity employeeEntity = employeeMapper.modelToEntity(employee);
EmployeeEntity savedEmployeeEntity = employeeRepository.save(employeeEntity);
Employee employeeModel = employeeMapper.entityToModel(savedEmployeeEntity);
return employeeModel;
}
@Component
public class EmployeeMapper implements EntityModelMapper<EmployeeEntity, Employee> {
@Autowired
private DepartmentMapper departmentMapper;
@Autowired
private JobTitleMapper jobTitleMapper;
@Override
public EmployeeEntity modelToEntity(Employee employee) {
Assert.notNull(employee, "Employee model cannot be null.");
EmployeeEntity employeeEntity = new EmployeeEntity();
DepartmentEntity departmentEntity = departmentMapper.modelToEntity(employee.getDepartment());
JobTitleEntity jobTitleEntity = jobTitleMapper.modelToEntity(employee.getJobTitle());
Employee employeeManager = employee.getManager();
if (employeeManager != null) {
EmployeeEntity employeeManagerEntity = modelToEntity(employeeManager);
employeeEntity.setManager(employeeManagerEntity);
}
employeeEntity.setId(employee.getId());
employeeEntity.setEmail(employee.getEmail());
employeeEntity.setFirstName(employee.getFirstName());
employeeEntity.setMiddleInitial(employee.getMiddleInitial());
employeeEntity.setLastName(employee.getLastName());
employeeEntity.setDepartment(departmentEntity);
employeeEntity.setJobTitle(jobTitleEntity);
employeeEntity.setIsManager(employee.getIsManager());
employeeEntity.setSkypeName(employee.getSkypeName());
employeeEntity.setIsActive(employee.getIsActive());
return employeeEntity;
}
同样,我能够创建新记录,但是使用完全相同的流程来更新现有记录是当我遇到no table named employee_department
错误,但是如果我将storeOrUpdate
更改为类似内容
public Employee storeOrUpdate(Employee employee) {
EmployeeEntity preExistingEmployeeEntity = employeeRepository.findByEmail(employee.getEmail());
EmployeeEntity savedEmployeeEntity = employeeRepository.save(preExistingEmployeeEntity);
Employee employeeModel = employeeMapper.entityToModel(savedEmployeeEntity);
return employeeModel;
}
正在“更新”的Employee
直接来自数据库,则一切工作都像一个超级按钮。
@Entity
@Table(name = "employee")
public class EmployeeEntity extends BaseEntity {
@Column(name = "first_name")
@NotEmpty
@Size(min = 1)
private String firstName;
@Column(name = "middle_initial")
private Character middleInitial;
@Column(name = "last_name")
@NotEmpty
@Size(min = 1)
private String lastName;
@Column(name = "email")
@NotEmpty
@Size(min = 1)
private String email;
@Column(name = "skype_name")
@NotEmpty
@Size(min = 1)
private String skypeName;
@ManyToOne
@JoinColumn(name = "job_title_id")
private JobTitleEntity jobTitle;
@ManyToOne
@JoinColumn(name = "manager_id")
private EmployeeEntity manager;
@ManyToOne
@JoinColumn(name = "department_id")
private DepartmentEntity department;
@OneToMany(mappedBy = "manager")
private Set<EmployeeEntity> ManagedEmployees;
@OneToMany
private Set<DepartmentEntity> ManagedDepartments;
@Column(name = "is_manager")
@NotNull
private boolean isManager;
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public Character getMiddleInitial() {
return middleInitial;
}
public void setMiddleInitial(Character middleInitial) {
this.middleInitial = middleInitial;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getSkypeName() {
return skypeName;
}
public void setSkypeName(String skypeName) {
this.skypeName = skypeName;
}
public JobTitleEntity getJobTitle() {
return jobTitle;
}
public void setJobTitle(JobTitleEntity jobTitle) {
this.jobTitle = jobTitle;
}
public EmployeeEntity getManager() {
return manager;
}
public void setManager(EmployeeEntity manager) {
this.manager = manager;
}
public DepartmentEntity getDepartment() {
return department;
}
public void setDepartment(DepartmentEntity department) {
this.department = department;
}
public Set<EmployeeEntity> getManagedEmployees() {
return ManagedEmployees;
}
public void setManagedEmployees(Set<EmployeeEntity> managedEmployees) {
ManagedEmployees = managedEmployees;
}
public Set<DepartmentEntity> getManagedDepartments() {
return ManagedDepartments;
}
public void setManagedDepartments(Set<DepartmentEntity> managedDepartments) {
ManagedDepartments = managedDepartments;
}
public boolean getIsManager() {
return isManager;
}
public void setIsManager(boolean manager) {
isManager = manager;
}
}
@Entity
@Table(name = "department")
public class DepartmentEntity extends BaseEntity {
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "manager_id")
private EmployeeEntity manager;
@Column(name = "name", nullable = false, length = 50, unique = true)
@NotNull
@NotEmpty
@Size(min = 1, max = 45)
private String name;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "parent_department_id", referencedColumnName = "ID")
private DepartmentEntity parentDepartment;
@OneToMany(mappedBy = "parentDepartment")
private Set<DepartmentEntity> departments = new HashSet<>(0);
@OneToMany(mappedBy = "department")
private Set<EmployeeEntity> employees = new HashSet<>(0);
public String getName() {
return this.name;
}
public DepartmentEntity getParentDepartment() {
return this.parentDepartment;
}
public void setName(String name) {
this.name = name;
}
public void setParentDepartment(DepartmentEntity departmentEntity) {
this.parentDepartment = departmentEntity;
}
public EmployeeEntity getManager() {
return manager;
}
public void setManager(EmployeeEntity manager) {
this.manager = manager;
}
public Set<DepartmentEntity> getChildDepartments() {
return departments;
}
public void setChildDepartments(Set<DepartmentEntity> departments) {
this.departments = departments;
}
public Set<EmployeeEntity> getDepartmentEmployees() {
return employees;
}
public void setDepartmentEmployees(Set<EmployeeEntity> employees) {
this.employees = employees;
}
}
@Entity
@Table(name = "job_title")
public class JobTitleEntity extends BaseEntity {
@Column(name = "name", unique = true)
@NotEmpty
private String name;
@OneToMany
private Set<EmployeeEntity> titleEmployees;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<EmployeeEntity> getTitleEmployees() {
return titleEmployees;
}
public void setTitleEmployees(Set<EmployeeEntity> titleEmployees) {
this.titleEmployees = titleEmployees;
}
}
Servlet.service() for servlet [dispatcher] in context with path [/api/orgchart] threw exception [Request processing failed; nested exception is org.springframework.dao.InvalidDataAccessResourceUsageException: could not execute statement; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not execute statement] with root cause
java.sql.SQLSyntaxErrorException: Table 'orgchart_api.employee_department' doesn't exist
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:120)
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97)
at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122)
at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:974)
at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1113)
at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1061)
at com.mysql.cj.jdbc.ClientPreparedStatement.executeLargeUpdate(ClientPreparedStatement.java:1381)
at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdate(ClientPreparedStatement.java:1046)
at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeUpdate(NewProxyPreparedStatement.java:105)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:204)
at org.hibernate.engine.jdbc.batch.internal.NonBatchingBatch.addToBatch(NonBatchingBatch.java:45)
at org.hibernate.persister.collection.AbstractCollectionPersister.remove(AbstractCollectionPersister.java:1203)
at org.hibernate.action.internal.CollectionRemoveAction.execute(CollectionRemoveAction.java:96)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:560)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:434)
at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:337)
at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39)
at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1295)
at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:468)
at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:3135)
at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2352)
at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:485)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:147)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$100(JdbcResourceLocalTransactionCoordinatorImpl.java:38)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:231)
at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:65)
at org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:61)
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:517)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:765)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:734)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:518)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:292)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:133)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:57)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213)
at com.sun.proxy.$Proxy126.save(Unknown Source)
at com.orgchart.service.EmployeeService.storeOrUpdate(EmployeeService.java:53)
at com.orgchart.web.controller.EmployeeController.createOrUpdateEmployee(EmployeeController.java:35)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:133)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:97)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:967)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:901)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:650)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:731)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:218)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:110)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:506)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:169)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:445)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1087)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:637)
at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:316)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
答案 0 :(得分:1)
@OneToMany
上的EmployeeEntity.ManagedDepartments
注释未指定mappedBy
属性,双向关系在“一个”侧是必需的。看来这种关系确实是双向的,因此应该是
@OneToMany(mappedBy = "manager")
private Set<DepartmentEntity> ManagedDepartments;
如果该关系实际上是单向的,则它将被映射到一个辅助表,就像在错误情况下您的JPA提供程序正在寻找的那样。此外,在实践中实际上是否查找该表还取决于特定实体对象的详细信息以及如何操作它们。