Announcement Announcement Module
Collapse
No announcement yet.
Error deleting owning side of one-to-one relationship Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Error deleting owning side of one-to-one relationship

    Goal

    I want to have a bidirectional one-to-one relationship between a Person and a Passport, such that a Person can have either zero or one Passports, and each Passport is owned by exactly one Person (not real life, but work with me). When I delete a Person, any Passport they have should also be deleted, but not vice versa (i.e. you should be able to delete a Passport without deleting its holder). In the database, I want the foreign key to be in the "passport" table.

    Here's my script:

    Code:
    project --topLevelPackage one
    persistence setup --provider HIBERNATE --database HYPERSONIC_IN_MEMORY 
    entity --class ~.Person
    entity --class ~.Passport
    field reference --class ~.Passport --fieldName holder   --type ~.Person
    field reference --class ~.Person   --fieldName passport --type ~.Passport
    controller all --package ~.web
    I've edited my entities to look like this (noting that the Roo 1.0.2 shell doesn't seem to allow the creation of 1-1 relationships):

    Code:
    @Entity
    @RooJavaBean
    @RooToString
    @RooEntity
    public class Person {
    
        // Inverse side ("passport" table has the FK column)
        @OneToOne(cascade = CascadeType.ALL, mappedBy = "holder")
        private Passport passport;
    
        @Override    
        public String toString() {
          return super.toString();  // avoids circular references
        }
    }
    Code:
    @Entity
    @RooJavaBean
    @RooToString
    @RooEntity
    public class Passport {
    
        // Owning side (this table has the FK column)
        @OneToOne
        @JoinColumn
        private Person holder;
    
        @Override    
        public String toString() {
          return super.toString();  // avoids circular references
        }
    }
    Outcome

    If I delete a Person from the Person list, it's all good; both the Person and the Passport are deleted. So the cascading works. But if I delete a Passport, I get this rather unhelpful error (and no errors in the Jetty console):

    Code:
    Could not commit JPA transaction; nested exception is  javax.persistence.RollbackException: Error while commiting the transaction
    Code:
    org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:476)
    org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:754)
    org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:723)
    org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:394)
    org.springframework.transaction.aspectj.AbstractTransactionAspect.ajc$afterReturning$org_springframework_transaction_aspectj_AbstractTransactionAspect$3$2a73e96c(AbstractTransactionAspect.aj:78)
    one.Passport_Roo_Entity.ajc$interMethod$one_Passport_Roo_Entity$one_Passport$remove(Passport_Roo_Entity.aj:61)
    one.Passport.remove(Passport.java:1)
    one.Passport_Roo_Entity.ajc$interMethodDispatch1$one_Passport_Roo_Entity$one_Passport$remove(Passport_Roo_Entity.aj)
    one.web.PassportController_Roo_Controller.ajc$interMethod$one_web_PassportController_Roo_Controller$one_web_PassportController$delete(PassportController_Roo_Controller.aj:79)
    one.web.PassportController.delete(PassportController.java:1)
    sun.reflect.NativeMethodAccessorImpl.invoke0(Native  Method)
    sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    java.lang.reflect.Method.invoke(Method.java:597)
    org.springframework.web.bind.annotation.support.HandlerMethodInvoker.doInvokeMethod(HandlerMethodInvoker.java:710)
    org.springframework.web.bind.annotation.support.HandlerMethodInvoker.invokeHandlerMethod(HandlerMethodInvoker.java:167)
    org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.invokeHandlerMethod(AnnotationMethodHandlerAdapter.java:414)
    org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.handle(AnnotationMethodHandlerAdapter.java:402)
    org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:771)
    org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:716)
    org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:647)
    org.springframework.web.servlet.FrameworkServlet.doDelete(FrameworkServlet.java:585)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:733)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:820)
    org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:487)
    org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:362)
    org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)
    org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:181)
    org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:726)
    org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:405)
    org.mortbay.jetty.servlet.Dispatcher.forward(Dispatcher.java:285)
    org.mortbay.jetty.servlet.Dispatcher.forward(Dispatcher.java:126)
    org.tuckey.web.filters.urlrewrite.NormalRewrittenUrl.doRewrite(NormalRewrittenUrl.java:195)
    org.tuckey.web.filters.urlrewrite.RuleChain.handleRewrite(RuleChain.java:159)
    org.tuckey.web.filters.urlrewrite.RuleChain.doRules(RuleChain.java:141)
    org.tuckey.web.filters.urlrewrite.UrlRewriter.processRequest(UrlRewriter.java:90)
    org.tuckey.web.filters.urlrewrite.UrlRewriteFilter.doFilter(UrlRewriteFilter.java:417)
    org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1084)
    org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:68)
    org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)
    org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1084)
    org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88)
    org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)
    org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1084)
    org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter.doFilterInternal(OpenEntityManagerInViewFilter.java:113)
    org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:76)
    org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1084)
    org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:360)
    org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)
    org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:181)
    org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:726)
    org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:405)
    org.mortbay.jetty.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:206)
    org.mortbay.jetty.handler.HandlerCollection.handle(HandlerCollection.java:114)
    org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
    org.mortbay.jetty.Server.handle(Server.java:324)
    org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:505)
    org.mortbay.jetty.HttpConnection$RequestHandler.content(HttpConnection.java:843)
    org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:648)
    org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:211)
    org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:380)
    org.mortbay.io.nio.SelectChannelEndPoint.run(SelectChannelEndPoint.java:395)
    org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.java:488
    Workaround

    If I push in the PassportController#delete method and insert the highlighted line:

    Code:
    @RequestMapping(value = "/passport/{id}", method = RequestMethod.DELETE)
    public String delete(@PathVariable("id") Long id,
        @RequestParam(value = "page", required = false) Integer page,
        @RequestParam(value = "size", required = false) Integer size)
    {
        if (id == null) throw new IllegalArgumentException("An Identifier is required");
        Passport passport = Passport.findPassport(id);
        passport.getHolder().setPassport(null);  // added this line
        passport.remove();
        return "redirect:/passport?page=" + ((page == null) ? "1" : page.toString()) + "&size=" + ((size == null) ? "10" : size.toString());
    }
    .. then it all works nicely. But is that workaround actually necessary, or is there something wrong with my JPA annotations?

  • #2
    Yes, currently the support of the JPA @OneToOne has somewhat limited support in Roo. Can you please open a Jira ticket with your description below? Maybe even attach the complete script (and manual editing steps) to replicate the app. This will probably not be fixed in time for the 1.1 M1 release but possibly for M2.

    -Stefan

    Comment


    • #3
      Stefan, I'm happy to log an issue if necessary, but are you sure it's a problem with Roo? I'm still not clear whether my workaround is desirable or correct; are you saying that it's OK and Roo should add it by default?

      Comment


      • #4
        Andrew,

        My asking for a Jira ticket was more intended to say that I need to review exactly what Roo should do to help users which such scenarios. I am not sure if your workaround would be generally advisable. Maybe, all we need to do is to offer this as a solution for this specific problem in the Roo documentation.

        The problem with the forum is that these threads vanish at some stage and all your valuable input is lost, so creating a jira ticket will help us to track the issue (which may result in a code change or just docs adjustments).

        Cheers,
        Stefan

        Comment


        • #5
          Done

          OK, I've logged it as ROO-669.

          Comment

          Working...
          X