From fa526f601173aa6de6dc1b65d3c069001a581467 Mon Sep 17 00:00:00 2001 From: Janario Oliveira Date: Thu, 29 Oct 2015 21:14:33 -0200 Subject: [PATCH] HHH-9475 Cannot mix @MapKey with @Convert - Moved classes to reuse in test case; Added copy of type from referenced MapKey (cherry picked from commit 80e851e7d0a13f7aab3f683d2b46fbb985fdbe46) --- .../hibernate/cfg/annotations/MapBinder.java | 20 +- .../org/hibernate/mapping/SimpleValue.java | 8 + .../test/converter/map/ColorType.java | 42 ++ .../converter/map/ColorTypeConverter.java | 27 ++ .../map/MapKeyAttributeConverterTest.java | 382 ++++++++++++++++++ .../converter/map/MapKeyConversionTest.java | 49 --- 6 files changed, 468 insertions(+), 60 deletions(-) create mode 100644 hibernate-core/src/test/java/org/hibernate/test/converter/map/ColorType.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/converter/map/ColorTypeConverter.java create mode 100644 hibernate-core/src/test/java/org/hibernate/test/converter/map/MapKeyAttributeConverterTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapBinder.java index 9e008ec493..ccb0c2c593 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/MapBinder.java @@ -6,11 +6,18 @@ */ package org.hibernate.cfg.annotations; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Random; +import javax.persistence.AttributeOverride; +import javax.persistence.AttributeOverrides; +import javax.persistence.MapKeyClass; + import org.hibernate.AnnotationException; import org.hibernate.AssertionFailure; import org.hibernate.FetchMode; import org.hibernate.MappingException; -import org.hibernate.annotations.MapKeyType; import org.hibernate.annotations.common.reflection.ClassLoadingException; import org.hibernate.annotations.common.reflection.XClass; import org.hibernate.annotations.common.reflection.XProperty; @@ -44,14 +51,6 @@ import org.hibernate.mapping.ToOne; import org.hibernate.mapping.Value; import org.hibernate.sql.Template; -import javax.persistence.AttributeOverride; -import javax.persistence.AttributeOverrides; -import javax.persistence.MapKeyClass; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.Random; - /** * Implementation to bind a Map * @@ -390,8 +389,7 @@ public class MapBinder extends CollectionBinder { } else { targetValue = new SimpleValue( getBuildingContext().getMetadataCollector(), collection.getCollectionTable() ); - targetValue.setTypeName( sourceValue.getTypeName() ); - targetValue.setTypeParameters( sourceValue.getTypeParameters() ); + targetValue.copyTypeFrom( sourceValue ); } Iterator columns = sourceValue.getColumnIterator(); Random random = new Random(); diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java b/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java index 2bb1d2da1d..b221030d7c 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/SimpleValue.java @@ -525,6 +525,14 @@ public class SimpleValue implements KeyValue { return typeParameters; } + public void copyTypeFrom( SimpleValue sourceValue ) { + setTypeName( sourceValue.getTypeName() ); + setTypeParameters( sourceValue.getTypeParameters() ); + + type = sourceValue.type; + attributeConverterDescriptor = sourceValue.attributeConverterDescriptor; + } + @Override public String toString() { return getClass().getName() + '(' + columns.toString() + ')'; diff --git a/hibernate-core/src/test/java/org/hibernate/test/converter/map/ColorType.java b/hibernate-core/src/test/java/org/hibernate/test/converter/map/ColorType.java new file mode 100644 index 0000000000..ed19fa39cc --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/converter/map/ColorType.java @@ -0,0 +1,42 @@ +/* + * 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 . + */ +package org.hibernate.test.converter.map; + +/** + * @author Steve Ebersole + * an enum-like class (converters are technically not allowed to apply to enums) + */ +public class ColorType { + public static ColorType BLUE = new ColorType( "blue" ); + public static ColorType RED = new ColorType( "red" ); + public static ColorType YELLOW = new ColorType( "yellow" ); + + private final String color; + + public ColorType(String color) { + this.color = color; + } + + public String toExternalForm() { + return color; + } + + public static ColorType fromExternalForm(String color) { + if ( BLUE.color.equals( color ) ) { + return BLUE; + } + else if ( RED.color.equals( color ) ) { + return RED; + } + else if ( YELLOW.color.equals( color ) ) { + return YELLOW; + } + else { + throw new RuntimeException( "Unknown color : " + color ); + } + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/converter/map/ColorTypeConverter.java b/hibernate-core/src/test/java/org/hibernate/test/converter/map/ColorTypeConverter.java new file mode 100644 index 0000000000..a83208e7ca --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/converter/map/ColorTypeConverter.java @@ -0,0 +1,27 @@ +/* + * 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 . + */ +package org.hibernate.test.converter.map; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +/** + * @author Steve Ebersole + */ +@Converter(autoApply = true) +public class ColorTypeConverter implements AttributeConverter { + + @Override + public String convertToDatabaseColumn(ColorType attribute) { + return attribute == null ? null : attribute.toExternalForm(); + } + + @Override + public ColorType convertToEntityAttribute(String dbData) { + return dbData == null ? null : ColorType.fromExternalForm( dbData ); + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/converter/map/MapKeyAttributeConverterTest.java b/hibernate-core/src/test/java/org/hibernate/test/converter/map/MapKeyAttributeConverterTest.java new file mode 100644 index 0000000000..c091f29406 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/converter/map/MapKeyAttributeConverterTest.java @@ -0,0 +1,382 @@ +/* + * 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 . + */ +package org.hibernate.test.converter.map; + +import java.util.HashMap; +import java.util.Map; +import javax.persistence.AttributeConverter; +import javax.persistence.CascadeType; +import javax.persistence.Convert; +import javax.persistence.Converter; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.MapKey; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.hibernate.Transaction; + +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * @author Janario Oliveira + */ +public class MapKeyAttributeConverterTest extends BaseNonConfigCoreFunctionalTestCase { + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + MapEntity.class, MapValue.class, + + ColorTypeConverter.class, + + CustomColorTypeConverter.class, + ImplicitEnumMapKeyConverter.class, + ExplicitEnumMapKeyConverter.class, + ImplicitEnumMapKeyOverridedConverter.class + }; + } + + @Test + public void testImplicitType() { + MapValue mapValue = create(); + mapValue.implicitType = ColorType.BLUE; + mapValue.mapEntity.implicitType.put( mapValue.implicitType, mapValue ); + + MapEntity found = persist( mapValue.mapEntity ); + + assertEquals( 1, found.implicitType.size() ); + MapValue foundValue = found.implicitType.get( ColorType.BLUE ); + assertEquals( ColorType.BLUE, foundValue.implicitType ); + + assertEquals( "blue", findDatabaseValue( foundValue, "implicitType" ) ); + getSession().close(); + } + + @Test + public void testExplicitType() { + MapValue mapValue = create(); + mapValue.explicitType = ColorType.RED; + mapValue.mapEntity.explicitType.put( mapValue.explicitType, mapValue ); + + MapEntity found = persist( mapValue.mapEntity ); + + assertEquals( 1, found.explicitType.size() ); + MapValue foundValue = found.explicitType.get( ColorType.RED ); + assertEquals( ColorType.RED, foundValue.explicitType ); + + assertEquals( "COLOR-red", findDatabaseValue( foundValue, "explicitType" ) ); + getSession().close(); + } + + @Test + public void testEnumDefaultType() { + MapValue mapValue = create(); + mapValue.enumDefault = EnumMapKey.VALUE_1; + mapValue.mapEntity.enumDefaultType.put( mapValue.enumDefault, mapValue ); + + MapEntity found = persist( mapValue.mapEntity ); + + assertEquals( 1, found.enumDefaultType.size() ); + MapValue foundValue = found.enumDefaultType.get( EnumMapKey.VALUE_1 ); + assertEquals( EnumMapKey.VALUE_1, foundValue.enumDefault ); + + assertEquals( 0, findDatabaseValue( foundValue, "enumDefault" ) ); + getSession().close(); + } + + @Test + public void testEnumExplicitOrdinalType() { + MapValue mapValue = create(); + mapValue.enumExplicitOrdinal = EnumMapKey.VALUE_2; + mapValue.mapEntity.enumExplicitOrdinalType.put( mapValue.enumExplicitOrdinal, mapValue ); + + MapEntity found = persist( mapValue.mapEntity ); + + assertEquals( 1, found.enumExplicitOrdinalType.size() ); + MapValue foundValue = found.enumExplicitOrdinalType.get( EnumMapKey.VALUE_2 ); + assertEquals( EnumMapKey.VALUE_2, foundValue.enumExplicitOrdinal ); + + assertEquals( 1, findDatabaseValue( foundValue, "enumExplicitOrdinal" ) ); + getSession().close(); + } + + @Test + public void testEnumExplicitStringType() { + MapValue mapValue = create(); + mapValue.enumExplicitString = EnumMapKey.VALUE_1; + mapValue.mapEntity.enumExplicitStringType.put( mapValue.enumExplicitString, mapValue ); + + MapEntity found = persist( mapValue.mapEntity ); + + assertEquals( 1, found.enumExplicitStringType.size() ); + MapValue foundValue = found.enumExplicitStringType.get( EnumMapKey.VALUE_1 ); + assertEquals( EnumMapKey.VALUE_1, foundValue.enumExplicitString ); + + assertEquals( "VALUE_1", findDatabaseValue( foundValue, "enumExplicitString" ) ); + getSession().close(); + } + + @Test + public void testEnumExplicitType() { + MapValue mapValue = create(); + mapValue.enumExplicit = EnumMapKey.VALUE_2; + mapValue.mapEntity.enumExplicitType.put( mapValue.enumExplicit, mapValue ); + + MapEntity found = persist( mapValue.mapEntity ); + + assertEquals( 1, found.enumExplicitType.size() ); + MapValue foundValue = found.enumExplicitType.get( EnumMapKey.VALUE_2 ); + assertEquals( EnumMapKey.VALUE_2, foundValue.enumExplicit ); + + assertEquals( "2", findDatabaseValue( foundValue, "enumExplicit" ) ); + getSession().close(); + } + + @Test + public void testEnumImplicitType() { + MapValue mapValue = create(); + mapValue.enumImplicit = ImplicitEnumMapKey.VALUE_2; + mapValue.mapEntity.enumImplicitType.put( mapValue.enumImplicit, mapValue ); + + MapEntity found = persist( mapValue.mapEntity ); + + assertEquals( 1, found.enumImplicitType.size() ); + MapValue foundValue = found.enumImplicitType.get( ImplicitEnumMapKey.VALUE_2 ); + assertEquals( ImplicitEnumMapKey.VALUE_2, foundValue.enumImplicit ); + + assertEquals( "I2", findDatabaseValue( foundValue, "enumImplicit" ) ); + getSession().close(); + } + + @Test + public void testEnumImplicitOverrideOrdinalType() { + MapValue mapValue = create(); + mapValue.enumImplicitOverrideOrdinal = ImplicitEnumMapKey.VALUE_1; + mapValue.mapEntity.enumImplicitOverrideOrdinalType.put( mapValue.enumImplicitOverrideOrdinal, mapValue ); + + MapEntity found = persist( mapValue.mapEntity ); + + assertEquals( 1, found.enumImplicitOverrideOrdinalType.size() ); + MapValue foundValue = found.enumImplicitOverrideOrdinalType.get( ImplicitEnumMapKey.VALUE_1 ); + assertEquals( ImplicitEnumMapKey.VALUE_1, foundValue.enumImplicitOverrideOrdinal ); + + assertEquals( 0, findDatabaseValue( foundValue, "enumImplicitOverrideOrdinal" ) ); + getSession().close(); + } + + @Test + public void testEnumImplicitOverrideStringType() { + MapValue mapValue = create(); + mapValue.enumImplicitOverrideString = ImplicitEnumMapKey.VALUE_2; + mapValue.mapEntity.enumImplicitOverrideStringType.put( mapValue.enumImplicitOverrideString, mapValue ); + + MapEntity found = persist( mapValue.mapEntity ); + + assertEquals( 1, found.enumImplicitOverrideStringType.size() ); + MapValue foundValue = found.enumImplicitOverrideStringType.get( ImplicitEnumMapKey.VALUE_2 ); + assertEquals( ImplicitEnumMapKey.VALUE_2, foundValue.enumImplicitOverrideString ); + + assertEquals( "VALUE_2", findDatabaseValue( foundValue, "enumImplicitOverrideString" ) ); + getSession().close(); + } + + @Test + public void testEnumImplicitOverridedType() { + MapValue mapValue = create(); + mapValue.enumImplicitOverrided = ImplicitEnumMapKey.VALUE_1; + mapValue.mapEntity.enumImplicitOverridedType.put( mapValue.enumImplicitOverrided, mapValue ); + + MapEntity found = persist( mapValue.mapEntity ); + + assertEquals( 1, found.enumImplicitOverridedType.size() ); + MapValue foundValue = found.enumImplicitOverridedType.get( ImplicitEnumMapKey.VALUE_1 ); + assertEquals( ImplicitEnumMapKey.VALUE_1, foundValue.enumImplicitOverrided ); + + assertEquals( "O1", findDatabaseValue( foundValue, "enumImplicitOverrided" ) ); + getSession().close(); + } + + + private MapValue create() { + MapEntity mapEntity = new MapEntity(); + return new MapValue( mapEntity ); + } + + private MapEntity persist(MapEntity mapEntity) { + Transaction tx = openSession().getTransaction(); + tx.begin(); + mapEntity = (MapEntity) getSession().merge( mapEntity ); + + tx.commit(); + getSession().close(); + + mapEntity = openSession().get( MapEntity.class, mapEntity.id ); + return mapEntity; + } + + private Object findDatabaseValue(MapValue mapValue, String column) { + return getSession() + .createSQLQuery( "select mv." + column + " from map_value mv where mv.id=:id" ) + .setParameter( "id", mapValue.id ) + .uniqueResult(); + } + + @Entity + @Table(name = "map_entity") + public static class MapEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + @OneToMany(mappedBy = "mapEntity", cascade = CascadeType.ALL) + @MapKey(name = "implicitType") + private Map implicitType = new HashMap(); + @OneToMany(mappedBy = "mapEntity", cascade = CascadeType.ALL) + @MapKey(name = "explicitType") + private Map explicitType = new HashMap(); + + @OneToMany(mappedBy = "mapEntity", cascade = CascadeType.ALL) + @MapKey(name = "enumDefault") + private Map enumDefaultType = new HashMap(); + @OneToMany(mappedBy = "mapEntity", cascade = CascadeType.ALL) + @MapKey(name = "enumExplicitOrdinal") + private Map enumExplicitOrdinalType = new HashMap(); + @OneToMany(mappedBy = "mapEntity", cascade = CascadeType.ALL) + @MapKey(name = "enumExplicitString") + private Map enumExplicitStringType = new HashMap(); + + @OneToMany(mappedBy = "mapEntity", cascade = CascadeType.ALL) + @MapKey(name = "enumExplicit") + private Map enumExplicitType = new HashMap(); + @OneToMany(mappedBy = "mapEntity", cascade = CascadeType.ALL) + @MapKey(name = "enumImplicit") + private Map enumImplicitType = new HashMap(); + @OneToMany(mappedBy = "mapEntity", cascade = CascadeType.ALL) + @MapKey(name = "enumImplicitOverrideOrdinal") + private Map enumImplicitOverrideOrdinalType = new HashMap(); + @OneToMany(mappedBy = "mapEntity", cascade = CascadeType.ALL) + @MapKey(name = "enumImplicitOverrideString") + private Map enumImplicitOverrideStringType = new HashMap(); + + @OneToMany(mappedBy = "mapEntity", cascade = CascadeType.ALL) + @MapKey(name = "enumImplicitOverrided") + private Map enumImplicitOverridedType = new HashMap(); + } + + @Entity + @Table(name = "map_value") + public static class MapValue { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + @ManyToOne + @JoinColumn(name = "map_entity_id") + private MapEntity mapEntity; + + private ColorType implicitType; + @Convert(converter = CustomColorTypeConverter.class) + private ColorType explicitType; + + private EnumMapKey enumDefault; + @Enumerated + private EnumMapKey enumExplicitOrdinal; + @Enumerated(EnumType.STRING) + private EnumMapKey enumExplicitString; + @Convert(converter = ExplicitEnumMapKeyConverter.class) + private EnumMapKey enumExplicit; + + private ImplicitEnumMapKey enumImplicit; + @Enumerated + private ImplicitEnumMapKey enumImplicitOverrideOrdinal; + @Enumerated(EnumType.STRING) + private ImplicitEnumMapKey enumImplicitOverrideString; + + @Convert(converter = ImplicitEnumMapKeyOverridedConverter.class) + private ImplicitEnumMapKey enumImplicitOverrided; + + protected MapValue() { + } + + public MapValue(MapEntity mapEntity) { + this.mapEntity = mapEntity; + } + } + + public enum EnumMapKey { + VALUE_1, + VALUE_2 + } + + public enum ImplicitEnumMapKey { + VALUE_1, + VALUE_2 + } + + + @Converter + public static class CustomColorTypeConverter implements AttributeConverter { + @Override + public String convertToDatabaseColumn(ColorType attribute) { + return attribute == null ? null : "COLOR-" + attribute.toExternalForm(); + } + + @Override + public ColorType convertToEntityAttribute(String dbData) { + return dbData == null ? null : ColorType.fromExternalForm( dbData.substring( 6 ) ); + } + } + + @Converter + public static class ExplicitEnumMapKeyConverter implements AttributeConverter { + @Override + public String convertToDatabaseColumn(EnumMapKey attribute) { + return attribute == null ? null : attribute.name().substring( attribute.name().length() - 1 ); + } + + @Override + public EnumMapKey convertToEntityAttribute(String dbData) { + return dbData == null ? null : EnumMapKey.valueOf( "VALUE_" + dbData ); + } + } + + @Converter(autoApply = true) + public static class ImplicitEnumMapKeyConverter implements AttributeConverter { + @Override + public String convertToDatabaseColumn(ImplicitEnumMapKey attribute) { + return attribute == null ? null : "I" + attribute.name().substring( attribute.name().length() - 1 ); + } + + @Override + public ImplicitEnumMapKey convertToEntityAttribute(String dbData) { + return dbData == null ? null : ImplicitEnumMapKey.valueOf( "VALUE_" + dbData.substring( 1 ) ); + } + } + + + @Converter + public static class ImplicitEnumMapKeyOverridedConverter implements AttributeConverter { + @Override + public String convertToDatabaseColumn(ImplicitEnumMapKey attribute) { + return attribute == null ? null : + ( "O" + attribute.name().substring( attribute.name().length() - 1 ) ); + } + + @Override + public ImplicitEnumMapKey convertToEntityAttribute(String dbData) { + return dbData == null ? null : ImplicitEnumMapKey.valueOf( "VALUE_" + dbData.substring( 1 ) ); + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/converter/map/MapKeyConversionTest.java b/hibernate-core/src/test/java/org/hibernate/test/converter/map/MapKeyConversionTest.java index 5911163f6e..9a7d513144 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/converter/map/MapKeyConversionTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/converter/map/MapKeyConversionTest.java @@ -8,9 +8,7 @@ package org.hibernate.test.converter.map; import java.util.HashMap; import java.util.Map; -import javax.persistence.AttributeConverter; import javax.persistence.CollectionTable; -import javax.persistence.Converter; import javax.persistence.ElementCollection; import javax.persistence.Entity; import javax.persistence.FetchType; @@ -77,51 +75,4 @@ public class MapKeyConversionTest extends BaseNonConfigCoreFunctionalTestCase { this.id = id; } } - - - // an enum-like class (converters are technically not allowed to apply to enums) - public static class ColorType { - public static ColorType BLUE = new ColorType( "blue" ); - public static ColorType RED = new ColorType( "red" ); - public static ColorType YELLOW = new ColorType( "yellow" ); - - private final String color; - - public ColorType(String color) { - this.color = color; - } - - public String toExternalForm() { - return color; - } - - public static ColorType fromExternalForm(String color) { - if ( BLUE.color.equals( color ) ) { - return BLUE; - } - else if ( RED.color.equals( color ) ) { - return RED; - } - else if ( YELLOW.color.equals( color ) ) { - return YELLOW; - } - else { - throw new RuntimeException( "Unknown color : " + color ); - } - } - } - - @Converter( autoApply = true ) - public static class ColorTypeConverter implements AttributeConverter { - - @Override - public String convertToDatabaseColumn(ColorType attribute) { - return attribute == null ? null : attribute.toExternalForm(); - } - - @Override - public ColorType convertToEntityAttribute(String dbData) { - return ColorType.fromExternalForm( dbData ); - } - } }