5 Replies Latest reply on Nov 1, 2006 12:48 PM by alexg79

    EJB3 PersistanceManager confusion

    jrosskopf

      Hello List,

      I try to do my first steps with EJB3 and set up a small example persisting a detatched pojo entity (which is generated in a client) through a stateless session bean.

      I´ll attach the code of the three components at the end of the message.
      When I invoke the the method, that should persist the entity I get the following exception:

      Exception in thread "main" javax.ejb.EJBException: javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: com.toolsforjobs.j2ee.test.server.domain.Debtor
       at org.jboss.ejb3.tx.Ejb3TxPolicy.handleExceptionInOurTx(Ejb3TxPolicy.java:69)
       at org.jboss.aspects.tx.TxPolicy.invokeInOurTx(TxPolicy.java:83)
       at org.jboss.aspects.tx.TxInterceptor$Required.invoke(TxInterceptor.java:197)
       at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:101)
       at org.jboss.aspects.tx.TxPropagationInterceptor.invoke(TxPropagationInterceptor.java:76)
       at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:101)
       at org.jboss.ejb3.stateless.StatelessInstanceInterceptor.invoke(StatelessInstanceInterceptor.java:62)
       at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:101)
       at org.jboss.aspects.security.AuthenticationInterceptor.invoke(AuthenticationInterceptor.java:78)
       at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:101)
       at org.jboss.ejb3.ENCPropagationInterceptor.invoke(ENCPropagationInterceptor.java:47)
       at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:101)
       at org.jboss.ejb3.asynchronous.AsynchronousInterceptor.invoke(AsynchronousInterceptor.java:106)
       at org.jboss.aop.joinpoint.MethodInvocation.invokeNext(MethodInvocation.java:101)
       at org.jboss.ejb3.stateless.StatelessContainer.dynamicInvoke(StatelessContainer.java:225)
       at org.jboss.aop.Dispatcher.invoke(Dispatcher.java:106)
       at org.jboss.aspects.remoting.AOPRemotingInvocationHandler.invoke(AOPRemotingInvocationHandler.java:82)
       at org.jboss.remoting.ServerInvoker.invoke(ServerInvoker.java:828)
       at org.jboss.remoting.ServerInvoker.invoke(ServerInvoker.java:681)
       at org.jboss.remoting.transport.socket.ServerThread.processInvocation(ServerThread.java:358)
       at org.jboss.remoting.transport.socket.ServerThread.dorun(ServerThread.java:398)
       at org.jboss.remoting.transport.socket.ServerThread.run(ServerThread.java:239)
      


      But that is exactly what I´m wanting: Saving a detached entry ;-)
      What I´m doing wrong?

      Regards
      ---
      Joachim

      The Entity Bean
      package com.toolsforjobs.j2ee.test.server.domain;
      
      import java.io.Serializable;
      
      import javax.persistence.CascadeType;
      import javax.persistence.Entity;
      import javax.persistence.GeneratedValue;
      import javax.persistence.Id;
      import javax.persistence.OneToOne;
      import javax.persistence.Transient;
      
      @Entity
      public class Debtor implements Serializable{
       @Transient
       private static final long serialVersionUID = 3154162344396113561L;
      
       int id;
       String idAtCustomer;
       double balance;
      
       //Address address;
      
       /*
       @OneToOne(cascade={CascadeType.ALL})
       public Address getAddress() {
       return address;
       }
       */
       public double getBalance() {
       return balance;
       }
      
       @Id @GeneratedValue
       public int getId() {
       return id;
       }
      
       public String getIdAtCustomer() {
       return idAtCustomer;
       }
       /*
       public void setAddress(Address address) {
       this.address = address;
       }
       */
       public void setBalance(double balance) {
       this.balance = balance;
       }
      
       public void setId(int id) {
       this.id = id;
       }
      
       public void setIdAtCustomer(String idAtCustomer) {
       this.idAtCustomer = idAtCustomer;
       }
      }
      


      The Stateless Session Bean
      package com.toolsforjobs.j2ee.test.server;
      
      import javax.ejb.Stateless;
      import javax.persistence.EntityManager;
      import javax.persistence.PersistenceContext;
      
      import com.toolsforjobs.j2ee.test.server.domain.Debtor;
      
      @Stateless
      public class DebtorAgentBean implements DebtorAgentRemote {
      
       @PersistenceContext(unitName="tfj-dbs") EntityManager manager;
      
       public Debtor findDebtor(int id) {
       return manager.find(Debtor.class, id);
       }
      
       public void saveDebtor(Debtor deb) {
       manager.persist(deb);
       }
      
      }
      


      And the calling client
      package com.toolsforjobs.j2ee.test.client;
      
      import javax.naming.Context;
      import javax.rmi.PortableRemoteObject;
      
      import com.toolsforjobs.j2ee.test.server.DebtorAgentRemote;
      import com.toolsforjobs.j2ee.test.server.domain.Address;
      import com.toolsforjobs.j2ee.test.server.domain.Debtor;
      
      public class DebtorClient {
       public static void main(String[] args) {
       try {
       Context jndiContext = getInitialContext();
       Object ref = jndiContext.lookup("DebtorAgentBean/remote");
       DebtorAgentRemote dao = (DebtorAgentRemote) PortableRemoteObject.narrow(ref, DebtorAgentRemote.class);
      
       Debtor deb1 = new Debtor();
       deb1.setId(1);
       deb1.setBalance(100.0d);
       deb1.setIdAtCustomer("toolsforjobs");
      
       dao.saveDebtor(deb1);
       } catch (javax.naming.NamingException ne) {
       ne.printStackTrace();
       }
       }
      
       public static Context getInitialContext() throws javax.naming.NamingException {
       return new javax.naming.InitialContext();
       }
      }
      


        • 1. Re: EJB3 PersistanceManager confusion
          alrubinger

          Hiya.

          Because you're specifying:

          Debtor deb1 = new Debtor();
          deb1.setId(1);


          ...the entity manager will see this as "detached" - in other words, an object that is already persisted in the database (because it has a primary key), but is not currently managed by the entity manager.

          The "persist" method is used to perform an SQL Insert for a *new* object; one without a PK.

          So you can either:

          1) Use EntityManager.merge() to synchronize these values with the database
          2) Don't specify a PK; don't set the ID.

          ...depending on your PK Generation strategy.



          • 2. Re: EJB3 PersistanceManager confusion
            jrosskopf

            Hey Al,

            thank you. That solves my problem!

            Bye
            ---
            Joachim

            • 3. Re: EJB3 PersistanceManager confusion
              lpiccoli

              hi al,

              can u explain the strategy us described reviously


              2) Don't specify the PK; dont set the ID


              Given the ID in the debtor class is an int.

               public void setId(int id) {
               this.id = id;
               }
              
              


              I have a question

              1) The default value with be '0'. How does the entitymanager.merge differenciate between '0' default and a debtor.setId(0)?

              I can see how it can identified as 'null' by using a Integer object instead of an int.

              many thanks

              -lp

              • 4. Re: EJB3 PersistanceManager confusion
                alrubinger

                 

                1) The default value with be '0'. How does the entitymanager.merge differentiate between '0' default and a debtor.setId(0)?


                I don't know. Pretty sure I've never had a row with a PK of 0. But I haven't looked into the implementation code to see how Hibernate handles this case specifically.

                As a general rule, I DO use the wrapper classes (ie. Long and Integer) for IDs so that I have the ability to specify "null".

                S,
                ALR

                • 5. Re: EJB3 PersistanceManager confusion
                  alexg79

                   

                  1) The default value with be '0'. How does the entitymanager.merge differenciate between '0' default and a debtor.setId(0)?

                  My guess is that the entity manager disregards the given PK if you have specified a generation strategy. The only other alternative I can think of is that 0 is considered a special value that has the same meaning as null.
                  I use atomic primary keys in my project. You should always use persist() when you're generating a new entity, and merge() only when updating an existing one.
                  The difference between persist() and merge() is that merge will look up the existing entity with the given PK, and if it doesn't exist, it will create a new entity, while persist() will not touch existing entities and should throw an exception if an entity with the given PK exists already.