diff --git a/hibernate-core/src/main/java/org/hibernate/internal/NaturalIdMultiLoadAccessStandard.java b/hibernate-core/src/main/java/org/hibernate/internal/NaturalIdMultiLoadAccessStandard.java index 55c98ecb1ed6..07e8af8500df 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/NaturalIdMultiLoadAccessStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/NaturalIdMultiLoadAccessStandard.java @@ -34,7 +34,7 @@ public class NaturalIdMultiLoadAccessStandard implements NaturalIdMultiLoadAc private Integer batchSize; private boolean returnOfDeletedEntitiesEnabled; - private boolean orderedReturnEnabled = true; + private boolean orderedReturnEnabled = false; public NaturalIdMultiLoadAccessStandard(EntityPersister entityDescriptor, SessionImpl session) { this.entityDescriptor = entityDescriptor; diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/ExecutionContextWithSubselectFetchHandler.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/ExecutionContextWithSubselectFetchHandler.java index e05e929b8662..d0acc63d0e13 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/ExecutionContextWithSubselectFetchHandler.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/ExecutionContextWithSubselectFetchHandler.java @@ -4,9 +4,11 @@ */ package org.hibernate.loader.ast.internal; +import org.hibernate.LockOptions; import org.hibernate.engine.spi.EntityHolder; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SubselectFetch; +import org.hibernate.query.internal.SimpleQueryOptions; import org.hibernate.query.spi.QueryOptions; import org.hibernate.sql.exec.internal.BaseExecutionContext; @@ -14,20 +16,30 @@ class ExecutionContextWithSubselectFetchHandler extends BaseExecutionContext { private final SubselectFetch.RegistrationHandler subSelectFetchableKeysHandler; private final boolean readOnly; + private final QueryOptions queryOptions; public ExecutionContextWithSubselectFetchHandler( SharedSessionContractImplementor session, SubselectFetch.RegistrationHandler subSelectFetchableKeysHandler) { - this( session, subSelectFetchableKeysHandler, false ); + super( session ); + this.subSelectFetchableKeysHandler = subSelectFetchableKeysHandler; + this.readOnly = false; + this.queryOptions = QueryOptions.NONE; } public ExecutionContextWithSubselectFetchHandler( SharedSessionContractImplementor session, SubselectFetch.RegistrationHandler subSelectFetchableKeysHandler, - boolean readOnly) { + boolean readOnly, + LockOptions lockOptions) { super( session ); this.subSelectFetchableKeysHandler = subSelectFetchableKeysHandler; this.readOnly = readOnly; + this.queryOptions = determineQueryOptions( readOnly, lockOptions ); + } + + private QueryOptions determineQueryOptions(boolean readOnly, LockOptions lockOptions) { + return new SimpleQueryOptions( lockOptions, readOnly ? true : null ); } @Override @@ -39,6 +51,11 @@ public void registerLoadingEntityHolder(EntityHolder holder) { @Override public QueryOptions getQueryOptions() { - return readOnly ? QueryOptions.READ_ONLY : super.getQueryOptions(); + return queryOptions; + } + + @Override + public boolean upgradeLocks() { + return true; } } diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderArrayParam.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderArrayParam.java index 1c8ab3effbbc..9567cf816350 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderArrayParam.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderArrayParam.java @@ -142,7 +142,8 @@ public LockOptions getLockOptions() { JdbcParametersList.singleton( jdbcParameter ), jdbcParameterBindings ), - TRUE.equals( loadOptions.getReadOnly( session ) ) + TRUE.equals( loadOptions.getReadOnly( session ) ), + lockOptions ), RowTransformerStandardImpl.instance(), null, diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderStandard.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderStandard.java index b5a9a2ef33d7..62b052e424cc 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiIdEntityLoaderStandard.java @@ -173,7 +173,8 @@ public LockOptions getLockOptions() { new ExecutionContextWithSubselectFetchHandler( session, fetchableKeysHandler( session, sqlAst, jdbcParameters, jdbcParameterBindings ), - TRUE.equals( loadOptions.getReadOnly( session ) ) + TRUE.equals( loadOptions.getReadOnly( session ) ), + lockOptions ), RowTransformerStandardImpl.instance(), null, diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiNaturalIdLoadingBatcher.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiNaturalIdLoadingBatcher.java index 8a74761c8219..16fb16a63dfb 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiNaturalIdLoadingBatcher.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/MultiNaturalIdLoadingBatcher.java @@ -51,6 +51,8 @@ interface KeyValueResolver { private final JdbcOperationQuerySelect jdbcSelect; + private final LockOptions lockOptions; + public MultiNaturalIdLoadingBatcher( EntityMappingType entityDescriptor, ModelPart restrictedPart, @@ -88,6 +90,7 @@ public LockOptions getLockOptions() { return lockOptions; } } ); + this.lockOptions = lockOptions; } public List multiLoad(Object[] naturalIdValues, SharedSessionContractImplementor session) { @@ -163,7 +166,7 @@ private List performLoad( return session.getJdbcServices().getJdbcSelectExecutor().list( jdbcSelect, jdbcParamBindings, - new ExecutionContextWithSubselectFetchHandler( session, subSelectFetchableKeysHandler ), + new ExecutionContextWithSubselectFetchHandler( session, subSelectFetchableKeysHandler, false, lockOptions ), RowTransformerStandardImpl.instance(), null, ListResultsConsumer.UniqueSemantic.FILTER, diff --git a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdExecutionContext.java b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdExecutionContext.java index 96e05fd0fff5..f0635c77957d 100644 --- a/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdExecutionContext.java +++ b/hibernate-core/src/main/java/org/hibernate/loader/ast/internal/SingleIdExecutionContext.java @@ -76,4 +76,8 @@ public void registerLoadingEntityHolder(EntityHolder holder) { subSelectFetchableKeysHandler.addKey( holder ); } + @Override + public boolean upgradeLocks() { + return true; + } } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/loading/multiLoad/MultiLoadLockingTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/loading/multiLoad/MultiLoadLockingTest.java new file mode 100644 index 000000000000..e17304f959f3 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/loading/multiLoad/MultiLoadLockingTest.java @@ -0,0 +1,529 @@ +/* + * SPDX-License-Identifier: LGPL-2.1-or-later + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.orm.test.loading.multiLoad; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.Serializable; +import java.util.List; +import java.util.function.Function; + +import jakarta.persistence.Embeddable; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Version; +import org.hibernate.LockMode; +import org.hibernate.LockOptions; +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.annotations.NaturalId; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.dialect.Dialect; +import org.hibernate.dialect.PostgreSQLDialect; +import org.hibernate.dialect.PostgreSQLSqlAstTranslator; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.sql.ast.tree.Statement; +import org.hibernate.testing.jdbc.SQLStatementInspector; +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.JiraKey; +import org.hibernate.testing.orm.junit.ServiceRegistry; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.hibernate.testing.orm.junit.Setting; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Basic; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + + +@DomainModel( + annotatedClasses = { + MultiLoadLockingTest.Customer.class, + MultiLoadLockingTest.EntityWithAggregateId.class, + MultiLoadLockingTest.User.class + } + ) +@SessionFactory(useCollectingStatementInspector = true) +@ServiceRegistry( + settings = { + @Setting(name = AvailableSettings.USE_QUERY_CACHE, value = "true"), + @Setting(name = AvailableSettings.USE_SECOND_LEVEL_CACHE, value = "true") + } +) +@JiraKey(value = "HHH-18992") +public class MultiLoadLockingTest { + + private SQLStatementInspector sqlStatementInspector; + + private final List customerList = List.of( + new Customer(1L, "Customer A"), + new Customer(2L, "Customer B"), + new Customer(3L, "Customer C"), + new Customer(4L, "Customer D"), + new Customer(5L, "Customer E") + ); + + private final List customerIdsAsLongs = customerList + .stream() + .map( Customer::getId ) + .toList(); + + private final List customerIdsAsObjects = customerList + .stream() + .map( (Function) Customer::getId ) + .toList(); + + private final List customerNaturalIdsAsObjects = customerList + .stream() + .map( (Function) Customer::getName ) + .toList(); + + private final List entityWithAggregateIdList = List.of( + new EntityWithAggregateId( new EntityWithAggregateId.Key( "1", "1" ), "Entity A" ), + new EntityWithAggregateId( new EntityWithAggregateId.Key( "2", "2" ), "Entity B" ), + new EntityWithAggregateId( new EntityWithAggregateId.Key( "3", "3" ), "Entity C" ), + new EntityWithAggregateId( new EntityWithAggregateId.Key( "4", "4" ), "Entity D" ), + new EntityWithAggregateId( new EntityWithAggregateId.Key( "5", "5" ), "Entity E" ) + ); + + private final List entityWithAggregateIdKeys = entityWithAggregateIdList + .stream() + .map(EntityWithAggregateId::getKey) + .toList(); + + private final List entityWithAggregateIdKeysAsObjects = entityWithAggregateIdList + .stream() + .map( (Function) EntityWithAggregateId::getKey ) + .toList(); + + private final List entityWithAggregateIdNaturalIdsAsObjects = entityWithAggregateIdList + .stream() + .map( (Function) EntityWithAggregateId::getData ) + .toList(); + + public final List userList = List.of( + new User(1, "User 1"), + new User(2, "User 2"), + new User(3, "User 3"), + new User(4, "User 4"), + new User(5, "User 5") + ); + + private final List userIds = userList + .stream() + .map(User::getId) + .toList(); + + private final List userIdsAsObjects = userList + .stream() + .map( (Function) User::getId ) + .toList(); + + private final List userNaturalIdsAsObjects = userList + .stream() + .map( (Function) User::getName ) + .toList(); + + + @BeforeEach + public void prepareTestDataAndClearL2C(SessionFactoryScope scope) { + sqlStatementInspector = scope.getCollectingStatementInspector(); + + scope.inTransaction(session -> { + customerList.forEach( session::persist ); + entityWithAggregateIdList.forEach( session::persist ); + userList.forEach( session::persist ); + }); + scope.getSessionFactory().getCache().evictAll(); + sqlStatementInspector.clear(); + } + + @AfterEach + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> scope.getSessionFactory().getSchemaManager().truncate() ); + scope.getSessionFactory().getCache().evictAll(); + } + + + // (1) simple Id entity w/ pessimistic read lock + @Test + void testMultiLoadSimpleIdEntityPessimisticReadLock(SessionFactoryScope scope) { + final LockOptions lockOptions = new LockOptions(LockMode.PESSIMISTIC_READ); + final String lockString = scope.getSessionFactory().getJdbcServices().getDialect().getForUpdateString(lockOptions); + + // test byMultipleIds + scope.inTransaction( session -> { + List customersLoaded = session.byMultipleIds(Customer.class) + .with( lockOptions ) + .multiLoad(customerIdsAsLongs); + assertNotNull(customersLoaded); + assertEquals(customerList.size(), customersLoaded.size()); + customersLoaded.forEach(customer -> assertEquals(LockMode.PESSIMISTIC_READ, session.getCurrentLockMode(customer)) ); + checkStatement( lockString ); + } ); + // test findMultiple + scope.inTransaction( session -> { + List customersLoaded = session.findMultiple(Customer.class, customerIdsAsObjects, LockMode.PESSIMISTIC_READ); + assertNotNull(customersLoaded); + assertEquals(customerList.size(), customersLoaded.size()); + customersLoaded.forEach(customer -> assertEquals(LockMode.PESSIMISTIC_READ, session.getCurrentLockMode(customer)) ); + checkStatement( lockString ); + } ); + // test byMultipleNaturalId + scope.inTransaction( session -> { + List customersLoaded = session.byMultipleNaturalId(Customer.class) + .with( lockOptions ) + .multiLoad( customerNaturalIdsAsObjects ); + assertNotNull(customersLoaded); + assertEquals(customerList.size(), customersLoaded.size()); + customersLoaded.forEach(customer -> assertEquals(LockMode.PESSIMISTIC_READ, session.getCurrentLockMode(customer)) ); + checkStatement( lockString ); + } ); + } + + // (2) composite Id entity w/ pessimistic read lock (one of the entities already in L1C) + @Test + void testMultiLoadCompositeIdEntityPessimisticReadLockAlreadyInSession(SessionFactoryScope scope) { + final LockOptions lockOptions = new LockOptions(LockMode.PESSIMISTIC_READ); + final String lockString = scope.getSessionFactory().getJdbcServices().getDialect().getForUpdateString(lockOptions); + + scope.inTransaction( session -> { + EntityWithAggregateId entityInL1C = session + .find(EntityWithAggregateId.class, entityWithAggregateIdList.get(0).getKey()); + assertNotNull(entityInL1C); + sqlStatementInspector.clear(); + + // test byMultipleIds + List entitiesLoaded = session.byMultipleIds(EntityWithAggregateId.class) + .with( lockOptions ) + .multiLoad(entityWithAggregateIdKeys); + assertNotNull(entitiesLoaded); + assertEquals(entityWithAggregateIdList.size(), entitiesLoaded.size()); + entitiesLoaded.forEach(entity -> assertEquals(LockMode.PESSIMISTIC_READ, session.getCurrentLockMode(entity)) ); + checkStatement( lockString ); + } ); + // test findMultiple + scope.inTransaction( session -> { + EntityWithAggregateId entityInL1C = session + .find(EntityWithAggregateId.class, entityWithAggregateIdList.get(0).getKey()); + assertNotNull(entityInL1C); + sqlStatementInspector.clear(); + + List entitiesLoaded = session.findMultiple(EntityWithAggregateId.class, + entityWithAggregateIdKeysAsObjects, LockMode.PESSIMISTIC_READ ); + assertNotNull(entitiesLoaded); + assertEquals(entityWithAggregateIdList.size(), entitiesLoaded.size()); + entitiesLoaded.forEach(entity -> assertEquals(LockMode.PESSIMISTIC_READ, session.getCurrentLockMode(entity)) ); + checkStatement( lockString ); + } ); + // test byMultipleNaturalId + scope.inTransaction( session -> { + EntityWithAggregateId entityInL1C = session + .find(EntityWithAggregateId.class, entityWithAggregateIdList.get(0).getKey()); + assertNotNull(entityInL1C); + sqlStatementInspector.clear(); + + List entitiesLoaded = session.byMultipleNaturalId(EntityWithAggregateId.class) + .with( lockOptions ) + .multiLoad( entityWithAggregateIdNaturalIdsAsObjects ); + assertNotNull(entitiesLoaded); + assertEquals(entityWithAggregateIdList.size(), entitiesLoaded.size()); + entitiesLoaded.forEach(entity -> assertEquals(LockMode.PESSIMISTIC_READ, session.getCurrentLockMode(entity)) ); + checkStatement( lockString ); + } ); + } + + // (3) simple Id entity w/ pessimistic write lock (one in L1C & some in L2C) + @Test + public void testMultiLoadSimpleIdEntityPessimisticWriteLockSomeInL1CAndSomeInL2C(SessionFactoryScope scope) { + final Integer userInL2CId = userIds.get(0); + final Integer userInL1CId = userIds.get(1); + final LockOptions lockOptions = new LockOptions(LockMode.PESSIMISTIC_WRITE); + Dialect dialect = scope.getSessionFactory().getJdbcServices().getDialect(); + String lockString; + if ( PostgreSQLDialect.class.isAssignableFrom( dialect.getClass() ) ) { + PgSqlAstTranslatorExt translator = new PgSqlAstTranslatorExt( scope.getSessionFactory(), null ); + lockString = translator.getForUpdate(); + } + else { + lockString = scope.getSessionFactory().getJdbcServices().getDialect().getForUpdateString(lockOptions); + } + + scope.inTransaction( session -> { + User userInL2C = session.find(User.class, userInL2CId); + assertNotNull(userInL2C); + } ); + // test byMultipleIds + scope.inTransaction( session -> { + assertTrue(session.getFactory().getCache().containsEntity(User.class, userInL2CId)); + User userInL1C = session.find(User.class, userInL1CId); + assertNotNull(userInL1C); + sqlStatementInspector.clear(); + + List usersLoaded = session.byMultipleIds(User.class) + .with( lockOptions ) + .multiLoad(userIds); + assertNotNull(usersLoaded); + assertEquals(userList.size(), usersLoaded.size()); + usersLoaded.forEach(user -> assertEquals(LockMode.PESSIMISTIC_WRITE, session.getCurrentLockMode(user)) ); + checkStatement( lockString ); + } ); + // test findMultiple + scope.inTransaction( session -> { + User userInL1C = session.find(User.class, userInL1CId); + assertNotNull(userInL1C); + sqlStatementInspector.clear(); + + List usersLoaded = session.findMultiple(User.class, userIdsAsObjects, LockMode.PESSIMISTIC_WRITE); + assertNotNull(usersLoaded); + assertEquals(userList.size(), usersLoaded.size()); + usersLoaded.forEach(user -> assertEquals(LockMode.PESSIMISTIC_WRITE, session.getCurrentLockMode(user)) ); + checkStatement( lockString ); + } ); + // test byMultipleNaturalId + scope.inTransaction( session -> { + User userInL1C = session.find(User.class, userInL1CId); + assertNotNull(userInL1C); + sqlStatementInspector.clear(); + + List usersLoaded = session.byMultipleNaturalId(User.class) + .with( lockOptions ) + .multiLoad( userNaturalIdsAsObjects ); + assertNotNull(usersLoaded); + assertEquals(userList.size(), usersLoaded.size()); + usersLoaded.forEach(user -> assertEquals(LockMode.PESSIMISTIC_WRITE, session.getCurrentLockMode(user)) ); + checkStatement( lockString ); + } ); + } + + // (4) simple Id entity w/ optimistic read lock + @Test + void testMultiLoadSimpleIdEntityOptimisticReadLock(SessionFactoryScope scope) { + // test byMultipleIds + scope.inTransaction( session -> { + List usersLoaded = session.byMultipleIds(User.class) + .with(new LockOptions(LockMode.OPTIMISTIC)) + .multiLoad(userIds); + assertNotNull(usersLoaded); + assertEquals(userList.size(), usersLoaded.size()); + usersLoaded.forEach(user -> assertEquals(LockMode.OPTIMISTIC, session.getCurrentLockMode(user)) ); + } ); + // test findMultiple + scope.inTransaction( session -> { + List usersLoaded = session.findMultiple(User.class, userIdsAsObjects, LockMode.OPTIMISTIC); + assertNotNull(usersLoaded); + assertEquals(userList.size(), usersLoaded.size()); + usersLoaded.forEach(user -> assertEquals(LockMode.OPTIMISTIC, session.getCurrentLockMode(user)) ); + } ); + // test byMultipleNaturalId + scope.inTransaction( session -> { + List usersLoaded = session.byMultipleNaturalId(User.class) + .with( new LockOptions(LockMode.OPTIMISTIC) ) + .multiLoad( userNaturalIdsAsObjects ); + assertNotNull(usersLoaded); + assertEquals(userList.size(), usersLoaded.size()); + usersLoaded.forEach(user -> assertEquals(LockMode.OPTIMISTIC, session.getCurrentLockMode(user)) ); + } ); + } + + + // (5) simple Id entity w/ optimistic force increment lock + @Test + void testMultiLoadSimpleIdEntityOptimisticForceIncrementLock(SessionFactoryScope scope) { + // test byMultipleIds + scope.inTransaction( session -> { + List usersLoaded = session.byMultipleIds(User.class) + .with(new LockOptions(LockMode.OPTIMISTIC_FORCE_INCREMENT)) + .multiLoad(userIds); + assertNotNull(usersLoaded); + assertEquals(userList.size(), usersLoaded.size()); + usersLoaded.forEach(user -> assertEquals(LockMode.OPTIMISTIC_FORCE_INCREMENT, session.getCurrentLockMode(user)) ); + } ); + // test findMultiple + scope.inTransaction( session -> { + List usersLoaded = session.findMultiple(User.class, userIdsAsObjects, LockMode.OPTIMISTIC_FORCE_INCREMENT); + assertNotNull(usersLoaded); + assertEquals(userList.size(), usersLoaded.size()); + usersLoaded.forEach(user -> assertEquals(LockMode.OPTIMISTIC_FORCE_INCREMENT, session.getCurrentLockMode(user)) ); + } ); + // test byMultipleNaturalId + scope.inTransaction( session -> { + List usersLoaded = session.byMultipleNaturalId(User.class) + .with( new LockOptions(LockMode.OPTIMISTIC_FORCE_INCREMENT) ) + .multiLoad( userNaturalIdsAsObjects ); + assertNotNull(usersLoaded); + assertEquals(userList.size(), usersLoaded.size()); + usersLoaded.forEach(user -> assertEquals(LockMode.OPTIMISTIC_FORCE_INCREMENT, session.getCurrentLockMode(user)) ); + } ); + } + + private void checkStatement(String lockString) { + assertEquals( 1,sqlStatementInspector.getSqlQueries().size() ); + assertTrue( sqlStatementInspector.getSqlQueries().get( 0 ).contains( lockString ) ); + sqlStatementInspector.clear(); + } + + // Ugly-ish hack to be able to access the PostgreSQLSqlAstTranslator.getForUpdate() method needed for testing the PostgreSQL dialects + private static class PgSqlAstTranslatorExt extends PostgreSQLSqlAstTranslator { + public PgSqlAstTranslatorExt(SessionFactoryImplementor sessionFactory, Statement statement) { + super( sessionFactory, statement ); + } + + @Override + protected String getForUpdate() { + return super.getForUpdate(); + } + } + + @Entity(name = "Customer") + public static class Customer { + @Id + private Long id; + @Basic(optional = false) + @NaturalId + private String name; + + protected Customer() { + } + + public Customer(Long id, String name) { + this.id = id; + this.name = name; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + @Entity(name = "EntityWithAggregateId") + public static class EntityWithAggregateId { + @EmbeddedId + private EntityWithAggregateId.Key key; + @NaturalId + private String data; + + public EntityWithAggregateId() { + } + + public EntityWithAggregateId(EntityWithAggregateId.Key key, String data) { + this.key = key; + this.data = data; + } + + public EntityWithAggregateId.Key getKey() { + return key; + } + + public void setKey(EntityWithAggregateId.Key key) { + this.key = key; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + + @Embeddable + public static class Key implements Serializable { + private String value1; + private String value2; + + public Key() { + } + + public Key(String value1, String value2) { + this.value1 = value1; + this.value2 = value2; + } + + public String getValue1() { + return value1; + } + + public void setValue1(String value1) { + this.value1 = value1; + } + + public String getValue2() { + return value2; + } + + public void setValue2(String value2) { + this.value2 = value2; + } + } + } + + @Entity(name = "MyUser") + @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) + public static class User { + @Id + int id; + + @Version + private int version; + + @NaturalId + private String name; + + public User() { + } + + public User(int id) { + this.id = id; + } + + public User(int id, String name) { + this.id = id; + this.name = name; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public int getVersion() { + return version; + } + + public void setVersion(Integer version) { + this.version = version; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + +}