diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentMap.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentMap.java index d3912a4928..02ea3a6f4e 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentMap.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/PersistentMap.java @@ -271,14 +271,35 @@ public class PersistentMap extends AbstractPersistentCollection implements Map { return map.toString(); } - public Object readFrom(ResultSet rs, CollectionPersister persister, CollectionAliases descriptor, Object owner) - throws HibernateException, SQLException { - Object element = persister.readElement( rs, owner, descriptor.getSuffixedElementAliases(), getSession() ); - Object index = persister.readIndex( rs, descriptor.getSuffixedIndexAliases(), getSession() ); - if ( element!=null ) map.put(index, element); + private transient List loadingEntries; + + public Object readFrom( + ResultSet rs, + CollectionPersister persister, + CollectionAliases descriptor, + Object owner) throws HibernateException, SQLException { + final Object element = persister.readElement( rs, owner, descriptor.getSuffixedElementAliases(), getSession() ); + if ( element != null ) { + final Object index = persister.readIndex( rs, descriptor.getSuffixedIndexAliases(), getSession() ); + if ( loadingEntries == null ) { + loadingEntries = new ArrayList(); + } + loadingEntries.add( new Object[] { index, element } ); + } return element; } + @Override + @SuppressWarnings("unchecked") + public boolean endRead() { + if ( loadingEntries != null ) { + for ( Object[] entry : loadingEntries ) { + map.put( entry[0], entry[1] ); + } + } + return super.endRead(); + } + public Iterator entries(CollectionPersister persister) { return map.entrySet().iterator(); } diff --git a/hibernate-core/src/test/java/org/hibernate/test/collection/map/hhh7557/EntityMapTest.java b/hibernate-core/src/test/java/org/hibernate/test/collection/map/hhh7557/EntityMapTest.java new file mode 100644 index 0000000000..5dfa28a905 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/collection/map/hhh7557/EntityMapTest.java @@ -0,0 +1,105 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, 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.collection.map.hhh7557; + +import java.util.HashMap; +import java.util.List; + +import org.hibernate.Session; + +import org.junit.Assert; +import org.junit.Test; + +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; + +/** + * @author Elizabeth Chatman + * @author Steve Ebersole + */ +public class EntityMapTest extends BaseCoreFunctionalTestCase { + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] {MapValue.class, MapKey.class, MapHolder.class}; + } + + @Test + public void testInsertIntoMap() throws Exception { + { + // Session 1: Insert 3 values into the map + Session session = openSession(); + session.beginTransaction(); + MapHolder mapHolder = new MapHolder(); + mapHolder.setMap( new HashMap() ); + addMapEntry( session, mapHolder, "A", "1" ); + addMapEntry( session, mapHolder, "B", "2" ); + addMapEntry( session, mapHolder, "C", "3" ); + session.save( mapHolder ); + // Verify there are 3 entries in the map + Assert.assertEquals( 3, mapHolder.getMap().size() ); + session.getTransaction().commit(); + session.close(); + } + + { + // Session 2: Add a 4th value to the map + Session session = openSession(); + session.beginTransaction(); + MapHolder mapHolder = getMapHolder( session ); + System.out.println( "Got MapHolder; checking map size -----" ); + Assert.assertEquals( 3, mapHolder.getMap().size() ); + System.out.println( "Got MapHolder; checked map size -----" ); + addMapEntry( session, mapHolder, "D", "4" ); + // Verify there are 4 entries in the map + Assert.assertEquals( 4, mapHolder.getMap().size() ); + session.getTransaction().commit(); + session.close(); + } + + { + // Session 3: Count the entries in the map + Session session = openSession(); + session.beginTransaction(); + MapHolder mapHolder = getMapHolder( session ); + // Fails here (expected:<4> but was:<1>) + Assert.assertEquals( 4, mapHolder.getMap().size() ); + session.getTransaction().commit(); + session.close(); + } + } + + private void addMapEntry(Session session, MapHolder mapHolder, String key, String value) { + System.out.println( "Inserting (" + key + "," + value + ") into map" ); + MapValue entityValue = new MapValue( value ); + session.save( entityValue ); + MapKey entityKey = new MapKey( key, entityValue ); + session.save( entityKey ); + mapHolder.getMap().put( entityKey, entityValue ); + } + + private MapHolder getMapHolder(Session session) { + List mapHolders = session.createQuery( "select distinct mh from MapHolder mh" ).list(); + Assert.assertEquals( 1, mapHolders.size() ); + return (MapHolder) mapHolders.get( 0 ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/collection/map/hhh7557/MapHolder.java b/hibernate-core/src/test/java/org/hibernate/test/collection/map/hhh7557/MapHolder.java new file mode 100644 index 0000000000..6055c7f2e9 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/collection/map/hhh7557/MapHolder.java @@ -0,0 +1,80 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, 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.collection.map.hhh7557; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.JoinTable; +import javax.persistence.ManyToMany; +import javax.persistence.MapKeyJoinColumn; +import javax.persistence.Table; +import java.util.Map; + +/** + * @author Elizabeth Chatman + * @author Steve Ebersole + */ +@Entity +@Table(name = "map_holder") +public class MapHolder { + private Long id; + private Map map; + + @Id + @GeneratedValue + @Column(name = "id", unique = true, nullable = false) + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + @ManyToMany + @JoinTable( + name = "map_key_map_value", + joinColumns = @JoinColumn(name = "map_holder_id", nullable = false), + inverseJoinColumns = @JoinColumn(name = "map_value_id", nullable = false) + ) + @MapKeyJoinColumn(name = "map_key_id", nullable = false) + public Map getMap() { + return map; + } + + public void setMap(Map map) { + this.map = map; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append( "MapHolder [id=" ).append( id ).append( "]" ); + return builder.toString(); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/collection/map/hhh7557/MapKey.java b/hibernate-core/src/test/java/org/hibernate/test/collection/map/hhh7557/MapKey.java new file mode 100644 index 0000000000..3bba1a0ae1 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/collection/map/hhh7557/MapKey.java @@ -0,0 +1,154 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, 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.collection.map.hhh7557; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +import org.jboss.logging.Logger; + +/** + * @author Elizabeth Chatman + * @author Steve Ebersole + */ +@Entity +@Table(name = "map_key", uniqueConstraints = { + @UniqueConstraint(columnNames = {"name", "default_map_value_id"}) +}) +public class MapKey { + private static final Logger log = Logger.getLogger( MapKey.class ); + + private Long id; + private String name; + private MapValue defaultValue; + + public MapKey() { + } + + public MapKey(String name, MapValue defaultValue) { + this.name = name; + this.defaultValue = defaultValue; + } + + @Id + @GeneratedValue + @Column(name = "id", unique = true, nullable = false) + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + @Column(name = "name", nullable = false) + public String getName() { + return name; + } + + public void setName(String name) { + log.tracef( "Setting name : %s", name ); + this.name = name; + } + + @ManyToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "default_map_value_id", nullable = false) + public MapValue getDefaultValue() { + return defaultValue; + } + + public void setDefaultValue(MapValue defaultValue) { + log.tracef( "Setting defaultValue : %s", defaultValue ); + this.defaultValue = defaultValue; + } + + private int previousHashCode = -1; + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((getDefaultValue() == null) ? 0 : getDefaultValue().hashCode()); + result = prime * result + ((getName() == null) ? 0 : getName().hashCode()); + log.tracef( + "Calculated hashcode [%s] = %s (previous=%s, changed?=%s)", + this, + result, + previousHashCode, + !(previousHashCode == -1 || previousHashCode == result) + ); + previousHashCode = result; + return result; + } + + @Override + public boolean equals(Object obj) { + log.tracef( "Checking equality : %s -> %s", this, obj ); + if ( this == obj ) { + return true; + } + if ( obj == null ) { + return false; + } + if ( !(obj instanceof MapKey) ) { + return false; + } + MapKey other = (MapKey) obj; + if ( getDefaultValue() == null ) { + if ( other.getDefaultValue() != null ) { + return false; + } + } + else if ( !getDefaultValue().equals( other.getDefaultValue() ) ) { + return false; + } + if ( getName() == null ) { + if ( other.getName() != null ) { + return false; + } + } + else if ( !getName().equals( other.getName() ) ) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append( "MapKey [id=" ).append( getId() ) + .append( ", name=" ).append( getName() ) + .append( ", defaultValue=" ).append( getDefaultValue() ) + .append( "]" ); + return builder.toString(); + } + +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/collection/map/hhh7557/MapValue.java b/hibernate-core/src/test/java/org/hibernate/test/collection/map/hhh7557/MapValue.java new file mode 100644 index 0000000000..0a1c1473d7 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/collection/map/hhh7557/MapValue.java @@ -0,0 +1,123 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, 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.collection.map.hhh7557; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.jboss.logging.Logger; + +/** + * @author Elizabeth Chatman + * @author Steve Ebersole + */ +@Entity +@Table(name = "map_value") +public class MapValue { + private static final Logger log = Logger.getLogger( MapValue.class ); + + private Long id; + private String name; + + public MapValue() { + } + + public MapValue(String name) { + this.name = name; + } + + @Id + @GeneratedValue + @Column(name = "id", unique = true, nullable = false) + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + @Column(name = "name", unique = true, nullable = false) + public String getName() { + return name; + } + + public void setName(String name) { + log.tracef( "Setting name : %s", name ); + this.name = name; + } + + int previousHashCode = -1; + + @Override + public int hashCode() { + + final int prime = 31; + int result = 1; + result = prime * result + ((getName() == null) ? 0 : getName().hashCode()); + log.tracef( + "Calculated hashcode [%s] = %s (previous=%s, changed?=%s)", + this, + result, + previousHashCode, + !(previousHashCode == -1 || previousHashCode == result) + ); + previousHashCode = result; + return result; + } + + @Override + public boolean equals(Object obj) { + log.tracef( "Checking equality : %s -> %s", this, obj ); + if ( this == obj ) { + return true; + } + if ( obj == null ) { + return false; + } + if ( !(obj instanceof MapValue) ) { + return false; + } + MapValue other = (MapValue) obj; + if ( getName() == null ) { + if ( other.getName() != null ) { + return false; + } + } + else if ( !getName().equals( other.getName() ) ) { + return false; + } + return true; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append( "MapValue [id=" ).append( getId() ).append( ", name=" ).append( getName() ).append( "]" ); + return builder.toString(); + } +}