JPA joinList and treat behaviour

Description

Joining to a List an calling treat as don't work like expected.

It's not possible to access Attributes of treated Class:

1 2 3 4 5 6 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)

I have patched org.hibernate.jpa.criteria.path.ListAttributeJoin to solve this:

ListAttributeJoin.java

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 //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; }

Sample Code:

JpaTest.java

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 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(); }

Entites.java

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 @MappedSuperclass 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; }

Environment

None

Status

Assignee

Andrea Boriero

Reporter

Philip Mair

Fix versions

backPortable

None

Suitable for new contributors

Yes, likely

Requires Release Note

None

Pull Request

None

backportDecision

None

Components

Affects versions

4.3.7
4.3.0.Beta3
5.1.0
5.0.9
5.2.0

Priority

Major
Configure