HHH-9495 - @Convert support for collections
This commit is contained in:
parent
8284b9ae2c
commit
f45b37da8e
|
@ -42,6 +42,7 @@ import org.hibernate.annotations.ManyToAny;
|
||||||
import org.hibernate.annotations.MapKeyType;
|
import org.hibernate.annotations.MapKeyType;
|
||||||
import org.hibernate.annotations.common.reflection.XClass;
|
import org.hibernate.annotations.common.reflection.XClass;
|
||||||
import org.hibernate.annotations.common.reflection.XProperty;
|
import org.hibernate.annotations.common.reflection.XProperty;
|
||||||
|
import org.hibernate.boot.model.source.spi.AttributePath;
|
||||||
import org.hibernate.boot.spi.MetadataBuildingContext;
|
import org.hibernate.boot.spi.MetadataBuildingContext;
|
||||||
import org.hibernate.internal.CoreLogging;
|
import org.hibernate.internal.CoreLogging;
|
||||||
import org.hibernate.internal.CoreMessageLogger;
|
import org.hibernate.internal.CoreMessageLogger;
|
||||||
|
@ -143,6 +144,7 @@ public class CollectionPropertyHolder extends AbstractPropertyHolder {
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( StringHelper.isEmpty( info.getAttributeName() ) ) {
|
if ( StringHelper.isEmpty( info.getAttributeName() ) ) {
|
||||||
|
// the @Convert did not name an attribute...
|
||||||
if ( canElementBeConverted && canKeyBeConverted ) {
|
if ( canElementBeConverted && canKeyBeConverted ) {
|
||||||
throw new IllegalStateException(
|
throw new IllegalStateException(
|
||||||
"@Convert placed on Map attribute [" + collection.getRole()
|
"@Convert placed on Map attribute [" + collection.getRole()
|
||||||
|
@ -158,30 +160,46 @@ public class CollectionPropertyHolder extends AbstractPropertyHolder {
|
||||||
// if neither, we should not be here...
|
// if neither, we should not be here...
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if ( canElementBeConverted && canKeyBeConverted ) {
|
// the @Convert named an attribute...
|
||||||
|
final String keyPath = removePrefix( info.getAttributeName(), "key" );
|
||||||
|
final String elementPath = removePrefix( info.getAttributeName(), "value" );
|
||||||
|
|
||||||
|
if ( canElementBeConverted && canKeyBeConverted && keyPath == null && elementPath == null ) {
|
||||||
// specified attributeName needs to have 'key.' or 'value.' prefix
|
// specified attributeName needs to have 'key.' or 'value.' prefix
|
||||||
if ( info.getAttributeName().startsWith( "key." ) ) {
|
throw new IllegalStateException(
|
||||||
keyAttributeConversionInfoMap.put(
|
"@Convert placed on Map attribute [" + collection.getRole()
|
||||||
info.getAttributeName().substring( 4 ),
|
+ "] must define attributeName of 'key' or 'value'"
|
||||||
info
|
);
|
||||||
);
|
}
|
||||||
}
|
|
||||||
else if ( info.getAttributeName().startsWith( "value." ) ) {
|
if ( keyPath != null ) {
|
||||||
elementAttributeConversionInfoMap.put(
|
keyAttributeConversionInfoMap.put( keyPath, info );
|
||||||
info.getAttributeName().substring( 6 ),
|
}
|
||||||
info
|
else if ( elementPath != null ) {
|
||||||
);
|
elementAttributeConversionInfoMap.put( elementPath, info );
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"@Convert placed on Map attribute [" + collection.getRole()
|
|
||||||
+ "] must define attributeName of 'key' or 'value'"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if path has the given prefix and remove it.
|
||||||
|
*
|
||||||
|
* @param path Path.
|
||||||
|
* @param prefix Prefix.
|
||||||
|
* @return Path without prefix, or null, if path did not have the prefix.
|
||||||
|
*/
|
||||||
|
private String removePrefix(String path, String prefix) {
|
||||||
|
if ( path.equals(prefix) ) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path.startsWith(prefix + ".")) {
|
||||||
|
return path.substring( prefix.length() + 1 );
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String normalizeCompositePath(String attributeName) {
|
protected String normalizeCompositePath(String attributeName) {
|
||||||
return attributeName;
|
return attributeName;
|
||||||
|
@ -219,8 +237,17 @@ public class CollectionPropertyHolder extends AbstractPropertyHolder {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected AttributeConversionInfo locateAttributeConversionInfo(String path) {
|
protected AttributeConversionInfo locateAttributeConversionInfo(String path) {
|
||||||
// todo : implement
|
final String key = removePrefix( path, "key" );
|
||||||
return null;
|
if ( key != null ) {
|
||||||
|
return keyAttributeConversionInfoMap.get( key );
|
||||||
|
}
|
||||||
|
|
||||||
|
final String element = removePrefix( path, "element" );
|
||||||
|
if ( element != null ) {
|
||||||
|
return elementAttributeConversionInfoMap.get( element );
|
||||||
|
}
|
||||||
|
|
||||||
|
return elementAttributeConversionInfoMap.get( path );
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getClassName() {
|
public String getClassName() {
|
||||||
|
|
|
@ -0,0 +1,201 @@
|
||||||
|
/*
|
||||||
|
* 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.net.MalformedURLException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.persistence.AttributeConverter;
|
||||||
|
import javax.persistence.CollectionTable;
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.Convert;
|
||||||
|
import javax.persistence.Converts;
|
||||||
|
import javax.persistence.ElementCollection;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.FetchType;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.JoinColumn;
|
||||||
|
import javax.persistence.MapKeyColumn;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
|
||||||
|
import org.hibernate.Session;
|
||||||
|
import org.hibernate.cfg.AvailableSettings;
|
||||||
|
import org.hibernate.mapping.Collection;
|
||||||
|
import org.hibernate.mapping.IndexedCollection;
|
||||||
|
import org.hibernate.mapping.PersistentClass;
|
||||||
|
import org.hibernate.mapping.Property;
|
||||||
|
import org.hibernate.type.descriptor.converter.AttributeConverterTypeAdapter;
|
||||||
|
|
||||||
|
import org.hibernate.testing.TestForIssue;
|
||||||
|
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test for {@link org.hibernate.cfg.CollectionPropertyHolder}.
|
||||||
|
*
|
||||||
|
* Tests that {@link javax.persistence.AttributeConverter}s are considered correctly for {@link javax.persistence.ElementCollection}.
|
||||||
|
*
|
||||||
|
* @author Markus Heiden
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
@TestForIssue( jiraKey = "HHH-9495" )
|
||||||
|
public class ElementCollectionTests extends BaseNonConfigCoreFunctionalTestCase {
|
||||||
|
@Override
|
||||||
|
protected Class[] getAnnotatedClasses() {
|
||||||
|
return new Class[] { TheEntity.class };
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSimpleConvertUsage() throws MalformedURLException {
|
||||||
|
// first some assertions of the metamodel
|
||||||
|
PersistentClass entityBinding = metadata().getEntityBinding( TheEntity.class.getName() );
|
||||||
|
assertNotNull( entityBinding );
|
||||||
|
|
||||||
|
Property setAttributeBinding = entityBinding.getProperty( "set" );
|
||||||
|
Collection setBinding = (Collection) setAttributeBinding.getValue();
|
||||||
|
assertTyping( AttributeConverterTypeAdapter.class, setBinding.getElement().getType() );
|
||||||
|
|
||||||
|
Property mapAttributeBinding = entityBinding.getProperty( "map" );
|
||||||
|
IndexedCollection mapBinding = (IndexedCollection) mapAttributeBinding.getValue();
|
||||||
|
assertTyping( AttributeConverterTypeAdapter.class, mapBinding.getIndex().getType() );
|
||||||
|
assertTyping( AttributeConverterTypeAdapter.class, mapBinding.getElement().getType() );
|
||||||
|
|
||||||
|
// now lets try to use the model, integration-testing-style!
|
||||||
|
TheEntity entity = new TheEntity(1);
|
||||||
|
|
||||||
|
Session s = openSession();
|
||||||
|
s.beginTransaction();
|
||||||
|
s.save( entity );
|
||||||
|
s.getTransaction().commit();
|
||||||
|
s.close();
|
||||||
|
|
||||||
|
s = openSession();
|
||||||
|
s.beginTransaction();
|
||||||
|
TheEntity retrieved = (TheEntity) s.load( TheEntity.class, 1 );
|
||||||
|
assertEquals( 1, retrieved.getSet().size() );
|
||||||
|
assertEquals(new ValueType("set_value"), retrieved.getSet().iterator().next());
|
||||||
|
assertEquals(1, retrieved.getMap().size());
|
||||||
|
assertEquals(new ValueType("map_value"), retrieved.getMap().get(new ValueType("map_key")));
|
||||||
|
s.delete( retrieved );
|
||||||
|
s.getTransaction().commit();
|
||||||
|
s.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Non-serializable value type.
|
||||||
|
*/
|
||||||
|
public static class ValueType {
|
||||||
|
private final String value;
|
||||||
|
|
||||||
|
public ValueType(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
return o instanceof ValueType &&
|
||||||
|
value.equals(((ValueType) o).value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return value.hashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converter for {@link ValueType}.
|
||||||
|
*/
|
||||||
|
public static class ValueTypeConverter implements AttributeConverter<ValueType, String> {
|
||||||
|
@Override
|
||||||
|
public String convertToDatabaseColumn(ValueType type) {
|
||||||
|
return type.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ValueType convertToEntityAttribute(String type) {
|
||||||
|
return new ValueType(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entity holding element collections.
|
||||||
|
*/
|
||||||
|
@Entity( name = "TheEntity" )
|
||||||
|
@Table(name = "entity")
|
||||||
|
public static class TheEntity {
|
||||||
|
@Id
|
||||||
|
public Integer id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Element set with converter.
|
||||||
|
*/
|
||||||
|
@Convert( converter = ValueTypeConverter.class )
|
||||||
|
@ElementCollection(fetch = FetchType.LAZY)
|
||||||
|
@CollectionTable(name = "entity_set", joinColumns = @JoinColumn(name = "entity_id", nullable = false))
|
||||||
|
@Column(name = "value", nullable = false)
|
||||||
|
public Set<ValueType> set = new HashSet<ValueType>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Element map with converters.
|
||||||
|
*/
|
||||||
|
@Converts({
|
||||||
|
@Convert(attributeName = "key", converter = ValueTypeConverter.class),
|
||||||
|
@Convert(attributeName = "value", converter = ValueTypeConverter.class)
|
||||||
|
})
|
||||||
|
@ElementCollection(fetch = FetchType.LAZY)
|
||||||
|
@CollectionTable(name = "entity_map", joinColumns = @JoinColumn(name = "entity_id", nullable = false))
|
||||||
|
@MapKeyColumn(name = "key", nullable = false)
|
||||||
|
@Column(name = "value", nullable = false)
|
||||||
|
public Map<ValueType, ValueType> map = new HashMap<ValueType, ValueType>();
|
||||||
|
|
||||||
|
public TheEntity() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public TheEntity(Integer id) {
|
||||||
|
this.id = id;
|
||||||
|
this.set.add(new ValueType("set_value"));
|
||||||
|
this.map.put( new ValueType( "map_key" ), new ValueType( "map_value" ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<ValueType> getSet() {
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<ValueType, ValueType> getMap() {
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue