I have two Entities one as
@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
}
and other as
@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
}
I want to implement CRUD operations on those two,
I save one job with some categories as
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.
then if I want to update the job by adding or removing categories from above saved Job, it is never updating (e.g, adding/removing) categories.
for updating Job I have method in JobService as
@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);
}
}
I get log
logger.info("Got job.getCategories() to update" + category.getName());
I’m getting updated (new added/old removed) list of all categories,
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
but when I see details of Job I get the categories listed which were added on the creation of job not updated ones, I guess it is not updating categories id values in junction table jobs_categories
.
No idea of Updating categories related to job?
UPDATE
When I use
public void persistJob(Job newJob) {
em.persist(newJob);
}
public void saveJob(Job job) {
em.merge(job);
}
public void persistCategory(Category newcat) {
em.persist(newcat);
}
without @Transactional
at every method I get 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$$53974ce3.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)
After adding @Transactional
it works got rid of above exception, I save new job using
@RequestMapping(value = "/add-job", method = RequestMethod.POST)
public String addJobDetail(@ModelAttribute("job") Job job) {
jobService.persistJob(job);
return "redirect:/";
}
I get all the things working perfect, but when I use saveJob
in JobController as
@RequestMapping(value = "/updated", method = RequestMethod.POST)
public String updateJob(@ModelAttribute("job") Job job) {
jobService.saveJob(job);
return "redirect:/";
}
there is no any category(no earlier added no updated ones) saved in updated job, any suggestions in this case.
Update – 2
As per your suggestion I added logger to
@Transactional
public void saveJob(Job job) {
logger.error("got job.getCategories() of " + job.getCategories().size());
em.merge(job);
}
at this point I get NullPointerException at
logger.error("got job.getCategories() of " + job.getCategories().size() + " size");
log/trace is
java.lang.NullPointerException
at com.rhcloud.jobsnetwork.service.JobService.saveJob(JobService.java:37)
at com.rhcloud.jobsnetwork.service.JobService$$FastClassBySpringCGLIB$$53974ce3.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$1.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$$41dc2ac2.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)
2
Answers
You have wrong mapping.
Try this:
and second entity:
P.s. edit column names as in table;
In your case, you have two entities, that can each exist on their own – category is fine without a job, and a job can have no categories assigned.
orphanRemoval
is used inOnetoMany
orOneToOne
, if you would like to perform a delete operation on the related entity that is no longer referenced from the parent entity(think composition), which isn’t the case here. I believe you merely want to remove the link between the job and a the particular Category(think association).You mapping is correct (bidirectional manytomany with Job as the relationship owner), I would suggest only minor modifications:
Use
java.util.Set
(improve performance with delete on join table, prevent ‘can not fetch multiple bags’ exception) for you collections and overrideequals
method(to make collection operations easier).Here we go:
And Category
One of the issues in your update function is that you override a reference to the collection in the Job entity.
jobObj.setCategories(job.getCategories());
You should always modify the original collection, never assign a new collection.
You might also consider removing the
Merge
cascade altogether from yourManyToMany
as it will make the code workflow easier to understand – categories will be managed separately and you only add already saved categories to the new Job instance(but this is just a suggestion, we will make it work even with the cascade 🙂 ).Now for some test code. I am using plain JPA, not Spring repositories, to make it simple to understand.
JobService(in your case it will be split into multiple classes, repositories..):
And lets test it:
TL;DR
For more info, please consult JPA Spec