8 Replies Latest reply on Nov 4, 2005 5:15 AM by Emmanuel Bernard

    EntityManager.merge() is doing INSERTs not UPDATEs

    Aaron Novice

      I have noticed that I am getting duplicate records in MySQL when doing merge()'s. When I pass an entity bean to my session bean for updating, and then call EntityManager.merge(), the EntityManager is performing another INSERT.

      I am running JBoss4.0.3 using MySQL Dialect.

      Below, the servlet looks up the session bean, loads a Ticket (EntityBean) from the session bean, and passes Ticket to the method closeTicket().

      In the session bean, I take the ticket object, modify it, then EntityManager.merge(ticket) to update the properties.

      Rather than performing an UPDATE to the Ticket table, the EntityManager performs another INSERT, creating duplicate records in my DB (other than the autogenerated ID)

      Is this by design? Did I miss a step?

      SERVLET:

      public class TestTicketManager extends ServletTestCase {
      
       InitialContext ctx;
      
       //load the session bean
       TicketManager ticketMgr;
      
       public static Test suite() {
       TestSuite testSuite = new TestSuite();
       testSuite.addTestSuite(TestTicketManager.class);
       return testSuite;
       }
      
       public void setUp() {
       try {
       ctx = new InitialContext();
       ticketMgr = (TicketManager)ctx.lookup(TicketManager.class.getName());
       } catch (NamingException e){
       e.printStackTrace();
       }
       }
      
       public void testCreateTicket() {
       long id = 0;
       String problem = "Testing from Cactus";
       id = ticketMgr.createTicket(problem);
       assertTrue(id>0);
       }
      
      
       public void testCloseTicket() {
       //load the first ticket
       long id = 1;
      
       //this is supposed to be an isolated test
       //find a way to remove the loadTicket() method.
       Ticket ticket = ticketMgr.loadTicket(new Long(id));
       ticketMgr.closeTicket(ticket,"The printer was not plugged in.");
      
       }
      }
      


      Session bean

      @Stateless
      public class TicketBean implements TicketManager {
      
       @PersistenceContext(unitName="TechDeskEM")
       EntityManager em;
      
       public long createTicket(String problem) {
       ..... snipped .....
       }
      
      
       public Ticket loadTicket(long ticketId) {
       Query query = em.createQuery("from Request req where req.class=Ticket and req.id=:id");
       query.setParameter("id",new Long(ticketId));
       return (Ticket) query.getSingleResult();
       }
      
      
       public void closeTicket(Ticket ticket, String resolution) {
       List<Note> history = ticket.getNotes();
       // Create the note closing the ticket
       Note note = new Note();
       note.setAction(Note.ACTION_CLOSED);
       note.setCreated(new Date());
       history.add(note);
       // apply the new history to the ticket
       ticket.setNotes(history);
       ticket.setResolution(resolution);
       ticket.setStatus(Ticket.STATUS_CLOSED);
       em.merge(ticket);
      
       }
      }
      


      Any help would be appreciated!

      --Aaron

        • 2. Re: EntityManager.merge() is doing INSERTs not UPDATEs
          Aaron Novice

           

          "epbernard" wrote:
          can you show the ticket source code


          Sorry. I thought that an Entity Bean would be trivial as it's just getter and setter methods. However, to be thorough, I should have posted it. Thanks and sorry.

          SUPERCLASS
          @Entity
          @Table(name="REQUEST")
          @Inheritance(strategy=InheritanceType.JOINED)
          public class Request implements Serializable {
          
           private long id;
           private User owner;
           private Date created;
           private List<Note> notes;
           private Date completed;
          
           public Request () {}
          
           @Id(generate=GeneratorType.AUTO)
           @Column(name="id")
           public long getId() {
           return id;
           }
          
           @Column(name="created")
           public Date getCreated() {
           return created;
           }
          
           @Column(name="completed")
           public Date getCompleted() {
           return completed;
           }
          
           @ManyToOne(fetch=FetchType.LAZY)
           @JoinColumn(name="user_id")
           public User getOwner() {
           return owner;
           }
          
           @OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER, mappedBy="request")
           public List<Note> getNotes () {
           return notes;
           }
          
           public void setId(long id) {
           this.id = id;
           }
          
           public void setOwner(User owner){
           this.owner = owner;
           }
          
           public void setNotes(List<Note> notes) {
           this.notes = notes;
           }
          
           public void setCreated(Date created) {
           this.created = created;
           }
          
           public void setCompleted(Date completed) {
           this.completed = completed;
           }
          }
          


          SUBCLASS:
          @Entity
          @Inheritance(strategy=InheritanceType.JOINED)
          @Table(name="TICKET")
          public class Ticket extends Request {
           //private long id;
           private User tech;
           private String problem;
           private String resolution;
           private String status;
          
           public static final String STATUS_ACTIVE ="Active";
           public static final String STATUS_ASSIGNED = "Assigned";
           //public static final String STATUS_SUSPENDED = "Suspended";
           public static final String STATUS_CLOSED = "Closed";
          
           public Ticket () {}
           // We do not need an ID, as this is joined to it's parent class id.
           //@Id(generate=GeneratorType.AUTO)
           //@Column(name="id")
           //public long getId() {
           // return id;
           //}
          
           @Column(name="problem")
           public String getProblem() {
           return problem;
           }
          
           @Column(name="resolution")
           public String getResolution() {
           return resolution;
           }
          
           @Column(name="status")
           public String getStatus() {
           return status;
           }
          
           @ManyToOne(fetch=FetchType.EAGER)
           @JoinColumn(name="tech_id")
           public User getTech() {
           return tech;
           }
          
           //public void setId(long id) {
           // this.id = id;
           //}
          
           public void setTech(User tech) {
           this.tech = tech;
           }
          
           public void setProblem(String problem) {
           this.problem = problem;
           }
          
           public void setStatus(String status) {
           this.status = status;
           }
          
           public void setResolution(String resolution) {
           this.resolution = resolution;
           }
          }
          


          • 3. Re: EntityManager.merge() is doing INSERTs not UPDATEs
            Aaron Novice

            So far, I've gotten around it by referencing the ID of the ticket, and having the ticket do a load() everytime. While not efficient, it's getting me by until I can fix this.

            However, this will pose serious problems in the future, as I will have many more Entity Beans interacting with others and performance will drop significantly. This is a fairly large project (of course, 'large' is a relative term)

            Thanks for the response!!!!

            --Aaron

            • 4. Re: EntityManager.merge() is doing INSERTs not UPDATEs
              Emmanuel Bernard Master

              Well I don't know, It seems the tx is not commited when you actually create the ticket.

              • 5. Re: EntityManager.merge() is doing INSERTs not UPDATEs
                Aaron Novice

                Thanks for the look-over and response, Emmanuel.

                Here is the snipped code that you believe may be an issue:

                public long createTicket(String problem) {
                 List<Note> history;
                 long ticketId;
                
                 //Create a new ticket
                 Ticket ticket = new Ticket();
                 ticket.setProblem(problem);
                 ticket.setCreated(new Date());
                 ticket.setStatus(Ticket.STATUS_ACTIVE);
                
                 //Initialize and populate history for the new ticket
                 history = new ArrayList<Note>();
                 Note note = new Note();
                 note.setAction(Note.ACTION_CREATED);
                 note.setCreated(new Date());
                 note.setRequest(ticket);
                 history.add(note);
                
                 //Add history to the ticket
                 ticket.setNotes(history);
                
                 //Save
                 em.persist(ticket);
                 ticketId = ticket.getId();
                
                 return ticketId;
                 }
                


                Could it be something I'm doing? Again, for right now, I'm just passing the Id of the ticket to the closeTicket() method, and I'm having the closeTicket() method do the lookup, modify, and then persist().

                However, it sure would be nice to actually pass the ticket in rather than re-loading the already loaded ticket.

                I'm still learning EJB3, but in using straight hibernate, you had to re-associate the detached object prior to doing anything with it. Am I to assume the EntityManager does this automatically? I believe that's what I read.

                Thanks again!

                • 6. Re: EntityManager.merge() is doing INSERTs not UPDATEs
                  Emmanuel Bernard Master

                  it seems that this piece of code is actually never commited in DB.

                  • 7. Re: EntityManager.merge() is doing INSERTs not UPDATEs
                    Aaron Novice

                    I manually entered in the data in the database in the import.sql statement when the App starts for my testing.

                    That way, I know for a fact, that this particular ticket will always have the id=1 , etc.

                    Can hibernate not play well with external apps editing the data of the DB? Running a select * brings back the ticket data in the DB.

                    I can load the ticket just fine.

                    But, after running the testCloseTicket() method, I get a duplicate row in my DB, with a new ID, using merge(). What does merge() need that my DB doesn't have?

                    What is the difference with adding the data manually or from a 3rd party as long as the DB fields are correctly filled in?

                    Thank you!

                    • 8. Re: EntityManager.merge() is doing INSERTs not UPDATEs
                      Emmanuel Bernard Master

                      What you describe works, it's part of the unit test suite. There should be something else in your environment that makes it wrong.