HHH-13361 Allow auditing entities with nested identifiers

This commit is contained in:
Chris Cranford 2021-12-18 17:37:18 -05:00 committed by Chris Cranford
parent c5f719ef39
commit b5755b6945
14 changed files with 460 additions and 16 deletions

View File

@ -24,6 +24,7 @@ import org.hibernate.envers.internal.entities.mapper.SimpleMapperBuilder;
import org.hibernate.envers.internal.entities.mapper.id.EmbeddedIdMapper;
import org.hibernate.envers.internal.entities.mapper.id.IdMapper;
import org.hibernate.envers.internal.entities.mapper.id.MultipleIdMapper;
import org.hibernate.envers.internal.entities.mapper.id.NestedEmbeddedIdMapper;
import org.hibernate.envers.internal.entities.mapper.id.SimpleIdMapperBuilder;
import org.hibernate.envers.internal.entities.mapper.id.SingleIdMapper;
import org.hibernate.loader.PropertyPath;
@ -32,6 +33,7 @@ import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.ToOne;
import org.hibernate.mapping.Value;
import org.hibernate.type.ComponentType;
import org.hibernate.type.ManyToOneType;
import org.hibernate.type.Type;
@ -63,7 +65,8 @@ public final class IdMetadataGenerator extends AbstractMetadataGenerator {
boolean key,
SimpleIdMapperBuilder mapper,
Property mappedProperty,
Property virtualProperty) {
Property virtualProperty,
boolean audited) {
if ( PropertyPath.IDENTIFIER_MAPPER_PROPERTY.equals( mappedProperty.getName() ) ) {
return false;
@ -93,6 +96,19 @@ public final class IdMetadataGenerator extends AbstractMetadataGenerator {
return added;
}
else if ( ComponentType.class.isInstance( mappedProperty.getType() ) ) {
final Component component = (Component) mappedProperty.getValue();
final NestedEmbeddedIdMapper nestedMapper;
if ( mapper != null ) {
final PropertyData propertyData = propertyAuditingData.resolvePropertyData();
nestedMapper = new NestedEmbeddedIdMapper( propertyData, component );
mapper.add( propertyData, nestedMapper );
}
else {
nestedMapper = null;
}
return addIdProperties( attributeContainer, component, null, nestedMapper, key, audited );
}
return addBasic( attributeContainer, propertyAuditingData, mappedProperty.getValue(), mapper, key );
}
@ -116,7 +132,7 @@ public final class IdMetadataGenerator extends AbstractMetadataGenerator {
virtualProperty = null;
}
if ( !addIdProperty( attributeContainer, key, mapper, property, virtualProperty ) ) {
if ( !addIdProperty( attributeContainer, key, mapper, property, virtualProperty, audited ) ) {
// If the entity is audited, and a non-supported id component is used, throw exception.
if ( audited ) {
throw new EnversMappingException(
@ -196,7 +212,7 @@ public final class IdMetadataGenerator extends AbstractMetadataGenerator {
if ( idMapper != null ) {
// Multiple id
final Component virtualComponent = (Component) persistentClass.getIdentifier();
mapper = new MultipleIdMapper( loadClass( virtualComponent ), persistentClass.getServiceRegistry() );
mapper = new MultipleIdMapper( virtualComponent );
if ( !addIdProperties( relation, idMapper, virtualComponent, mapper, false, audited ) ) {
return null;
@ -210,8 +226,7 @@ public final class IdMetadataGenerator extends AbstractMetadataGenerator {
else if ( idProp.isComposite() ) {
// Embedded id
final Component idComponent = (Component) idProp.getValue();
final Class<?> embeddableClass = loadClass( idComponent );
mapper = new EmbeddedIdMapper( getIdPropertyData( idProp ), embeddableClass, persistentClass.getServiceRegistry() );
mapper = new EmbeddedIdMapper( getIdPropertyData( idProp ), idComponent );
if ( !addIdProperties( relation, idComponent, null, mapper, false, audited ) ) {
return null;

View File

@ -17,15 +17,18 @@ import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.service.ServiceRegistry;
/**
* An abstract identifier mapper implementation specific for composite identifiers.
*
* @author Adam Warski (adam at warski dot org)
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
* @author Chris Cranford
*/
public abstract class AbstractCompositeIdMapper extends AbstractIdMapper implements SimpleIdMapperBuilder {
protected final Class compositeIdClass;
protected final Class<?> compositeIdClass;
protected Map<PropertyData, SingleIdMapper> ids;
protected Map<PropertyData, AbstractIdMapper> ids;
protected AbstractCompositeIdMapper(Class compositeIdClass, ServiceRegistry serviceRegistry) {
protected AbstractCompositeIdMapper(Class<?> compositeIdClass, ServiceRegistry serviceRegistry) {
super( serviceRegistry );
this.compositeIdClass = compositeIdClass;
ids = Tools.newLinkedHashMap();
@ -33,7 +36,12 @@ public abstract class AbstractCompositeIdMapper extends AbstractIdMapper impleme
@Override
public void add(PropertyData propertyData) {
ids.put( propertyData, new SingleIdMapper( getServiceRegistry(), propertyData ) );
add( propertyData, new SingleIdMapper( getServiceRegistry(), propertyData ) );
}
@Override
public void add(PropertyData propertyData, AbstractIdMapper idMapper) {
ids.put( propertyData, idMapper );
}
@Override
@ -43,7 +51,7 @@ public abstract class AbstractCompositeIdMapper extends AbstractIdMapper impleme
}
final Object compositeId = instantiateCompositeId();
for ( SingleIdMapper mapper : ids.values() ) {
for ( AbstractIdMapper mapper : ids.values() ) {
if ( !mapper.mapToEntityFromMap( compositeId, data ) ) {
return null;
}
@ -52,6 +60,11 @@ public abstract class AbstractCompositeIdMapper extends AbstractIdMapper impleme
return compositeId;
}
@Override
public void mapToEntityFromEntity(Object objectTo, Object objectFrom) {
// no-op; does nothing
}
protected Object instantiateCompositeId() {
return AccessController.doPrivileged(
new PrivilegedAction<Object>() {

View File

@ -13,6 +13,8 @@ import org.hibernate.envers.internal.tools.query.Parameters;
import org.hibernate.service.ServiceRegistry;
/**
* The base abstract class implementation for identifier mappers.
*
* @author Adam Warski (adam at warski dot org)
* @author Chris Cranford
*/
@ -139,6 +141,8 @@ public abstract class AbstractIdMapper implements IdMapper {
}
}
public abstract void mapToEntityFromEntity(Object objectTo, Object objectFrom);
private void handleNullValue(Parameters parameters, String alias, String propertyName, boolean equals) {
if ( equals ) {
parameters.addNullRestriction( alias, propertyName );

View File

@ -16,19 +16,27 @@ import java.util.Map;
import org.hibernate.envers.exception.AuditException;
import org.hibernate.envers.internal.entities.PropertyData;
import org.hibernate.envers.internal.tools.ReflectionTools;
import org.hibernate.mapping.Component;
import org.hibernate.property.access.spi.Getter;
import org.hibernate.property.access.spi.Setter;
import org.hibernate.service.ServiceRegistry;
/**
* An identifier mapper implementation for {@link jakarta.persistence.EmbeddedId} mappings.
*
* @author Adam Warski (adam at warski dot org)
* @author Chris Cranford
*/
public class EmbeddedIdMapper extends AbstractCompositeIdMapper implements SimpleIdMapperBuilder {
private PropertyData idPropertyData;
public EmbeddedIdMapper(PropertyData idPropertyData, Class compositeIdClass, ServiceRegistry serviceRegistry) {
super( compositeIdClass, serviceRegistry );
public EmbeddedIdMapper(PropertyData propertyData, Component component) {
super( component.getComponentClass(), component.getServiceRegistry() );
this.idPropertyData = propertyData;
}
private EmbeddedIdMapper(PropertyData idPropertyData, Class<?> compositeIdClass, ServiceRegistry serviceRegistry) {
super( compositeIdClass, serviceRegistry );
this.idPropertyData = idPropertyData;
}

View File

@ -14,6 +14,8 @@ import org.hibernate.envers.internal.tools.query.Parameters;
import org.hibernate.service.ServiceRegistry;
/**
* Base contract for all identifier mappers.
*
* @author Adam Warski (adam at warski dot org)
* @author Chris Cranford
*/

View File

@ -13,14 +13,23 @@ import java.util.Map;
import org.hibernate.Session;
import org.hibernate.envers.internal.entities.PropertyData;
import org.hibernate.mapping.Component;
import org.hibernate.service.ServiceRegistry;
/**
* An implementation of an identifier mapper for {@link jakarta.persistence.IdClass} or multiple
* {@link jakarta.persistence.Id} identifier mappings.
*
* @author Adam Warski (adam at warski dot org)
* @author Chris Cranford
*/
public class MultipleIdMapper extends AbstractCompositeIdMapper implements SimpleIdMapperBuilder {
public MultipleIdMapper(Class compositeIdClass, ServiceRegistry serviceRegistry) {
public MultipleIdMapper(Component component) {
super( component.getComponentClass(), component.getServiceRegistry() );
}
private MultipleIdMapper(Class<?> compositeIdClass, ServiceRegistry serviceRegistry) {
super( compositeIdClass, serviceRegistry );
}
@ -32,9 +41,9 @@ public class MultipleIdMapper extends AbstractCompositeIdMapper implements Simpl
@Override
public void mapToMapFromId(Session session, Map<String, Object> data, Object obj) {
if ( compositeIdClass.isInstance( obj ) ) {
for ( Map.Entry<PropertyData, SingleIdMapper> entry : ids.entrySet() ) {
for ( Map.Entry<PropertyData, AbstractIdMapper> entry : ids.entrySet() ) {
final PropertyData propertyData = entry.getKey();
final SingleIdMapper idMapper = entry.getValue();
final AbstractIdMapper idMapper = entry.getValue();
if ( propertyData.getVirtualReturnClass() == null ) {
idMapper.mapToMapFromEntity( data, obj );
@ -92,7 +101,7 @@ public class MultipleIdMapper extends AbstractCompositeIdMapper implements Simpl
}
final Object compositeId = instantiateCompositeId();
for ( SingleIdMapper mapper : ids.values() ) {
for ( AbstractIdMapper mapper : ids.values() ) {
mapper.mapToEntityFromEntity( compositeId, data );
}

View File

@ -0,0 +1,22 @@
/*
* 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.internal.entities.mapper.id;
import org.hibernate.envers.internal.entities.PropertyData;
import org.hibernate.mapping.Component;
/**
* An identifier mapper that is meant to support nested {@link jakarta.persistence.Embeddable} instances
* inside an existing {@link jakarta.persistence.EmbeddedId} identifier hierarchy.
*
* @author Chris Cranford
*/
public class NestedEmbeddedIdMapper extends EmbeddedIdMapper {
public NestedEmbeddedIdMapper(PropertyData propertyData, Component component) {
super( propertyData, component );
}
}

View File

@ -6,10 +6,21 @@
*/
package org.hibernate.envers.internal.entities.mapper.id;
import org.hibernate.envers.internal.entities.PropertyData;
import org.hibernate.envers.internal.entities.mapper.SimpleMapperBuilder;
/**
* A simple identifier builder contract.
*
* @author Adam Warski (adam at warski dot org)
* @author Chris Cranford
*/
public interface SimpleIdMapperBuilder extends IdMapper, SimpleMapperBuilder {
/**
* Add a custom identifier mapper to the builder.
*
* @param propertyData the property data
* @param idMapper the mapper
*/
void add(PropertyData propertyData, AbstractIdMapper idMapper);
}

View File

@ -21,7 +21,10 @@ import org.hibernate.proxy.HibernateProxy;
import org.hibernate.service.ServiceRegistry;
/**
* An implementation of an identifier mapper for a single basic attribute property.
*
* @author Adam Warski (adam at warski dot org)
* @author Chris Cranford
*/
public class SingleIdMapper extends AbstractIdMapper implements SimpleIdMapperBuilder {
private PropertyData propertyData;
@ -44,6 +47,11 @@ public class SingleIdMapper extends AbstractIdMapper implements SimpleIdMapperBu
this.propertyData = propertyData;
}
@Override
public void add(PropertyData propertyData, AbstractIdMapper idMapper) {
throw new AuditException( "This method is not allowed for a single identifier mapper" );
}
@Override
public boolean mapToEntityFromMap(final Object obj, Map data) {
if ( data == null || obj == null ) {
@ -143,6 +151,7 @@ public class SingleIdMapper extends AbstractIdMapper implements SimpleIdMapperBu
}
}
@Override
public void mapToEntityFromEntity(final Object objTo, final Object objFrom) {
if ( objTo == null || objFrom == null ) {
return;

View File

@ -0,0 +1,71 @@
/*
* 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.orm.test.envers.integration.ids.embeddedid;
import java.util.Set;
import jakarta.persistence.Access;
import jakarta.persistence.AccessType;
import jakarta.persistence.EmbeddedId;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import org.hibernate.envers.Audited;
@Entity
@Audited
@Table(name = "compositeentity")
@Access(value = AccessType.FIELD)
public class CompositeEntity {
@EmbeddedId
private CompositeEntityId codeObject = new CompositeEntityId();
@OneToMany(targetEntity = OwnerOfRelationCode.class, fetch = FetchType.LAZY, mappedBy = "compositeEntity")
private Set<OwnerOfRelationCode> ownerOfRelationCodes = new java.util.HashSet<>();
public CompositeEntityId getCodeObject() {
return codeObject;
}
public void setCodeObject(CompositeEntityId codeObject) {
this.codeObject = codeObject;
}
public Set<OwnerOfRelationCode> getOwnerOfRelationCodes() {
return ownerOfRelationCodes;
}
public void setOwnerOfRelationCodes(Set<OwnerOfRelationCode> ownerOfRelationCodes) {
this.ownerOfRelationCodes = ownerOfRelationCodes;
}
public String getFirstCode() {
return codeObject == null ? null : codeObject.getFirstCode();
}
public void setFirstCode(String firstCode) {
if ( codeObject == null ) {
codeObject = new CompositeEntityId();
}
codeObject.setFirstCode( firstCode );
}
public String getSecondCode() {
return codeObject == null ? null : codeObject.getSecondCode();
}
public void setSecondCode(String secondCode) {
if ( codeObject == null ) {
codeObject = new CompositeEntityId();
}
codeObject.setSecondCode( secondCode );
}
}

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.orm.test.envers.integration.ids.embeddedid;
import java.io.Serializable;
import jakarta.persistence.Embeddable;
@Embeddable
public class CompositeEntityId implements Serializable{
private String firstCode;
private String secondCode;
public String getFirstCode() {
return firstCode;
}
public void setFirstCode(String firstCode) {
this.firstCode = firstCode;
}
public String getSecondCode() {
return secondCode;
}
public void setSecondCode(String secondCode) {
this.secondCode = secondCode;
}
@Override
public String toString() {
return "CompositeEntityId{" +
"firstCode='" + firstCode + '\'' +
", secondCode='" + secondCode + '\'' +
'}';
}
}

View File

@ -0,0 +1,107 @@
/*
* 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.orm.test.envers.integration.ids.embeddedid;
import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import java.util.Arrays;
import org.hibernate.orm.test.envers.BaseEnversFunctionalTestCase;
import org.hibernate.orm.test.envers.BaseEnversJPAFunctionalTestCase;
import org.hibernate.orm.test.envers.Priority;
import org.hibernate.testing.TestForIssue;
import org.junit.Test;
@TestForIssue(jiraKey = "HHH-13361")
public class NestedEmbeddedIdentifiersTest extends BaseEnversJPAFunctionalTestCase {
private OwnerOfRelationCodeId id;
@Override
protected Class[] getAnnotatedClasses() {
return new Class<?>[] { OwnerOfRelationCode.class, CompositeEntity.class };
}
@Test
@Priority(10)
public void initData() {
// Revision 1, test insert
final OwnerOfRelationCode owner = doInJPA( this::entityManagerFactory, session -> {
CompositeEntity compositeEntity = new CompositeEntity();
compositeEntity.setFirstCode( "firstCode" );
compositeEntity.setSecondCode( "secondCode" );
session.persist( compositeEntity );
OwnerOfRelationCode ownerEntity = new OwnerOfRelationCode();
ownerEntity.setCompositeEntity( compositeEntity );
ownerEntity.setSecondIdentifier( "secondIdentifier" );
session.persist( ownerEntity );
return ownerEntity;
} );
this.id = owner.getCodeObject();
// Revision 2, test update
doInJPA( this::entityManagerFactory, session -> {
OwnerOfRelationCode ownerEntity = session.find( OwnerOfRelationCode.class, id );
ownerEntity.setDescription( "first description" );
} );
}
@Test
public void testRevisionCounts() {
assertEquals( Arrays.asList( 1, 2 ), getAuditReader().getRevisions( OwnerOfRelationCode.class, id ) );
}
@Test
public void testIdentifierAtRevision1() {
// select e__
// from org.hibernate.orm.test.envers.integration.ids.embeddedid.OwnerOfRelationCode_AUD e__
// where e__.originalId.REV.id = (select max(e2__.originalId.REV.id)
// from org.hibernate.orm.test.envers.integration.ids.embeddedid.OwnerOfRelationCode_AUD e2__
// where e2__.originalId.REV.id <= :revision
// and e__.originalId.compositeEntity = e2__.originalId.compositeEntity
// and e__.originalId.secondIdentifier = e2__.originalId.secondIdentifier)
// and e__.REVTYPE <> :_p0
// and e__.originalId.compositeEntity_firstCode = :_p1
// and e__.originalId.compositeEntity_secondCode = :_p2
// and e__.originalId.secondIdentifier = :_p3
//
// select e__
// from org.hibernate.orm.test.envers.integration.ids.embeddedid.OwnerOfRelationCode_AUD e__
// where e__.originalId.REV.id = (select max(e2__.originalId.REV.id)
// from org.hibernate.orm.test.envers.integration.ids.embeddedid.OwnerOfRelationCode_AUD e2__
// where e2__.originalId.REV.id <= :revision
// and e__.originalId.compositeEntity_firstCode = e2__.originalId.compositeEntity_firstCode
// and e__.originalId.compositeEntity_secondCode = e2__.originalId.compositeEntity_secondCode
// and e__.originalId.secondIdentifier = e2__.originalId.secondIdentifier)
// and e__.REVTYPE <> :_p0
// and e__.originalId.compositeEntity_firstCode = :_p1
// and e__.originalId.compositeEntity_secondCode = :_p2
// and e__.originalId.secondIdentifier = :_p3
final OwnerOfRelationCode rev1 = getAuditReader().find( OwnerOfRelationCode.class, id, 1 );
System.out.println( rev1 );
assertEquals( rev1.getCodeObject().getSecondIdentifier(), "secondIdentifier" );
assertEquals( rev1.getCodeObject().getCompositeEntity().getFirstCode(), "firstCode" );
assertEquals( rev1.getCodeObject().getCompositeEntity().getSecondCode(), "secondCode" );
assertNull( rev1.getDescription() );
}
@Test
public void testIdentifierAtRevision2() {
final OwnerOfRelationCode rev2 = getAuditReader().find( OwnerOfRelationCode.class, id, 2 );
System.out.println( rev2 );
assertEquals( rev2.getCodeObject().getSecondIdentifier(), "secondIdentifier" );
assertEquals( rev2.getCodeObject().getCompositeEntity().getFirstCode(), "firstCode" );
assertEquals( rev2.getCodeObject().getCompositeEntity().getSecondCode(), "secondCode" );
assertEquals( rev2.getDescription(), "first description" );
}
}

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.orm.test.envers.integration.ids.embeddedid;
import jakarta.persistence.Access;
import jakarta.persistence.AccessType;
import jakarta.persistence.EmbeddedId;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinColumns;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.MapsId;
import jakarta.persistence.Table;
import org.hibernate.envers.Audited;
@Entity
@Audited
@Table(name = "ownerOfRelationcode")
@Access(value = AccessType.FIELD)
public class OwnerOfRelationCode {
@EmbeddedId
private OwnerOfRelationCodeId codeObject = new OwnerOfRelationCodeId();
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumns({
@JoinColumn(name = "compositeEntity_firstCode", referencedColumnName = "firstCode", unique = false, nullable = false),
@JoinColumn(name = "compositeEntity_secondCode", referencedColumnName = "secondCode", unique = false, nullable = false) })
@MapsId("compositeEntity")
private CompositeEntity compositeEntity;
private String description;
public OwnerOfRelationCodeId getCodeObject() {
return codeObject;
}
public CompositeEntity getCompositeEntity() {
return compositeEntity;
}
public void setCompositeEntity(CompositeEntity compositeEntity) {
if ( codeObject == null ) {
codeObject = new OwnerOfRelationCodeId();
}
if ( compositeEntity != null ) {
codeObject.setCompositeEntity( compositeEntity.getCodeObject() );
}
this.compositeEntity = compositeEntity;
}
public String getSecondIdentifier() {
return codeObject == null ? null : codeObject.getSecondIdentifier();
}
public void setSecondIdentifier(String secondIdentifier) {
if ( codeObject == null ) {
codeObject = new OwnerOfRelationCodeId();
}
codeObject.setSecondIdentifier( secondIdentifier );
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
@Override
public String toString() {
return "OwnerOfRelationCode{" +
"codeObject=" + codeObject +
", compositeEntity=" + compositeEntity +
", description='" + description + '\'' +
'}';
}
}

View File

@ -0,0 +1,45 @@
/*
* 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.orm.test.envers.integration.ids.embeddedid;
import java.io.Serializable;
import jakarta.persistence.Embeddable;
import jakarta.persistence.Embedded;
@Embeddable
public class OwnerOfRelationCodeId implements Serializable {
@Embedded
private CompositeEntityId compositeEntity;
private String secondIdentifier;
public CompositeEntityId getCompositeEntity() {
return compositeEntity;
}
public void setCompositeEntity(CompositeEntityId compositeEntity) {
this.compositeEntity = compositeEntity;
}
public String getSecondIdentifier() {
return secondIdentifier;
}
public void setSecondIdentifier(String secondIdentifier) {
this.secondIdentifier = secondIdentifier;
}
@Override
public String toString() {
return "OwnerOfRelationCodeId{" +
"compositeEntity=" + compositeEntity +
", secondIdentifier='" + secondIdentifier + '\'' +
'}';
}
}