HHH-8051 Gracefully handle not-found to-one associations
This commit is contained in:
parent
28b8b33b88
commit
b384b37f39
|
@ -321,6 +321,12 @@ This behavior is great when you want to find the snapshot of a non-related entit
|
|||
The new (optional) behavior when this option is enabled forces the query to perform an exact-match instead.
|
||||
In order for these methods to return a non-`null` value, a revision entry must exist for the entity with the specified primary key and revision number; otherwise the result will be `null`.
|
||||
|
||||
`*org.hibernate.envers.global_relation_not_found_legacy_flag*` (default: `true` )::
|
||||
Globally defines whether legacy relation not-found behavior should be used or not.
|
||||
+
|
||||
By specifying `true`, any `EntityNotFoundException` errors will be thrown unless the `Audited` annotation explicitly specifies to _ignore_ not-found relations.
|
||||
By specifying `false`, any `EntityNotFoundException` will be be ignored unless the `Audited` annotation explicitly specifies to _raise the error_ rather than silently ignore not-found relations.
|
||||
|
||||
[IMPORTANT]
|
||||
====
|
||||
The following configuration options have been added recently and should be regarded as experimental:
|
||||
|
@ -332,6 +338,7 @@ The following configuration options have been added recently and should be regar
|
|||
. `org.hibernate.envers.original_id_prop_name`
|
||||
. `org.hibernate.envers.find_by_revision_exact_match`
|
||||
. `org.hibernate.envers.audit_strategy_validity_revend_timestamp_numeric`
|
||||
. `org.hibernate.envers.global_relation_not_found_legacy_flag`
|
||||
====
|
||||
|
||||
[[envers-additional-mappings]]
|
||||
|
|
|
@ -11,6 +11,8 @@ import java.lang.annotation.Retention;
|
|||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.hibernate.Incubating;
|
||||
|
||||
/**
|
||||
* When applied to a class, indicates that all of its properties should be audited.
|
||||
* When applied to a field, indicates that this field should be audited.
|
||||
|
@ -19,6 +21,7 @@ import java.lang.annotation.Target;
|
|||
* @author Tomasz Bech
|
||||
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
|
||||
* @author Michal Skowronek (mskowr at o2 dot pl)
|
||||
* @author Chris Cranford
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
|
||||
|
@ -30,6 +33,23 @@ public @interface Audited {
|
|||
*/
|
||||
RelationTargetAuditMode targetAuditMode() default RelationTargetAuditMode.AUDITED;
|
||||
|
||||
/**
|
||||
* Specifies if the entity that is the relation target isn't found, how should the system react.
|
||||
*
|
||||
* The default is to use the behavior configured based on the system property:
|
||||
* {@link org.hibernate.envers.configuration.EnversSettings#GLOBAL_RELATION_NOT_FOUND_LEGACY_FLAG}.
|
||||
*
|
||||
* When the configuration property is {@code true}, this is to use the legacy behavior which
|
||||
* implies that the system should throw the {@code EntityNotFoundException} errors unless
|
||||
* the user has explicitly specified the value {@link RelationTargetNotFoundAction#IGNORE}.
|
||||
*
|
||||
* When the configuration property is {@code false}, this is to use the new behavior which
|
||||
* implies that the system should ignore the {@code EntityNotFoundException} errors unless
|
||||
* the user has explicitly specified the value {@link RelationTargetNotFoundAction#ERROR}.
|
||||
*/
|
||||
@Incubating
|
||||
RelationTargetNotFoundAction targetNotFoundAction() default RelationTargetNotFoundAction.DEFAULT;
|
||||
|
||||
/**
|
||||
* Specifies the superclasses for which properties should be audited, even if the superclasses are not
|
||||
* annotated with {@link Audited}. Causes all properties of the listed classes to be audited, just as if the
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Defines the actions on how to handle {@code EntityNotFoundException} cases when a relation
|
||||
* between two entities (audited or not) cannot be found in the data store.
|
||||
*
|
||||
* @author Chris Cranford
|
||||
* @see org.hibernate.annotations.NotFoundAction
|
||||
*/
|
||||
public enum RelationTargetNotFoundAction {
|
||||
/**
|
||||
* Specifies that exception handling should be based on the global system property:
|
||||
* {@link org.hibernate.envers.configuration.EnversSettings#GLOBAL_RELATION_NOT_FOUND_LEGACY_FLAG}.
|
||||
*/
|
||||
DEFAULT,
|
||||
|
||||
/**
|
||||
* Specifies that exceptions should be thrown regardless of the global system property:
|
||||
* {@link org.hibernate.envers.configuration.EnversSettings#GLOBAL_RELATION_NOT_FOUND_LEGACY_FLAG}.
|
||||
*/
|
||||
ERROR,
|
||||
|
||||
/**
|
||||
* Specifies that exceptions should be ignored regardless of the global system property:
|
||||
* {@link org.hibernate.envers.configuration.EnversSettings#GLOBAL_RELATION_NOT_FOUND_LEGACY_FLAG}.
|
||||
*/
|
||||
IGNORE
|
||||
}
|
|
@ -71,6 +71,7 @@ public class Configuration {
|
|||
private final boolean modifiedFlagsEnabled;
|
||||
private final boolean modifiedFlagsDefined;
|
||||
private final boolean findByRevisionExactMatch;
|
||||
private final boolean globalLegacyRelationTargetNotFound;
|
||||
|
||||
private final boolean trackEntitiesChanged;
|
||||
private boolean trackEntitiesOverride;
|
||||
|
@ -128,6 +129,7 @@ public class Configuration {
|
|||
modifiedFlagsEnabled = configProps.getBoolean( EnversSettings.GLOBAL_WITH_MODIFIED_FLAG, false );
|
||||
|
||||
findByRevisionExactMatch = configProps.getBoolean( EnversSettings.FIND_BY_REVISION_EXACT_MATCH, false );
|
||||
globalLegacyRelationTargetNotFound = configProps.getBoolean( EnversSettings.GLOBAL_RELATION_NOT_FOUND_LEGACY_FLAG, true );
|
||||
|
||||
auditTablePrefix = configProps.getString( EnversSettings.AUDIT_TABLE_PREFIX, DEFAULT_PREFIX );
|
||||
auditTableSuffix = configProps.getString( EnversSettings.AUDIT_TABLE_SUFFIX, DEFAULT_SUFFIX );
|
||||
|
@ -226,6 +228,10 @@ public class Configuration {
|
|||
return findByRevisionExactMatch;
|
||||
}
|
||||
|
||||
public boolean isGlobalLegacyRelationTargetNotFound() {
|
||||
return globalLegacyRelationTargetNotFound;
|
||||
}
|
||||
|
||||
public boolean isRevisionEndTimestampEnabled() {
|
||||
return revisionEndTimestampEnabled;
|
||||
}
|
||||
|
|
|
@ -172,4 +172,15 @@ public interface EnversSettings {
|
|||
* @since 4.3.0
|
||||
*/
|
||||
String CASCADE_DELETE_REVISION = "org.hibernate.envers.cascade_delete_revision";
|
||||
|
||||
/**
|
||||
* Globally defines whether legacy relation not-found behavior should be used or not.
|
||||
* Defaults to {@code true}.
|
||||
*
|
||||
* By specifying {@code true}, any {@code EntityNotFoundException} will be thrown unless the containing
|
||||
* class or property explicitly specifies that use case to be ignored. Conversely, when specifying the
|
||||
* value {@code false}, the inverse applies and requires explicitly specifying the use case as error so
|
||||
* that the exception is thrown.
|
||||
*/
|
||||
String GLOBAL_RELATION_NOT_FOUND_LEGACY_FLAG = "org.hibernate.envers.global_relation_not_found_legacy_flag";
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import java.util.Locale;
|
|||
import java.util.Map;
|
||||
|
||||
import org.hibernate.envers.boot.EnversMappingException;
|
||||
import org.hibernate.envers.RelationTargetNotFoundAction;
|
||||
import org.hibernate.envers.configuration.internal.metadata.reader.AuditedPropertiesHolder;
|
||||
import org.hibernate.envers.configuration.internal.metadata.reader.ClassAuditingData;
|
||||
import org.hibernate.envers.configuration.internal.metadata.reader.ComponentAuditingData;
|
||||
|
@ -152,6 +153,7 @@ public class ClassesAuditingData {
|
|||
final PropertyAuditingData auditingData = new PropertyAuditingData(
|
||||
indexColumnName,
|
||||
propertyAccessorName,
|
||||
RelationTargetNotFoundAction.ERROR,
|
||||
false,
|
||||
true,
|
||||
indexValue
|
||||
|
@ -170,6 +172,7 @@ public class ClassesAuditingData {
|
|||
final PropertyAuditingData propertyAuditingData = new PropertyAuditingData(
|
||||
indexColumnName,
|
||||
propertyAccessorName,
|
||||
RelationTargetNotFoundAction.ERROR,
|
||||
true,
|
||||
true,
|
||||
indexValue
|
||||
|
|
|
@ -9,6 +9,7 @@ package org.hibernate.envers.configuration.internal.metadata;
|
|||
import org.hibernate.envers.boot.EnversMappingException;
|
||||
import org.hibernate.envers.boot.model.AttributeContainer;
|
||||
import org.hibernate.envers.boot.spi.EnversMetadataBuildingContext;
|
||||
import org.hibernate.envers.RelationTargetNotFoundAction;
|
||||
import org.hibernate.envers.configuration.internal.metadata.reader.PropertyAuditingData;
|
||||
import org.hibernate.envers.internal.entities.EntityConfiguration;
|
||||
import org.hibernate.envers.internal.entities.IdMappingData;
|
||||
|
@ -63,7 +64,7 @@ public final class ToOneRelationMetadataGenerator extends AbstractMetadataGenera
|
|||
referencedEntityName,
|
||||
relMapper,
|
||||
insertable,
|
||||
MappingTools.ignoreNotFound( value )
|
||||
shouldIgnoreNotFoundRelation( propertyAuditingData, value )
|
||||
);
|
||||
|
||||
// If the property isn't insertable, checking if this is not a "fake" bidirectional many-to-one relationship,
|
||||
|
@ -186,4 +187,16 @@ public final class ToOneRelationMetadataGenerator extends AbstractMetadataGenera
|
|||
)
|
||||
);
|
||||
}
|
||||
|
||||
private boolean shouldIgnoreNotFoundRelation(PropertyAuditingData propertyAuditingData, Value value) {
|
||||
final RelationTargetNotFoundAction action = propertyAuditingData.getRelationTargetNotFoundAction();
|
||||
if ( getMetadataBuildingContext().getConfiguration().isGlobalLegacyRelationTargetNotFound() ) {
|
||||
// When legacy is enabled, the user must explicitly specify IGNORE for it to be ignored.
|
||||
return MappingTools.ignoreNotFound( value ) || RelationTargetNotFoundAction.IGNORE.equals( action );
|
||||
}
|
||||
else {
|
||||
// When non-legacy is enabled, the situation is ignored when not ERROR
|
||||
return !RelationTargetNotFoundAction.ERROR.equals( action );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ import org.hibernate.envers.AuditOverrides;
|
|||
import org.hibernate.envers.Audited;
|
||||
import org.hibernate.envers.NotAudited;
|
||||
import org.hibernate.envers.RelationTargetAuditMode;
|
||||
import org.hibernate.envers.RelationTargetNotFoundAction;
|
||||
import org.hibernate.envers.boot.EnversMappingException;
|
||||
import org.hibernate.envers.boot.internal.ModifiedColumnNameResolver;
|
||||
import org.hibernate.envers.boot.spi.EnversMetadataBuildingContext;
|
||||
|
@ -594,6 +595,7 @@ public class AuditedPropertiesReader {
|
|||
}
|
||||
if ( aud != null ) {
|
||||
propertyData.setRelationTargetAuditMode( aud.targetAuditMode() );
|
||||
propertyData.setRelationTargetNotFoundAction( getRelationNotFoundAction( property, allClassAudited ) );
|
||||
propertyData.setUsingModifiedFlag( checkUsingModifiedFlag( aud ) );
|
||||
propertyData.setModifiedFlagName( ModifiedColumnNameResolver.getName( propertyName, modifiedFlagSuffix ) );
|
||||
if ( !StringTools.isEmpty( aud.modifiedColumnName() ) ) {
|
||||
|
@ -735,12 +737,42 @@ public class AuditedPropertiesReader {
|
|||
return overriddenAuditedClasses.contains( clazz );
|
||||
}
|
||||
|
||||
private RelationTargetNotFoundAction getRelationNotFoundAction(XProperty property, Audited classAudited) {
|
||||
final Audited propertyAudited = property.getAnnotation( Audited.class );
|
||||
|
||||
// class isn't annotated, check property
|
||||
if ( classAudited == null ) {
|
||||
if ( propertyAudited == null ) {
|
||||
// both class and property are not annotated, use default behavior
|
||||
return RelationTargetNotFoundAction.DEFAULT;
|
||||
}
|
||||
// Property is annotated use its value
|
||||
return propertyAudited.targetNotFoundAction();
|
||||
}
|
||||
|
||||
// if class is annotated, take its value by default
|
||||
RelationTargetNotFoundAction action = classAudited.targetNotFoundAction();
|
||||
if ( propertyAudited != null ) {
|
||||
// both places have audited, use the property value only if it is not DEFAULT
|
||||
if ( !propertyAudited.targetNotFoundAction().equals( RelationTargetNotFoundAction.DEFAULT ) ) {
|
||||
action = propertyAudited.targetNotFoundAction();
|
||||
}
|
||||
}
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
private static final Audited DEFAULT_AUDITED = new Audited() {
|
||||
@Override
|
||||
public RelationTargetAuditMode targetAuditMode() {
|
||||
return RelationTargetAuditMode.AUDITED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RelationTargetNotFoundAction targetNotFoundAction() {
|
||||
return RelationTargetNotFoundAction.DEFAULT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class[] auditParents() {
|
||||
return new Class[0];
|
||||
|
|
|
@ -15,6 +15,7 @@ import jakarta.persistence.EnumType;
|
|||
import org.hibernate.envers.AuditOverride;
|
||||
import org.hibernate.envers.AuditOverrides;
|
||||
import org.hibernate.envers.RelationTargetAuditMode;
|
||||
import org.hibernate.envers.RelationTargetNotFoundAction;
|
||||
import org.hibernate.envers.internal.entities.PropertyData;
|
||||
import org.hibernate.envers.internal.tools.StringTools;
|
||||
import org.hibernate.mapping.Value;
|
||||
|
@ -36,6 +37,7 @@ public class PropertyAuditingData {
|
|||
private String accessType;
|
||||
private final List<AuditOverrideData> auditJoinTableOverrides = new ArrayList<>( 0 );
|
||||
private RelationTargetAuditMode relationTargetAuditMode;
|
||||
private RelationTargetNotFoundAction relationTargetNotFoundAction;
|
||||
private String auditMappedBy;
|
||||
private String relationMappedBy;
|
||||
private String positionMappedBy;
|
||||
|
@ -68,6 +70,7 @@ public class PropertyAuditingData {
|
|||
name,
|
||||
accessType,
|
||||
RelationTargetAuditMode.AUDITED,
|
||||
RelationTargetNotFoundAction.DEFAULT,
|
||||
null,
|
||||
null,
|
||||
forceInsertable,
|
||||
|
@ -81,6 +84,7 @@ public class PropertyAuditingData {
|
|||
*
|
||||
* @param name the property name
|
||||
* @param accessType the access type
|
||||
* @param relationTargetNotFoundAction the relation target not found action
|
||||
* @param forceInsertable whether the property is forced insertable
|
||||
* @param synthetic whether the property is a synthetic, non-logic column-based property
|
||||
* @param value the mapping model's value
|
||||
|
@ -88,6 +92,7 @@ public class PropertyAuditingData {
|
|||
public PropertyAuditingData(
|
||||
String name,
|
||||
String accessType,
|
||||
RelationTargetNotFoundAction relationTargetNotFoundAction,
|
||||
boolean forceInsertable,
|
||||
boolean synthetic,
|
||||
Value value) {
|
||||
|
@ -95,6 +100,7 @@ public class PropertyAuditingData {
|
|||
name,
|
||||
accessType,
|
||||
RelationTargetAuditMode.AUDITED,
|
||||
relationTargetNotFoundAction,
|
||||
null,
|
||||
null,
|
||||
forceInsertable,
|
||||
|
@ -107,6 +113,7 @@ public class PropertyAuditingData {
|
|||
String name,
|
||||
String accessType,
|
||||
RelationTargetAuditMode relationTargetAuditMode,
|
||||
RelationTargetNotFoundAction relationTargetNotFoundAction,
|
||||
String auditMappedBy,
|
||||
String positionMappedBy,
|
||||
boolean forceInsertable,
|
||||
|
@ -116,6 +123,7 @@ public class PropertyAuditingData {
|
|||
this.beanName = name;
|
||||
this.accessType = accessType;
|
||||
this.relationTargetAuditMode = relationTargetAuditMode;
|
||||
this.relationTargetNotFoundAction = relationTargetNotFoundAction;
|
||||
this.auditMappedBy = auditMappedBy;
|
||||
this.positionMappedBy = positionMappedBy;
|
||||
this.forceInsertable = forceInsertable;
|
||||
|
@ -285,6 +293,14 @@ public class PropertyAuditingData {
|
|||
this.relationTargetAuditMode = relationTargetAuditMode;
|
||||
}
|
||||
|
||||
public RelationTargetNotFoundAction getRelationTargetNotFoundAction() {
|
||||
return relationTargetNotFoundAction;
|
||||
}
|
||||
|
||||
public void setRelationTargetNotFoundAction(RelationTargetNotFoundAction relationTargetNotFoundAction) {
|
||||
this.relationTargetNotFoundAction = relationTargetNotFoundAction;
|
||||
}
|
||||
|
||||
public boolean isSynthetic() {
|
||||
return synthetic;
|
||||
}
|
||||
|
|
|
@ -157,20 +157,7 @@ public class ToOneIdMapper extends AbstractToOneMapper {
|
|||
}
|
||||
else {
|
||||
final EntityInfo referencedEntity = getEntityInfo( enversService, referencedEntityName );
|
||||
boolean ignoreNotFound = false;
|
||||
if ( !referencedEntity.isAudited() ) {
|
||||
final String referencingEntityName = enversService.getEntitiesConfigurations().getEntityNameForVersionsEntityName( (String) data.get( "$type$" ) );
|
||||
if ( referencingEntityName == null && primaryKey == null ) {
|
||||
// HHH-11215 - Fix for NPE when Embeddable with ManyToOne inside ElementCollection
|
||||
// an embeddable in an element-collection
|
||||
// todo: perhaps the mapper should account for this instead?
|
||||
ignoreNotFound = true;
|
||||
}
|
||||
else {
|
||||
ignoreNotFound = enversService.getEntitiesConfigurations().getRelationDescription( referencingEntityName, getPropertyData().getName() ).isIgnoreNotFound();
|
||||
}
|
||||
}
|
||||
if ( ignoreNotFound ) {
|
||||
if ( isIgnoreNotFound( enversService, referencedEntity, data, primaryKey ) ) {
|
||||
// Eagerly loading referenced entity to silence potential (in case of proxy)
|
||||
// EntityNotFoundException or ObjectNotFoundException. Assigning null reference.
|
||||
value = ToOneEntityLoader.loadImmediate(
|
||||
|
@ -208,4 +195,25 @@ public class ToOneIdMapper extends AbstractToOneMapper {
|
|||
String prefix2) {
|
||||
delegate.addIdsEqualToQuery( parameters, prefix1, delegate, prefix2 );
|
||||
}
|
||||
|
||||
// todo: is referenced entity needed any longer?
|
||||
private boolean isIgnoreNotFound(
|
||||
EnversService enversService,
|
||||
EntityInfo referencedEntity,
|
||||
Map data,
|
||||
Object primaryKey) {
|
||||
final String referencingEntityName = enversService.getEntitiesConfigurations()
|
||||
.getEntityNameForVersionsEntityName( (String) data.get( "$type$" ) );
|
||||
|
||||
if ( referencingEntityName == null && primaryKey == null ) {
|
||||
// HHH-11215 - Fix for NPE when Embeddable with ManyToOne inside ElementCollection
|
||||
// an embeddable in an element-collection
|
||||
// todo: perhaps the mapper should account for this instead?
|
||||
return true;
|
||||
}
|
||||
|
||||
return enversService.getEntitiesConfigurations()
|
||||
.getRelationDescription( referencingEntityName, getPropertyData().getName() )
|
||||
.isIgnoreNotFound();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,236 @@
|
|||
/*
|
||||
* 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.basic;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
|
||||
import org.hibernate.envers.AuditReader;
|
||||
import org.hibernate.envers.AuditReaderFactory;
|
||||
import org.hibernate.envers.Audited;
|
||||
import org.hibernate.envers.RelationTargetAuditMode;
|
||||
import org.hibernate.envers.configuration.EnversSettings;
|
||||
import org.hibernate.orm.test.envers.BaseEnversJPAFunctionalTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.hibernate.testing.TestForIssue;
|
||||
|
||||
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
/**
|
||||
* Test that when the {@link EnversSettings#GLOBAL_RELATION_NOT_FOUND_LEGACY_FLAG} is {@code false}
|
||||
* that the ignore behavior is used by default rather than throwing {@code EntityNotFoundException}.
|
||||
*
|
||||
* @author Chris Cranford
|
||||
*/
|
||||
@TestForIssue(jiraKey = "HHH-8051")
|
||||
public class RelationTargetNotFoundConfigTest extends BaseEnversJPAFunctionalTestCase {
|
||||
@Override
|
||||
protected Class<?>[] getAnnotatedClasses() {
|
||||
return new Class<?>[] { Foo.class, Bar.class, FooBar.class };
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addConfigOptions(Map options) {
|
||||
super.addConfigOptions( options );
|
||||
options.put( EnversSettings.GLOBAL_RELATION_NOT_FOUND_LEGACY_FLAG, Boolean.FALSE );
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testRelationTargetNotFoundAction() {
|
||||
// Revision 1, initialize the data for test case
|
||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
final Bar bar = new Bar( 1 );
|
||||
entityManager.persist( bar );
|
||||
|
||||
final FooBar fooBar1 = new FooBar( 1, "fooBar" );
|
||||
entityManager.persist( fooBar1 );
|
||||
|
||||
final FooBar fooBar2 = new FooBar( 2, "fooBar2" );
|
||||
entityManager.persist( fooBar2 );
|
||||
|
||||
final Foo foo = new Foo( 1, bar, fooBar1, fooBar2 );
|
||||
entityManager.persist( foo );
|
||||
} );
|
||||
|
||||
// This test verifies that everything is fine before doing various record manipulation changes.
|
||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
final AuditReader auditReader = AuditReaderFactory.get(entityManager );
|
||||
final Foo rev1 = auditReader.find( Foo.class, 1, 1 );
|
||||
assertNotNull( rev1 );
|
||||
assertNotNull( rev1.getBar() );
|
||||
assertNotNull( rev1.getFooBar() );
|
||||
assertNotNull( rev1.getFooBar2() );
|
||||
} );
|
||||
|
||||
// Simulate the removal of main data table data by removing FooBar1 (an audited entity)
|
||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
// obviously we assume either there isn't a FK between tables or the users do something like this
|
||||
entityManager.createNativeQuery( "UPDATE Foo Set fooBar_id = NULL WHERE id = 1" ).executeUpdate();
|
||||
entityManager.createNativeQuery( "DELETE FROM FooBar WHERE id = 1" ).executeUpdate();
|
||||
} );
|
||||
|
||||
// This shouldn't fail because the audited entity data is cached in the audit table and exists.
|
||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
final AuditReader auditReader = AuditReaderFactory.get( entityManager );
|
||||
final Foo rev1 = auditReader.find( Foo.class, 1, 1 );
|
||||
assertNotNull( rev1 );
|
||||
assertNotNull( rev1.getFooBar() );
|
||||
} );
|
||||
|
||||
// Simulate the removal of envers data via purge process by removing FooBar2 (an audited entity)
|
||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
entityManager.createNativeQuery( "DELETE FROM FooBar_AUD WHERE id = 2" ).executeUpdate();
|
||||
} );
|
||||
|
||||
// Test querying history record where the reference audit row no longer exists.
|
||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
final AuditReader auditReader = AuditReaderFactory.get( entityManager );
|
||||
final Foo rev1 = auditReader.find( Foo.class, 1, 1 );
|
||||
assertNotNull( rev1 );
|
||||
// With RelationTargetNotFoundAction.ERROR, this would throw an EntityNotFoundException.
|
||||
assertNull( rev1.getFooBar2() );
|
||||
} );
|
||||
|
||||
// this simulates the removal of a non-audited entity from the main table
|
||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
// obviously we assume either there isn't a FK between tables or the users do something like this
|
||||
entityManager.createNativeQuery( "UPDATE Foo SET bar_id = NULL WHERE id = 1" ).executeUpdate();
|
||||
entityManager.createNativeQuery( "DELETE FROM Bar WHERE id = 1" ).executeUpdate();
|
||||
} );
|
||||
|
||||
// Test querying history record where the reference non-audited row no longer exists.
|
||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
final AuditReader auditReader = AuditReaderFactory.get( entityManager );
|
||||
final Foo rev1 = auditReader.find( Foo.class, 1, 1 );
|
||||
assertNotNull( rev1 );
|
||||
// With RelationTargetNotFoundAction.ERROR, this would throw an EntityNotFoundException
|
||||
assertNull( rev1.getBar() );
|
||||
} );
|
||||
}
|
||||
|
||||
@Audited
|
||||
@Entity(name = "Foo")
|
||||
public static class Foo {
|
||||
@Id
|
||||
private Integer id;
|
||||
|
||||
@ManyToOne
|
||||
@Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
|
||||
private Bar bar;
|
||||
|
||||
@ManyToOne
|
||||
private FooBar fooBar;
|
||||
|
||||
@ManyToOne
|
||||
private FooBar fooBar2;
|
||||
|
||||
Foo() {
|
||||
// Required by JPA
|
||||
}
|
||||
|
||||
Foo(Integer id, Bar bar, FooBar fooBar, FooBar fooBar2) {
|
||||
this.id = id;
|
||||
this.bar = bar;
|
||||
this.fooBar = fooBar;
|
||||
this.fooBar2 = fooBar2;
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Bar getBar() {
|
||||
return bar;
|
||||
}
|
||||
|
||||
public void setBar(Bar bar) {
|
||||
this.bar = bar;
|
||||
}
|
||||
|
||||
public FooBar getFooBar() {
|
||||
return fooBar;
|
||||
}
|
||||
|
||||
public void setFooBar(FooBar fooBar) {
|
||||
this.fooBar = fooBar;
|
||||
}
|
||||
|
||||
public FooBar getFooBar2() {
|
||||
return fooBar2;
|
||||
}
|
||||
|
||||
public void setFooBar2(FooBar fooBar2) {
|
||||
this.fooBar2 = fooBar2;
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name = "Bar")
|
||||
public static class Bar {
|
||||
@Id
|
||||
private Integer id;
|
||||
|
||||
Bar() {
|
||||
// Required by JPA
|
||||
}
|
||||
|
||||
Bar(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
@Audited
|
||||
@Entity(name = "FooBar")
|
||||
public static class FooBar {
|
||||
@Id
|
||||
private Integer id;
|
||||
private String name;
|
||||
|
||||
FooBar() {
|
||||
// Required by JPA
|
||||
}
|
||||
|
||||
FooBar(Integer id, String name) {
|
||||
this.id = id;
|
||||
this.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) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,241 @@
|
|||
/*
|
||||
* 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.basic;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.EntityNotFoundException;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
|
||||
import org.hibernate.envers.AuditReader;
|
||||
import org.hibernate.envers.AuditReaderFactory;
|
||||
import org.hibernate.envers.Audited;
|
||||
import org.hibernate.envers.RelationTargetAuditMode;
|
||||
import org.hibernate.orm.test.envers.BaseEnversJPAFunctionalTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.hibernate.testing.TestForIssue;
|
||||
|
||||
import static org.hibernate.testing.junit4.ExtraAssertions.assertTyping;
|
||||
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
* Test that when using the legacy default behavior, any {@code EntityNotFoundException} will
|
||||
* continue to be thrown, ala preserving pre 6.0 behavior.
|
||||
*
|
||||
* @author Chris Cranford
|
||||
*/
|
||||
@TestForIssue(jiraKey = "HHH-8051")
|
||||
public class RelationTargetNotFoundLegacyTest extends BaseEnversJPAFunctionalTestCase {
|
||||
@Override
|
||||
protected Class<?>[] getAnnotatedClasses() {
|
||||
return new Class<?>[] { Foo.class, Bar.class, FooBar.class };
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRelationTargetNotFoundAction() {
|
||||
// Revision 1, initialize the data for test case
|
||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
final Bar bar = new Bar( 1 );
|
||||
entityManager.persist( bar );
|
||||
|
||||
final FooBar fooBar1 = new FooBar( 1, "fooBar" );
|
||||
entityManager.persist( fooBar1 );
|
||||
|
||||
final FooBar fooBar2 = new FooBar( 2, "fooBar2" );
|
||||
entityManager.persist( fooBar2 );
|
||||
|
||||
final Foo foo = new Foo( 1, bar, fooBar1, fooBar2 );
|
||||
entityManager.persist( foo );
|
||||
} );
|
||||
|
||||
// This test verifies that everything is fine before doing various record manipulation changes.
|
||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
final AuditReader auditReader = AuditReaderFactory.get(entityManager );
|
||||
final Foo rev1 = auditReader.find( Foo.class, 1, 1 );
|
||||
assertNotNull( rev1 );
|
||||
assertNotNull( rev1.getBar() );
|
||||
assertNotNull( rev1.getFooBar() );
|
||||
assertNotNull( rev1.getFooBar2() );
|
||||
} );
|
||||
|
||||
// Simulate the removal of main data table data by removing FooBar1 (an audited entity)
|
||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
// obviously we assume either there isn't a FK between tables or the users do something like this
|
||||
entityManager.createNativeQuery( "UPDATE Foo Set fooBar_id = NULL WHERE id = 1" ).executeUpdate();
|
||||
entityManager.createNativeQuery( "DELETE FROM FooBar WHERE id = 1" ).executeUpdate();
|
||||
} );
|
||||
|
||||
// This shouldn't fail because the audited entity data is cached in the audit table and exists.
|
||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
final AuditReader auditReader = AuditReaderFactory.get( entityManager );
|
||||
final Foo rev1 = auditReader.find( Foo.class, 1, 1 );
|
||||
assertNotNull( rev1 );
|
||||
assertNotNull( rev1.getFooBar() );
|
||||
} );
|
||||
|
||||
// Simulate the removal of envers data via purge process by removing FooBar2 (an audited entity)
|
||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
entityManager.createNativeQuery( "DELETE FROM FooBar_AUD WHERE id = 2" ).executeUpdate();
|
||||
} );
|
||||
|
||||
// Test querying history record where the reference audit row no longer exists.
|
||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
final AuditReader auditReader = AuditReaderFactory.get( entityManager );
|
||||
final Foo rev1 = auditReader.find( Foo.class, 1, 1 );
|
||||
assertNotNull( rev1 );
|
||||
try {
|
||||
// With RelationTargetNotFoundAction.ERROR, this would throw an EntityNotFoundException.
|
||||
assertNull( rev1.getFooBar2() );
|
||||
fail( "This expected an EntityNotFoundException to be thrown" );
|
||||
}
|
||||
catch ( Exception e ) {
|
||||
assertTyping(EntityNotFoundException.class, e );
|
||||
}
|
||||
} );
|
||||
|
||||
// this simulates the removal of a non-audited entity from the main table
|
||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
// obviously we assume either there isn't a FK between tables or the users do something like this
|
||||
entityManager.createNativeQuery( "UPDATE Foo SET bar_id = NULL WHERE id = 1" ).executeUpdate();
|
||||
entityManager.createNativeQuery( "DELETE FROM Bar WHERE id = 1" ).executeUpdate();
|
||||
} );
|
||||
|
||||
// Test querying history record where the reference non-audited row no longer exists.
|
||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
final AuditReader auditReader = AuditReaderFactory.get( entityManager );
|
||||
final Foo rev1 = auditReader.find( Foo.class, 1, 1 );
|
||||
assertNotNull( rev1 );
|
||||
try {
|
||||
// With RelationTargetNotFoundAction.ERROR, this would throw an EntityNotFoundException
|
||||
assertNull( rev1.getBar() );
|
||||
fail( "This expected an EntityNotFoundException to be thrown" );
|
||||
}
|
||||
catch ( Exception e ) {
|
||||
assertTyping( EntityNotFoundException.class, e );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
@Audited
|
||||
@Entity(name = "Foo")
|
||||
public static class Foo {
|
||||
@Id
|
||||
private Integer id;
|
||||
|
||||
@ManyToOne
|
||||
@Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
|
||||
private Bar bar;
|
||||
|
||||
@ManyToOne
|
||||
private FooBar fooBar;
|
||||
|
||||
@ManyToOne
|
||||
private FooBar fooBar2;
|
||||
|
||||
Foo() {
|
||||
// Required by JPA
|
||||
}
|
||||
|
||||
Foo(Integer id, Bar bar, FooBar fooBar, FooBar fooBar2) {
|
||||
this.id = id;
|
||||
this.bar = bar;
|
||||
this.fooBar = fooBar;
|
||||
this.fooBar2 = fooBar2;
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Bar getBar() {
|
||||
return bar;
|
||||
}
|
||||
|
||||
public void setBar(Bar bar) {
|
||||
this.bar = bar;
|
||||
}
|
||||
|
||||
public FooBar getFooBar() {
|
||||
return fooBar;
|
||||
}
|
||||
|
||||
public void setFooBar(FooBar fooBar) {
|
||||
this.fooBar = fooBar;
|
||||
}
|
||||
|
||||
public FooBar getFooBar2() {
|
||||
return fooBar2;
|
||||
}
|
||||
|
||||
public void setFooBar2(FooBar fooBar2) {
|
||||
this.fooBar2 = fooBar2;
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name = "Bar")
|
||||
public static class Bar {
|
||||
@Id
|
||||
private Integer id;
|
||||
|
||||
Bar() {
|
||||
// Required by JPA
|
||||
}
|
||||
|
||||
Bar(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
@Audited
|
||||
@Entity(name = "FooBar")
|
||||
public static class FooBar {
|
||||
@Id
|
||||
private Integer id;
|
||||
private String name;
|
||||
|
||||
FooBar() {
|
||||
// Required by JPA
|
||||
}
|
||||
|
||||
FooBar(Integer id, String name) {
|
||||
this.id = id;
|
||||
this.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) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,227 @@
|
|||
/*
|
||||
* 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.basic;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.ManyToOne;
|
||||
|
||||
import org.hibernate.envers.AuditReader;
|
||||
import org.hibernate.envers.AuditReaderFactory;
|
||||
import org.hibernate.envers.Audited;
|
||||
import org.hibernate.envers.RelationTargetAuditMode;
|
||||
import org.hibernate.envers.RelationTargetNotFoundAction;
|
||||
import org.hibernate.orm.test.envers.BaseEnversJPAFunctionalTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.hibernate.testing.TestForIssue;
|
||||
|
||||
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
/**
|
||||
* Test that when using the override behavior for {@link RelationTargetNotFoundAction#IGNORE} that
|
||||
* when {@code EntityNotFoundException} would be thrown, they're actually ignored.
|
||||
*
|
||||
* @author Chris Cranford
|
||||
*/
|
||||
@TestForIssue(jiraKey = "HHH-8051")
|
||||
public class RelationTargetNotFoundTest extends BaseEnversJPAFunctionalTestCase {
|
||||
@Override
|
||||
protected Class<?>[] getAnnotatedClasses() {
|
||||
return new Class<?>[] { Foo.class, Bar.class, FooBar.class };
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRelationTargetNotFoundAction() {
|
||||
// Revision 1, initialize the data for test case
|
||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
final Bar bar = new Bar( 1 );
|
||||
entityManager.persist( bar );
|
||||
|
||||
final FooBar fooBar1 = new FooBar( 1, "fooBar" );
|
||||
entityManager.persist( fooBar1 );
|
||||
|
||||
final FooBar fooBar2 = new FooBar( 2, "fooBar2" );
|
||||
entityManager.persist( fooBar2 );
|
||||
|
||||
final Foo foo = new Foo( 1, bar, fooBar1, fooBar2 );
|
||||
entityManager.persist( foo );
|
||||
} );
|
||||
|
||||
// This test verifies that everything is fine before doing various record manipulation changes.
|
||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
final AuditReader auditReader = AuditReaderFactory.get(entityManager );
|
||||
final Foo rev1 = auditReader.find( Foo.class, 1, 1 );
|
||||
assertNotNull( rev1 );
|
||||
assertNotNull( rev1.getBar() );
|
||||
assertNotNull( rev1.getFooBar() );
|
||||
assertNotNull( rev1.getFooBar2() );
|
||||
} );
|
||||
|
||||
// Simulate the removal of main data table data by removing FooBar1 (an audited entity)
|
||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
// obviously we assume either there isn't a FK between tables or the users do something like this
|
||||
entityManager.createNativeQuery( "UPDATE Foo Set fooBar_id = NULL WHERE id = 1" ).executeUpdate();
|
||||
entityManager.createNativeQuery( "DELETE FROM FooBar WHERE id = 1" ).executeUpdate();
|
||||
} );
|
||||
|
||||
// This shouldn't fail because the audited entity data is cached in the audit table and exists.
|
||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
final AuditReader auditReader = AuditReaderFactory.get( entityManager );
|
||||
final Foo rev1 = auditReader.find( Foo.class, 1, 1 );
|
||||
assertNotNull( rev1 );
|
||||
assertNotNull( rev1.getFooBar() );
|
||||
} );
|
||||
|
||||
// Simulate the removal of envers data via purge process by removing FooBar2 (an audited entity)
|
||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
entityManager.createNativeQuery( "DELETE FROM FooBar_AUD WHERE id = 2" ).executeUpdate();
|
||||
} );
|
||||
|
||||
// Test querying history record where the reference audit row no longer exists.
|
||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
final AuditReader auditReader = AuditReaderFactory.get( entityManager );
|
||||
final Foo rev1 = auditReader.find( Foo.class, 1, 1 );
|
||||
assertNotNull( rev1 );
|
||||
// With RelationTargetNotFoundAction.ERROR, this would throw an EntityNotFoundException.
|
||||
assertNull( rev1.getFooBar2() );
|
||||
} );
|
||||
|
||||
// this simulates the removal of a non-audited entity from the main table
|
||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
// obviously we assume either there isn't a FK between tables or the users do something like this
|
||||
entityManager.createNativeQuery( "UPDATE Foo SET bar_id = NULL WHERE id = 1" ).executeUpdate();
|
||||
entityManager.createNativeQuery( "DELETE FROM Bar WHERE id = 1" ).executeUpdate();
|
||||
} );
|
||||
|
||||
// Test querying history record where the reference non-audited row no longer exists.
|
||||
doInJPA( this::entityManagerFactory, entityManager -> {
|
||||
final AuditReader auditReader = AuditReaderFactory.get( entityManager );
|
||||
final Foo rev1 = auditReader.find( Foo.class, 1, 1 );
|
||||
assertNotNull( rev1 );
|
||||
// With RelationTargetNotFoundAction.ERROR, this would throw an EntityNotFoundException
|
||||
assertNull( rev1.getBar() );
|
||||
} );
|
||||
}
|
||||
|
||||
@Audited(targetNotFoundAction = RelationTargetNotFoundAction.IGNORE)
|
||||
@Entity(name = "Foo")
|
||||
public static class Foo {
|
||||
@Id
|
||||
private Integer id;
|
||||
|
||||
@ManyToOne
|
||||
@Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
|
||||
private Bar bar;
|
||||
|
||||
@ManyToOne
|
||||
private FooBar fooBar;
|
||||
|
||||
@ManyToOne
|
||||
private FooBar fooBar2;
|
||||
|
||||
Foo() {
|
||||
// Required by JPA
|
||||
}
|
||||
|
||||
Foo(Integer id, Bar bar, FooBar fooBar, FooBar fooBar2) {
|
||||
this.id = id;
|
||||
this.bar = bar;
|
||||
this.fooBar = fooBar;
|
||||
this.fooBar2 = fooBar2;
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Bar getBar() {
|
||||
return bar;
|
||||
}
|
||||
|
||||
public void setBar(Bar bar) {
|
||||
this.bar = bar;
|
||||
}
|
||||
|
||||
public FooBar getFooBar() {
|
||||
return fooBar;
|
||||
}
|
||||
|
||||
public void setFooBar(FooBar fooBar) {
|
||||
this.fooBar = fooBar;
|
||||
}
|
||||
|
||||
public FooBar getFooBar2() {
|
||||
return fooBar2;
|
||||
}
|
||||
|
||||
public void setFooBar2(FooBar fooBar2) {
|
||||
this.fooBar2 = fooBar2;
|
||||
}
|
||||
}
|
||||
|
||||
@Entity(name = "Bar")
|
||||
public static class Bar {
|
||||
@Id
|
||||
private Integer id;
|
||||
|
||||
Bar() {
|
||||
// Required by JPA
|
||||
}
|
||||
|
||||
Bar(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Integer id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
@Audited
|
||||
@Entity(name = "FooBar")
|
||||
public static class FooBar {
|
||||
@Id
|
||||
private Integer id;
|
||||
private String name;
|
||||
|
||||
FooBar() {
|
||||
// Required by JPA
|
||||
}
|
||||
|
||||
FooBar(Integer id, String name) {
|
||||
this.id = id;
|
||||
this.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) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue