HHH-12282 - Allow disabling of invalidation of second-level cache entries for multi-table entities
This commit is contained in:
parent
5e397e9cb3
commit
8cfe4126f1
|
@ -1212,6 +1212,14 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions {
|
||||||
this.jpaCompliance.setClosedCompliance( enabled );
|
this.jpaCompliance.setClosedCompliance( enabled );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void enableJpaProxyCompliance(boolean enabled) {
|
||||||
|
this.jpaCompliance.setProxyCompliance( enabled );
|
||||||
|
}
|
||||||
|
|
||||||
|
public void enableJpaCachingCompliance(boolean enabled) {
|
||||||
|
this.jpaCompliance.setCachingCompliance( enabled );
|
||||||
|
}
|
||||||
|
|
||||||
public void markAsJpaBootstrap() {
|
public void markAsJpaBootstrap() {
|
||||||
this.jpaBootstrap = true;
|
this.jpaBootstrap = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1834,6 +1834,12 @@ public interface AvailableSettings extends org.hibernate.jpa.AvailableSettings {
|
||||||
*/
|
*/
|
||||||
String JPA_PROXY_COMPLIANCE = "hibernate.jpa.compliance.proxy";
|
String JPA_PROXY_COMPLIANCE = "hibernate.jpa.compliance.proxy";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see JpaCompliance#isJpaCacheComplianceEnabled()
|
||||||
|
* @since 5.3
|
||||||
|
*/
|
||||||
|
String JPA_CACHING_COMPLIANCE = "hibernate.jpa.compliance.caching";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True/False setting indicating if the value stored in the table used by the {@link javax.persistence.TableGenerator}
|
* True/False setting indicating if the value stored in the table used by the {@link javax.persistence.TableGenerator}
|
||||||
* is the last value generated or the next value to be used.
|
* is the last value generated or the next value to be used.
|
||||||
|
|
|
@ -77,4 +77,16 @@ public interface JpaCompliance {
|
||||||
* @return {@code true} indicates to behave in the spec-defined way
|
* @return {@code true} indicates to behave in the spec-defined way
|
||||||
*/
|
*/
|
||||||
boolean isJpaProxyComplianceEnabled();
|
boolean isJpaProxyComplianceEnabled();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Should Hibernate comply with all aspects of caching as defined by JPA? Or can
|
||||||
|
* it deviate to perform things it believes will be "better"?
|
||||||
|
*
|
||||||
|
* @implNote Effects include marking all secondary tables as non-optional. The reason
|
||||||
|
* being that optional secondary tables can lead to entity cache being invalidated rather
|
||||||
|
* than updated.
|
||||||
|
*
|
||||||
|
* @return {@code true} says to act the spec-defined way.
|
||||||
|
*/
|
||||||
|
boolean isJpaCacheComplianceEnabled();
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ public class JpaComplianceImpl implements JpaCompliance {
|
||||||
private boolean listCompliance;
|
private boolean listCompliance;
|
||||||
private boolean closedCompliance;
|
private boolean closedCompliance;
|
||||||
private boolean proxyCompliance;
|
private boolean proxyCompliance;
|
||||||
|
private boolean cachingCompliance;
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("ConstantConditions")
|
@SuppressWarnings("ConstantConditions")
|
||||||
|
@ -52,6 +53,11 @@ public class JpaComplianceImpl implements JpaCompliance {
|
||||||
configurationSettings,
|
configurationSettings,
|
||||||
jpaByDefault
|
jpaByDefault
|
||||||
);
|
);
|
||||||
|
cachingCompliance = ConfigurationHelper.getBoolean(
|
||||||
|
AvailableSettings.JPA_CACHING_COMPLIANCE,
|
||||||
|
configurationSettings,
|
||||||
|
jpaByDefault
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -79,6 +85,11 @@ public class JpaComplianceImpl implements JpaCompliance {
|
||||||
return proxyCompliance;
|
return proxyCompliance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isJpaCacheComplianceEnabled() {
|
||||||
|
return cachingCompliance;
|
||||||
|
}
|
||||||
|
|
||||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
// Mutators
|
// Mutators
|
||||||
|
|
||||||
|
@ -97,4 +108,12 @@ public class JpaComplianceImpl implements JpaCompliance {
|
||||||
public void setClosedCompliance(boolean closedCompliance) {
|
public void setClosedCompliance(boolean closedCompliance) {
|
||||||
this.closedCompliance = closedCompliance;
|
this.closedCompliance = closedCompliance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setProxyCompliance(boolean proxyCompliance) {
|
||||||
|
this.proxyCompliance = proxyCompliance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCachingCompliance(boolean cachingCompliance) {
|
||||||
|
this.cachingCompliance = cachingCompliance;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,6 +95,7 @@ import org.hibernate.loader.entity.UniqueEntityLoader;
|
||||||
import org.hibernate.mapping.Column;
|
import org.hibernate.mapping.Column;
|
||||||
import org.hibernate.mapping.Component;
|
import org.hibernate.mapping.Component;
|
||||||
import org.hibernate.mapping.Formula;
|
import org.hibernate.mapping.Formula;
|
||||||
|
import org.hibernate.mapping.Join;
|
||||||
import org.hibernate.mapping.PersistentClass;
|
import org.hibernate.mapping.PersistentClass;
|
||||||
import org.hibernate.mapping.Property;
|
import org.hibernate.mapping.Property;
|
||||||
import org.hibernate.mapping.Selectable;
|
import org.hibernate.mapping.Selectable;
|
||||||
|
@ -152,6 +153,7 @@ public abstract class AbstractEntityPersister
|
||||||
private final SessionFactoryImplementor factory;
|
private final SessionFactoryImplementor factory;
|
||||||
private final boolean canReadFromCache;
|
private final boolean canReadFromCache;
|
||||||
private final boolean canWriteToCache;
|
private final boolean canWriteToCache;
|
||||||
|
private final boolean invalidateCache;
|
||||||
private final EntityRegionAccessStrategy cacheAccessStrategy;
|
private final EntityRegionAccessStrategy cacheAccessStrategy;
|
||||||
private final NaturalIdRegionAccessStrategy naturalIdRegionAccessStrategy;
|
private final NaturalIdRegionAccessStrategy naturalIdRegionAccessStrategy;
|
||||||
private final boolean isLazyPropertiesCacheable;
|
private final boolean isLazyPropertiesCacheable;
|
||||||
|
@ -845,6 +847,50 @@ public abstract class AbstractEntityPersister
|
||||||
|
|
||||||
this.cacheEntryHelper = buildCacheEntryHelper();
|
this.cacheEntryHelper = buildCacheEntryHelper();
|
||||||
|
|
||||||
|
if ( creationContext.getSessionFactory().getSessionFactoryOptions().isSecondLevelCacheEnabled() ) {
|
||||||
|
this.invalidateCache = canWriteToCache && determineWhetherToInvalidateCache( persistentClass, creationContext );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.invalidateCache = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("RedundantIfStatement")
|
||||||
|
private boolean determineWhetherToInvalidateCache(
|
||||||
|
PersistentClass persistentClass,
|
||||||
|
PersisterCreationContext creationContext) {
|
||||||
|
if ( hasFormulaProperties() ) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( isVersioned() ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( entityMetamodel.isDynamicUpdate() ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to check whether the user may have circumvented this logic (JPA TCK)
|
||||||
|
final boolean complianceEnabled = creationContext.getSessionFactory()
|
||||||
|
.getSessionFactoryOptions()
|
||||||
|
.getJpaCompliance()
|
||||||
|
.isJpaCacheComplianceEnabled();
|
||||||
|
if ( complianceEnabled ) {
|
||||||
|
// The JPA TCK (inadvertently, but still...) requires that we cache
|
||||||
|
// entities with secondary tables even though Hibernate historically
|
||||||
|
// invalidated them
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( persistentClass.getJoinClosureSpan() >= 1 ) {
|
||||||
|
// todo : this should really consider optionality of the secondary tables in count
|
||||||
|
// non-optional tables do not cause this bypass
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
@ -1273,8 +1319,7 @@ public abstract class AbstractEntityPersister
|
||||||
* item.
|
* item.
|
||||||
*/
|
*/
|
||||||
public boolean isCacheInvalidationRequired() {
|
public boolean isCacheInvalidationRequired() {
|
||||||
return hasFormulaProperties() ||
|
return invalidateCache;
|
||||||
( !isVersioned() && ( entityMetamodel.isDynamicUpdate() || getTableSpan() > 1 ) );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isLazyPropertiesCacheable() {
|
public boolean isLazyPropertiesCacheable() {
|
||||||
|
|
|
@ -243,7 +243,12 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
|
||||||
while ( joinItr.hasNext() ) {
|
while ( joinItr.hasNext() ) {
|
||||||
Join join = (Join) joinItr.next();
|
Join join = (Join) joinItr.next();
|
||||||
|
|
||||||
isNullableTable[tableIndex++] = join.isOptional();
|
isNullableTable[tableIndex++] = join.isOptional() ||
|
||||||
|
creationContext.getSessionFactory()
|
||||||
|
.getSessionFactoryOptions()
|
||||||
|
.getJpaCompliance()
|
||||||
|
.isJpaCacheComplianceEnabled();
|
||||||
|
|
||||||
|
|
||||||
Table table = join.getTable();
|
Table table = join.getTable();
|
||||||
final String tableName = determineTableName( table, jdbcEnvironment );
|
final String tableName = determineTableName( table, jdbcEnvironment );
|
||||||
|
|
|
@ -178,7 +178,11 @@ public class SingleTableEntityPersister extends AbstractEntityPersister {
|
||||||
Join join = (Join) joinIter.next();
|
Join join = (Join) joinIter.next();
|
||||||
qualifiedTableNames[j] = determineTableName( join.getTable(), jdbcEnvironment );
|
qualifiedTableNames[j] = determineTableName( join.getTable(), jdbcEnvironment );
|
||||||
isInverseTable[j] = join.isInverse();
|
isInverseTable[j] = join.isInverse();
|
||||||
isNullableTable[j] = join.isOptional();
|
isNullableTable[j] = join.isOptional()
|
||||||
|
|| creationContext.getSessionFactory()
|
||||||
|
.getSessionFactoryOptions()
|
||||||
|
.getJpaCompliance()
|
||||||
|
.isJpaCacheComplianceEnabled();
|
||||||
cascadeDeleteEnabled[j] = join.getKey().isCascadeDeleteEnabled() &&
|
cascadeDeleteEnabled[j] = join.getKey().isCascadeDeleteEnabled() &&
|
||||||
factory.getDialect().supportsCascadeDelete();
|
factory.getDialect().supportsCascadeDelete();
|
||||||
|
|
||||||
|
@ -244,7 +248,12 @@ public class SingleTableEntityPersister extends AbstractEntityPersister {
|
||||||
isConcretes.add( persistentClass.isClassOrSuperclassJoin( join ) );
|
isConcretes.add( persistentClass.isClassOrSuperclassJoin( join ) );
|
||||||
isDeferreds.add( join.isSequentialSelect() );
|
isDeferreds.add( join.isSequentialSelect() );
|
||||||
isInverses.add( join.isInverse() );
|
isInverses.add( join.isInverse() );
|
||||||
isNullables.add( join.isOptional() );
|
isNullables.add(
|
||||||
|
join.isOptional() || creationContext.getSessionFactory()
|
||||||
|
.getSessionFactoryOptions()
|
||||||
|
.getJpaCompliance()
|
||||||
|
.isJpaCacheComplianceEnabled()
|
||||||
|
);
|
||||||
isLazies.add( lazyAvailable && join.isLazy() );
|
isLazies.add( lazyAvailable && join.isLazy() );
|
||||||
if ( join.isSequentialSelect() && !persistentClass.isClassOrSuperclassJoin( join ) ) {
|
if ( join.isSequentialSelect() && !persistentClass.isClassOrSuperclassJoin( join ) ) {
|
||||||
hasDeferred = true;
|
hasDeferred = true;
|
||||||
|
|
|
@ -43,6 +43,7 @@ public class JpaComplianceTestingImpl implements JpaCompliance {
|
||||||
private boolean listCompliance;
|
private boolean listCompliance;
|
||||||
private boolean closedCompliance;
|
private boolean closedCompliance;
|
||||||
private boolean proxyCompliance;
|
private boolean proxyCompliance;
|
||||||
|
private boolean cacheCompliance;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isJpaQueryComplianceEnabled() {
|
public boolean isJpaQueryComplianceEnabled() {
|
||||||
|
@ -68,4 +69,9 @@ public class JpaComplianceTestingImpl implements JpaCompliance {
|
||||||
public boolean isJpaProxyComplianceEnabled() {
|
public boolean isJpaProxyComplianceEnabled() {
|
||||||
return proxyCompliance;
|
return proxyCompliance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isJpaCacheComplianceEnabled() {
|
||||||
|
return cacheCompliance;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,211 @@
|
||||||
|
/*
|
||||||
|
* 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.test.jpa.compliance.tck2_2.caching;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import javax.persistence.Cacheable;
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.SecondaryTable;
|
||||||
|
import javax.persistence.SharedCacheMode;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
import javax.persistence.Version;
|
||||||
|
|
||||||
|
import org.hibernate.Hibernate;
|
||||||
|
import org.hibernate.boot.MetadataSources;
|
||||||
|
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
|
||||||
|
import org.hibernate.cfg.AvailableSettings;
|
||||||
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
|
import org.hibernate.stat.spi.StatisticsImplementor;
|
||||||
|
|
||||||
|
import org.hibernate.testing.junit4.BaseUnitTestCase;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.hamcrest.CoreMatchers;
|
||||||
|
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hibernate.testing.transaction.TransactionUtil2.inTransaction;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Steve Ebersole
|
||||||
|
*/
|
||||||
|
public class CachingWithSecondaryTablesTests extends BaseUnitTestCase {
|
||||||
|
private SessionFactoryImplementor sessionFactory;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUnstrictUnversioned() {
|
||||||
|
sessionFactory = buildSessionFactory( Person.class, false );
|
||||||
|
|
||||||
|
final StatisticsImplementor statistics = sessionFactory.getStatistics();
|
||||||
|
|
||||||
|
inTransaction(
|
||||||
|
sessionFactory,
|
||||||
|
s -> s.persist( new Person( "1", "John Doe", true ) )
|
||||||
|
);
|
||||||
|
|
||||||
|
// it should not be in the cache because it should be invalidated instead
|
||||||
|
assertEquals( statistics.getSecondLevelCachePutCount(), 0 );
|
||||||
|
assertFalse( sessionFactory.getCache().contains( Person.class, "1" ) );
|
||||||
|
|
||||||
|
inTransaction(
|
||||||
|
sessionFactory,
|
||||||
|
s -> {
|
||||||
|
statistics.clear();
|
||||||
|
|
||||||
|
final Person person = s.get( Person.class, "1" );
|
||||||
|
assertTrue( Hibernate.isInitialized( person ) );
|
||||||
|
assertThat( statistics.getSecondLevelCacheHitCount(), CoreMatchers.is( 0L) );
|
||||||
|
|
||||||
|
statistics.clear();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testStrictUnversioned() {
|
||||||
|
sessionFactory = buildSessionFactory( Person.class, true );
|
||||||
|
|
||||||
|
final StatisticsImplementor statistics = sessionFactory.getStatistics();
|
||||||
|
|
||||||
|
inTransaction(
|
||||||
|
sessionFactory,
|
||||||
|
s -> s.persist( new Person( "1", "John Doe", true ) )
|
||||||
|
);
|
||||||
|
|
||||||
|
// this time it should be iun the cache because we enabled JPA compliance
|
||||||
|
assertEquals( statistics.getSecondLevelCachePutCount(), 1 );
|
||||||
|
assertTrue( sessionFactory.getCache().contains( Person.class, "1" ) );
|
||||||
|
|
||||||
|
inTransaction(
|
||||||
|
sessionFactory,
|
||||||
|
s -> {
|
||||||
|
statistics.clear();
|
||||||
|
|
||||||
|
final Person person = s.get( Person.class, "1" );
|
||||||
|
assertTrue( Hibernate.isInitialized( person ) );
|
||||||
|
assertThat( statistics.getSecondLevelCacheHitCount(), CoreMatchers.is( 1L) );
|
||||||
|
|
||||||
|
statistics.clear();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testVersioned() {
|
||||||
|
sessionFactory = buildSessionFactory( VersionedPerson.class, false );
|
||||||
|
|
||||||
|
final StatisticsImplementor statistics = sessionFactory.getStatistics();
|
||||||
|
|
||||||
|
inTransaction(
|
||||||
|
sessionFactory,
|
||||||
|
s -> s.persist( new VersionedPerson( "1", "John Doe", true ) )
|
||||||
|
);
|
||||||
|
|
||||||
|
// versioned data should be cacheable regardless
|
||||||
|
assertEquals( statistics.getSecondLevelCachePutCount(), 1 );
|
||||||
|
assertTrue( sessionFactory.getCache().contains( VersionedPerson.class, "1" ) );
|
||||||
|
|
||||||
|
inTransaction(
|
||||||
|
sessionFactory,
|
||||||
|
s -> {
|
||||||
|
statistics.clear();
|
||||||
|
|
||||||
|
final VersionedPerson person = s.get( VersionedPerson.class, "1" );
|
||||||
|
assertTrue( Hibernate.isInitialized( person ) );
|
||||||
|
assertThat( statistics.getSecondLevelCacheHitCount(), CoreMatchers.is( 1L ) );
|
||||||
|
|
||||||
|
statistics.clear();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private SessionFactoryImplementor buildSessionFactory(Class entityClass, boolean strict) {
|
||||||
|
final Map settings = new HashMap();
|
||||||
|
settings.put( AvailableSettings.USE_SECOND_LEVEL_CACHE, "true" );
|
||||||
|
settings.put( AvailableSettings.JPA_SHARED_CACHE_MODE, SharedCacheMode.ENABLE_SELECTIVE );
|
||||||
|
settings.put( AvailableSettings.GENERATE_STATISTICS, "true" );
|
||||||
|
settings.put( AvailableSettings.HBM2DDL_AUTO, "create-drop" );
|
||||||
|
if ( strict ) {
|
||||||
|
settings.put( AvailableSettings.JPA_CACHING_COMPLIANCE, "true" );
|
||||||
|
}
|
||||||
|
|
||||||
|
final StandardServiceRegistryBuilder ssrb = new StandardServiceRegistryBuilder()
|
||||||
|
.applySettings( settings );
|
||||||
|
|
||||||
|
return (SessionFactoryImplementor) new MetadataSources( ssrb.build() )
|
||||||
|
.addAnnotatedClass( Person.class )
|
||||||
|
.addAnnotatedClass( VersionedPerson.class)
|
||||||
|
.buildMetadata()
|
||||||
|
.buildSessionFactory();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void cleanupData() {
|
||||||
|
if ( sessionFactory == null ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
inTransaction(
|
||||||
|
sessionFactory,
|
||||||
|
s -> {
|
||||||
|
s.createQuery( "delete from Person" ).executeUpdate();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
sessionFactory.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity( name = "Person" )
|
||||||
|
@Table( name = "persons" )
|
||||||
|
@Cacheable()
|
||||||
|
@SecondaryTable( name = "crm_persons" )
|
||||||
|
public static class Person {
|
||||||
|
@Id
|
||||||
|
public String id;
|
||||||
|
public String name;
|
||||||
|
|
||||||
|
@Column( table = "crm_persons" )
|
||||||
|
public boolean crmMarketingSchpele;
|
||||||
|
|
||||||
|
public Person() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public Person(String id, String name, boolean crmMarketingSchpele) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Entity( name = "VersionedPerson" )
|
||||||
|
@Table( name = "versioned_persons" )
|
||||||
|
@Cacheable()
|
||||||
|
@SecondaryTable( name = "crm_persons2" )
|
||||||
|
public static class VersionedPerson {
|
||||||
|
@Id
|
||||||
|
public String id;
|
||||||
|
public String name;
|
||||||
|
|
||||||
|
@Version public int version;
|
||||||
|
|
||||||
|
@Column( table = "crm_persons2" )
|
||||||
|
public boolean crmMarketingSchpele;
|
||||||
|
|
||||||
|
public VersionedPerson() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public VersionedPerson(String id, String name, boolean crmMarketingSchpele) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue