(This is a rewrite of my other question)
I have a Spring WebMVC app that uses Hibernate as its backend. Since my domain model is changing constantly and I am not using a
There's only one problem with that: Base
should be annotated with @MappedSuperClass
instead of @Entity
. After that--and supplying a pom--it works fine, either with or without @Transactional
on the test method. I put it on github. You can browse it or clone and run it with
git clone git://github.com/zzantozz/testbed tmp
cd tmp
mvn clean test -pl stackoverflow/7809543-hibernate-spring-jpa
I think in distilling it, you cut out what was causing the problem. Sometimes when you're just completely lost, it's helpful to make a branch of your project (if you're in git) or a copy of it in a separate directory (if you're burdened with SVN) and start hacking out the stuff that isn't problematic until you can get it down to exactly the code/configuration that's causing the problem.
Update: Understanding dawns. First issue: the reason it behaves differently depending on whether the test is @Transactional or not is that your test transaction is different from your DataGenerator
transaction. In the test, the transaction is rolled back at the end of the test. In DataGenerator
, it's committed. More importantly the EntityManager isn't flushed in the test because that typically only happens at commit time. flush() is what causes SQL to be issued to the database, and that's where your error is coming from. If you inject your test with an EntityManager and call flush()
on it at the end of the test method, then you'll see the same behavior in your @Transactional test as what you're seeing in your non-@Transactional one now. This is pretty standard fare for tests when you're doing transaction rollbacks to keep your db clean.
Second issue: Because Base is an entity and not just a superclass containing some common fields like I initially thought, you're dealing with inheritance mapping. (Annotation-based inheritance is covered in the separate Hibernate Annotations reference.) Whether you realized it or not, you've implicitly chosen the "single table" inheritance strategy. It's the JPA default for mapping inheritance, for better or worse. That means that all fields of Base, ChildTypeA, and ChildTypeB are contained in the Base table. If you turn logging down to debug, you'll see that Hibernate is generating this table structure:
create table Base (DTYPE varchar(31) not null, id bigint generated by default as identity (start with 1), primary key (id))
create table Container (id bigint generated by default as identity (start with 1), primary key (id))
create table Container_Base (Container_id bigint not null, childrenB_id bigint not null, childrenA_id bigint not null, primary key (Container_id, childrenA_id), unique (childrenB_id), unique (childrenA_id))
Your trouble is coming from Container_Base. If you look carefully, you'll see that an entry in that table is required to have both a ChildTypeA primary key and a ChildTypeB primary key. This doesn't accurately reflect your object model, where they're two, unrelated collections. I'm not sure why Hibernate is doing that. My guess is that it just sees two (implicit) join table mappings--that's the two join tables for your one-to-many relationships--that have the same table name and combines them together. This looks like a candidate for a bug report to me. Anyway, there are at least two ways to solve this:
@JoinTable(name = "containerChildB")
as appropriate. This will create the two separate join tables you need but leave Base, ChildTypeA, and ChildTypeB all living in the same table.