3

Ok, I was using EJB 3.0 with hibernate, we dropped our .ear file into Easy-Beans 1.0.1 (with Hibernate) deploy directory embedded into Apache Tomcat 6.0.18. So my database had to persist things like this:

@Entity
@Table(name="AUTHOR")
public class Author implements Serializable {

//////////////////////////////////////////////////////////////////
// Fields
//////////////////////////////////////////////////////////////////

@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "A_ID", unique=true, nullable = false)
private Integer id;

@Column (name = "A_NAME", unique = false, nullable = false)
private String name;

@Column (name = "A_LASTNAME", unique = false, nullable = false)
private String lastname;

@OneToMany(cascade = {ALL}, fetch = EAGER, mappedBy = "author")
private Set<BookAuthor> bookAuthors = new HashSet<BookAuthor>();

  @Override
  public boolean equals(Object o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;

      Author author = (Author) o;

      if (id != null ? !id.equals(author.id) : author.id != null) return false;

      return true;
  }

  @Override
  public int hashCode() {
      return id != null ? id.hashCode() : 0;
  }  
}

@Entity
@Table(name = "BOOK" )
public class Book implements Serializable {
//////////////////////////////////////////////////////////////////
// Fields
//////////////////////////////////////////////////////////////////
@Id
@GeneratedValue (strategy = IDENTITY)
@Column(name = "B_ID", unique = true, nullable = false)
private Integer bid;

@Column(name = "B_YEAR", unique = false, nullable = true)
private Integer year;

@Column(name = "B_ISBN", unique = false, nullable = false)
private String isbn;

@Column(name = "B_TITLE", unique = false, nullable = false)
private String title;

@OneToMany(cascade = {ALL}, fetch = EAGER, mappedBy = "book")
private Set<BookAuthor> bookAuthors = new HashSet<BookAuthor>();

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    Book book = (Book) o;

    if (isbn != null ? !isbn.equals(book.isbn) : book.isbn != null) return false;
    if (bid != null ? !kid.equals(book.bid) : book.bid != null) return false;

    return true;
  }

  @Override
  public int hashCode() {
    int result = bid != null ? bid.hashCode() : 0;
    result = 31 * result + (isbn != null ? isbn.hashCode() : 0);
    return result;
  }

}

@Entity
@Table(name = "BOOK_AUTHOR")
public class BookAuthor implements Serializable {

  //////////////////////////////////////////////////////////////////
  // Fields
  //////////////////////////////////////////////////////////////////
  @Id
  @GeneratedValue(strategy = IDENTITY)
  @Column(name = "BA_ID", unique=true, nullable = false)
  private Integer id;

  @Column(name = "BA_ROLE", unique = false, nullable = true)
  private String role;

  @ManyToOne
  @JoinColumn (name = "A_ID", referencedColumnName = "A_ID", nullable = false)
  private Author author;

  @ManyToOne
  @JoinColumn (name = "B_ID", referencedColumnName = "B_ID", nullable = false)
  private Book book;

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    BookAuthor that = (BookAuthor) o;

    if (auhtor != null ? !author.equals(that.author) : that.author != null) return false;
    if (book != null ? !book.equals(that.book) : that.book!= null) return false;
    if (id != null ? !id.equals(that.id) : that.id != null) return false;

    return true;
  }

  @Override
  public int hashCode() {
    int result = id != null ? id.hashCode() : 0;
    result = 31 * result + (author != null ? author.hashCode() : 0);
    result = 31 * result + (book != null ? book.hashCode() : 0);
    return result;
  }

}

So when removing items I had an entity bean that went something like this:

@Stateless
@Local(DeleteBookAuthor.class)
public class DeleteBookAuthorBean implements DeleteBookAuthor
{

   @PersistenceContext(unitName="Library")
   protected EntityManager em;

   @Override
   public void removeById(Integer id) {
      try{

        Query q = em.createQuery("SELECT ba FROM BookAuthor ba WHERE id = ?1");
        q.setParameter(1,id);

        BookAuthor ba = (BookAuthor) q.getSingleResult();

        ba.getAuthor().getBookAuthors().remove(ba);
        ba.getBook().getBookAuthors().remove(ba);

        em.remove(ba);
      }catch (Exception e){
         e.printStackTrace();
      }
   }
}

Unfortunatelly, when my Servlet calls this bean it returns a "deleted entity passed to persist" exception; However changing the lines:

Query q = em.createQuery("SELECT ba FROM BookAuthor ba WHERE id = ?1");
q.setParameter(1,id);        
BookAuthor ba = (BookAuthor) q.getSingleResult(); 

to

BookAuthor ba = em.find(BookAuthor.class, id)

makes the problem go away. The question I ask is why? In a similar situation I used em.createQuery to retrieve and delete muliple entities and it worked without a hitch. So why won't it work now?

UPDATE: Calling Query q=... and then removing the BookAuthors removes the BookAuthors from Books but not from the Authors. In second case it is removed from both sets. Both ba have same hash and return true when compared using baQuery.equals(baFind) .

Any clarifications why two functions would return same object but calling remove would behave differently depending on whether query/find is called?

Daniel Fath
  • 16,453
  • 7
  • 47
  • 82

2 Answers2

3

Perhaps it's related to the lack of equals()/hashCode() in BookAuthor. If so, it looks like in the case of query you have several different instances of BookAuthor with the same state, so they are not removed from the corresponding sets in Author and Book.

axtavt
  • 239,438
  • 41
  • 511
  • 482
  • `equals`, `hashCode` and `toString` exist in original classes as well as getters/setters/constructor. I didn't consider them important for review so I skip them. `equals` and `hashCode` only compare their keys. So in Author equals only compares id. – Daniel Fath Dec 02 '10 at 20:53
  • @Daniel: Anyways, are you sure that `BookAuthor` is actually removed from the sets? – axtavt Dec 02 '10 at 20:57
  • No, in first case it is not removed from sets :/ In the second it is. – Daniel Fath Dec 02 '10 at 21:54
  • @Daniel: I see you compare classes as `getClass() != o.getClass()`. It may fail if one of the classes is a lazy-loading proxy. – axtavt Dec 02 '10 at 22:49
  • Would `instanceof` help in that case? – Daniel Fath Dec 02 '10 at 22:53
  • 1
    generally it is considered a bad practice to use IDs in equals and hash code - what if your object is not attached to the session and, therefore, IDs are 0 or null? I always use "natural" keys. In case of `Author` this would be the combination of `name` and `lastName`. See this question for reference: http://stackoverflow.com/questions/27581/overriding-equals-and-hashcode-in-java – Denys Kniazhev-Support Ukraine Dec 03 '10 at 08:23
  • @Daniel: Yes, you can try something like `o instanceof BookAuthor`, however it may cause problems if you have inheritence hierarchies with `BookAuthor`. – axtavt Dec 03 '10 at 12:53
  • @denis_k: Thanks, I didn't know that. since this was homework it was suggested to us to use ID in equals in order to make sure that object equality = entity equality. On another note, natural keys are harder to implement in EJB and in case of Author there are cases in RL when different people have same last/first name. @axtavt: Ok, I'll try that later this evening. `BookAuthor` really isn't inherited so there should be no problem. – Daniel Fath Dec 03 '10 at 15:10
  • @axtavt: `instanceof` doesn't seem to work. The problem remains. All my entity beans are independent but it still fails to removes `BookAuthor` in first case for whatever reason. Still thanks for helping. – Daniel Fath Dec 03 '10 at 19:28
  • Axtavt, your help has been much appreciated, so I'm rewarding you with this answer even though the problem has not been completely clarified. – Daniel Fath Dec 12 '10 at 14:01
1

As far as I know, Query.getSingleResult() flushes the session under some circumstances, and I'm not sure aboutEntityManager.find(). I would follow axtavt's suggestion and doublecheck if your entity is removed from sets properly. And also I would call this statement before em.remove(ba); to make sure your ba object was re-attached to the session:

ba = em.merge(ba);