Hello,
I am developing a simple application, where I use JPA. My JPA implementation provider is Hibernate Entity Manager.
In my application, I have a Category entity. The requirement is that a category can have its sub-categories and that it is easy to get category's parent as well as its list of children (if available of course).
So, here is how I implemented it in JPA terms (I omit the unnecessary code):
Code:
@Entity
@Table(name = "CATEGORY")
public class Category implements Serializable {
private static final long serialVersionUID = 8682428250257781834L;
private int id;
private Category parent;
private Collection<Category> subCategories;
public Category() {
super();
}
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "ID")
public int getId() {
return id;
}
@ManyToOne
@JoinColumn(name = "PARENT", referencedColumnName = "ID")
public Category getParent() {
return parent;
}
public void setParent(Category parent) {
this.parent = parent;
}
@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
public Collection<Category> getSubCategories() {
if (subCategories == null) {
subCategories = new ArrayList<Category>();
}
return subCategories;
}
public void setSubCategories(Collection<Category> subCategories) {
this.subCategories = subCategories;
}
}
Then I have a data access object (DAO) for this entity, and one of its methods is the delete one:
Code:
public class JpaCategoriesDao implements CategoriesDao {
public void deleteCategory(Category cat) throws ComparatorDaoException {
if (cat == null) {
log.warn("The category to delete is null");
return;
}
entityManager.getTransaction().begin();
Category catx = entityManager.find(Category.class, cat.getId());
if (catx != null) {
[b] entityManager.remove(catx);
entityManager.getTransaction().commit();
[/b] log.debug("Category " + cat + " was successfully removed.");
} else {
log.debug("Category " + cat + " does not exist.");
entityManager.getTransaction().rollback();
}
}
}
And now some details about the failing test.
Here is how I setup the test in-memory database:
Code:
insertCategory(1000002, "computers", "all kinds of computers");
insertCategory(1000003, "laptops", "portable computers", 1000002);
The above two rows mean that category 1000003 is child of 1000002.
Then I delete the child:
Code:
// Find both child and parent categories
Category cat = entityManager.find(Category.class, 1000003);
Category parent = entityManager.find(Category.class, 1000002);
dao.deleteCategory(cat); // Delete the child category
The last row calls the code from the second snippet in this post. According to the Hibernate Entity Manager logs, it executes:
Code:
Hibernate: delete from CATEGORY where ID=?
Hibernate: delete from CATEGORY where ID=?
And finally fails with:
Code:
org.hibernate.exception.GenericJDBCException: Could not execute JDBC batch update
at org.hibernate.exception.SQLStateConverter.handledNonSpecificException(SQLStateConverter.java:126)
at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:114)
at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66)
at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:275)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:266)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:172)
at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:50)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1027)
at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:365)
at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:137)
at org.hibernate.ejb.TransactionImpl.commit(TransactionImpl.java:54)
at bg.price.comparator.dao.jpa.JpaCategoriesDao.deleteCategory(JpaCategoriesDao.java:210)
at bg.price.comparator.dao.jpa.JpaCategoriesDaoTest.testDeleteSubCategoryCascadesToParent(JpaCategoriesDaoTest.java:216)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at junit.framework.TestCase.runTest(TestCase.java:164)
at junit.framework.TestCase.runBare(TestCase.java:130)
at junit.framework.TestResult$1.protect(TestResult.java:106)
at junit.framework.TestResult.runProtected(TestResult.java:124)
at junit.framework.TestResult.run(TestResult.java:109)
at junit.framework.TestCase.run(TestCase.java:120)
at junit.framework.TestSuite.runTest(TestSuite.java:230)
at junit.framework.TestSuite.run(TestSuite.java:225)
at org.eclipse.jdt.internal.junit.runner.junit3.JUnit3TestReference.run(JUnit3TestReference.java:130)
at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: java.sql.BatchUpdateException: failed batch
at org.hsqldb.jdbc.jdbcStatement.executeBatch(Unknown Source)
at org.hsqldb.jdbc.jdbcPreparedStatement.executeBatch(Unknown Source)
at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:70)
at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:268)
... 28 more
So... My question is what am I doing wrong in declaring the category/subcategories relations? Or maybe I call the delete in a wrong way?
Thank you!
Ivan