Merge remote-tracking branch 'upstream/master' into wip/6.0
This commit is contained in:
commit
bb4c4d0767
|
@ -32,8 +32,11 @@ import org.hibernate.boot.spi.BootstrapContext;
|
|||
import org.hibernate.boot.spi.MetadataBuildingOptions;
|
||||
import org.hibernate.boot.spi.MetadataContributor;
|
||||
import org.hibernate.boot.spi.MetadataImplementor;
|
||||
import org.hibernate.cfg.AvailableSettings;
|
||||
import org.hibernate.cfg.MetadataSourceType;
|
||||
import org.hibernate.dialect.Dialect;
|
||||
import org.hibernate.engine.config.spi.ConfigurationService;
|
||||
import org.hibernate.engine.config.spi.StandardConverters;
|
||||
import org.hibernate.engine.jdbc.spi.JdbcServices;
|
||||
import org.hibernate.type.BasicType;
|
||||
import org.hibernate.type.BasicTypeRegistry;
|
||||
|
@ -94,10 +97,16 @@ public class MetadataBuildingProcess {
|
|||
final MetadataSources sources,
|
||||
final BootstrapContext bootstrapContext) {
|
||||
final ManagedResourcesImpl managedResources = ManagedResourcesImpl.baseline( sources, bootstrapContext );
|
||||
final ConfigurationService configService = bootstrapContext.getServiceRegistry().getService( ConfigurationService.class );
|
||||
final boolean xmlMappingEnabled = configService.getSetting(
|
||||
AvailableSettings.XML_MAPPING_ENABLED,
|
||||
StandardConverters.BOOLEAN,
|
||||
true
|
||||
);
|
||||
ScanningCoordinator.INSTANCE.coordinateScan(
|
||||
managedResources,
|
||||
bootstrapContext,
|
||||
sources.getXmlMappingBinderAccess()
|
||||
xmlMappingEnabled ? sources.getXmlMappingBinderAccess() : null
|
||||
);
|
||||
return managedResources;
|
||||
}
|
||||
|
@ -285,7 +294,7 @@ public class MetadataBuildingProcess {
|
|||
final EntityHierarchyBuilder hierarchyBuilder = new EntityHierarchyBuilder();
|
||||
// final MappingBinder mappingBinder = new MappingBinder( true );
|
||||
// We need to disable validation here. It seems Envers is not producing valid (according to schema) XML
|
||||
final MappingBinder mappingBinder = new MappingBinder( classLoaderService, false );
|
||||
final MappingBinder mappingBinder = options.isXmlMappingEnabled() ? new MappingBinder( classLoaderService, false ) : null;
|
||||
for ( AdditionalJaxbMappingProducer producer : producers ) {
|
||||
log.tracef( "Calling AdditionalJaxbMappingProducer : %s", producer );
|
||||
Collection<MappingDocument> additionalMappings = producer.produceAdditionalMappings(
|
||||
|
|
|
@ -737,7 +737,8 @@ public abstract class CollectionType extends AbstractType implements Association
|
|||
// need to put the merged elements in a new collection
|
||||
Object result = ( target == null ||
|
||||
target == original ||
|
||||
target == LazyPropertyInitializer.UNFETCHED_PROPERTY ) ?
|
||||
target == LazyPropertyInitializer.UNFETCHED_PROPERTY ||
|
||||
target instanceof PersistentCollection && ( (PersistentCollection) target ).isWrapper( original ) ) ?
|
||||
instantiateResult( original ) : target;
|
||||
|
||||
//for arrays, replaceElements() may return a different reference, since
|
||||
|
|
|
@ -6,6 +6,10 @@
|
|||
*/
|
||||
package org.hibernate.test.annotations.selectbeforeupdate;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import javax.persistence.ElementCollection;
|
||||
import javax.persistence.Embeddable;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.JoinColumn;
|
||||
|
@ -136,6 +140,37 @@ public class UpdateDetachedTest extends BaseCoreFunctionalTestCase{
|
|||
assertEquals( Integer.valueOf( 1 ), foo.getVersion() );
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestForIssue(jiraKey = "HHH-14319")
|
||||
public void testUpdateDetachedWithAttachedPersistentSet() {
|
||||
final Bar bar = new Bar( 5, "Bar" );
|
||||
final Set<Comment> comments = new HashSet<>();
|
||||
comments.add( new Comment( "abc", "me" ) );
|
||||
bar.comments = comments;
|
||||
|
||||
// this should generate versions
|
||||
TransactionUtil.doInHibernate( this::sessionFactory, session -> {
|
||||
session.save( bar );
|
||||
} );
|
||||
final Bar loadedBar = TransactionUtil.doInHibernate( this::sessionFactory, session -> {
|
||||
// We set the comments to the hash set and leave it "dirty"
|
||||
Bar b = session.find( Bar.class, bar.getId() );
|
||||
b.comments = comments;
|
||||
|
||||
// During flushing, the comments HashSet becomes the backing collection of new PersistentSet which replaces the old entry
|
||||
session.flush();
|
||||
|
||||
// Replace the persistent collection with the backing collection in the field
|
||||
b.comments = comments;
|
||||
|
||||
// It's vital that we try merging a detached instance
|
||||
session.detach( b );
|
||||
return (Bar) session.merge( b );
|
||||
} );
|
||||
|
||||
assertEquals( 1, loadedBar.comments.size() );
|
||||
}
|
||||
|
||||
@Entity(name = "Foo")
|
||||
@SelectBeforeUpdate
|
||||
public static class Foo {
|
||||
|
@ -198,6 +233,8 @@ public class UpdateDetachedTest extends BaseCoreFunctionalTestCase{
|
|||
private String name;
|
||||
@Version
|
||||
private Integer version;
|
||||
@ElementCollection
|
||||
private Set<Comment> comments;
|
||||
|
||||
Bar() {
|
||||
|
||||
|
@ -231,5 +268,43 @@ public class UpdateDetachedTest extends BaseCoreFunctionalTestCase{
|
|||
public void setVersion(Integer version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public Set<Comment> getComments() {
|
||||
return comments;
|
||||
}
|
||||
|
||||
public void setComments(Set<Comment> comments) {
|
||||
this.comments = comments;
|
||||
}
|
||||
}
|
||||
|
||||
@Embeddable
|
||||
public static class Comment {
|
||||
private String comment;
|
||||
private String author;
|
||||
|
||||
public Comment() {
|
||||
}
|
||||
|
||||
public Comment(String comment, String author) {
|
||||
this.comment = comment;
|
||||
this.author = author;
|
||||
}
|
||||
|
||||
public String getComment() {
|
||||
return comment;
|
||||
}
|
||||
|
||||
public void setComment(String comment) {
|
||||
this.comment = comment;
|
||||
}
|
||||
|
||||
public String getAuthor() {
|
||||
return author;
|
||||
}
|
||||
|
||||
public void setAuthor(String author) {
|
||||
this.author = author;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,14 +20,19 @@ import org.hibernate.envers.internal.tools.EntityTools;
|
|||
import org.hibernate.envers.internal.tools.query.Parameters;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
import org.hibernate.proxy.HibernateProxy;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
|
||||
/**
|
||||
* @author Adam Warski (adam at warski dot org)
|
||||
* @author HernпїЅn Chanfreau
|
||||
* @author Michal Skowronek (mskowr at o2 dot pl)
|
||||
* @author Chris Cranford
|
||||
* @author Luke Chen
|
||||
*/
|
||||
public class ToOneIdMapper extends AbstractToOneMapper {
|
||||
private static final Logger log = Logger.getLogger( ToOneIdMapper.class );
|
||||
private final IdMapper delegate;
|
||||
private final String referencedEntityName;
|
||||
private final boolean nonInsertableFake;
|
||||
|
@ -58,9 +63,19 @@ public class ToOneIdMapper extends AbstractToOneMapper {
|
|||
// bi-directional relation, we always store the "old", unchanged data, to prevent storing changes made
|
||||
// to this field. It is the responsibility of the collection to properly update it if it really changed.
|
||||
Object entity = nonInsertableFake ? oldObj : newObj;
|
||||
|
||||
// fix HHH-13760 - try to aggressively un-proxy this entity to help get the correct type of data later
|
||||
// in mapToMapFromEntity. But it might fail while getImplementation() if object is deleted or other reasons.
|
||||
// We catch the exception and fallback to call mapToMapFromEntity directly with the HibernateProxy entity
|
||||
if ( lazyMapping && entity instanceof HibernateProxy ) {
|
||||
try {
|
||||
entity = ((HibernateProxy) entity).getHibernateLazyInitializer().getImplementation();
|
||||
}
|
||||
catch ( PersistenceException e ) {
|
||||
log.debug( "Ignore PersistenceException while initializing the entity, " +
|
||||
"and fallback to call mapToMapFromEntity directly" );
|
||||
}
|
||||
}
|
||||
|
||||
delegate.mapToMapFromEntity( newData, entity );
|
||||
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.envers.test.integration.manytoone.lazy;
|
||||
|
||||
import org.hibernate.envers.NotAudited;
|
||||
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.Table;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Luke Chen
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "child_user")
|
||||
public class ChildUser extends User {
|
||||
|
||||
@OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade = CascadeType.REMOVE)
|
||||
@NotAudited
|
||||
private List<Shipment> shipmentList;
|
||||
|
||||
ChildUser() {
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.envers.test.integration.manytoone.lazy;
|
||||
|
||||
import org.hibernate.Hibernate;
|
||||
import org.hibernate.envers.configuration.EnversSettings;
|
||||
import org.hibernate.envers.test.BaseEnversFunctionalTestCase;
|
||||
import org.hibernate.envers.test.Priority;
|
||||
import org.hibernate.testing.TestForIssue;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Tests that proxies can still be resolved correctly in ToOneIdMapper even the object is already deleted and can't
|
||||
* find in cache. This can happen if the deleted object is an inherited object, and when the child object is deleted,
|
||||
* we cannot find the object with the parent class name anymore.
|
||||
*
|
||||
* @author Luke Chen
|
||||
*/
|
||||
@TestForIssue(jiraKey = "HHH-13945")
|
||||
public class ManyToOneLazyDeleteTest extends BaseEnversFunctionalTestCase {
|
||||
private Long shipmentId;
|
||||
private User user;
|
||||
|
||||
@Override
|
||||
protected Class<?>[] getAnnotatedClasses() {
|
||||
return new Class<?>[] { Shipment.class, Address.class, AddressVersion.class, User.class, ChildUser.class };
|
||||
}
|
||||
|
||||
@Test
|
||||
@Priority(10)
|
||||
public void initData() {
|
||||
|
||||
this.shipmentId = doInHibernate( this::sessionFactory, session -> {
|
||||
final Shipment shipment = new Shipment( Instant.now(), "system", Instant.now().plus( Duration.ofDays( 3 ) ), "abcd123", null, null );
|
||||
session.persist( shipment );
|
||||
session.flush();
|
||||
|
||||
final Address origin = new Address( Instant.now(), "system", "Valencia#1" );
|
||||
final Address destination = new Address( Instant.now(), "system", "Madrid#3" );
|
||||
final AddressVersion originVersion0 = origin.addInitialVersion( "Poligono Manises" );
|
||||
final AddressVersion destinationVersion0 = destination.addInitialVersion( "Poligono Alcobendas" );
|
||||
user = new ChildUser();
|
||||
session.persist( origin );
|
||||
session.persist( destination );
|
||||
session.persist( user );
|
||||
|
||||
session.flush();
|
||||
shipment.setUser( user );
|
||||
shipment.setOrigin( originVersion0 );
|
||||
shipment.setDestination( destinationVersion0 );
|
||||
|
||||
session.merge( shipment );
|
||||
session.flush();
|
||||
|
||||
return shipment.getId();
|
||||
} );
|
||||
|
||||
doInHibernate( this::sessionFactory, session -> {
|
||||
final Shipment shipment = session.get( Shipment.class, shipmentId );
|
||||
session.remove(shipment);
|
||||
// Cast the User instance to the ChildUser, and delete the child one, so the cache for
|
||||
// the User instance will not be there, and entityNotFound exception will be thrown while envers processing it
|
||||
ChildUser childUser = session.get(ChildUser.class, user.getId());
|
||||
session.remove(childUser);
|
||||
|
||||
session.flush();
|
||||
} );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addSettings(Map settings) {
|
||||
super.addSettings( settings );
|
||||
|
||||
settings.put(EnversSettings.STORE_DATA_AT_DELETE, "true");
|
||||
}
|
||||
}
|
|
@ -32,7 +32,7 @@ public class ManyToOneLazyFetchTest extends BaseEnversFunctionalTestCase {
|
|||
|
||||
@Override
|
||||
protected Class<?>[] getAnnotatedClasses() {
|
||||
return new Class<?>[] { Shipment.class, Address.class, AddressVersion.class };
|
||||
return new Class<?>[] { Shipment.class, Address.class, AddressVersion.class, User.class, ChildUser.class };
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -62,6 +62,11 @@ public class Shipment extends BaseDomainEntity {
|
|||
@Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
|
||||
private AddressVersion destination;
|
||||
|
||||
@ManyToOne(optional = true, fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "user_id", referencedColumnName = "id", nullable = true)
|
||||
@Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
|
||||
private User user;
|
||||
|
||||
Shipment() {
|
||||
}
|
||||
|
||||
|
@ -104,4 +109,8 @@ public class Shipment extends BaseDomainEntity {
|
|||
public void setDestination(AddressVersion destination) {
|
||||
this.destination = destination;
|
||||
}
|
||||
|
||||
public void setUser(User user) {
|
||||
this.user = user;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.envers.test.integration.manytoone.lazy;
|
||||
|
||||
import org.hibernate.envers.NotAudited;
|
||||
|
||||
import javax.persistence.CascadeType;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.Table;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Luke Chen
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "user_tbl")
|
||||
public class User {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
protected long id = 0;
|
||||
|
||||
@OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade = CascadeType.REMOVE)
|
||||
@NotAudited
|
||||
private List<Shipment> shipmentList;
|
||||
|
||||
User() {
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue