diff --git a/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java b/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java index fba620402f..5a18231060 100644 --- a/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java +++ b/hibernate-core/src/main/java/org/hibernate/collection/internal/AbstractPersistentCollection.java @@ -683,9 +683,19 @@ public boolean needsRecreate(CollectionPersister persister) { // the param would have to be bound twice. Until we eventually add "parameter bind points" concepts to the // AST in ORM 5+, handling this type of condition is either extremely difficult or impossible. Forcing // recreation isn't ideal, but not really any other option in ORM 4. - if ( persister.getElementType() instanceof CompositeType ) { - CompositeType componentType = (CompositeType) persister.getElementType(); - return !componentType.hasNotNullProperty(); + // Selecting a type used in where part of update statement + // (must match condidion in org.hibernate.persister.collection.BasicCollectionPersister.doUpdateRows). + // See HHH-9474 + Type whereType; + if ( persister.hasIndex() ) { + whereType = persister.getIndexType(); + } + else { + whereType = persister.getElementType(); + } + if ( whereType instanceof CompositeType ) { + CompositeType componentIndexType = (CompositeType) whereType; + return !componentIndexType.hasNotNullProperty(); } return false; } diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/collectionelement/recreate/Poi.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/collectionelement/recreate/Poi.java new file mode 100644 index 0000000000..e71169afd5 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/collectionelement/recreate/Poi.java @@ -0,0 +1,51 @@ +/* + * 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.annotations.collectionelement.recreate; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import org.hibernate.annotations.GenericGenerator; + +/** + * @author Sergey Astakhov + */ +@Entity +@GenericGenerator(name = "increment", strategy = "increment") +public class Poi { + + @Id + @GeneratedValue + private Integer id; + + private String name; + + public Poi() { + } + + public Poi(String _name) { + name = _name; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String _name) { + name = _name; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/collectionelement/recreate/PoiArrival.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/collectionelement/recreate/PoiArrival.java new file mode 100644 index 0000000000..69935d2eea --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/collectionelement/recreate/PoiArrival.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.annotations.collectionelement.recreate; + +import javax.persistence.Embeddable; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import java.util.Date; + +/** + * @author Sergey Astakhov + */ +@Embeddable +public class PoiArrival { + + @Temporal(TemporalType.TIMESTAMP) + private Date expectedTime; + + @Temporal(TemporalType.TIMESTAMP) + private Date arriveTime; + + public Date getExpectedTime() { + return expectedTime; + } + + public void setExpectedTime(Date _expectedTime) { + expectedTime = _expectedTime; + } + + public Date getArriveTime() { + return arriveTime; + } + + public void setArriveTime(Date _arriveTime) { + arriveTime = _arriveTime; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/collectionelement/recreate/RaceExecution.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/collectionelement/recreate/RaceExecution.java new file mode 100644 index 0000000000..f6dc672ddd --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/collectionelement/recreate/RaceExecution.java @@ -0,0 +1,77 @@ +/* + * 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.annotations.collectionelement.recreate; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import javax.persistence.*; + +import org.hibernate.annotations.GenericGenerator; + +/** + * @author Sergey Astakhov + */ +@Entity +@GenericGenerator(name = "increment", strategy = "increment") +public class RaceExecution { + + @Id + @GeneratedValue + private Integer id; + + @ElementCollection + @MapKeyClass(Poi.class) + @MapKeyJoinColumn(name = "poi", nullable = false) + @CollectionTable(name = "race_poi_arrival", joinColumns = @JoinColumn(name = "race_id")) + private Map poiArrival; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Map getPoiArrival() { + return poiArrival; + } + + public void setPoiArrival(Map _poiArrival) { + poiArrival = _poiArrival; + } + + public void arriveToPoi(Poi poi, Date time) { + if ( poiArrival == null ) { + poiArrival = new HashMap(); + } + + PoiArrival arrival = poiArrival.get( poi ); + if ( arrival == null ) { + arrival = new PoiArrival(); + poiArrival.put( poi, arrival ); + } + + arrival.setArriveTime( time ); + } + + public void expectedArrive(Poi poi, Date time) { + if ( poiArrival == null ) { + poiArrival = new HashMap(); + } + + PoiArrival arrival = poiArrival.get( poi ); + if ( arrival == null ) { + arrival = new PoiArrival(); + poiArrival.put( poi, arrival ); + } + + arrival.setExpectedTime( time ); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/annotations/collectionelement/recreate/RecreateCollectionTest.java b/hibernate-core/src/test/java/org/hibernate/test/annotations/collectionelement/recreate/RecreateCollectionTest.java new file mode 100644 index 0000000000..4b30dc1c1e --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/annotations/collectionelement/recreate/RecreateCollectionTest.java @@ -0,0 +1,86 @@ +/* + * 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.annotations.collectionelement.recreate; + +import org.hibernate.BaseSessionEventListener; +import org.hibernate.Session; + +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Test; + +import java.util.Date; + +import static org.junit.Assert.assertEquals; + +/** + * @author Sergey Astakhov + */ +public class RecreateCollectionTest extends BaseCoreFunctionalTestCase { + + private static class StatementsCounterListener extends BaseSessionEventListener { + int statements; + + @Override + public void jdbcExecuteStatementEnd() { + statements++; + } + } + + @Test + @TestForIssue(jiraKey = "HHH-9474") + public void testUpdateCollectionOfElements() throws Exception { + Session s = openSession(); + + s.getTransaction().begin(); + + Poi poi1 = new Poi( "Poi 1" ); + Poi poi2 = new Poi( "Poi 2" ); + + s.save( poi1 ); + s.save( poi2 ); + + RaceExecution race = new RaceExecution(); + + s.save( race ); + + Date currentTime = new Date(); + + race.arriveToPoi( poi1, currentTime ); + race.expectedArrive( poi2, new Date( currentTime.getTime() + 60 * 1000 ) ); + + s.flush(); + + assertEquals( 2, race.getPoiArrival().size() ); + + StatementsCounterListener statementsCounterListener = new StatementsCounterListener(); + + s.addEventListeners( statementsCounterListener ); + + race.arriveToPoi( poi2, new Date( currentTime.getTime() + 2 * 60 * 1000 ) ); + + s.flush(); + + assertEquals( 2, race.getPoiArrival().size() ); + + // There is should be one UPDATE statement. Without fix there is one DELETE and two INSERT-s. + + assertEquals( 1, statementsCounterListener.statements ); + + s.getTransaction().rollback(); + s.close(); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Poi.class, + RaceExecution.class + }; + } + +}