HHH-12662 - JPQL queries fail when using the Java attribute type which has an associated AttributeConverter (only the DB column type works)

This commit is contained in:
Vlad Mihalcea 2018-06-05 14:10:29 +03:00
parent bff9e2e450
commit 30fec4be79
3 changed files with 248 additions and 1 deletions

View File

@ -3,6 +3,7 @@
:modeldir: ../../../../../main/java/org/hibernate/userguide/model
:sourcedir: ../../../../../test/java/org/hibernate/userguide/mapping
:resourcedir: ../../../../../test/resources/org/hibernate/userguide/
:converter-sourcedir: ../../../../../../../hibernate-core/src/test/java/org/hibernate/test/converter
:extrasdir: extras
Basic value types usually map a single database column, to a single, non-aggregated Java type.
@ -536,6 +537,66 @@ JPA explicitly disallows the use of an AttributeConverter with an attribute mark
So if using the AttributeConverter approach, be sure not to mark the attribute as `@Enumerated`.
====
[[basic-attribute-converter-query-parameter]]
====== Using the AttributeConverter entity property as a query parameter
Assuming you have the following entity:
[[basic-attribute-converter-query-parameter-entity-example]]
.`Photo` entity with `AttributeConverter`
====
[source, JAVA, indent=0]
----
include::{converter-sourcedir}/ConverterTest.java[tags=basic-attribute-converter-query-parameter-entity-example]
----
====
And the `Caption` class looks as follows:
[[basic-attribute-converter-query-parameter-object-example]]
.`Caption` Java object
====
[source, JAVA, indent=0]
----
include::{converter-sourcedir}/ConverterTest.java[tags=basic-attribute-converter-query-parameter-object-example]
----
====
And we have an `AttributeConverter` to handle the `Caption` Java object:
[[basic-attribute-converter-query-parameter-converter-example]]
.`Caption` Java object AttributeConverter
====
[source, JAVA, indent=0]
----
include::{converter-sourcedir}/ConverterTest.java[tags=basic-attribute-converter-query-parameter-converter-example]
----
====
Traditionally, you could only use the dbData `Caption` representation, which in our case is a `String`, when referencing the `caption` entity property.
[[basic-attribute-converter-query-parameter-converter-dbdata-example]]
.Filtering by the `Caption` property using the DB data representation
====
[source, JAVA, indent=0]
----
include::{converter-sourcedir}/ConverterTest.java[tags=basic-attribute-converter-query-parameter-converter-dbdata-example]
----
====
In order to use the Java object `Caption` representation, you have to get the associated Hibernate `Type`.
[[basic-attribute-converter-query-parameter-converter-object-example]]
.Filtering by the `Caption` property using the Java Object representation
====
[source, JAVA, indent=0]
----
include::{converter-sourcedir}/ConverterTest.java[tags=basic-attribute-converter-query-parameter-converter-object-example]
----
====
By passing the associated Hibernate `Type`, you can use the `Caption` object when binding the query parameter value.
[[basic-hbm-attribute-converter]]
====== Mapping an AttributeConverter using HBM mappings

View File

@ -62,10 +62,10 @@ public class QueryParameterBindingImpl<T> implements QueryParameterBinding<T> {
if ( isBindingValidationRequired ) {
validate( value, clarifiedType );
}
bindValue( value );
if ( clarifiedType != null ) {
this.bindType = clarifiedType;
}
bindValue( value );
}
@Override

View File

@ -0,0 +1,186 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.test.converter;
import javax.persistence.AttributeConverter;
import javax.persistence.Convert;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.hibernate.SessionFactory;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.metamodel.spi.MetamodelImplementor;
import org.hibernate.query.Query;
import org.hibernate.type.Type;
import org.hibernate.type.descriptor.converter.AttributeConverterTypeAdapter;
import org.hibernate.testing.TestForIssue;
import org.junit.Test;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
import static org.junit.Assert.assertEquals;
/**
* @author Vlad Mihalcea
*/
@TestForIssue( jiraKey = "HHH-12662")
public class ConverterTest extends BaseEntityManagerFunctionalTestCase {
@Override
protected Class[] getAnnotatedClasses() {
return new Class[] { Photo.class };
}
@Override
protected void afterEntityManagerFactoryBuilt() {
doInJPA( this::entityManagerFactory, entityManager -> {
Photo photo = new Photo();
photo.setId( 1 );
photo.setName( "Dorobanțul" );
photo.setCaption( new Caption( "Nicolae Grigorescu" ) );
entityManager.persist( photo );
} );
}
@Test
public void testJPQLUpperDbValueBindParameter() throws Exception {
doInJPA( this::entityManagerFactory, entityManager -> {
//tag::basic-attribute-converter-query-parameter-converter-dbdata-example[]
Photo photo = entityManager.createQuery(
"select p " +
"from Photo p " +
"where upper(caption) = upper(:caption) ", Photo.class )
.setParameter( "caption", "Nicolae Grigorescu" )
.getSingleResult();
//end::basic-attribute-converter-query-parameter-converter-dbdata-example[]
assertEquals( "Dorobanțul", photo.getName() );
} );
}
@Test
public void testJPQLUpperAttributeValueBindParameterType() throws Exception {
doInJPA( this::entityManagerFactory, entityManager -> {
//tag::basic-attribute-converter-query-parameter-converter-object-example[]
SessionFactory sessionFactory = entityManager.getEntityManagerFactory()
.unwrap( SessionFactory.class );
MetamodelImplementor metamodelImplementor = (MetamodelImplementor) sessionFactory.getMetamodel();
Type captionType = metamodelImplementor
.entityPersister( Photo.class.getName() )
.getPropertyType( "caption" );
Photo photo = (Photo) entityManager.createQuery(
"select p " +
"from Photo p " +
"where upper(caption) = upper(:caption) ", Photo.class )
.unwrap( Query.class )
.setParameter( "caption", new Caption("Nicolae Grigorescu"), captionType)
.getSingleResult();
//end::basic-attribute-converter-query-parameter-converter-object-example[]
assertEquals( "Dorobanțul", photo.getName() );
} );
}
//tag::basic-attribute-converter-query-parameter-object-example[]
public static class Caption {
private String text;
public Caption(String text) {
this.text = text;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
@Override
public boolean equals(Object o) {
if ( this == o ) {
return true;
}
if ( o == null || getClass() != o.getClass() ) {
return false;
}
Caption caption = (Caption) o;
return text != null ? text.equals( caption.text ) : caption.text == null;
}
@Override
public int hashCode() {
return text != null ? text.hashCode() : 0;
}
}
//end::basic-attribute-converter-query-parameter-object-example[]
//tag::basic-attribute-converter-query-parameter-converter-example[]
public static class CaptionConverter
implements AttributeConverter<Caption, String> {
@Override
public String convertToDatabaseColumn(Caption attribute) {
return attribute.getText();
}
@Override
public Caption convertToEntityAttribute(String dbData) {
return new Caption( dbData );
}
}
//end::basic-attribute-converter-query-parameter-converter-example[]
//tag::basic-attribute-converter-query-parameter-entity-example[]
@Entity(name = "Photo")
public static class Photo {
@Id
private Integer id;
private String name;
@Convert(converter = CaptionConverter.class)
private Caption caption;
//Getters and setters are omitted for brevity
//end::basic-attribute-converter-query-parameter-entity-example[]
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Caption getCaption() {
return caption;
}
public void setCaption(Caption caption) {
this.caption = caption;
}
//tag::basic-attribute-converter-query-parameter-entity-example[]
}
//end::basic-attribute-converter-query-parameter-entity-example[]
}