HHH-8697 - AttributeConverter not called when value is null;
HHH-9320 - AttributeConverter result ignored on extraction when ResultSet.wasNull
(cherry picked from commit 27e8aae279
)
This commit is contained in:
parent
14829949ad
commit
ff2b0fbb29
|
@ -46,7 +46,7 @@ public interface ValueExtractor<X> {
|
|||
*/
|
||||
public X extract(ResultSet rs, String name, WrapperOptions options) throws SQLException;
|
||||
|
||||
public X extract(CallableStatement rs, int index, WrapperOptions options) throws SQLException;
|
||||
public X extract(CallableStatement statement, int index, WrapperOptions options) throws SQLException;
|
||||
|
||||
public X extract(CallableStatement statement, String[] paramNames, WrapperOptions options) throws SQLException;
|
||||
}
|
||||
|
|
|
@ -38,6 +38,8 @@ import org.hibernate.type.descriptor.sql.BasicBinder;
|
|||
import org.hibernate.type.descriptor.sql.BasicExtractor;
|
||||
import org.hibernate.type.descriptor.sql.SqlTypeDescriptor;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* Adapter for incorporating JPA {@link AttributeConverter} handling into the SqlTypeDescriptor contract.
|
||||
* <p/>
|
||||
|
@ -50,6 +52,8 @@ import org.hibernate.type.descriptor.sql.SqlTypeDescriptor;
|
|||
* @author Steve Ebersole
|
||||
*/
|
||||
public class AttributeConverterSqlTypeDescriptorAdapter implements SqlTypeDescriptor {
|
||||
private static final Logger log = Logger.getLogger( AttributeConverterSqlTypeDescriptorAdapter.class );
|
||||
|
||||
private final AttributeConverter converter;
|
||||
private final SqlTypeDescriptor delegate;
|
||||
private final JavaTypeDescriptor intermediateJavaTypeDescriptor;
|
||||
|
@ -84,10 +88,10 @@ public class AttributeConverterSqlTypeDescriptorAdapter implements SqlTypeDescri
|
|||
public <X> ValueBinder<X> getBinder(JavaTypeDescriptor<X> javaTypeDescriptor) {
|
||||
// Get the binder for the intermediate type representation
|
||||
final ValueBinder realBinder = delegate.getBinder( intermediateJavaTypeDescriptor );
|
||||
return new BasicBinder<X>( javaTypeDescriptor, this ) {
|
||||
|
||||
return new ValueBinder<X>() {
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void doBind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException {
|
||||
public void bind(PreparedStatement st, X value, int index, WrapperOptions options) throws SQLException {
|
||||
final Object convertedValue;
|
||||
try {
|
||||
convertedValue = converter.convertToDatabaseColumn( value );
|
||||
|
@ -98,6 +102,8 @@ public class AttributeConverterSqlTypeDescriptorAdapter implements SqlTypeDescri
|
|||
catch (RuntimeException re) {
|
||||
throw new PersistenceException( "Error attempting to apply AttributeConverter", re );
|
||||
}
|
||||
|
||||
log.debugf( "Converted value on binding : %s -> %s", value, convertedValue );
|
||||
realBinder.bind( st, convertedValue, index, options );
|
||||
}
|
||||
};
|
||||
|
@ -108,27 +114,34 @@ public class AttributeConverterSqlTypeDescriptorAdapter implements SqlTypeDescri
|
|||
|
||||
@Override
|
||||
public <X> ValueExtractor<X> getExtractor(JavaTypeDescriptor<X> javaTypeDescriptor) {
|
||||
// Get the extractor for the intermediate type representation
|
||||
final ValueExtractor realExtractor = delegate.getExtractor( intermediateJavaTypeDescriptor );
|
||||
return new BasicExtractor<X>( javaTypeDescriptor, this ) {
|
||||
|
||||
return new ValueExtractor<X>() {
|
||||
@Override
|
||||
protected X doExtract(ResultSet rs, String name, WrapperOptions options) throws SQLException {
|
||||
public X extract(ResultSet rs, String name, WrapperOptions options) throws SQLException {
|
||||
return doConversion( realExtractor.extract( rs, name, options ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected X doExtract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
|
||||
public X extract(CallableStatement statement, int index, WrapperOptions options) throws SQLException {
|
||||
return doConversion( realExtractor.extract( statement, index, options ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected X doExtract(CallableStatement statement, String name, WrapperOptions options) throws SQLException {
|
||||
return doConversion( realExtractor.extract( statement, new String[] {name}, options ) );
|
||||
public X extract(CallableStatement statement, String[] paramNames, WrapperOptions options) throws SQLException {
|
||||
if ( paramNames.length > 1 ) {
|
||||
throw new IllegalArgumentException( "Basic value extraction cannot handle multiple output parameters" );
|
||||
}
|
||||
return doConversion( realExtractor.extract( statement, paramNames, options ) );
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private X doConversion(Object extractedValue) {
|
||||
try {
|
||||
return (X) converter.convertToEntityAttribute( extractedValue );
|
||||
X convertedValue = (X) converter.convertToEntityAttribute( extractedValue );
|
||||
log.debugf( "Converted value on extraction: %s -> %s", extractedValue, convertedValue );
|
||||
return convertedValue;
|
||||
}
|
||||
catch (PersistenceException pe) {
|
||||
throw pe;
|
||||
|
|
|
@ -0,0 +1,180 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* Copyright (c) 2015, Red Hat Inc. or third-party contributors as
|
||||
* indicated by the @author tags or express copyright attribution
|
||||
* statements applied by the authors. All third-party contributions are
|
||||
* distributed under license by Red Hat Inc.
|
||||
*
|
||||
* This copyrighted material is made available to anyone wishing to use, modify,
|
||||
* copy, or redistribute it subject to the terms and conditions of the GNU
|
||||
* Lesser General Public License, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
|
||||
* for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this distribution; if not, write to:
|
||||
* Free Software Foundation, Inc.
|
||||
* 51 Franklin Street, Fifth Floor
|
||||
* Boston, MA 02110-1301 USA
|
||||
*/
|
||||
package org.hibernate.test.type.converter;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Locale;
|
||||
import javax.persistence.AttributeConverter;
|
||||
import javax.persistence.Convert;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.jdbc.Work;
|
||||
|
||||
import org.hibernate.testing.TestForIssue;
|
||||
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
public class NullHandlingTests extends BaseCoreFunctionalTestCase {
|
||||
@Override
|
||||
protected Class<?>[] getAnnotatedClasses() {
|
||||
return new Class[] { TheEntity.class };
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestForIssue( jiraKey = "HHH-8697" )
|
||||
public void testNullReplacementOnBinding() {
|
||||
TheEntity theEntity = new TheEntity( 1 );
|
||||
|
||||
Session session = openSession();
|
||||
session.beginTransaction();
|
||||
// at this point TheEntity.sex is null
|
||||
// lets make sure that the converter is given a chance to adjust that to UNKNOWN...
|
||||
session.save( theEntity );
|
||||
session.getTransaction().commit();
|
||||
session.close();
|
||||
|
||||
session = openSession();
|
||||
session.beginTransaction();
|
||||
session.doWork(
|
||||
new Work() {
|
||||
@Override
|
||||
public void execute(Connection connection) throws SQLException {
|
||||
ResultSet rs = connection.createStatement().executeQuery( "select sex from the_entity where id=1" );
|
||||
try {
|
||||
if ( !rs.next() ) {
|
||||
throw new RuntimeException( "Could not locate inserted row" );
|
||||
}
|
||||
|
||||
String sexDbValue = rs.getString( 1 );
|
||||
|
||||
if ( rs.next() ) {
|
||||
throw new RuntimeException( "Found more than one row" );
|
||||
}
|
||||
|
||||
assertEquals( Sex.UNKNOWN.name().toLowerCase( Locale.ENGLISH ), sexDbValue );
|
||||
}
|
||||
finally {
|
||||
rs.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
session.getTransaction().commit();
|
||||
session.close();
|
||||
|
||||
session = openSession();
|
||||
session.beginTransaction();
|
||||
session.delete( theEntity );
|
||||
session.getTransaction().commit();
|
||||
session.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestForIssue( jiraKey = "HHH-9320" )
|
||||
public void testNullReplacementOnExtraction() {
|
||||
Session session = openSession();
|
||||
session.beginTransaction();
|
||||
session.doWork(
|
||||
new Work() {
|
||||
@Override
|
||||
public void execute(Connection connection) throws SQLException {
|
||||
connection.createStatement().execute( "insert into the_entity(id, sex) values (1, null)" );
|
||||
}
|
||||
}
|
||||
);
|
||||
session.getTransaction().commit();
|
||||
session.close();
|
||||
|
||||
session = openSession();
|
||||
session.beginTransaction();
|
||||
// at this point TheEntity.sex is null in the database
|
||||
// lets load it and make sure that the converter is given a chance to adjust that to UNKNOWN...
|
||||
TheEntity theEntity = (TheEntity) session.get( TheEntity.class, 1 );
|
||||
session.getTransaction().commit();
|
||||
session.close();
|
||||
|
||||
assertEquals( Sex.UNKNOWN, theEntity.sex );
|
||||
|
||||
session = openSession();
|
||||
session.beginTransaction();
|
||||
session.delete( theEntity );
|
||||
session.getTransaction().commit();
|
||||
session.close();
|
||||
}
|
||||
|
||||
@Entity( name = "TheEntity" )
|
||||
@Table( name = "the_entity" )
|
||||
public static class TheEntity {
|
||||
@Id
|
||||
public Integer id;
|
||||
|
||||
@Convert( converter = SexConverter.class )
|
||||
public Sex sex;
|
||||
|
||||
public TheEntity() {
|
||||
}
|
||||
|
||||
public TheEntity(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
public static enum Sex {
|
||||
MALE,
|
||||
FEMALE,
|
||||
UNKNOWN
|
||||
}
|
||||
|
||||
public static class SexConverter implements AttributeConverter<Sex,String> {
|
||||
@Override
|
||||
public String convertToDatabaseColumn(Sex attribute) {
|
||||
// HHH-8697
|
||||
if ( attribute == null ) {
|
||||
return Sex.UNKNOWN.name().toLowerCase( Locale.ENGLISH );
|
||||
}
|
||||
|
||||
return attribute.name().toLowerCase( Locale.ENGLISH );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Sex convertToEntityAttribute(String dbData) {
|
||||
// HHH-9320
|
||||
if ( dbData == null ) {
|
||||
return Sex.UNKNOWN;
|
||||
}
|
||||
|
||||
return Sex.valueOf( dbData.toUpperCase( Locale.ENGLISH ) );
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue