HHH-10557 fix @Loader applied to a collection
the issue here is we have no @CollectionResult for annotation-based result set mappings
This commit is contained in:
parent
0db49aa2d5
commit
bfdd7f648b
|
@ -16,11 +16,9 @@ import jakarta.persistence.ElementCollection;
|
||||||
import jakarta.persistence.Entity;
|
import jakarta.persistence.Entity;
|
||||||
import jakarta.persistence.GeneratedValue;
|
import jakarta.persistence.GeneratedValue;
|
||||||
import jakarta.persistence.Id;
|
import jakarta.persistence.Id;
|
||||||
import jakarta.persistence.NamedNativeQueries;
|
|
||||||
import jakarta.persistence.NamedNativeQuery;
|
import jakarta.persistence.NamedNativeQuery;
|
||||||
|
|
||||||
import org.hibernate.annotations.Loader;
|
import org.hibernate.annotations.Loader;
|
||||||
import org.hibernate.annotations.ResultCheckStyle;
|
|
||||||
import org.hibernate.annotations.SQLDelete;
|
import org.hibernate.annotations.SQLDelete;
|
||||||
import org.hibernate.annotations.SQLDeleteAll;
|
import org.hibernate.annotations.SQLDeleteAll;
|
||||||
import org.hibernate.annotations.SQLInsert;
|
import org.hibernate.annotations.SQLInsert;
|
||||||
|
@ -37,6 +35,7 @@ import org.hibernate.testing.TestForIssue;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.hibernate.annotations.ResultCheckStyle.COUNT;
|
||||||
import static org.hibernate.cfg.AvailableSettings.DEFAULT_LIST_SEMANTICS;
|
import static org.hibernate.cfg.AvailableSettings.DEFAULT_LIST_SEMANTICS;
|
||||||
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
|
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
@ -71,9 +70,11 @@ public class CollectionLoaderTest extends BaseEntityManagerFunctionalTestCase {
|
||||||
session.doWork(connection -> {
|
session.doWork(connection -> {
|
||||||
try(Statement statement = connection.createStatement();) {
|
try(Statement statement = connection.createStatement();) {
|
||||||
statement.executeUpdate(String.format( "ALTER TABLE person %s valid %s",
|
statement.executeUpdate(String.format( "ALTER TABLE person %s valid %s",
|
||||||
getDialect().getAddColumnString(), ddlTypeRegistry.getTypeName( Types.BOOLEAN, getDialect())));
|
getDialect().getAddColumnString(),
|
||||||
|
ddlTypeRegistry.getTypeName( Types.BOOLEAN, getDialect())));
|
||||||
statement.executeUpdate(String.format( "ALTER TABLE Person_phones %s valid %s",
|
statement.executeUpdate(String.format( "ALTER TABLE Person_phones %s valid %s",
|
||||||
getDialect().getAddColumnString(), ddlTypeRegistry.getTypeName( Types.BOOLEAN, getDialect())));
|
getDialect().getAddColumnString(),
|
||||||
|
ddlTypeRegistry.getTypeName( Types.BOOLEAN, getDialect())));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -91,55 +92,42 @@ public class CollectionLoaderTest extends BaseEntityManagerFunctionalTestCase {
|
||||||
return person;
|
return person;
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
doInJPA(this::entityManagerFactory, entityManager -> {
|
||||||
|
Long postId = _person.getId();
|
||||||
doInJPA(this::entityManagerFactory, entityManager -> {
|
Person person = entityManager.find(Person.class, postId);
|
||||||
Long postId = _person.getId();
|
assertEquals(2, person.getPhones().size());
|
||||||
Person person = entityManager.find(Person.class, postId);
|
person.getPhones().remove(0);
|
||||||
assertEquals(2, person.getPhones().size());
|
person.setName("Mr. John Doe");
|
||||||
person.getPhones().remove(0);
|
});
|
||||||
person.setName("Mr. John Doe");
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
doInJPA(this::entityManagerFactory, entityManager -> {
|
doInJPA(this::entityManagerFactory, entityManager -> {
|
||||||
Long postId = _person.getId();
|
Long postId = _person.getId();
|
||||||
Person person = entityManager.find(Person.class, postId);
|
Person person = entityManager.find(Person.class, postId);
|
||||||
assertEquals(1, person.getPhones().size());
|
assertEquals(1, person.getPhones().size());
|
||||||
});
|
});
|
||||||
}
|
|
||||||
catch (Exception e) {
|
|
||||||
log.error("Throws NullPointerException because the bag is not initialized by the @Loader");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//tag::sql-custom-crud-example[]
|
//tag::sql-custom-crud-example[]
|
||||||
@Entity(name = "Person")
|
@Entity(name = "Person")
|
||||||
@SQLInsert(
|
@SQLInsert(sql = "INSERT INTO person (name, id, valid) VALUES (?, ?, true) ", check = COUNT)
|
||||||
sql = "INSERT INTO person (name, id, valid) VALUES (?, ?, true) ",
|
@SQLUpdate(sql = "UPDATE person SET name = ? where id = ? ")
|
||||||
check = ResultCheckStyle.COUNT
|
@SQLDelete(sql = "UPDATE person SET valid = false WHERE id = ? ")
|
||||||
)
|
|
||||||
@SQLUpdate(
|
|
||||||
sql = "UPDATE person SET name = ? where id = ? ")
|
|
||||||
@SQLDelete(
|
|
||||||
sql = "UPDATE person SET valid = false WHERE id = ? ")
|
|
||||||
@Loader(namedQuery = "find_valid_person")
|
@Loader(namedQuery = "find_valid_person")
|
||||||
@NamedNativeQueries({
|
@NamedNativeQuery(
|
||||||
@NamedNativeQuery(
|
name = "find_valid_person",
|
||||||
name = "find_valid_person",
|
query = "SELECT id, name " +
|
||||||
query = "SELECT id, name " +
|
"FROM person " +
|
||||||
"FROM person " +
|
"WHERE id = ? and valid = true",
|
||||||
"WHERE id = ? and valid = true",
|
resultClass = Person.class
|
||||||
resultClass = Person.class
|
)
|
||||||
),
|
@NamedNativeQuery(
|
||||||
@NamedNativeQuery(
|
name = "find_valid_phones",
|
||||||
name = "find_valid_phones",
|
query = "SELECT phones " +
|
||||||
query = "SELECT person_id, phones " +
|
"FROM Person_phones " +
|
||||||
"FROM Person_phones " +
|
"WHERE person_id = ? and valid = true "
|
||||||
"WHERE person_id = ? and valid = true "
|
)
|
||||||
)
|
|
||||||
})
|
|
||||||
public static class Person {
|
public static class Person {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
|
@ -149,10 +137,8 @@ public class CollectionLoaderTest extends BaseEntityManagerFunctionalTestCase {
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
@ElementCollection
|
@ElementCollection
|
||||||
@SQLInsert(
|
@SQLInsert(sql = "INSERT INTO person_phones (person_id, phones, valid) VALUES (?, ?, true) ")
|
||||||
sql = "INSERT INTO person_phones (person_id, phones, valid) VALUES (?, ?, true) ")
|
@SQLDeleteAll(sql = "UPDATE person_phones SET valid = false WHERE person_id = ?")
|
||||||
@SQLDeleteAll(
|
|
||||||
sql = "UPDATE person_phones SET valid = false WHERE person_id = ?")
|
|
||||||
@Loader(namedQuery = "find_valid_phones")
|
@Loader(namedQuery = "find_valid_phones")
|
||||||
private List<String> phones = new ArrayList<>();
|
private List<String> phones = new ArrayList<>();
|
||||||
|
|
||||||
|
|
|
@ -231,8 +231,8 @@ public final class CollectionEntry implements Serializable {
|
||||||
loadedKey = getCurrentKey();
|
loadedKey = getCurrentKey();
|
||||||
setLoadedPersister( getCurrentPersister() );
|
setLoadedPersister( getCurrentPersister() );
|
||||||
|
|
||||||
boolean resnapshot = collection.wasInitialized() &&
|
boolean resnapshot = collection.wasInitialized()
|
||||||
( isDoremove() || isDorecreate() || isDoupdate() );
|
&& ( isDoremove() || isDorecreate() || isDoupdate() );
|
||||||
if ( resnapshot ) {
|
if ( resnapshot ) {
|
||||||
snapshot = loadedPersister == null || !loadedPersister.isMutable() ?
|
snapshot = loadedPersister == null || !loadedPersister.isMutable() ?
|
||||||
null :
|
null :
|
||||||
|
|
|
@ -8,15 +8,19 @@ package org.hibernate.loader.ast.internal;
|
||||||
|
|
||||||
import org.hibernate.FlushMode;
|
import org.hibernate.FlushMode;
|
||||||
import org.hibernate.collection.spi.PersistentCollection;
|
import org.hibernate.collection.spi.PersistentCollection;
|
||||||
|
import org.hibernate.engine.spi.CollectionKey;
|
||||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||||
import org.hibernate.loader.ast.spi.CollectionLoader;
|
import org.hibernate.loader.ast.spi.CollectionLoader;
|
||||||
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
||||||
import org.hibernate.persister.collection.CollectionPersister;
|
import org.hibernate.persister.collection.CollectionPersister;
|
||||||
|
import org.hibernate.query.QueryTypeMismatchException;
|
||||||
import org.hibernate.query.named.NamedQueryMemento;
|
import org.hibernate.query.named.NamedQueryMemento;
|
||||||
import org.hibernate.query.spi.QueryImplementor;
|
import org.hibernate.query.spi.QueryImplementor;
|
||||||
|
|
||||||
import jakarta.persistence.Parameter;
|
import jakarta.persistence.Parameter;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Steve Ebersole
|
* @author Steve Ebersole
|
||||||
*/
|
*/
|
||||||
|
@ -36,10 +40,31 @@ public class CollectionLoaderNamedQuery implements CollectionLoader {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PersistentCollection<?> load(Object key, SharedSessionContractImplementor session) {
|
public PersistentCollection<?> load(Object key, SharedSessionContractImplementor session) {
|
||||||
final QueryImplementor<PersistentCollection<?>> query = namedQueryMemento.toQuery( session );
|
final QueryImplementor<?> query = namedQueryMemento.toQuery( session );
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
query.setParameter( (Parameter<Object>) query.getParameters().iterator().next(), key );
|
query.setParameter( (Parameter<Object>) query.getParameters().iterator().next(), key );
|
||||||
query.setHibernateFlushMode( FlushMode.MANUAL );
|
query.setHibernateFlushMode( FlushMode.MANUAL );
|
||||||
return query.getResultList().get( 0 );
|
final List<?> resultList = query.getResultList();
|
||||||
|
// TODO: we need a good way to inspect the query itself to see what it returns
|
||||||
|
if ( !resultList.isEmpty() && resultList.get(0) instanceof PersistentCollection<?> ) {
|
||||||
|
// in hbm.xml files we have the <load-collection/> element
|
||||||
|
return (PersistentCollection<?>) resultList.get(0);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// using annotations we have no way to specify a @CollectionResult
|
||||||
|
final CollectionKey collectionKey = new CollectionKey( persister, key );
|
||||||
|
final PersistentCollection<?> collection = session.getPersistenceContextInternal().getCollection( collectionKey );
|
||||||
|
for ( Object element : resultList ) {
|
||||||
|
if ( element != null && !persister.getElementType().getReturnedClass().isInstance( element ) ) {
|
||||||
|
throw new QueryTypeMismatchException( "Collection loader for '" + persister.getRole()
|
||||||
|
+ "' returned an instance of '" + element.getClass().getName() + "'" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
collection.beforeInitialize( persister, resultList.size() );
|
||||||
|
collection.injectLoadedState( getLoadable(), resultList );
|
||||||
|
collection.afterInitialize();
|
||||||
|
session.getPersistenceContextInternal().getCollectionEntry( collection ).postInitialize( collection );
|
||||||
|
return collection;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -360,7 +360,7 @@ public class ResultSetMappingImpl implements ResultSetMapping {
|
||||||
&& Objects.equals( legacyFetchBuilders, that.legacyFetchBuilders );
|
&& Objects.equals( legacyFetchBuilders, that.legacyFetchBuilders );
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return !that.isDynamic && mappingIdentifier.equals( that.mappingIdentifier );
|
return !that.isDynamic && mappingIdentifier != null && mappingIdentifier.equals( that.mappingIdentifier );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue