I'm doing some comparison between EclipseLink and Hibernate 4.3 with JPA 2.1.
I found a difference between both implementations with joinList and treat:
Code:
private static void runDemo( EntityManager em ) throws Exception
{
em.getTransaction().begin();
TestEntity e;
long start = System.currentTimeMillis();
//let us create 10 dummy data, every one will have a TestEntityMultiExtensionA instance
//5 of them will have a TestEntityMultiExtensionB instance
for( int i=0; i<10; i++ )
{
e = new TestEntity();
e.customValues = new ArrayList<TestKeyValueEntity>();
TestEntityExtensionA meA = new TestEntityExtensionA();
meA.setParent( e );
meA.customValueA = "extA_"+i;
e.setExtension( meA );
if( i % 2 == 0 )
{
TestEntityExtensionB meB = new TestEntityExtensionB();
meB.customValueB = "extB_"+i;
meB.setParent( e );
e.setExtension( meB );
}
em.persist( e );
}
em.getTransaction().commit();
System.out.println( "\n\nCreation Time: " + ( System.currentTimeMillis() - start ) + "ms" );
//for testing without use of cache
em.clear();
em.getEntityManagerFactory().getCache().evictAll();
start = System.currentTimeMillis();
List<Tuple> result;
final CriteriaBuilder cb = em.getCriteriaBuilder();
//creating a Tuple query from TestEntity
final CriteriaQuery<Tuple> cq = cb.createTupleQuery();
final Root<TestEntity> root = cq.from( TestEntity.class );
final List<Selection<?>> selections = new LinkedList<Selection<?>>();
selections.add( root.get( "id" ) );
//now let us join to TestEntityMultiExtensionA and TestEntityMultiExtensionB:
final ListJoin< TestEntity, TestEntityExtension > baseJoinExtA = root.<TestEntity, TestEntityExtension>joinList( "extensions", JoinType.LEFT );
//additional on restriction to fetch only instances of TestEntityMultiExtensionA
baseJoinExtA.on( cb.equal( baseJoinExtA.get( "extensionType" ), TestEntityExtensionA.class.getName() ) );
//treat joined TestEntityExtension as TestEntityExtensionA to provide access of TestEntityExtensionA properties
final ListJoin<TestEntity, TestEntityExtensionA> joinExtA = cb.treat(
baseJoinExtA,
TestEntityExtensionA.class
);
selections.add( joinExtA.get( "id" ) );
selections.add( joinExtA.get( "customValueA" ) );
final ListJoin< TestEntity, TestEntityExtension > baseJoinExtB = root.<TestEntity, TestEntityExtension>joinList( "extensions", JoinType.LEFT );
//additional on restriction to fetch only instances of TestEntityMultiExtensionB
baseJoinExtB.on( cb.equal( baseJoinExtB.get( "extensionType" ), TestEntityExtensionB.class.getName() ) );
//treat joined TestEntityExtension as TestEntityExtensionB to provide access of TestEntityExtensionB properties
final ListJoin<TestEntity, TestEntityExtensionB> joinExtB = cb.treat(
baseJoinExtB,
TestEntityExtensionB.class
);
selections.add( joinExtB.get( "id" ) );
selections.add( joinExtB.get( "customValueB" ) );
cq.multiselect( selections );
result = em.createQuery( cq ).getResultList();
System.out.println( "\n\nFetch Time: " + ( System.currentTimeMillis() - start ) + "ms" );
for( Tuple r : result )
System.out.println( Arrays.toString( r.toArray() ) );
em.close();
}
Entities:
Code:
@MappedSuperclass
//EclipseLink Annotation
@PrimaryKey(validation=IdValidation.ZERO)
public abstract class Entity {
//JPA Annotations
@Id
@Column(name="id")
@GeneratedValue( generator = EntityConstants.HILO_GENERATOR )
//Hibernate Annotations, IdGenerator is a custom extension of MultipleHiLoPerTableGenerator which uses the SimpleClass name as valueName
@GenericGenerator( name = EntityConstants.HILO_GENERATOR, strategy = IdGenerator.NAME )
private long id;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
}
@javax.persistence.Entity(name="tests")
public class TestEntity extends Entity{
@OneToMany( cascade={CascadeType.ALL}, mappedBy="parent" )
@BatchFetch( size=EntityConstants.DEFAULT_BATCH_FETCH_SIZE, value=BatchFetchType.IN )
@CascadeOnDelete
private List<TestEntityExtension> extensions;
@Override
public List<TestEntityExtension> getExtensions() {
return extensions;
}
@Override
public void setExtensions(List<TestEntityExtension> extensions) {
this.extensions = extensions;
}
@Override
public <EE extends TestEntityExtension> EE getExtension( Class<EE> extension) {
if( extensions == null || extension == null )
return null;
for( E ext : extensions )
{
if( ext.getClass() == extension )
return (EE) ext;
}
return null;
}
@Override
public void setExtension( TestEntityExtension extension ) {
if( extensions == null )
extensions = new ArrayList<TestEntityExtension>();
if( extension != null )
{
@SuppressWarnings("unchecked")
final TestEntityExtension existingExtension = getExtension( extensions, (Class<? extends TestEntityExtension>)extension.getClass() );
if( existingExtension == null )
extensions.add( extension );
else if( existingExtension != extension )
{
extensions.remove( existingExtension );
extensions.add( extension );
}
}
}
}
@javax.persistence.Entity(name="tests_extensions")
@Inheritance(strategy=InheritanceType.JOINED)
@DiscriminatorColumn( name=IExtension.EXTENSION_TYPE_COLUMN_NAME, discriminatorType=DiscriminatorType.STRING )
@DiscriminatorOptions( force=true, insert=true)
public abstract class TestEntityExtension extends Entity
{
@Column(name=IExtension.EXTENSION_TYPE_COLUMN_NAME)
private String extensionType = getClass().getName();
public String getExtensionType() {
return extensionType;
}
@ManyToOne
@JoinColumn(name="test_id", nullable=false)
private TestEntity parent;
@Override
public TestEntity getParent() {
return parent;
}
@Override
public void setParent(TestEntity parent) {
this.parent = parent;
}
}
@Entity(name="tests_extensions_a")
public class TestEntityExtensionA extends TestEntityExtension {
@Column(name = "custom_value_a")
public String customValueA;
}
@Entity(name="tests_extensions_b")
public class TestEntityExtensionB extends TestEntityExtension {
@Column(name = "custom_value_b")
public String customValueB;
}
With EclipseLink the whole thing works like expected:
Code:
Creation Time: 22ms
Fetch Time: 9ms
[1, 1, extA_0, 2, extB_0]
[2, 3, extA_1, 0, null]
[3, 4, extA_2, 5, extB_2]
[4, 6, extA_3, 0, null]
[5, 7, extA_4, 8, extB_4]
[6, 9, extA_5, 0, null]
[7, 10, extA_6, 11, extB_6]
[8, 12, extA_7, 0, null]
[9, 13, extA_8, 14, extB_8]
[10, 15, extA_9, 0, null]
But with Hibernate i get following error:
Code:
Creation Time: 22ms
java.lang.IllegalArgumentException: Unable to resolve attribute [customValueA] against path [null]
at org.hibernate.jpa.criteria.path.AbstractPathImpl.unknownAttribute(AbstractPathImpl.java:112)
at org.hibernate.jpa.criteria.path.AbstractPathImpl.locateAttribute(AbstractPathImpl.java:209)
at org.hibernate.jpa.criteria.path.AbstractPathImpl.get(AbstractPathImpl.java:180)
at JpaTest.runDemo(Tester2.java:119)
at JpaTest.main(Tester2.java:41)
So i checked the sources of Hibernate and patched the Class org.hibernate.jpa.criteria.path.ListAttributeJoin:
Code:
//treat patch member
private ManagedType<?> treatedManagedType = null;
@Override
public <T extends E> ListAttributeJoin<O,T> treatAs(Class<T> treatAsType) {
//original implementation:
//return new TreatedListAttributeJoin<O,T>( this, treatAsType );
this.resetJavaType( treatAsType );
this.treatedManagedType = criteriaBuilder().getEntityManagerFactory().getMetamodel().managedType( treatAsType );
return (ListAttributeJoin<O, T>) this;
}
@Override
protected ManagedType<E> locateManagedType() {
if( treatedManagedType == null )
return super.locateManagedType();
else
return (ManagedType<E>) treatedManagedType;
}
Now the whole thing works like expected with Hibernate:
Code:
Creation Time: 20ms
Fetch Time: 20ms
[1, 1, extA_0, 2, extB_0]
[2, 3, extA_1, null, null]
[3, 4, extA_2, 5, extB_2]
[4, 6, extA_3, null, null]
[5, 7, extA_4, 8, extB_4]
[6, 9, extA_5, null, null]
[7, 10, extA_6, 11, extB_6]
[8, 12, extA_7, null, null]
[9, 13, extA_8, 14, extB_8]
[10, 15, extA_9, null, null]
For me the original Implementation is a bug.