I decided to implement an unidirectional many-to-one association.
A Shelf contains many books. Each book can exist on only one shelf. No book can exist without being assigned a shelf.
The above would be represented in database by two tables - Shelf and a Book.
The Book would have a foreign-key reference to Shelf. In the Hibernate modelling the same could be represented by an Entity shelf that would have a collection of components (books.) This would however make it difficult to use the books with any other entities.
For example if we decided to give the book to a particular person to read there is no book identifier that could be bound to the person. In more technical terms the object book has valid justifications to have an independent life cycle and hence is represented as an Entity.
The association between the book and the shelf here is represented as a many-to-one mapping.
Cascade settings can also be applied for deletion.
Ideally a shelf must be created first before books must be saved. Similarly no books must be on the shelf when a shelf is to be deleted. For these reasons having the Book entity manage the shelf life-cycle does not make sense.
So I decided to go the other way - let Shelf manage the books that reside on it. And then I thought why not try and merge the two relations ?
A Shelf contains many books. Each book can exist on only one shelf. No book can exist without being assigned a shelf.
The above would be represented in database by two tables - Shelf and a Book.
The Book would have a foreign-key reference to Shelf. In the Hibernate modelling the same could be represented by an Entity shelf that would have a collection of components (books.) This would however make it difficult to use the books with any other entities.
For example if we decided to give the book to a particular person to read there is no book identifier that could be bound to the person. In more technical terms the object book has valid justifications to have an independent life cycle and hence is represented as an Entity.
The association between the book and the shelf here is represented as a many-to-one mapping.
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.collection.unidirectional2"> <class name="Shelf" table="SHELF"> <id name="id" type="integer"> <column name="ID" /> <generator class="native" /> </id> <property name="code" type="string"> <column name="CODE" length="50" not-null="true" /> </property> </class> </hibernate-mapping>The shelf class here has no relation to the book class.The mapping for book is below.
The book class includes the association to shelf. The not-null constraint here enforces that a book must be present on a shelf (used within the DDL only). The java classes for the entities is as below:<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.collection.unidirectional2"> <class name="Book" table="BOOK"> <id name="id" type="integer"> <column name="ID" /> <generator class="native" /> </id> <property name="name" type="string"> <column name="Name" length="50" not-null="true" /> </property> <many-to-one name="shelf" class="Shelf" foreign-key="BOOK_FK_1"> <column name="SHELF_ID" not-null="true"></column> </many-to-one> </class> </hibernate-mapping>
public class Shelf { private Integer id; private String code; //getter - setter methods } The class for Book includes a reference to the Shelf object:On start up the logs indicate the executed DDL statements :public class Book { private String name; private Integer id; private Shelf shelf; //getter - setter methods }
2531 [main] DEBUG org.hibernate.tool.hbm2ddl.SchemaExport - create table BOOK ( ID integer not null auto_increment, Name varchar(50) not null, SHELF_ID integer not null, primary key (ID) ) 2578 [main] DEBUG org.hibernate.tool.hbm2ddl.SchemaExport - create table SHELF ( ID integer not null auto_increment, CODE varchar(50) not null, primary key (ID) ) 2578 [main] DEBUG org.hibernate.tool.hbm2ddl.SchemaExport - alter table BOOK add index SHELF_FK_1 (SHELF_ID), add constraint SHELF_FK_1 foreign key (SHELF_ID) references SHELF (ID)I tried creating a single shelf with two books
static void create() { Shelf shelf1 = new Shelf(); shelf1.setCode("SH01"); Book book1 = new Book(); book1.setName("Lord Of The Rings"); book1.setShelf(shelf1); Book book2 = new Book(); book2.setName("Simply Fly"); book2.setShelf(shelf1); Session session = sessionFactory.openSession(); Transaction t = session.beginTransaction(); session.save(shelf1); session.save(book1); session.save(book2); t.commit(); System.out.println("The shelf with name " + shelf1.getCode() + " was created with id " + shelf1.getId()); System.out.println("Book1 saved with id " + book1.getId()
+ " and Book2 saved with id " + book2.getId());
}
The executed code will fire an sql insert for each of the save call made in the transaction.2938 [main] DEBUG org.hibernate.SQL - insert into SHELF (CODE) values (?) ... 2984 [main] DEBUG org.hibernate.SQL - insert into BOOK (Name, SHELF_ID) values (?, ?) ... 3000 [main] DEBUG org.hibernate.SQL - insert into BOOK (Name, SHELF_ID) values (?, ?) 3000 [main] DEBUG org.hibernate.impl.SessionImpl - after transaction completion The shelf with code SH01 was created with id 1 Book1 saved with id 1 and Book2 saved with id 2It is possible to reduce the number of save calls by using the association to mange cascading operations.(Although this is not a clean code in this example )We can save a shelf by simply saving the book the shelf is associated with it. The change needed would be to introduce cascade settings in the relationship.
<many-to-one name="shelf" class="Shelf" foreign-key="BOOK_FK_1" cascade="save-update"> <column name="SHELF_ID" not-null="true"></column> </many-to-one>The save for the above entities can be modified to below:
Transaction t = session.beginTransaction();
// session.save(shelf1);
session.save(book1);
session.save(book2);
t.commit();
I first executed the code without commenting the first save (one involving shelf). As I had used cascade update settings for the Book, I had expected some issue to occur if Hibernate tried to save the associated (and already existing shelf record) as a part of the cascade settings. However the session smartly detected that Shelf was a persistent instance and so the save operation wasn't necessary. 6453 [main] DEBUG org.hibernate.event.def.AbstractFlushingEventListener - flush ing session 6453 [main] DEBUG org.hibernate.event.def.AbstractFlushingEventListener - proce ssing flush-time cascades 6453 [main] DEBUG org.hibernate.engine.Cascade - processing cascade ACTION_SAVE _UPDATE for: com.collection.undirectional2.Book 6453 [main] DEBUG org.hibernate.engine.CascadingAction - cascading to saveOrUpd ate: com.collection.undirectional2.Shelf 6453 [main] DEBUG org.hibernate.event.def.AbstractSaveEventListener - persisten t instance of: com.collection.undirectional2.Shelf 6453 [main] DEBUG org.hibernate.event.def.DefaultSaveOrUpdateEventListener - ig noring persistent instance 6453 [main] DEBUG org.hibernate.event.def.DefaultSaveOrUpdateEventListener - ob ject already associated with session: [com.collection.undirectional2.Shelf#1] 6453 [main] DEBUG org.hibernate.engine.Cascade - done processing cascade ACTION _SAVE_UPDATE for: com.collection.undirectional2.BookWith the commented line, Hibernate will very smartly decide the sequence of SQL operations to execute and generate the same sequence of SQL statements as it did before.
Cascade settings can also be applied for deletion.
<many-to-one name="shelf" class="Shelf" foreign-key="BOOK_FK_1"
cascade="save-update,delete"> <column name="SHELF_ID" not-null="true"></column> </many-to-one>On trying to delete book1
Transaction t = session.beginTransaction(); Book book1 = (Book) session.get(Book.class, 1); session.delete(book1); t.commit();The logs generated indicating a failure to complete deletion:
2859 [main] ERROR org.hibernate.util.JDBCExceptionReporter - Cannot delete or u pdate a parent row: a foreign key constraint fails (`collections`.`book`, CONSTR AINT `BOOK_FK_1` FOREIGN KEY (`SHELF_ID`) REFERENCES `shelf` (`ID`)) Exception in thread "main"org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch updateShelf1 is bound to two Books - book1 and book2. Attempts to delete book1 fails as book2 is also placed on Shelf1. Hibernate expects the user to perform this delink manually. The entity book2 needs to be reassigned to a different shelf before Shelf1 can be deleted.
Ideally a shelf must be created first before books must be saved. Similarly no books must be on the shelf when a shelf is to be deleted. For these reasons having the Book entity manage the shelf life-cycle does not make sense.
So I decided to go the other way - let Shelf manage the books that reside on it. And then I thought why not try and merge the two relations ?
No comments:
Post a Comment