From 1e381282765e6ee5d6466b8304ebaba364b26f34 Mon Sep 17 00:00:00 2001 From: Nikita Koksharov Date: Fri, 27 May 2022 11:11:46 +0300 Subject: [PATCH] Feature - Hibernate 6 support #4322 --- redisson-hibernate/pom.xml | 1 + .../redisson-hibernate-6/pom.xml | 98 +++++++ .../hibernate/JndiRedissonRegionFactory.java | 58 +++++ .../hibernate/RedissonCacheKeysFactory.java | 63 +++++ .../hibernate/RedissonRegionFactory.java | 241 ++++++++++++++++++ .../redisson/hibernate/RedissonStorage.java | 196 ++++++++++++++ .../RedissonStrategyRegistrationProvider.java | 42 +++ ...stry.selector.StrategyRegistrationProvider | 1 + .../OSGI-INF/blueprint/blueprint.xml | 11 + .../redisson/hibernate/CollectionTest.java | 123 +++++++++ .../org/redisson/hibernate/ItemReadWrite.java | 76 ++++++ .../redisson/hibernate/ItemTransactional.java | 76 ++++++ .../org/redisson/hibernate/ReadWriteTest.java | 94 +++++++ .../redisson/hibernate/TransactionalTest.java | 180 +++++++++++++ .../src/test/resources/hibernate.properties | 4 + .../src/test/resources/redisson.yaml | 2 + 16 files changed, 1266 insertions(+) create mode 100644 redisson-hibernate/redisson-hibernate-6/pom.xml create mode 100644 redisson-hibernate/redisson-hibernate-6/src/main/java/org/redisson/hibernate/JndiRedissonRegionFactory.java create mode 100644 redisson-hibernate/redisson-hibernate-6/src/main/java/org/redisson/hibernate/RedissonCacheKeysFactory.java create mode 100644 redisson-hibernate/redisson-hibernate-6/src/main/java/org/redisson/hibernate/RedissonRegionFactory.java create mode 100644 redisson-hibernate/redisson-hibernate-6/src/main/java/org/redisson/hibernate/RedissonStorage.java create mode 100644 redisson-hibernate/redisson-hibernate-6/src/main/java/org/redisson/hibernate/RedissonStrategyRegistrationProvider.java create mode 100644 redisson-hibernate/redisson-hibernate-6/src/main/resources/META-INF/services/org.hibernate.boot.registry.selector.StrategyRegistrationProvider create mode 100644 redisson-hibernate/redisson-hibernate-6/src/main/resources/OSGI-INF/blueprint/blueprint.xml create mode 100644 redisson-hibernate/redisson-hibernate-6/src/test/java/org/redisson/hibernate/CollectionTest.java create mode 100644 redisson-hibernate/redisson-hibernate-6/src/test/java/org/redisson/hibernate/ItemReadWrite.java create mode 100644 redisson-hibernate/redisson-hibernate-6/src/test/java/org/redisson/hibernate/ItemTransactional.java create mode 100644 redisson-hibernate/redisson-hibernate-6/src/test/java/org/redisson/hibernate/ReadWriteTest.java create mode 100644 redisson-hibernate/redisson-hibernate-6/src/test/java/org/redisson/hibernate/TransactionalTest.java create mode 100644 redisson-hibernate/redisson-hibernate-6/src/test/resources/hibernate.properties create mode 100644 redisson-hibernate/redisson-hibernate-6/src/test/resources/redisson.yaml diff --git a/redisson-hibernate/pom.xml b/redisson-hibernate/pom.xml index 563a79656..7c5072681 100644 --- a/redisson-hibernate/pom.xml +++ b/redisson-hibernate/pom.xml @@ -18,6 +18,7 @@ redisson-hibernate-5 redisson-hibernate-52 redisson-hibernate-53 + redisson-hibernate-6 diff --git a/redisson-hibernate/redisson-hibernate-6/pom.xml b/redisson-hibernate/redisson-hibernate-6/pom.xml new file mode 100644 index 000000000..152ab6faa --- /dev/null +++ b/redisson-hibernate/redisson-hibernate-6/pom.xml @@ -0,0 +1,98 @@ + + 4.0.0 + + + org.redisson + redisson-hibernate + 3.17.3-SNAPSHOT + ../ + + + redisson-hibernate-6 + jar + + Redisson/Hibernate-6.x+ + + + + org.hibernate.orm + hibernate-core + 6.0.2.Final + true + + + + org.hibernate.orm + hibernate-testing + 6.0.2.Final + test + + + + org.junit.jupiter + junit-jupiter-engine + 5.7.2 + test + + + + org.junit.jupiter + junit-jupiter-params + 5.7.2 + test + + + + + + + maven-jar-plugin + 3.1.2 + + + + ${maven.build.timestamp} + redisson.hibernate53 + + + + + + + com.mycila + license-maven-plugin + 3.0 + + ${basedir} +
${basedir}/../../header.txt
+ false + true + false + + src/main/java/org/redisson/ + + + target/** + + true + + JAVADOC_STYLE + + true + true + UTF-8 +
+ + + + check + + + +
+ +
+
+ + +
diff --git a/redisson-hibernate/redisson-hibernate-6/src/main/java/org/redisson/hibernate/JndiRedissonRegionFactory.java b/redisson-hibernate/redisson-hibernate-6/src/main/java/org/redisson/hibernate/JndiRedissonRegionFactory.java new file mode 100644 index 000000000..f1e6b6cc5 --- /dev/null +++ b/redisson-hibernate/redisson-hibernate-6/src/main/java/org/redisson/hibernate/JndiRedissonRegionFactory.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2013-2021 Nikita Koksharov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson.hibernate; + +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.cache.CacheException; +import org.hibernate.engine.jndi.JndiException; +import org.hibernate.engine.jndi.spi.JndiService; +import org.hibernate.internal.util.config.ConfigurationHelper; +import org.redisson.api.RedissonClient; + +import java.util.Map; + +/** + * Hibernate Cache region factory based on Redisson. + * Uses Redisson instance located in JNDI. + * + * @author Nikita Koksharov + * + */ +public class JndiRedissonRegionFactory extends RedissonRegionFactory { + + private static final long serialVersionUID = -4814502675083325567L; + + public static final String JNDI_NAME = CONFIG_PREFIX + "jndi_name"; + + @Override + protected RedissonClient createRedissonClient(StandardServiceRegistry registry, Map properties) { + String jndiName = ConfigurationHelper.getString(JNDI_NAME, properties); + if (jndiName == null) { + throw new CacheException(JNDI_NAME + " property not set"); + } + + try { + return (RedissonClient) registry.getService(JndiService.class).locate(jndiName); + } catch (JndiException e) { + throw new CacheException(e); + } + } + + @Override + protected void releaseFromUse() { + } + +} diff --git a/redisson-hibernate/redisson-hibernate-6/src/main/java/org/redisson/hibernate/RedissonCacheKeysFactory.java b/redisson-hibernate/redisson-hibernate-6/src/main/java/org/redisson/hibernate/RedissonCacheKeysFactory.java new file mode 100644 index 000000000..5eef3261b --- /dev/null +++ b/redisson-hibernate/redisson-hibernate-6/src/main/java/org/redisson/hibernate/RedissonCacheKeysFactory.java @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2013-2021 Nikita Koksharov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson.hibernate; + +import io.netty.buffer.ByteBuf; +import org.hibernate.PropertyNotFoundException; +import org.hibernate.cache.internal.DefaultCacheKeysFactory; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.ReflectHelper; +import org.hibernate.persister.collection.CollectionPersister; +import org.redisson.client.codec.Codec; + +import java.io.IOException; +import java.lang.reflect.Field; + +/** + * + * @author Nikita Koksharov + * + */ +public class RedissonCacheKeysFactory extends DefaultCacheKeysFactory { + + private final Codec codec; + + public RedissonCacheKeysFactory(Codec codec) { + this.codec = codec; + } + + @Override + public Object createCollectionKey(Object id, CollectionPersister persister, SessionFactoryImplementor factory, String tenantIdentifier) { + try { + String[] parts = persister.getRole().split("\\."); + Field f = ReflectHelper.findField(id.getClass(), parts[parts.length - 1]); + + Object prev = f.get(id); + f.set(id, null); + ByteBuf state = codec.getMapKeyEncoder().encode(id); + Object newId = codec.getMapKeyDecoder().decode(state, null); + state.release(); + f.set(id, prev); + return super.createCollectionKey(newId, persister, factory, tenantIdentifier); + } catch (PropertyNotFoundException e) { + return super.createCollectionKey(id, persister, factory, tenantIdentifier); + } catch (IllegalAccessException | IOException e) { + throw new IllegalStateException(e); + } + } + + +} diff --git a/redisson-hibernate/redisson-hibernate-6/src/main/java/org/redisson/hibernate/RedissonRegionFactory.java b/redisson-hibernate/redisson-hibernate-6/src/main/java/org/redisson/hibernate/RedissonRegionFactory.java new file mode 100644 index 000000000..5fa72b140 --- /dev/null +++ b/redisson-hibernate/redisson-hibernate-6/src/main/java/org/redisson/hibernate/RedissonRegionFactory.java @@ -0,0 +1,241 @@ +/** + * Copyright (c) 2013-2021 Nikita Koksharov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson.hibernate; + +import org.hibernate.boot.registry.StandardServiceRegistry; +import org.hibernate.boot.registry.selector.spi.StrategySelector; +import org.hibernate.boot.spi.SessionFactoryOptions; +import org.hibernate.cache.CacheException; +import org.hibernate.cache.cfg.spi.DomainDataRegionBuildingContext; +import org.hibernate.cache.cfg.spi.DomainDataRegionConfig; +import org.hibernate.cache.spi.CacheKeysFactory; +import org.hibernate.cache.spi.DomainDataRegion; +import org.hibernate.cache.spi.access.AccessType; +import org.hibernate.cache.spi.support.*; +import org.hibernate.cfg.Environment; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.internal.util.config.ConfigurationHelper; +import org.redisson.Redisson; +import org.redisson.api.RMapCache; +import org.redisson.api.RScript; +import org.redisson.api.RedissonClient; +import org.redisson.client.codec.LongCodec; +import org.redisson.config.Config; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.Map; + +/** + * Hibernate Cache region factory based on Redisson. + * Creates own Redisson instance during region start. + * + * @author Nikita Koksharov + * + */ +public class RedissonRegionFactory extends RegionFactoryTemplate { + + private static final long serialVersionUID = 3785315696581773811L; + + public static final String QUERY_DEF = "query"; + + public static final String COLLECTION_DEF = "collection"; + + public static final String ENTITY_DEF = "entity"; + + public static final String NATURAL_ID_DEF = "naturalid"; + + public static final String TIMESTAMPS_DEF = "timestamps"; + + public static final String MAX_ENTRIES_SUFFIX = ".eviction.max_entries"; + + public static final String TTL_SUFFIX = ".expiration.time_to_live"; + + public static final String MAX_IDLE_SUFFIX = ".expiration.max_idle_time"; + + public static final String CONFIG_PREFIX = "hibernate.cache.redisson."; + + public static final String REDISSON_CONFIG_PATH = CONFIG_PREFIX + "config"; + + public static final String FALLBACK = CONFIG_PREFIX + "fallback"; + + private RedissonClient redisson; + private CacheKeysFactory cacheKeysFactory; + protected boolean fallback; + + @Override + protected CacheKeysFactory getImplicitCacheKeysFactory() { + return cacheKeysFactory; + } + + @Override + protected void prepareForUse(SessionFactoryOptions settings, @SuppressWarnings("rawtypes") Map properties) throws CacheException { + this.redisson = createRedissonClient(settings.getServiceRegistry(), properties); + + String fallbackValue = (String) properties.getOrDefault(FALLBACK, "false"); + fallback = Boolean.valueOf(fallbackValue); + + StrategySelector selector = settings.getServiceRegistry().getService(StrategySelector.class); + cacheKeysFactory = selector.resolveDefaultableStrategy(CacheKeysFactory.class, + properties.get(Environment.CACHE_KEYS_FACTORY), new RedissonCacheKeysFactory(redisson.getConfig().getCodec())); + } + + protected RedissonClient createRedissonClient(StandardServiceRegistry registry, Map properties) { + Config config = null; + if (!properties.containsKey(REDISSON_CONFIG_PATH)) { + config = loadConfig(RedissonRegionFactory.class.getClassLoader(), "redisson.json"); + if (config == null) { + config = loadConfig(RedissonRegionFactory.class.getClassLoader(), "redisson.yaml"); + } + } else { + String configPath = ConfigurationHelper.getString(REDISSON_CONFIG_PATH, properties); + config = loadConfig(RedissonRegionFactory.class.getClassLoader(), configPath); + if (config == null) { + config = loadConfig(configPath); + } + } + + if (config == null) { + throw new CacheException("Unable to locate Redisson configuration"); + } + + return Redisson.create(config); + } + + private Config loadConfig(String configPath) { + try { + return Config.fromYAML(new File(configPath)); + } catch (IOException e) { + // trying next format + try { + return Config.fromJSON(new File(configPath)); + } catch (IOException e1) { + throw new CacheException("Can't parse default yaml config", e1); + } + } + } + + private Config loadConfig(ClassLoader classLoader, String fileName) { + InputStream is = classLoader.getResourceAsStream(fileName); + if (is != null) { + try { + return Config.fromYAML(is); + } catch (IOException e) { + try { + is = classLoader.getResourceAsStream(fileName); + return Config.fromJSON(is); + } catch (IOException e1) { + throw new CacheException("Can't parse yaml config", e1); + } + } + } + return null; + } + + @Override + protected void releaseFromUse() { + redisson.shutdown(); + } + + @Override + public boolean isMinimalPutsEnabledByDefault() { + return true; + } + + @Override + public AccessType getDefaultAccessType() { + return AccessType.TRANSACTIONAL; + } + + @Override + public long nextTimestamp() { + long time = System.currentTimeMillis() << 12; + try { + return redisson.getScript(LongCodec.INSTANCE).eval(RScript.Mode.READ_WRITE, + "local currentTime = redis.call('get', KEYS[1]);" + + "if currentTime == false then " + + "redis.call('set', KEYS[1], ARGV[1]); " + + "return ARGV[1]; " + + "end;" + + "local nextValue = math.max(tonumber(ARGV[1]), tonumber(currentTime) + 1); " + + "redis.call('set', KEYS[1], nextValue); " + + "return nextValue;", + RScript.ReturnType.INTEGER, Arrays.asList("redisson-hibernate-timestamp"), time); + } catch (Exception e) { + if (fallback) { + return super.nextTimestamp(); + } + throw e; + } + } + + @Override + public DomainDataRegion buildDomainDataRegion( + DomainDataRegionConfig regionConfig, + DomainDataRegionBuildingContext buildingContext) { + verifyStarted(); + return new DomainDataRegionImpl( + regionConfig, + this, + createDomainDataStorageAccess( regionConfig, buildingContext ), + getImplicitCacheKeysFactory(), + buildingContext + ); + } + + @Override + protected DomainDataStorageAccess createDomainDataStorageAccess(DomainDataRegionConfig regionConfig, + DomainDataRegionBuildingContext buildingContext) { + String defaultKey = null; + if (!regionConfig.getCollectionCaching().isEmpty()) { + defaultKey = COLLECTION_DEF; + } else if (!regionConfig.getEntityCaching().isEmpty()) { + defaultKey = ENTITY_DEF; + } else if (!regionConfig.getNaturalIdCaching().isEmpty()) { + defaultKey = NATURAL_ID_DEF; + } else { + throw new IllegalArgumentException("Unable to determine entity cache type!"); + } + + RMapCache mapCache = getCache(qualifyName(regionConfig.getRegionName()), buildingContext.getSessionFactory().getProperties(), defaultKey); + return new RedissonStorage(mapCache, ((Redisson)redisson).getConnectionManager(), buildingContext.getSessionFactory().getProperties(), defaultKey); + } + + private String qualifyName(String name) { + return RegionNameQualifier.INSTANCE.qualify(name, getOptions()); + } + + @Override + protected StorageAccess createQueryResultsRegionStorageAccess(String regionName, + SessionFactoryImplementor sessionFactory) { + RMapCache mapCache = getCache(qualifyName(regionName), sessionFactory.getProperties(), QUERY_DEF); + return new RedissonStorage(mapCache, ((Redisson)redisson).getConnectionManager(), sessionFactory.getProperties(), QUERY_DEF); + } + + @Override + protected StorageAccess createTimestampsRegionStorageAccess(String regionName, + SessionFactoryImplementor sessionFactory) { + RMapCache mapCache = getCache(qualifyName(regionName), sessionFactory.getProperties(), TIMESTAMPS_DEF); + return new RedissonStorage(mapCache, ((Redisson)redisson).getConnectionManager(), sessionFactory.getProperties(), TIMESTAMPS_DEF); + } + + protected RMapCache getCache(String regionName, Map properties, String defaultKey) { + return redisson.getMapCache(regionName); + } + +} diff --git a/redisson-hibernate/redisson-hibernate-6/src/main/java/org/redisson/hibernate/RedissonStorage.java b/redisson-hibernate/redisson-hibernate-6/src/main/java/org/redisson/hibernate/RedissonStorage.java new file mode 100644 index 000000000..357992a81 --- /dev/null +++ b/redisson-hibernate/redisson-hibernate-6/src/main/java/org/redisson/hibernate/RedissonStorage.java @@ -0,0 +1,196 @@ +/** + * Copyright (c) 2013-2021 Nikita Koksharov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson.hibernate; + +import org.hibernate.cache.CacheException; +import org.hibernate.cache.spi.support.DomainDataStorageAccess; +import org.hibernate.engine.spi.SharedSessionContractImplementor; +import org.redisson.api.RFuture; +import org.redisson.api.RMapCache; +import org.redisson.connection.ConnectionManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * + * @author Nikita Koksharov + * + */ +public class RedissonStorage implements DomainDataStorageAccess { + + private static final Logger logger = LoggerFactory.getLogger(RedissonStorage.class); + + private final RMapCache mapCache; + + private final ConnectionManager connectionManager; + + int ttl; + int maxIdle; + int size; + boolean fallback; + volatile boolean fallbackMode; + + public RedissonStorage(RMapCache mapCache, ConnectionManager connectionManager, Map properties, String defaultKey) { + super(); + this.mapCache = mapCache; + this.connectionManager = connectionManager; + + String maxEntries = getProperty(properties, mapCache.getName(), defaultKey, RedissonRegionFactory.MAX_ENTRIES_SUFFIX); + if (maxEntries != null) { + size = Integer.valueOf(maxEntries); + mapCache.setMaxSize(size); + } + String timeToLive = getProperty(properties, mapCache.getName(), defaultKey, RedissonRegionFactory.TTL_SUFFIX); + if (timeToLive != null) { + ttl = Integer.valueOf(timeToLive); + } + String maxIdleTime = getProperty(properties, mapCache.getName(), defaultKey, RedissonRegionFactory.MAX_IDLE_SUFFIX); + if (maxIdleTime != null) { + maxIdle = Integer.valueOf(maxIdleTime); + } + + String fallbackValue = (String) properties.getOrDefault(RedissonRegionFactory.FALLBACK, "false"); + fallback = Boolean.valueOf(fallbackValue); + } + + private String getProperty(Map properties, String name, String defaultKey, String suffix) { + String maxEntries = (String) properties.get(RedissonRegionFactory.CONFIG_PREFIX + name + suffix); + if (maxEntries != null) { + return maxEntries; + } + String defValue = (String) properties.get(RedissonRegionFactory.CONFIG_PREFIX + defaultKey + suffix); + if (defValue != null) { + return defValue; + } + return null; + } + + private void ping() { + fallbackMode = true; + connectionManager.newTimeout(t -> { + RFuture future = mapCache.isExistsAsync(); + future.whenComplete((r, ex) -> { + if (ex == null) { + fallbackMode = false; + } else { + ping(); + } + }); + }, 1, TimeUnit.SECONDS); + } + + @Override + public Object getFromCache(Object key, SharedSessionContractImplementor session) { + if (fallbackMode) { + return null; + } + try { + if (maxIdle == 0 && size == 0) { + return mapCache.getWithTTLOnly(key); + } + + return mapCache.get(key); + } catch (Exception e) { + if (fallback) { + ping(); + logger.error(e.getMessage(), e); + return null; + } + throw new CacheException(e); + } + } + + @Override + public void putIntoCache(Object key, Object value, SharedSessionContractImplementor session) { + if (fallbackMode) { + return; + } + try { + mapCache.fastPut(key, value, ttl, TimeUnit.MILLISECONDS, maxIdle, TimeUnit.MILLISECONDS); + } catch (Exception e) { + if (fallback) { + ping(); + logger.error(e.getMessage(), e); + return; + } + throw new CacheException(e); + } + } + + @Override + public boolean contains(Object key) { + if (fallbackMode) { + return false; + } + try { + return mapCache.containsKey(key); + } catch (Exception e) { + if (fallback) { + ping(); + logger.error(e.getMessage(), e); + return false; + } + throw new CacheException(e); + } + } + + @Override + public void evictData() { + if (fallbackMode) { + return; + } + try { + mapCache.clear(); + } catch (Exception e) { + if (fallback) { + ping(); + logger.error(e.getMessage(), e); + return; + } + throw new CacheException(e); + } + } + + @Override + public void evictData(Object key) { + if (fallbackMode) { + return; + } + try { + mapCache.fastRemove(key); + } catch (Exception e) { + if (fallback) { + ping(); + logger.error(e.getMessage(), e); + return; + } + throw new CacheException(e); + } + } + + @Override + public void release() { + try { + mapCache.destroy(); + } catch (Exception e) { + throw new CacheException(e); + } + } + +} diff --git a/redisson-hibernate/redisson-hibernate-6/src/main/java/org/redisson/hibernate/RedissonStrategyRegistrationProvider.java b/redisson-hibernate/redisson-hibernate-6/src/main/java/org/redisson/hibernate/RedissonStrategyRegistrationProvider.java new file mode 100644 index 000000000..d323b5302 --- /dev/null +++ b/redisson-hibernate/redisson-hibernate-6/src/main/java/org/redisson/hibernate/RedissonStrategyRegistrationProvider.java @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2013-2021 Nikita Koksharov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.redisson.hibernate; + +import java.util.Collections; + +import org.hibernate.boot.registry.selector.SimpleStrategyRegistrationImpl; +import org.hibernate.boot.registry.selector.StrategyRegistration; +import org.hibernate.boot.registry.selector.StrategyRegistrationProvider; +import org.hibernate.cache.spi.RegionFactory; + +/** + * + * @author Nikita Koksharov + * + */ +public class RedissonStrategyRegistrationProvider implements StrategyRegistrationProvider { + + @Override + public Iterable getStrategyRegistrations() { + return Collections.singleton(new SimpleStrategyRegistrationImpl( + RegionFactory.class, + RedissonRegionFactory.class, + "redisson", + RedissonRegionFactory.class.getName(), + RedissonRegionFactory.class.getSimpleName())); + } + +} diff --git a/redisson-hibernate/redisson-hibernate-6/src/main/resources/META-INF/services/org.hibernate.boot.registry.selector.StrategyRegistrationProvider b/redisson-hibernate/redisson-hibernate-6/src/main/resources/META-INF/services/org.hibernate.boot.registry.selector.StrategyRegistrationProvider new file mode 100644 index 000000000..321aa8648 --- /dev/null +++ b/redisson-hibernate/redisson-hibernate-6/src/main/resources/META-INF/services/org.hibernate.boot.registry.selector.StrategyRegistrationProvider @@ -0,0 +1 @@ +org.redisson.hibernate.RedissonStrategyRegistrationProvider \ No newline at end of file diff --git a/redisson-hibernate/redisson-hibernate-6/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/redisson-hibernate/redisson-hibernate-6/src/main/resources/OSGI-INF/blueprint/blueprint.xml new file mode 100644 index 000000000..2f0ca41f8 --- /dev/null +++ b/redisson-hibernate/redisson-hibernate-6/src/main/resources/OSGI-INF/blueprint/blueprint.xml @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/redisson-hibernate/redisson-hibernate-6/src/test/java/org/redisson/hibernate/CollectionTest.java b/redisson-hibernate/redisson-hibernate-6/src/test/java/org/redisson/hibernate/CollectionTest.java new file mode 100644 index 000000000..05dd5fa18 --- /dev/null +++ b/redisson-hibernate/redisson-hibernate-6/src/test/java/org/redisson/hibernate/CollectionTest.java @@ -0,0 +1,123 @@ +package org.redisson.hibernate; + +import org.hibernate.Session; +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.cfg.Configuration; +import org.hibernate.cfg.Environment; +import org.hibernate.stat.Statistics; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import jakarta.persistence.*; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * + * @author Nikita Koksharov + * + */ +public class CollectionTest extends BaseCoreFunctionalTestCase { + + @Entity + @Cache(usage = CacheConcurrencyStrategy.READ_ONLY) + public static class A implements Serializable { + @Id + Long id; + + @Column(unique = true) String uniqueField; + + @ManyToMany + @JoinTable( + name = "a_b", + joinColumns = @JoinColumn( + name = "unique_field", referencedColumnName = "uniqueField"), + inverseJoinColumns = @JoinColumn( + name = "b_id", referencedColumnName = "id")) + @Cache(usage = CacheConcurrencyStrategy.READ_ONLY) + private List bs = new ArrayList<>(); + } + + @Entity + @Cache(usage = CacheConcurrencyStrategy.READ_ONLY) + public static class B implements Serializable { + @Id Long id; + + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { A.class, B.class }; + } + + @Override + protected void configure(Configuration cfg) { + super.configure(cfg); + cfg.setProperty(Environment.DRIVER, org.h2.Driver.class.getName()); + cfg.setProperty(Environment.URL, "jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1;"); + cfg.setProperty(Environment.USER, "sa"); + cfg.setProperty(Environment.PASS, ""); + cfg.setProperty(Environment.CACHE_REGION_PREFIX, ""); + cfg.setProperty(Environment.GENERATE_STATISTICS, "true"); + + cfg.setProperty(Environment.SHOW_SQL, "true"); + cfg.setProperty(Environment.USE_SECOND_LEVEL_CACHE, "true"); + cfg.setProperty(Environment.USE_QUERY_CACHE, "true"); + cfg.setProperty(Environment.CACHE_REGION_FACTORY, RedissonRegionFactory.class.getName()); + } + + @Before + public void before() { + sessionFactory().getCache().evictEntityData(); + sessionFactory().getStatistics().clear(); + } + + @Test + public void testQuery() { + Statistics stats = sessionFactory().getStatistics(); + + Session s = openSession(); + s.beginTransaction(); + + A a = new A(); + a.id = 1L; + a.uniqueField = "1"; + B b = new B(); + b.id = 1L; + s.save(b); + a.bs.add(b); + s.save(a); + s.flush(); + s.getTransaction().commit(); + + s = openSession(); + s.beginTransaction(); + A a1 = s.get(A.class, 1L); + System.out.println("here1"); + assertThat(a1.bs).hasSize(1); + s.getTransaction().commit(); + + Assert.assertEquals(0, stats.getDomainDataRegionStatistics("org.redisson.hibernate.CollectionTest$A.bs").getHitCount()); + + s = openSession(); + s.beginTransaction(); + A a2 = s.get(A.class, 1L); + B b2 = a2.bs.iterator().next(); + assertThat(a2.bs.size()).isEqualTo(1); + s.getTransaction().commit(); + + s.close(); + + Assert.assertEquals(1, stats.getDomainDataRegionStatistics("org.redisson.hibernate.CollectionTest$A.bs").getHitCount()); + + stats.logSummary(); + + } + +} diff --git a/redisson-hibernate/redisson-hibernate-6/src/test/java/org/redisson/hibernate/ItemReadWrite.java b/redisson-hibernate/redisson-hibernate-6/src/test/java/org/redisson/hibernate/ItemReadWrite.java new file mode 100644 index 000000000..9e8dd1360 --- /dev/null +++ b/redisson-hibernate/redisson-hibernate-6/src/test/java/org/redisson/hibernate/ItemReadWrite.java @@ -0,0 +1,76 @@ +package org.redisson.hibernate; + +import java.util.ArrayList; +import java.util.List; + +import jakarta.persistence.*; + +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.annotations.GenericGenerator; +import org.hibernate.annotations.NaturalId; +import org.hibernate.annotations.NaturalIdCache; + +/** + * + * @author Nikita Koksharov + * + */ +@Entity +@NamedQueries(@NamedQuery(name = "testQuery", query = "from ItemReadWrite where name = :name")) +@Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = "item") +@NaturalIdCache +public class ItemReadWrite { + + @Id + @GeneratedValue(generator = "increment") + @GenericGenerator(name = "increment", strategy = "increment") + private Long id; + + private String name; + + @NaturalId + private String nid; + + @ElementCollection + @JoinTable(name = "Entries", joinColumns = @JoinColumn(name="Item_id")) + @Column(name = "entry", nullable = false) + @Cache(usage = CacheConcurrencyStrategy.READ_WRITE, region = "item_entries") + private List entries = new ArrayList(); + + public ItemReadWrite() { + } + + public ItemReadWrite(String name) { + this.name = name; + } + + public String getNid() { + return nid; + } + + public void setNid(String nid) { + this.nid = nid; + } + + 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; + } + + public List getEntries() { + return entries; + } + +} diff --git a/redisson-hibernate/redisson-hibernate-6/src/test/java/org/redisson/hibernate/ItemTransactional.java b/redisson-hibernate/redisson-hibernate-6/src/test/java/org/redisson/hibernate/ItemTransactional.java new file mode 100644 index 000000000..54fe5e0e3 --- /dev/null +++ b/redisson-hibernate/redisson-hibernate-6/src/test/java/org/redisson/hibernate/ItemTransactional.java @@ -0,0 +1,76 @@ +package org.redisson.hibernate; + +import java.util.ArrayList; +import java.util.List; + +import jakarta.persistence.*; + +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.annotations.GenericGenerator; +import org.hibernate.annotations.NaturalId; +import org.hibernate.annotations.NaturalIdCache; + +/** + * + * @author Nikita Koksharov + * + */ +@Entity +@NamedQueries(@NamedQuery(name = "testQuery", query = "from ItemTransactional where name = :name")) +@Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL, region = "item") +@NaturalIdCache +public class ItemTransactional { + + @Id + @GeneratedValue(generator = "increment") + @GenericGenerator(name = "increment", strategy = "increment") + private Long id; + + private String name; + + @NaturalId + private String nid; + + @ElementCollection + @JoinTable(name = "Entries", joinColumns = @JoinColumn(name="Item_id")) + @Column(name = "entry", nullable = false) + @Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL, region = "item_entries") + private List entries = new ArrayList(); + + public ItemTransactional() { + } + + public ItemTransactional(String name) { + this.name = name; + } + + public String getNid() { + return nid; + } + + public void setNid(String nid) { + this.nid = nid; + } + + 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; + } + + public List getEntries() { + return entries; + } + +} diff --git a/redisson-hibernate/redisson-hibernate-6/src/test/java/org/redisson/hibernate/ReadWriteTest.java b/redisson-hibernate/redisson-hibernate-6/src/test/java/org/redisson/hibernate/ReadWriteTest.java new file mode 100644 index 000000000..0cf8ed4d1 --- /dev/null +++ b/redisson-hibernate/redisson-hibernate-6/src/test/java/org/redisson/hibernate/ReadWriteTest.java @@ -0,0 +1,94 @@ +package org.redisson.hibernate; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; + +import org.hibernate.query.Query; +import org.hibernate.Session; +import org.hibernate.cfg.Configuration; +import org.hibernate.cfg.Environment; +import org.hibernate.stat.Statistics; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +/** + * + * @author Nikita Koksharov + * + */ +public class ReadWriteTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { ItemReadWrite.class}; + } + + @Override + protected void configure(Configuration cfg) { + super.configure(cfg); + cfg.setProperty(Environment.DRIVER, org.h2.Driver.class.getName()); + cfg.setProperty(Environment.URL, "jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1;"); + cfg.setProperty(Environment.USER, "sa"); + cfg.setProperty(Environment.PASS, ""); + cfg.setProperty(Environment.CACHE_REGION_PREFIX, ""); + cfg.setProperty(Environment.GENERATE_STATISTICS, "true"); + + cfg.setProperty(Environment.USE_SECOND_LEVEL_CACHE, "true"); + cfg.setProperty(Environment.USE_QUERY_CACHE, "true"); + cfg.setProperty(Environment.CACHE_REGION_FACTORY, RedissonRegionFactory.class.getName()); + + cfg.setProperty("hibernate.cache.redisson.item.eviction.max_entries", "100"); + cfg.setProperty("hibernate.cache.redisson.item.expiration.time_to_live", "1500"); + cfg.setProperty("hibernate.cache.redisson.item.expiration.max_idle_time", "1000"); + } + + @Before + public void before() { + sessionFactory().getCache().evictAllRegions(); + sessionFactory().getStatistics().clear(); + } + + @Test + public void testQuery() { + Statistics stats = sessionFactory().getStatistics(); + + Session s = openSession(); + s.beginTransaction(); + ItemReadWrite item = new ItemReadWrite("data"); + item.getEntries().addAll(Arrays.asList("a", "b", "c")); + s.save(item); + s.flush(); + s.getTransaction().commit(); + + s = openSession(); + s.beginTransaction(); + Query query = s.getNamedQuery("testQuery"); + query.setCacheable(true); + query.setCacheRegion("myTestQuery"); + query.setParameter("name", "data"); + item = query.uniqueResult(); + s.getTransaction().commit(); + s.close(); + + Assert.assertEquals(1, stats.getDomainDataRegionStatistics("myTestQuery").getPutCount()); + + s = openSession(); + s.beginTransaction(); + Query query2 = s.getNamedQuery("testQuery"); + query2.setCacheable(true); + query2.setCacheRegion("myTestQuery"); + query2.setParameter("name", "data"); + item = query2.uniqueResult(); + s.delete(item); + s.getTransaction().commit(); + s.close(); + + Assert.assertEquals(1, stats.getDomainDataRegionStatistics("myTestQuery").getHitCount()); + + stats.logSummary(); + } + +} diff --git a/redisson-hibernate/redisson-hibernate-6/src/test/java/org/redisson/hibernate/TransactionalTest.java b/redisson-hibernate/redisson-hibernate-6/src/test/java/org/redisson/hibernate/TransactionalTest.java new file mode 100644 index 000000000..b08cbf85d --- /dev/null +++ b/redisson-hibernate/redisson-hibernate-6/src/test/java/org/redisson/hibernate/TransactionalTest.java @@ -0,0 +1,180 @@ +package org.redisson.hibernate; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; + +import org.hibernate.Session; +import org.hibernate.cfg.Configuration; +import org.hibernate.cfg.Environment; +import org.hibernate.query.Query; +import org.hibernate.stat.Statistics; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +/** + * + * @author Nikita Koksharov + * + */ +public class TransactionalTest extends BaseCoreFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { ItemTransactional.class}; + } + + @Override + protected void configure(Configuration cfg) { + super.configure(cfg); + cfg.setProperty(Environment.DRIVER, org.h2.Driver.class.getName()); + cfg.setProperty(Environment.URL, "jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1;"); + cfg.setProperty(Environment.USER, "sa"); + cfg.setProperty(Environment.PASS, ""); + cfg.setProperty(Environment.CACHE_REGION_PREFIX, ""); + cfg.setProperty(Environment.GENERATE_STATISTICS, "true"); + + cfg.setProperty(Environment.USE_SECOND_LEVEL_CACHE, "true"); + cfg.setProperty(Environment.USE_QUERY_CACHE, "true"); + cfg.setProperty(Environment.CACHE_REGION_FACTORY, RedissonRegionFactory.class.getName()); + } + + @Before + public void before() { + sessionFactory().getCache().evictAllRegions(); + sessionFactory().getStatistics().clear(); + } + + @Test + public void testQuery() { + Statistics stats = sessionFactory().getStatistics(); + + Session s = openSession(); + s.beginTransaction(); + ItemTransactional item = new ItemTransactional("data"); + item.getEntries().addAll(Arrays.asList("a", "b", "c")); + s.save(item); + s.flush(); + s.getTransaction().commit(); + + s = openSession(); + s.beginTransaction(); + Query query = s.getNamedQuery("testQuery"); + query.setCacheable(true); + query.setCacheRegion("myTestQuery"); + query.setParameter("name", "data"); + item = (ItemTransactional) query.uniqueResult(); + s.getTransaction().commit(); + s.close(); + + Assert.assertEquals(1, stats.getDomainDataRegionStatistics("myTestQuery").getPutCount()); + + s = openSession(); + s.beginTransaction(); + Query query2 = s.getNamedQuery("testQuery"); + query2.setCacheable(true); + query2.setCacheRegion("myTestQuery"); + query2.setParameter("name", "data"); + item = (ItemTransactional) query2.uniqueResult(); + s.delete(item); + s.getTransaction().commit(); + s.close(); + + Assert.assertEquals(1, stats.getDomainDataRegionStatistics("myTestQuery").getHitCount()); + + stats.logSummary(); + + } + + @Test + public void testCollection() { + Long id = null; + + Statistics stats = sessionFactory().getStatistics(); + Session s = openSession(); + s.beginTransaction(); + ItemTransactional item = new ItemTransactional("data"); + item.getEntries().addAll(Arrays.asList("a", "b", "c")); + id = (Long) s.save(item); + s.flush(); + s.getTransaction().commit(); + + s = openSession(); + s.beginTransaction(); + item = (ItemTransactional) s.get(ItemTransactional.class, id); + assertThat(item.getEntries()).containsExactly("a", "b", "c"); + s.getTransaction().commit(); + s.close(); + + Assert.assertEquals(1, stats.getDomainDataRegionStatistics("item_entries").getPutCount()); + + s = openSession(); + s.beginTransaction(); + item = (ItemTransactional) s.get(ItemTransactional.class, id); + assertThat(item.getEntries()).containsExactly("a", "b", "c"); + s.delete(item); + s.getTransaction().commit(); + s.close(); + + Assert.assertEquals(1, stats.getDomainDataRegionStatistics("item_entries").getHitCount()); + } + + @Test + public void testNaturalId() { + Statistics stats = sessionFactory().getStatistics(); + Session s = openSession(); + s.beginTransaction(); + ItemTransactional item = new ItemTransactional("data"); + item.setNid("123"); + s.save(item); + s.flush(); + s.getTransaction().commit(); + + Assert.assertEquals(1, stats.getDomainDataRegionStatistics("item").getPutCount()); + Assert.assertEquals(1, stats.getNaturalIdStatistics(ItemTransactional.class.getName()).getCachePutCount()); + + s = openSession(); + s.beginTransaction(); + item = (ItemTransactional) s.bySimpleNaturalId(ItemTransactional.class).load("123"); + assertThat(item).isNotNull(); + s.delete(item); + s.getTransaction().commit(); + s.close(); + + Assert.assertEquals(1, stats.getDomainDataRegionStatistics("item").getHitCount()); + Assert.assertEquals(1, stats.getNaturalIdStatistics(ItemTransactional.class.getName()).getCacheHitCount()); + + sessionFactory().getStatistics().logSummary(); + } + + @Test + public void testUpdateWithRefreshThenRollback() { + Statistics stats = sessionFactory().getStatistics(); + Long id = null; + Session s = openSession(); + s.beginTransaction(); + ItemTransactional item = new ItemTransactional( "data" ); + id = (Long) s.save( item ); + s.flush(); + s.getTransaction().commit(); + + Assert.assertEquals(1, stats.getDomainDataRegionStatistics("item").getPutCount()); + + s = openSession(); + s.beginTransaction(); + item = (ItemTransactional) s.get(ItemTransactional.class, id); + item.setName("newdata"); + s.update(item); + s.flush(); + s.refresh(item); + s.getTransaction().rollback(); + s.clear(); + s.close(); + + Assert.assertEquals(1, stats.getDomainDataRegionStatistics("item").getHitCount()); + } + + +} diff --git a/redisson-hibernate/redisson-hibernate-6/src/test/resources/hibernate.properties b/redisson-hibernate/redisson-hibernate-6/src/test/resources/hibernate.properties new file mode 100644 index 000000000..a2a990282 --- /dev/null +++ b/redisson-hibernate/redisson-hibernate-6/src/test/resources/hibernate.properties @@ -0,0 +1,4 @@ +hibernate.connection.driver_class=org.h2.Driver +hibernate.connection.url=jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1 +hibernate.connection.username=sa +hibernate.connection.password= \ No newline at end of file diff --git a/redisson-hibernate/redisson-hibernate-6/src/test/resources/redisson.yaml b/redisson-hibernate/redisson-hibernate-6/src/test/resources/redisson.yaml new file mode 100644 index 000000000..9fd9bb723 --- /dev/null +++ b/redisson-hibernate/redisson-hibernate-6/src/test/resources/redisson.yaml @@ -0,0 +1,2 @@ +singleServerConfig: + address: "redis://127.0.0.1:6379" \ No newline at end of file