更新联结 table 中的值很麻烦 spring 数据 JPA
updating value in junction table is troublesome spring data JPA
我有两个实体,一个是
@Entity
public class Job {
@Id
@GeneratedValue
private Integer id;
@Size(min = 5,max = 50, message = "Valid title is 5-50 chars")
private String title;
@ManyToMany(cascade = {CascadeType.MERGE})
@JoinTable(name = "jobs_categories",
joinColumns = @JoinColumn(name = "jobs_id"),
inverseJoinColumns = @JoinColumn(name = "categories_id"))
private List<Category> categories;
// omitting setters/getters
}
和其他
@Entity
public class Category {
@Id
@GeneratedValue
private Integer id;
@Size(min = 5,max = 15, message = "Valid name is 5-15 chars")
private String name;
@ManyToMany(mappedBy = "categories", cascade = {CascadeType.MERGE})
private List<Job> jobs;
// omitting setters/getters
}
我想对这两个实现 CRUD 操作,
我将一份包含某些类别的工作保存为
Job title - Fullstack JavaScript CSS3 / HTML5 / Developer
Categories - AngularJS, Backbone.js, CSS, CSS3, Graphic design, HTML, HTML5,
JavaScript, MongoDB Node.js, twitter bootstrap, Web design.
那么如果我想通过在上面保存的作业中添加或删除类别来更新作业,它永远不会更新(例如,adding/removing)类别。
为了更新作业,我在 JobService 中有方法
@Service
public class JobService{
@Autowired
private JobRepository jobRepository;
@Autowired
private CategoryRepository categoryRepository;
@Autowired
private LocalContainerEntityManagerFactoryBean entityManagerFactory;
private Session session;
Logger logger = LoggerFactory.getLogger(JobService.class);
public void updateJobCategory(Job job) {
EntityManager entityManager = entityManagerFactory.getObject().createEntityManager();
session= entityManager.unwrap(org.hibernate.Session.class);
logger.info("got Job ID as ==> " + String.valueOf(job.getId()));
Job jobObj = (Job) session.get(Job.class, job.getId());
jobObj.getCategories().clear();
logger.info("got Job ID as ==> " + String.valueOf(jobObj.getId()));
if (!job.getCategories().isEmpty()) {
logger.info("got job.getCategories() as not Null");
List<Category> categories = job.getCategories();
for (Category category : categories) {
logger.info("Good job.getCategories() to update ==> " + category.getName());
}
logger.info("adding job.getCategories() to update ==> ");
jobObj.setCategories(job.getCategories());
}else {
logger.info("got job.getCategories() as Null");
}
session.update(jobObj);
}
public void update(Job job) {
jobRepository.update(job.getId(), job.getTitle());
updateJobCategory(job);
}
}
我得到日志
logger.info("Got job.getCategories() to update" + category.getName());
我正在更新(删除了新的 added/old)所有类别的列表,
Hibernate: update job set title=? where id=?
INFO : com.rhcloud.jobsnetwork.service.JobService - got Job ID as ==> 1
Hibernate: select job0_.id as id1_1_0_, job0_.title as title7_1_0_ from job job0_ where job0_.id=?
Hibernate: select categories0_.jobs_id as jobs_id1_1_0_, categories0_.categories_id as categori2_2_0_, category1_.id as id1_0_1_, category1_.name as name2_0_1_ from jobs_categories categories0_ inner join category category1_ on categories0_.categories_id=category1_.id where categories0_.jobs_id=?
INFO : com.rhcloud.jobsnetwork.service.JobService - got Job ID as ==> 1
INFO : com.rhcloud.jobsnetwork.service.JobService - got job.getCategories() as not Null
INFO : com.rhcloud.jobsnetwork.service.JobService - Good job.getCategories() to update ==> iOS App Dev
INFO : com.rhcloud.jobsnetwork.service.JobService - Good job.getCategories() to update ==> Android App Dev
INFO : com.rhcloud.jobsnetwork.service.JobService - adding job.getCategories() to update
但是当我看到作业的详细信息时,我得到了列出的类别,这些类别是在创建作业时添加的,而不是更新的类别,我猜它没有更新交界处的类别 ID 值 table jobs_categories
.
不知道更新与工作相关的类别?
更新
当我使用
public void persistJob(Job newJob) {
em.persist(newJob);
}
public void saveJob(Job job) {
em.merge(job);
}
public void persistCategory(Category newcat) {
em.persist(newcat);
}
没有@Transactional
在每个方法我得到expception
javax.persistence.TransactionRequiredException: No transactional EntityManager available
at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:275)
at com.sun.proxy.$Proxy167.persist(Unknown Source)
at com.rhcloud.jobsnetwork.service.JobService.persistJob(JobService.java:29)
at com.rhcloud.jobsnetwork.service.JobService$$FastClassBySpringCGLIB$974ce3.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:649)
at com.rhcloud.jobsnetwork.service.JobService$$EnhancerBySpringCGLIB$$c08e8d16.persistJob(<generated>)
at com.rhcloud.jobsnetwork.controllers.JobController.addJobDetail(JobController.java:86)
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:497)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:221)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:137)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:110)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:776)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:705)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:959)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:966)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:868)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:650)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:842)
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:220)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:505)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:170)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:957)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:423)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1079)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:620)
at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:318)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
添加 @Transactional
后,它成功摆脱了上述异常,我使用
保存新工作
@RequestMapping(value = "/add-job", method = RequestMethod.POST)
public String addJobDetail(@ModelAttribute("job") Job job) {
jobService.persistJob(job);
return "redirect:/";
}
我让所有的事情都完美无缺,但是当我在 JobController 中使用 saveJob
作为
@RequestMapping(value = "/updated", method = RequestMethod.POST)
public String updateJob(@ModelAttribute("job") Job job) {
jobService.saveJob(job);
return "redirect:/";
}
更新的作业中没有保存任何类别(没有以前添加的没有更新的),在此情况下有任何建议。
更新 - 2
根据您的建议,我将记录器添加到
@Transactional
public void saveJob(Job job) {
logger.error("got job.getCategories() of " + job.getCategories().size());
em.merge(job);
}
此时我在
处得到 NullPointerException
logger.error("got job.getCategories() of " + job.getCategories().size() + " size");
log/trace 是
java.lang.NullPointerException
at com.rhcloud.jobsnetwork.service.JobService.saveJob(JobService.java:37)
at com.rhcloud.jobsnetwork.service.JobService$$FastClassBySpringCGLIB$974ce3.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:717)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
at org.springframework.transaction.interceptor.TransactionInterceptor.proceedWithInvocation(TransactionInterceptor.java:98)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:262)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:95)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:653)
at com.rhcloud.jobsnetwork.service.JobService$$EnhancerBySpringCGLIB$dc2ac2.saveJob(<generated>)
at com.rhcloud.jobsnetwork.controllers.JobController.updateJob(JobController.java:109)
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:497)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:221)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:137)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:110)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:776)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:705)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:959)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:966)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:868)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:650)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:842)
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:220)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:505)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:170)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:957)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:423)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1079)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:620)
at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:318)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
你的映射有误。
试试这个:
@Entity
public class Category {
@Id
@GeneratedValue
private Integer id;
@Size(min = 5,max = 15, message = "Valid name is 5-15 chars")
private String name;
@ManyToMany(cascade = CascadeType.ALL,orphanRemoval = true)
@JoinColumn(name = "jobs_id",referencedColumnName = "jobs_id")
private List<Job> jobs;
// omitting setters/getters
}
和第二个实体:
@Entity
public class Job {
@Id
@GeneratedValue
private Integer id;
@Size(min = 5,max = 50, message = "Valid title is 5-50 chars")
private String title;
@ManyToMany(cascade = CascadeType.ALL,orphanRemoval = true)
@JoinColumn(name = "categories_id",referencedColumnName = "categories_id")
private List<Category> categories;
// omitting setters/getters
}
P.s。像 table 一样编辑列名;
在您的情况下,您有两个实体,每个实体都可以独立存在 - 类别没有工作就可以,工作可以没有分配类别。 orphanRemoval
用于 OnetoMany
或 OneToOne
,如果您想对不再从父实体引用的相关实体执行删除操作(想想组合),这不是'情况并非如此。我相信您只是想删除工作和特定类别(思考关联)之间的 link。
你的映射是正确的(双向 manytomany 与 Job 作为关系所有者),我建议只做小的修改:
为您的集合使用 java.util.Set
(通过在连接 [=60= 时删除提高性能],防止 'can not fetch multiple bags' 异常)并覆盖 equals
方法(使集合操作更容易)。
我们开始:
@Entity
public class Job {
@Id
@GeneratedValue
private Integer id;
private String title;
@ManyToMany(fetch=FetchType.EAGER,cascade = { CascadeType.MERGE })
@JoinTable(name = "jobs_categories", joinColumns = @JoinColumn(name = "jobs_id") , inverseJoinColumns = @JoinColumn(name = "categories_id") )
private Set<Category> categories = new HashSet<Category>();
和类别
@Entity
public class Category {
@Id
@GeneratedValue
private Integer id;
private String name;
@ManyToMany(mappedBy = "categories", cascade = { CascadeType.MERGE })
private List<Job> jobs;
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Category other = (Category) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
更新函数中的一个问题是您覆盖了对 Job 实体中集合的引用。
jobObj.setCategories(job.getCategories());
您应该始终修改原始集合,切勿分配新集合。
您还可以考虑从 ManyToMany
中完全删除 Merge
级联,因为这将使代码工作流程更易于理解 - 类别将单独管理,您只需将已保存的类别添加到新的 Job 实例(但这只是一个建议,我们会让它与级联一起工作 :))。
现在来一些测试代码。我使用的是纯 JPA,而不是 Spring 存储库,以使其易于理解。
JobService(在你的情况下它将被分成多个类,存储库..):
public class JobService {
@PersistenceContext
EntityManager em;
public void persistJob(Job newJob) {
em.persist(newJob);
}
public Job getJob(Integer jobId) {
return em.find(Job.class, jobId);
}
public void saveJob(Job job) {
em.merge(job);
}
public void persistCategory(Category newcat) {
em.persist(newcat);
}
public List<Category> getAllCategories() {
return em.createQuery("from Category", Category.class).getResultList();
}
}
让我们测试一下:
Job job = new Job();
job.setTitle("Web developer");
Category catJS = new Category();
catJS.setName("javascript");
Category catCSS = new Category();
catCSS.setName("CSS");
Category catNG = new Category();
catNG.setName("AngualrJS");
Category catHTML = new Category();
catHTML.setName("HTML5");
//persist categories separately
jobservice.persistCategory(catNG);
jobservice.persistCategory(catCSS);
jobservice.persistCategory(catHTML);
jobservice.persistCategory(catJS);
//at this point, we have 4 categories as managed entities
//add the saved categories to a new job instance
job.getCategories().add(catHTML);
job.getCategories().add(catJS);
//save job in DB
jobservice.persistJob(job);
//we do not need to set Job references in Category objects, because Job is the relationship owner - it will correctly update the FKs in join table
assertNotNull("job should be saved", job.getId());
Job fromDb = jobservice.getJob(job.getId());//reload from DB
assertNotNull("job should be stored in DB", fromDb);
assertTrue("job should have 2 categories", fromDb.getCategories().size() == 2);
//great si far so good
//remove one of the assigned categories from Job(this is where you need that **equals** method in Category )
fromDb.getCategories().remove(catHTML);
assertTrue("job should not have only one category in Memory", fromDb.getCategories().size() ==1);
jobservice.saveJob(fromDb);
//we do not want to delete the *HTML* category from DB, just remove it from the particular job
Job fromDbAfterUpdate = jobservice.getJob(job.getId());
assertTrue("job should not have only one category in BD", fromDbAfterUpdate.getCategories().size() ==1);
List<Category> allCategories = jobservice.getAllCategories();
assertTrue("there should still be 4 categories in DB", allCategories.size() == 4);
//now lets test the Merge cascade
Category unsavedCategory = new Category();
unsavedCategory.setName("jquery");
fromDbAfterUpdate.getCategories().add(unsavedCategory );
//we have added an unsaved category to an existing job. As Job has cascade merge on it categories, the *unsavedCategory* will be saved and then linked to job via job_category join table
jobservice.saveJob(fromDbAfterUpdate);
fromDbAfterUpdate = jobservice.getJob(fromDbAfterUpdate.getId());
assertTrue("job should now have 2 categories", fromDbAfterUpdate.getCategories().size() ==2);
// one more test, add and remove at the same time
fromDbAfterUpdate.getCategories().remove(catJS);
fromDbAfterUpdate.getCategories().add(catHTML);
fromDbAfterUpdate.getCategories().add(catCSS);
jobservice.saveJob(fromDbAfterUpdate);
fromDbAfterUpdate = jobservice.getJob(fromDbAfterUpdate.getId());
assertTrue("job should now have 3 categories", fromDbAfterUpdate.getCategories().size() ==3);
allCategories = jobservice.getAllCategories();
assertTrue("there should be 5 categories in DB", allCategories.size() == 5);
TL;DR
- 保持你的更新方法简单 - 你不需要从数据库中获取对象并在托管对象和分离对象之间移动东西,让 JPA 合并完成它的事情
- 永远不要更改对托管实体集合的引用 - 直接对集合执行操作
- 在您真正需要它们之前省略任何级联
更多信息请咨询JPA Spec
我有两个实体,一个是
@Entity
public class Job {
@Id
@GeneratedValue
private Integer id;
@Size(min = 5,max = 50, message = "Valid title is 5-50 chars")
private String title;
@ManyToMany(cascade = {CascadeType.MERGE})
@JoinTable(name = "jobs_categories",
joinColumns = @JoinColumn(name = "jobs_id"),
inverseJoinColumns = @JoinColumn(name = "categories_id"))
private List<Category> categories;
// omitting setters/getters
}
和其他
@Entity
public class Category {
@Id
@GeneratedValue
private Integer id;
@Size(min = 5,max = 15, message = "Valid name is 5-15 chars")
private String name;
@ManyToMany(mappedBy = "categories", cascade = {CascadeType.MERGE})
private List<Job> jobs;
// omitting setters/getters
}
我想对这两个实现 CRUD 操作, 我将一份包含某些类别的工作保存为
Job title - Fullstack JavaScript CSS3 / HTML5 / Developer
Categories - AngularJS, Backbone.js, CSS, CSS3, Graphic design, HTML, HTML5,
JavaScript, MongoDB Node.js, twitter bootstrap, Web design.
那么如果我想通过在上面保存的作业中添加或删除类别来更新作业,它永远不会更新(例如,adding/removing)类别。
为了更新作业,我在 JobService 中有方法
@Service
public class JobService{
@Autowired
private JobRepository jobRepository;
@Autowired
private CategoryRepository categoryRepository;
@Autowired
private LocalContainerEntityManagerFactoryBean entityManagerFactory;
private Session session;
Logger logger = LoggerFactory.getLogger(JobService.class);
public void updateJobCategory(Job job) {
EntityManager entityManager = entityManagerFactory.getObject().createEntityManager();
session= entityManager.unwrap(org.hibernate.Session.class);
logger.info("got Job ID as ==> " + String.valueOf(job.getId()));
Job jobObj = (Job) session.get(Job.class, job.getId());
jobObj.getCategories().clear();
logger.info("got Job ID as ==> " + String.valueOf(jobObj.getId()));
if (!job.getCategories().isEmpty()) {
logger.info("got job.getCategories() as not Null");
List<Category> categories = job.getCategories();
for (Category category : categories) {
logger.info("Good job.getCategories() to update ==> " + category.getName());
}
logger.info("adding job.getCategories() to update ==> ");
jobObj.setCategories(job.getCategories());
}else {
logger.info("got job.getCategories() as Null");
}
session.update(jobObj);
}
public void update(Job job) {
jobRepository.update(job.getId(), job.getTitle());
updateJobCategory(job);
}
}
我得到日志
logger.info("Got job.getCategories() to update" + category.getName());
我正在更新(删除了新的 added/old)所有类别的列表,
Hibernate: update job set title=? where id=?
INFO : com.rhcloud.jobsnetwork.service.JobService - got Job ID as ==> 1
Hibernate: select job0_.id as id1_1_0_, job0_.title as title7_1_0_ from job job0_ where job0_.id=?
Hibernate: select categories0_.jobs_id as jobs_id1_1_0_, categories0_.categories_id as categori2_2_0_, category1_.id as id1_0_1_, category1_.name as name2_0_1_ from jobs_categories categories0_ inner join category category1_ on categories0_.categories_id=category1_.id where categories0_.jobs_id=?
INFO : com.rhcloud.jobsnetwork.service.JobService - got Job ID as ==> 1
INFO : com.rhcloud.jobsnetwork.service.JobService - got job.getCategories() as not Null
INFO : com.rhcloud.jobsnetwork.service.JobService - Good job.getCategories() to update ==> iOS App Dev
INFO : com.rhcloud.jobsnetwork.service.JobService - Good job.getCategories() to update ==> Android App Dev
INFO : com.rhcloud.jobsnetwork.service.JobService - adding job.getCategories() to update
但是当我看到作业的详细信息时,我得到了列出的类别,这些类别是在创建作业时添加的,而不是更新的类别,我猜它没有更新交界处的类别 ID 值 table jobs_categories
.
不知道更新与工作相关的类别?
更新
当我使用
public void persistJob(Job newJob) {
em.persist(newJob);
}
public void saveJob(Job job) {
em.merge(job);
}
public void persistCategory(Category newcat) {
em.persist(newcat);
}
没有@Transactional
在每个方法我得到expception
javax.persistence.TransactionRequiredException: No transactional EntityManager available
at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:275)
at com.sun.proxy.$Proxy167.persist(Unknown Source)
at com.rhcloud.jobsnetwork.service.JobService.persistJob(JobService.java:29)
at com.rhcloud.jobsnetwork.service.JobService$$FastClassBySpringCGLIB$974ce3.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:649)
at com.rhcloud.jobsnetwork.service.JobService$$EnhancerBySpringCGLIB$$c08e8d16.persistJob(<generated>)
at com.rhcloud.jobsnetwork.controllers.JobController.addJobDetail(JobController.java:86)
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:497)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:221)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:137)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:110)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:776)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:705)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:959)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:966)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:868)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:650)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:842)
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:220)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:505)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:170)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:957)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:423)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1079)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:620)
at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:318)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
添加 @Transactional
后,它成功摆脱了上述异常,我使用
@RequestMapping(value = "/add-job", method = RequestMethod.POST)
public String addJobDetail(@ModelAttribute("job") Job job) {
jobService.persistJob(job);
return "redirect:/";
}
我让所有的事情都完美无缺,但是当我在 JobController 中使用 saveJob
作为
@RequestMapping(value = "/updated", method = RequestMethod.POST)
public String updateJob(@ModelAttribute("job") Job job) {
jobService.saveJob(job);
return "redirect:/";
}
更新的作业中没有保存任何类别(没有以前添加的没有更新的),在此情况下有任何建议。
更新 - 2
根据您的建议,我将记录器添加到
@Transactional
public void saveJob(Job job) {
logger.error("got job.getCategories() of " + job.getCategories().size());
em.merge(job);
}
此时我在
处得到 NullPointerException logger.error("got job.getCategories() of " + job.getCategories().size() + " size");
log/trace 是
java.lang.NullPointerException
at com.rhcloud.jobsnetwork.service.JobService.saveJob(JobService.java:37)
at com.rhcloud.jobsnetwork.service.JobService$$FastClassBySpringCGLIB$974ce3.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:717)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
at org.springframework.transaction.interceptor.TransactionInterceptor.proceedWithInvocation(TransactionInterceptor.java:98)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:262)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:95)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:653)
at com.rhcloud.jobsnetwork.service.JobService$$EnhancerBySpringCGLIB$dc2ac2.saveJob(<generated>)
at com.rhcloud.jobsnetwork.controllers.JobController.updateJob(JobController.java:109)
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:497)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:221)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:137)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:110)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:776)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:705)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:959)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:966)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:868)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:650)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:842)
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:220)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:505)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:170)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:957)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:423)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1079)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:620)
at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:318)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
你的映射有误。 试试这个:
@Entity
public class Category {
@Id
@GeneratedValue
private Integer id;
@Size(min = 5,max = 15, message = "Valid name is 5-15 chars")
private String name;
@ManyToMany(cascade = CascadeType.ALL,orphanRemoval = true)
@JoinColumn(name = "jobs_id",referencedColumnName = "jobs_id")
private List<Job> jobs;
// omitting setters/getters
}
和第二个实体:
@Entity
public class Job {
@Id
@GeneratedValue
private Integer id;
@Size(min = 5,max = 50, message = "Valid title is 5-50 chars")
private String title;
@ManyToMany(cascade = CascadeType.ALL,orphanRemoval = true)
@JoinColumn(name = "categories_id",referencedColumnName = "categories_id")
private List<Category> categories;
// omitting setters/getters
}
P.s。像 table 一样编辑列名;
在您的情况下,您有两个实体,每个实体都可以独立存在 - 类别没有工作就可以,工作可以没有分配类别。 orphanRemoval
用于 OnetoMany
或 OneToOne
,如果您想对不再从父实体引用的相关实体执行删除操作(想想组合),这不是'情况并非如此。我相信您只是想删除工作和特定类别(思考关联)之间的 link。
你的映射是正确的(双向 manytomany 与 Job 作为关系所有者),我建议只做小的修改:
为您的集合使用 java.util.Set
(通过在连接 [=60= 时删除提高性能],防止 'can not fetch multiple bags' 异常)并覆盖 equals
方法(使集合操作更容易)。
我们开始:
@Entity
public class Job {
@Id
@GeneratedValue
private Integer id;
private String title;
@ManyToMany(fetch=FetchType.EAGER,cascade = { CascadeType.MERGE })
@JoinTable(name = "jobs_categories", joinColumns = @JoinColumn(name = "jobs_id") , inverseJoinColumns = @JoinColumn(name = "categories_id") )
private Set<Category> categories = new HashSet<Category>();
和类别
@Entity
public class Category {
@Id
@GeneratedValue
private Integer id;
private String name;
@ManyToMany(mappedBy = "categories", cascade = { CascadeType.MERGE })
private List<Job> jobs;
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Category other = (Category) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
更新函数中的一个问题是您覆盖了对 Job 实体中集合的引用。
jobObj.setCategories(job.getCategories());
您应该始终修改原始集合,切勿分配新集合。
您还可以考虑从 ManyToMany
中完全删除 Merge
级联,因为这将使代码工作流程更易于理解 - 类别将单独管理,您只需将已保存的类别添加到新的 Job 实例(但这只是一个建议,我们会让它与级联一起工作 :))。
现在来一些测试代码。我使用的是纯 JPA,而不是 Spring 存储库,以使其易于理解。
JobService(在你的情况下它将被分成多个类,存储库..):
public class JobService {
@PersistenceContext
EntityManager em;
public void persistJob(Job newJob) {
em.persist(newJob);
}
public Job getJob(Integer jobId) {
return em.find(Job.class, jobId);
}
public void saveJob(Job job) {
em.merge(job);
}
public void persistCategory(Category newcat) {
em.persist(newcat);
}
public List<Category> getAllCategories() {
return em.createQuery("from Category", Category.class).getResultList();
}
}
让我们测试一下:
Job job = new Job();
job.setTitle("Web developer");
Category catJS = new Category();
catJS.setName("javascript");
Category catCSS = new Category();
catCSS.setName("CSS");
Category catNG = new Category();
catNG.setName("AngualrJS");
Category catHTML = new Category();
catHTML.setName("HTML5");
//persist categories separately
jobservice.persistCategory(catNG);
jobservice.persistCategory(catCSS);
jobservice.persistCategory(catHTML);
jobservice.persistCategory(catJS);
//at this point, we have 4 categories as managed entities
//add the saved categories to a new job instance
job.getCategories().add(catHTML);
job.getCategories().add(catJS);
//save job in DB
jobservice.persistJob(job);
//we do not need to set Job references in Category objects, because Job is the relationship owner - it will correctly update the FKs in join table
assertNotNull("job should be saved", job.getId());
Job fromDb = jobservice.getJob(job.getId());//reload from DB
assertNotNull("job should be stored in DB", fromDb);
assertTrue("job should have 2 categories", fromDb.getCategories().size() == 2);
//great si far so good
//remove one of the assigned categories from Job(this is where you need that **equals** method in Category )
fromDb.getCategories().remove(catHTML);
assertTrue("job should not have only one category in Memory", fromDb.getCategories().size() ==1);
jobservice.saveJob(fromDb);
//we do not want to delete the *HTML* category from DB, just remove it from the particular job
Job fromDbAfterUpdate = jobservice.getJob(job.getId());
assertTrue("job should not have only one category in BD", fromDbAfterUpdate.getCategories().size() ==1);
List<Category> allCategories = jobservice.getAllCategories();
assertTrue("there should still be 4 categories in DB", allCategories.size() == 4);
//now lets test the Merge cascade
Category unsavedCategory = new Category();
unsavedCategory.setName("jquery");
fromDbAfterUpdate.getCategories().add(unsavedCategory );
//we have added an unsaved category to an existing job. As Job has cascade merge on it categories, the *unsavedCategory* will be saved and then linked to job via job_category join table
jobservice.saveJob(fromDbAfterUpdate);
fromDbAfterUpdate = jobservice.getJob(fromDbAfterUpdate.getId());
assertTrue("job should now have 2 categories", fromDbAfterUpdate.getCategories().size() ==2);
// one more test, add and remove at the same time
fromDbAfterUpdate.getCategories().remove(catJS);
fromDbAfterUpdate.getCategories().add(catHTML);
fromDbAfterUpdate.getCategories().add(catCSS);
jobservice.saveJob(fromDbAfterUpdate);
fromDbAfterUpdate = jobservice.getJob(fromDbAfterUpdate.getId());
assertTrue("job should now have 3 categories", fromDbAfterUpdate.getCategories().size() ==3);
allCategories = jobservice.getAllCategories();
assertTrue("there should be 5 categories in DB", allCategories.size() == 5);
TL;DR
- 保持你的更新方法简单 - 你不需要从数据库中获取对象并在托管对象和分离对象之间移动东西,让 JPA 合并完成它的事情
- 永远不要更改对托管实体集合的引用 - 直接对集合执行操作
- 在您真正需要它们之前省略任何级联
更多信息请咨询JPA Spec