Question

Let me start by saying, I'm not a Java programmer, but I'm trying to learn some new (for me) things.

I have following two DAOs: Shelf

package org.sample.bookshelf.model;

import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.OneToMany;
import javax.persistence.Table;

@Entity  
@Table(name="shelves") 
public class Shelf {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    private String name;

    @OneToMany(cascade=CascadeType.ALL)  
    @JoinTable(name="shelf_books",   
        joinColumns = {@JoinColumn(name="shelf_id", referencedColumnName="id")},  
        inverseJoinColumns = {@JoinColumn(name="book_id", referencedColumnName="id")}  
    )
    private Set<Book> shelfBooks; 

    public Shelf() {}

    public Shelf(String name) {
        this.name = name;
    }

    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }


    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    public Set<Book> getShelfBooks() {
        return shelfBooks;
    }
    public void setShelfBooks(Set<Book> shelfBooks) {
        this.shelfBooks = shelfBooks;
    }
}

and Book

package org.sample.bookshelf.model;

import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.OneToOne;
import javax.persistence.Table;

@Entity  
@Table(name="books") 
public class Book {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    private String title;

    @OneToOne(cascade=CascadeType.ALL)
    @JoinTable(name="shelf_books",
        joinColumns = {@JoinColumn(name="book_id", referencedColumnName="id")},  
        inverseJoinColumns = {@JoinColumn(name="shelf_id", referencedColumnName="id")}  
    )
    private Shelf block;

    public Book() {
    }

    public Book(String title)
    {
        this.title = title;
    }

    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public Shelf getShelf() {
        return block;
    }

    public void setShelf(Shelf block) {
        this.block = block;
    }
}

as you can see they are associated by by table: *shelf_books*

Now in BookDaoImpl I wanted to have method with all-or-nothing behaviour, that would let me save multiple books, it looks as follows:

@Override
public void saveMulti(List<Book> books) {
    Session sess = sessionFactory.openSession();
    org.hibernate.Transaction tx = null;
    try {
        tx = sess.beginTransaction();
        int i = 0;
        for (Book b : books) {
            logger.debug(String.format("%d %s ", b.getId(), b.getTitle()));
            sess.saveOrUpdate(b);

            i++;
            if (i == 20) {
                sess.flush();
                sess.clear();
                i = 0;
            }
        }
        logger.debug("saving...");
        tx.commit();
        logger.debug("done...");

    } catch (RuntimeException e) {
        if (tx != null) tx.rollback();
        e.printStackTrace();

    } finally {
        sess.close();
    }
}

For some reason, data to association table is added than deleted, it's visible in the following log containing delete collection org.sample.bookshelf.model.Shelf.shelfBooks:

16:08:13,857 DEBUG BookDaoImpl:58 - saving...
16:08:13,857 DEBUG AbstractTransactionImpl:175 - committing
16:08:13,857 DEBUG AbstractFlushingEventListener:149 - Processing flush-time cascades
16:08:13,857 DEBUG AbstractFlushingEventListener:189 - Dirty checking collections
16:08:13,859 DEBUG AbstractFlushingEventListener:123 - Flushed: 0 insertions, 1 updates, 0 deletions to 11 objects
16:08:13,859 DEBUG AbstractFlushingEventListener:130 - Flushed: 0 (re)creations, 0 updates, 1 removals to 0 collections
16:08:13,859 DEBUG EntityPrinter:114 - Listing entities:
16:08:13,860 DEBUG EntityPrinter:121 - org.sample.bookshelf.model.Book{id=2, title=The City of the Sun, block=org.sample.bookshelf.model.Shelf#1}
16:08:13,860 DEBUG EntityPrinter:121 - org.sample.bookshelf.model.Shelf{id=1, shelfBooks=null, name=dystopia}
16:08:13,860 DEBUG EntityPrinter:121 - org.sample.bookshelf.model.Book{id=1, title=1984, block=org.sample.bookshelf.model.Shelf#1}
16:08:13,860 DEBUG EntityPrinter:121 - org.sample.bookshelf.model.Book{id=6, title=Logan's Run, block=org.sample.bookshelf.model.Shelf#1}
16:08:13,860 DEBUG EntityPrinter:121 - org.sample.bookshelf.model.Book{id=5, title=Slaughterhouse Five, block=org.sample.bookshelf.model.Shelf#1}
16:08:13,860 DEBUG EntityPrinter:121 - org.sample.bookshelf.model.Book{id=4, title=Fahrenheit 451, block=org.sample.bookshelf.model.Shelf#1}
16:08:13,861 DEBUG EntityPrinter:121 - org.sample.bookshelf.model.Book{id=3, title=The Honeymoon Trip of Mr. Hamilton, block=org.sample.bookshelf.model.Shelf#1}
16:08:13,861 DEBUG EntityPrinter:121 - org.sample.bookshelf.model.Book{id=10, title=Brave New World, block=org.sample.bookshelf.model.Shelf#1}
16:08:13,861 DEBUG EntityPrinter:121 - org.sample.bookshelf.model.Book{id=9, title=Lord of the Flies, block=org.sample.bookshelf.model.Shelf#1}
16:08:13,861 DEBUG EntityPrinter:121 - org.sample.bookshelf.model.Book{id=8, title=Do Androids Dream of Electric Sheep?, block=org.sample.bookshelf.model.Shelf#1}
16:08:13,861 DEBUG EntityPrinter:121 - org.sample.bookshelf.model.Book{id=7, title=The Futurological Congress, block=org.sample.bookshelf.model.Shelf#1}
16:08:13,867 DEBUG SQL:109 - /* update org.sample.bookshelf.model.Shelf */ update shelves set name=? where id=?
16:08:13,869 DEBUG AbstractCollectionPersister:1174 - Deleting collection: [org.sample.bookshelf.model.Shelf.shelfBooks#1]
16:08:13,869 DEBUG SQL:109 - /* delete collection org.sample.bookshelf.model.Shelf.shelfBooks */ delete from shelf_books where shelf_id=?
16:08:13,870 DEBUG AbstractCollectionPersister:1232 - Done deleting collection
16:08:14,199 DEBUG JdbcTransaction:113 - committed JDBC Connection
16:08:14,199 DEBUG JdbcTransaction:126 - re-enabling autocommit
16:08:14,200 DEBUG BookDaoImpl:60 - done...

I guess for some reason Shelf doesn't know about added books, to make it complete, this is how I add the data:

        String titles[] = {
                "1984",
                "The City of the Sun",
                "The Honeymoon Trip of Mr. Hamilton",
                "Fahrenheit 451",
                "Slaughterhouse Five",
                "Logan's Run",
                "The Futurological Congress",
                "Do Androids Dream of Electric Sheep?",
                "Lord of the Flies",
                "Brave New World",      
        };
        Vector<Book> books = new Vector<Book>(titles.length);
        for (int i=0; i<titles.length; ++i) {
            Book book = new Book(titles[i]);
            book.setShelf(shelf);
            books.add(book);
        }
        bookDao.saveMulti(books);

as you can see I'm not calling shelf.setShelfBooks anywhere, If I'll do that, everything seems to be ok, but what hibernate does, looks bad, take a look at fragment of following log:

16:20:15,015 DEBUG EntityPrinter:121 - org.sample.bookshelf.model.Book{id=8, title=Do Androids Dream of Electric Sheep?, block=org.sample.bookshelf.model.Shelf#1}
16:20:15,015 DEBUG EntityPrinter:121 - org.sample.bookshelf.model.Book{id=7, title=Fahrenheit 451, block=org.sample.bookshelf.model.Shelf#1}
16:20:15,021 DEBUG SQL:109 - /* update org.sample.bookshelf.model.Shelf */ update shelves set name=? where id=?
16:20:15,023 DEBUG AbstractCollectionPersister:1174 - Deleting collection: [org.sample.bookshelf.model.Shelf.shelfBooks#1]
16:20:15,023 DEBUG SQL:109 - /* delete collection org.sample.bookshelf.model.Shelf.shelfBooks */ delete from shelf_books where shelf_id=?
16:20:15,026 DEBUG AbstractCollectionPersister:1232 - Done deleting collection
16:20:15,027 DEBUG AbstractCollectionPersister:1256 - Inserting collection: [org.sample.bookshelf.model.Shelf.shelfBooks#1]
16:20:15,028 DEBUG SQL:109 - /* insert collection row org.sample.bookshelf.model.Shelf.shelfBooks */ insert into shelf_books (shelf_id, book_id) values (?, ?)
16:20:15,030 DEBUG SQL:109 - /* insert collection row org.sample.bookshelf.model.Shelf.shelfBooks */ insert into shelf_books (shelf_id, book_id) values (?, ?)
16:20:15,030 DEBUG SQL:109 - /* insert collection row org.sample.bookshelf.model.Shelf.shelfBooks */ insert into shelf_books (shelf_id, book_id) values (?, ?)

I have feeling, I'm doing it wrong, can anyone tell me what should be PROPER way to do what I'd like?

Was it helpful?

Solution

I didn't went further than the two entities, because their mapping is wrong, which is probably the cause of your problems.

First problem:

You declare the books as

@OneToMany(cascade=CascadeType.ALL)  
@JoinTable(name="shelf_books",   
    joinColumns = {@JoinColumn(name="shelf_id", referencedColumnName="id")},  
    inverseJoinColumns = {@JoinColumn(name="book_id", referencedColumnName="id")}  
)
private Set<Book> shelfBooks; 

That means that a OneToMany association exists, mapped by a join table.

Then you declare the following:

@OneToOne(cascade=CascadeType.ALL)
@JoinTable(name="shelf_books",
    joinColumns = {@JoinColumn(name="book_id", referencedColumnName="id")},  
    inverseJoinColumns = {@JoinColumn(name="shelf_id", referencedColumnName="id")}  
)
private Shelf block;

That doesn't make sense.

You probably want the association to be bidirectional. Since it's a OneToMany on one side, it must be a ManyToOne one the other side. And in a bidirectional association, one side MUST be the inverse side (using the mappedBy attribute,a nd not declaring how the association is mapped, since it's already defined on the other side), and one side MUST be the owner side. In a OneToMany association, the inverse side MUST be the one side. So the set of books must be declared as:

@OneToMany(cascade = CascadeType.ALL, mappedBy = "block") 
private Set<Book> shelfBooks; 

Also note that using CascadeType.ALL on a ManyToOne doesn't make much sense. You don't want a shelf to be deleted as soon as one of the book it contains is deleted. I would advise removing all the cascades unless you really understand what the implications are.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top