Skip to content

Commit 0f72aef

Browse files
committed
HHH-18546 Add test for issue
1 parent f79de81 commit 0f72aef

File tree

1 file changed

+339
-0
lines changed

1 file changed

+339
-0
lines changed
Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,339 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.orm.test.cache;
6+
7+
import java.util.Properties;
8+
9+
import org.hibernate.cache.CacheException;
10+
import org.hibernate.cache.cfg.spi.DomainDataRegionBuildingContext;
11+
import org.hibernate.cache.cfg.spi.DomainDataRegionConfig;
12+
import org.hibernate.cache.cfg.spi.EntityDataCachingConfig;
13+
import org.hibernate.cache.internal.DefaultCacheKeysFactory;
14+
import org.hibernate.cache.spi.CacheKeysFactory;
15+
import org.hibernate.cache.spi.DomainDataRegion;
16+
import org.hibernate.cache.spi.access.AccessType;
17+
import org.hibernate.cache.spi.access.EntityDataAccess;
18+
import org.hibernate.cache.spi.access.SoftLock;
19+
import org.hibernate.cache.spi.support.AbstractEntityDataAccess;
20+
import org.hibernate.cache.spi.support.DomainDataRegionImpl;
21+
import org.hibernate.cache.spi.support.DomainDataStorageAccess;
22+
import org.hibernate.cache.spi.support.RegionFactoryTemplate;
23+
import org.hibernate.cfg.AvailableSettings;
24+
import org.hibernate.engine.spi.SharedSessionContractImplementor;
25+
26+
import org.hibernate.testing.cache.CachingRegionFactory;
27+
import org.hibernate.testing.cache.MapStorageAccessImpl;
28+
import org.hibernate.testing.orm.junit.DomainModel;
29+
import org.hibernate.testing.orm.junit.JiraKey;
30+
import org.hibernate.testing.orm.junit.ServiceRegistry;
31+
import org.hibernate.testing.orm.junit.SessionFactory;
32+
import org.hibernate.testing.orm.junit.SessionFactoryScope;
33+
import org.hibernate.testing.orm.junit.Setting;
34+
import org.hibernate.testing.orm.junit.SettingProvider;
35+
import org.junit.jupiter.api.AfterEach;
36+
import org.junit.jupiter.api.BeforeEach;
37+
import org.junit.jupiter.api.Test;
38+
39+
import org.jboss.logging.Logger;
40+
41+
import jakarta.persistence.Cacheable;
42+
import jakarta.persistence.Entity;
43+
import jakarta.persistence.Id;
44+
import jakarta.persistence.Table;
45+
46+
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
47+
48+
@DomainModel(
49+
annotatedClasses = {
50+
PendingBulkOperationCleanupActionTest.Customer.class
51+
}
52+
)
53+
@SessionFactory()
54+
@ServiceRegistry(
55+
settings = {
56+
@Setting(name = AvailableSettings.USE_SECOND_LEVEL_CACHE, value = "true"),
57+
@Setting(name = AvailableSettings.ALLOW_UPDATE_OUTSIDE_TRANSACTION, value = "true"),
58+
},
59+
settingProviders = @SettingProvider(
60+
settingName = AvailableSettings.CACHE_REGION_FACTORY,
61+
provider = PendingBulkOperationCleanupActionTest.CacheRegionFactoryProvider.class
62+
)
63+
)
64+
@JiraKey( "HHH-18546" )
65+
public class PendingBulkOperationCleanupActionTest {
66+
67+
private static final TestCachingRegionFactory CACHING_REGION_FACTORY = new TestCachingRegionFactory();
68+
69+
public static class CacheRegionFactoryProvider implements SettingProvider.Provider<TestCachingRegionFactory> {
70+
@Override
71+
public TestCachingRegionFactory getSetting() {
72+
return CACHING_REGION_FACTORY;
73+
}
74+
}
75+
76+
@BeforeEach
77+
public void before(SessionFactoryScope scope) {
78+
scope.inTransaction(
79+
session ->
80+
session.persist( new Customer( 1, "Samuel" ) )
81+
);
82+
CACHING_REGION_FACTORY.getTestDomainDataRegion().getTestEntityDataAccess().reset();
83+
}
84+
85+
@AfterEach
86+
public void after(SessionFactoryScope scope) {
87+
scope.inTransaction(
88+
session ->
89+
session.createQuery( "delete Customer" ).executeUpdate()
90+
);
91+
CACHING_REGION_FACTORY.getTestDomainDataRegion().getTestEntityDataAccess().reset();
92+
}
93+
94+
@Test
95+
public void testUpdateCachedEntity(SessionFactoryScope scope) {
96+
scope.inTransaction(
97+
session ->
98+
session.createNativeQuery( "update Customer set id = id" ).executeUpdate()
99+
);
100+
assertThat( isLockRegionCalled() )
101+
.as( "EntityDataAccess lockRegion method has not been called" )
102+
.isTrue();
103+
// Region unlock is a BulkOperationCleanUpAction executed after Transaction commit
104+
assertThat( isUnlockRegionCalled() )
105+
.as( "EntityDataAccess unlockRegion method has not been called" )
106+
.isTrue();
107+
}
108+
109+
@Test
110+
public void testPendingBulkOperationActionsAreExecutedWhenSessionIsClosed(SessionFactoryScope scope) {
111+
scope.inSession(
112+
session ->
113+
session.createNativeQuery( "update Customer set id = id" ).executeUpdate()
114+
);
115+
116+
assertThat( isLockRegionCalled() )
117+
.as( "EntityDataAccess lockRegion method has not been called" )
118+
.isTrue();
119+
120+
// Because the update is executed outside a transaction, region unlock BulkOperationCleanUpAction has not been executed
121+
// and when the session is closed it's a pending action
122+
assertThat( isUnlockRegionCalled() )
123+
.as( "EntityDataAccess unlockRegion method has not been called" )
124+
.isTrue();
125+
}
126+
127+
private static boolean isUnlockRegionCalled() {
128+
return CACHING_REGION_FACTORY.getTestDomainDataRegion()
129+
.getTestEntityDataAccess()
130+
.isUnlockRegionCalled();
131+
}
132+
133+
private static boolean isLockRegionCalled() {
134+
return CACHING_REGION_FACTORY.getTestDomainDataRegion()
135+
.getTestEntityDataAccess()
136+
.isLockRegionCalled();
137+
}
138+
139+
@Entity(name = "Customer")
140+
@Table(name = "Customer")
141+
@Cacheable
142+
public static class Customer {
143+
@Id
144+
private int id;
145+
146+
private String name;
147+
148+
public Customer() {
149+
}
150+
151+
public Customer(int id, String name) {
152+
this.id = id;
153+
this.name = name;
154+
}
155+
156+
public int getId() {
157+
return id;
158+
}
159+
160+
public void setId(int id) {
161+
this.id = id;
162+
}
163+
164+
public String getName() {
165+
return name;
166+
}
167+
168+
public void setName(String name) {
169+
this.name = name;
170+
}
171+
}
172+
173+
public static class TestCachingRegionFactory extends CachingRegionFactory {
174+
private static final Logger LOG = Logger.getLogger( org.hibernate.testing.cache.CachingRegionFactory.class.getName() );
175+
176+
public static final String DEFAULT_ACCESSTYPE = "DefaultAccessType";
177+
private final CacheKeysFactory cacheKeysFactory;
178+
private TestDomainDataRegion testDomainDataRegion;
179+
180+
public TestCachingRegionFactory() {
181+
this( DefaultCacheKeysFactory.INSTANCE, null );
182+
}
183+
184+
public TestCachingRegionFactory(CacheKeysFactory cacheKeysFactory) {
185+
this( cacheKeysFactory, null );
186+
}
187+
188+
public TestCachingRegionFactory(Properties properties) {
189+
this( DefaultCacheKeysFactory.INSTANCE, properties );
190+
}
191+
192+
public TestCachingRegionFactory(CacheKeysFactory cacheKeysFactory, Properties properties) {
193+
LOG.warn( "org.hibernate.testing.cache.CachingRegionFactory should be only used for testing." );
194+
this.cacheKeysFactory = cacheKeysFactory;
195+
}
196+
197+
@Override
198+
public DomainDataRegion buildDomainDataRegion(
199+
DomainDataRegionConfig regionConfig, DomainDataRegionBuildingContext buildingContext) {
200+
if ( testDomainDataRegion == null ) {
201+
testDomainDataRegion = new TestDomainDataRegion(
202+
regionConfig,
203+
this,
204+
new MapStorageAccessImpl(),
205+
cacheKeysFactory,
206+
buildingContext
207+
);
208+
}
209+
return testDomainDataRegion;
210+
}
211+
212+
@Override
213+
protected void releaseFromUse() {
214+
testDomainDataRegion = null;
215+
}
216+
217+
public TestDomainDataRegion getTestDomainDataRegion() {
218+
return testDomainDataRegion;
219+
}
220+
}
221+
222+
public static class TestDomainDataRegion extends DomainDataRegionImpl {
223+
224+
TestEntityDataAccess testEntityDataAccess;
225+
226+
public TestDomainDataRegion(
227+
DomainDataRegionConfig regionConfig,
228+
RegionFactoryTemplate regionFactory,
229+
DomainDataStorageAccess domainDataStorageAccess,
230+
CacheKeysFactory defaultKeysFactory,
231+
DomainDataRegionBuildingContext buildingContext) {
232+
super( regionConfig, regionFactory, domainDataStorageAccess, defaultKeysFactory, buildingContext );
233+
}
234+
235+
@Override
236+
public EntityDataAccess generateEntityAccess(EntityDataCachingConfig entityAccessConfig) {
237+
if ( testEntityDataAccess == null ) {
238+
testEntityDataAccess = new TestEntityDataAccess(
239+
this,
240+
getEffectiveKeysFactory(),
241+
getCacheStorageAccess()
242+
);
243+
}
244+
return testEntityDataAccess;
245+
}
246+
247+
public TestEntityDataAccess getTestEntityDataAccess() {
248+
return testEntityDataAccess;
249+
}
250+
251+
@Override
252+
public void destroy() throws CacheException {
253+
testEntityDataAccess = null;
254+
}
255+
}
256+
257+
public static class TestEntityDataAccess extends AbstractEntityDataAccess {
258+
259+
private boolean isUnlockRegionCalled = false;
260+
private boolean lockRegionCalled = false;
261+
262+
public TestEntityDataAccess(
263+
DomainDataRegion region,
264+
CacheKeysFactory cacheKeysFactory,
265+
DomainDataStorageAccess storageAccess) {
266+
super( region, cacheKeysFactory, storageAccess );
267+
}
268+
269+
@Override
270+
public boolean insert(SharedSessionContractImplementor session, Object key, Object value, Object version) {
271+
return false;
272+
}
273+
274+
@Override
275+
public boolean afterInsert(SharedSessionContractImplementor session, Object key, Object value, Object version) {
276+
return false;
277+
}
278+
279+
@Override
280+
public boolean update(
281+
SharedSessionContractImplementor session,
282+
Object key,
283+
Object value,
284+
Object currentVersion,
285+
Object previousVersion) {
286+
return false;
287+
}
288+
289+
@Override
290+
public boolean afterUpdate(
291+
SharedSessionContractImplementor session,
292+
Object key,
293+
Object value,
294+
Object currentVersion,
295+
Object previousVersion,
296+
SoftLock lock) {
297+
return false;
298+
}
299+
300+
@Override
301+
public AccessType getAccessType() {
302+
return null;
303+
}
304+
305+
@Override
306+
public SoftLock lockRegion() {
307+
lockRegionCalled = true;
308+
return super.lockRegion();
309+
}
310+
311+
@Override
312+
public void unlockRegion(SoftLock lock) {
313+
super.unlockRegion( lock );
314+
isUnlockRegionCalled = true;
315+
}
316+
317+
@Override
318+
public void destroy() {
319+
super.destroy();
320+
isUnlockRegionCalled = false;
321+
}
322+
323+
public boolean isUnlockRegionCalled() {
324+
return isUnlockRegionCalled;
325+
}
326+
327+
public boolean isLockRegionCalled() {
328+
return lockRegionCalled;
329+
}
330+
331+
public void reset() {
332+
this.isUnlockRegionCalled = false;
333+
this.lockRegionCalled = false;
334+
}
335+
336+
}
337+
338+
339+
}

0 commit comments

Comments
 (0)