Our site open new forum on english

събота, 6 февруари 2010 08:05

Our site open new forum on english

http://en.javabg.eu/ – here you can visit our english forum.

Popularity: 13% [?]

Hibernate – Many-to-many relationship example

понеделник, 8 февруари 2010 11:43

Hibernate – Many-to-many relationship example

“Many-to-many” example

This is a many-to-many relationship table design, a STOCK table has more than one CATEGORY, and CATEGORY can belong to more than one STOCK, the relationship is linked with a third table called STOCK_CATEGORY.

many-to-many-relationship

Hibernate implementation

In Hibernate, this “many-to-many” relationship can implemented in two ways :

  • Hibernate XML Mapping file
  • Hibernate Annotation

1. Hibernate XML mapping file

In XML mapping way, you need three Java classes to hold the table data,

  • Stock.java -> STOCK table
  • Category.java -> CATEGORY table
  • StockCategory.java -> STOCK_CATEGORY table

and three XML mapping files to describe the table relationship.

  • Stock.hbm.xml -> STOCK table
  • Category.hbm.xml -> CATEGORY table
  • StockCategory.hbm.xml -> STOCK_CATEGORY table
Stock.java
public class Stock implements java.io.Serializable {
   private Set<StockCategory> stockCategories = new HashSet<StockCategory>(0);
   ...
   public Set<StockCategory> getStockCategories() {
	return this.stockCategories;
   }

   public void setStockCategories(Set<StockCategory> stockCategories) {
	this.stockCategories = stockCategories;
   }
Category.java
public class Category implements java.io.Serializable {
   private Set<StockCategory> stockCategories = new HashSet<StockCategory>(0);
   ...
   public Set<StockCategory> getStockCategories() {
	return this.stockCategories;
   }

   public void setStockCategories(Set<StockCategory> stockCategories) {
	this.stockCategories = stockCategories;
   }
StockCategory.java
public class StockCategory implements java.io.Serializable {
   private Integer stockCategoryId;
   private Stock stock;
   private Category category;
   ...
Stock.hbm.xml
...
<class name="com.mkyong.common.Stock" table="stock" ...>
   <id name="stockId" type="java.lang.Integer">
      <column name="STOCK_ID" />
      <generator class="identity" />
   </id>
   ...
   <set name="stockCategories" inverse="true" lazy="true" 
       table="stock_category" ...>
       <key>
         <column name="STOCK_ID" not-null="true" />
       </key>
          <one-to-many class="com.mkyong.common.StockCategory" />
   </set>
...

Set inverse=”true” in set variable, Stock does not want to maintain the relationship.

Category.hbm.xml
...
<class name="com.mkyong.common.Category" table="category" ...>
   <id name="categoryId" type="java.lang.Integer">
       <column name="CATEGORY_ID" />
       <generator class="identity" />
   </id>
   ...
   <set name="stockCategories" inverse="true" lazy="true" 
       table="stock_category" ...>
      <key>
         <column name="CATEGORY_ID" not-null="true" />
      </key>
         <one-to-many class="com.mkyong.common.StockCategory" />
   </set>
...

Set inverse=”true” in set variable, Category does not want to maintain the relationship also.

StockCategory.hbm.xml
...
<class name="com.mkyong.common.StockCategory" table="stock_category" ...>
   <id name="stockCategoryId" type="java.lang.Integer">
      <column name="STOCK_CATEGORY_ID" />
      <generator class="identity" />
   </id>
   <many-to-one name="stock" class="com.mkyong.common.Stock" ...>
       <column name="STOCK_ID" not-null="true" />
   </many-to-one>
   <many-to-one name="category" class="com.mkyong.common.Category" ...>
        <column name="CATEGORY_ID" not-null="true" />
    </many-to-one>
</class>
...

StockCategory maintain the relationship between Stock and Category, see inverse=”true” explanation.

2. Hibernate annotation

In annotation way, three classes are required, all the relationships are declared inside the Java classes.

  • Stock.java -> STOCK table
  • category.java -> CATEGORY table
  • StockCategory.java -> STOCK_CATEGORY table
Stock.java
...
@Entity
@Table(name = "stock", catalog = "mkyong", uniqueConstraints = {
		@UniqueConstraint(columnNames = "STOCK_NAME"),
		@UniqueConstraint(columnNames = "STOCK_CODE") })
public class Stock implements java.io.Serializable {
	private Set<StockCategory> stockCategories =
                                         new HashSet<StockCategory>(0);
	...
	@Id
	@GeneratedValue(strategy = IDENTITY)
	@Column(name = "STOCK_ID", unique = true, nullable = false)
	public Integer getStockId() {
		return this.stockId;
	}
        ...
	@OneToMany(fetch = FetchType.LAZY, mappedBy = "stock")
	public Set<StockCategory> getStockCategories() {
		return this.stockCategories;
	}

	public void setStockCategories(Set<StockCategory> stockCategories) {
		this.stockCategories = stockCategories;
	}
...
Category.java
...
@Entity
@Table(name = "category", catalog = "mkyong")
public class Category implements java.io.Serializable {
	private Set<StockCategory> stockCategories =
                                       new HashSet<StockCategory>(0);
        ...
	@Id
	@GeneratedValue(strategy = IDENTITY)
	@Column(name = "CATEGORY_ID", unique = true, nullable = false)
	public Integer getCategoryId() {
		return this.categoryId;
	}
	...
	@OneToMany(fetch = FetchType.LAZY, mappedBy = "category")
	public Set<StockCategory> getStockCategories() {
		return this.stockCategories;
	}

	public void setStockCategories(Set<StockCategory> stockCategories) {
		this.stockCategories = stockCategories;
	}
...
StockCategory.java
@Entity
@Table(name = "stock_category", catalog = "mkyong",
        uniqueConstraints = @UniqueConstraint(columnNames = {
        "STOCK_ID", "CATEGORY_ID" }))
public class StockCategory implements java.io.Serializable {
	private Integer stockCategoryId;
	private Stock stock;
	private Category category;
	...
	@Id
	@GeneratedValue(strategy = IDENTITY)
	@Column(name = "STOCK_CATEGORY_ID", unique = true, nullable = false)
	public Integer getStockCategoryId() {
		return this.stockCategoryId;
	}

	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "STOCK_ID", nullable = false)
	public Stock getStock() {
		return this.stock;
	}

	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "CATEGORY_ID", nullable = false)
	public Category getCategory() {
		return this.category;
	}
...

The code is self-explanatory, all the @annotation are the XML mapping tag substitution.

Test It

Run the example, both annotation and XML mapping file will generate the same output. Hibernate inserts a row into the STOCK table, a row into the CATEGORY table, and both relationship in STOCK_CATEGORY table.

        Stock stock = new Stock();
        Category category = new Category();
        ...
        session.save(stock);
        session.save(category);
        ...
        StockCategory stockCategory = new StockCategory(stock, category);
        session.save(stockCategory);
Output
Hibernate:
    insert into mkyong.stock
    (STOCK_CODE, STOCK_NAME)
    values (?, ?)

Hibernate:
    insert into mkyong.category
    (`DESC`, NAME)
    values (?, ?)

Hibernate:
    insert into mkyong.stock_category
    (CATEGORY_ID, STOCK_ID)
    values (?, ?)

Popularity: 42% [?]

Cascade – JPA & Hibernate annotation common mistake

неделя, 7 февруари 2010 07:54

Cascade – JPA & Hibernate annotation common mistake

Mistake

In the one-to-many example, many developers declared the JPA cascade options as following :

...
@Entity
@Table(name = "stock", catalog = "mkyong", uniqueConstraints = {
		@UniqueConstraint(columnNames = "STOCK_NAME"),
		@UniqueConstraint(columnNames = "STOCK_CODE") })
public class Stock implements java.io.Serializable {
    ...
    private Set<StockDailyRecord> stockDailyRecords =
                                              new HashSet<StockDailyRecord>(0);

    @OneToMany(fetch = FetchType.LAZY,
       cascade = {CascadeType.PERSIST,CascadeType.MERGE },
       mappedBy = "stock")
    public Set<StockDailyRecord> getStockDailyRecords() {
	return this.stockDailyRecords;
    }

    public void setStockDailyRecords(Set<StockDailyRecord> stockDailyRecords) {
	this.stockDailyRecords = stockDailyRecords;
    }
    ...

Save it with Hibernate session.

        stockDailyRecords.setStock(stock);
        stock.getStockDailyRecords().add(stockDailyRecords);

        session.save(stock);
        session.getTransaction().commit();

What the code trying to do is when you save a “stock”, it will save the associated stockDailyRecords as well. Everything look fine, but THIS IS NOT WORKING, the cascade options will not execute and save the stockDailyRecords. Can you spot the mistake?

Explanation

Look in the code, @OneToMany is from JPA , it expected a JPA cascade – javax.persistence.CascadeType. However when you save it with Hibernate session, org.hibernate.engine.Cascade will do the following checking …

	if ( style.doCascade( action ) ) {
		cascadeProperty(
	          persister.getPropertyValue( parent, i, entityMode ),
		  types[i],
    	          style,
	          anything,
	          false
		);
	}

The Hibernate save process will causing a ACTION_SAVE_UPDATE action, but the JPA will pass a ACTION_PERSIST and ACTION_MERGE, it will not match and causing the cascade failed to execute.

@see source code

  • org.hibernate.engine.Cascade
  • org.hibernate.engine.CascadeStyle
  • org.hibernate.engine.CascadingAction

Solution

Delete the JPA cascade – javax.persistence.CascadeType, replace it with Hibernate cascade – org.hibernate.annotations.Cascade, with CascadeType.SAVE_UPDATE.

...
@Entity
@Table(name = "stock", catalog = "mkyong", uniqueConstraints = {
		@UniqueConstraint(columnNames = "STOCK_NAME"),
		@UniqueConstraint(columnNames = "STOCK_CODE") })
public class Stock implements java.io.Serializable {
    ...
    private Set<StockDailyRecord> stockDailyRecords =
                                              new HashSet<StockDailyRecord>(0);

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "stock")
    @Cascade({CascadeType.SAVE_UPDATE})
    public Set<StockDailyRecord> getStockDailyRecords() {
	return this.stockDailyRecords;
    }

    public void setStockDailyRecords(Set<StockDailyRecord> stockDailyRecords) {
	this.stockDailyRecords = stockDailyRecords;
    }
    ...

Now, it work as what you expected.

Conclusion

It look like an incompatible issue between JPA and Hibernate cascade annotation, if Hibernate is a JPA implementation, what may causing the misunderstand in between?

Popularity: 21% [?]