Dima Shtef
Dima Shtef

Reputation: 21

Hibernate second level entity cache invalidation in multitenant environment with Infinispan

I'm trying to implement hibernate 2lvl entity cache invalidation. As second level cache provider I use Infinispan. Entities are configured in persistence.xml file:

<persistence-unit name="default" transaction-type="JTA">
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
        <jta-data-source>java:jboss/datasources/MSSQLDS</jta-data-source>
        <properties>
            <property name="jboss.as.jpa.providerModule" value="org.hibernate:5.3" />
            <property name="jboss.entity.manager.factory.jndi.name" value="java:jboss/EntityManagerFactory"/>
            <property name="jboss.entity.manager.jndi.name" value="java:jboss/EntityManager"/>
            <property name="hibernate.multiTenancy" value="DATABASE"/>
            <property name="hibernate.multi_tenant_connection_provider" value="com.hibernate.DatabaseMultiTenantProviderImpl" />
            <property name="hibernate.tenant_identifier_resolver" value="com.hibernate.DatabaseTenantResolverImpl" />
            <property name="hibernate.dialect" value="org.hibernate.dialect.SQLServer2012Dialect"/>
            <property name="hibernate.show_sql" value="false" />
            <property name="hibernate.format_sql" value="true" />

            <property name="hibernate.use_sql_comments" value="false" />
            <property name="hibernate.jpa.compliance.global_id_generators" value="false" />
            
            <!--  l2 cache configuration -->
            <property name="hibernate.cache.use_query_cache" value="true"/>
            <property name="hibernate.cache.use_second_level_cache" value="true"/>
            <!-- For testing purposes only -->
            <property name="hibernate.generate_statistics" value="false" />
            <!-- Use Infinispan second level cache provider -->
            <property name="hibernate.cache.region.factory_class" value="infinispan"/>
            <property name="hibernate.transaction.factory_class" value="org.hibernate.transaction.CMTTransactionFactory"/>
            <property name="hibernate.transaction.manager_lookup_class" value="org.hibernate.transaction.JBossTransactionManagerLookup"/>
            <!--
               Force using local configuration when only using a single node.
               Otherwise a clustered configuration is loaded.
            -->
            <property name="hibernate.cache.infinispan.cfg" value="org/infinispan/hibernate/cache/commons/builder/infinispan-configs-local.xml"/>
            

            <!-- added specific configuration for each cached entity -->
            <property name="hibernate.cache.infinispan.destiny.ear/entity-ejb3.jar#default.com.entity.ejb3.ConfigSite.cfg" value="small-cache" />
            <property name="hibernate.cache.infinispan.destiny.ear/entity-ejb3.jar#default.com.entity.ejb3.CircItem.cfg" value="small-cache" />
        </properties>
    </persistence-unit>

There is a cache container, configured in standalone.xml(WildFly):

<cache-container name="hibernate" marshaller="JBOSS" modules="org.infinispan.hibernate-cache" default-cache="entity">
                <local-cache name="entity">
                    <transaction mode="NONE"/>
                    <expiration max-idle="120000"/>
                </local-cache>
                <local-cache name="small-cache">
                    <transaction mode="NONE"/>
                    <expiration lifespan="${CACHE_MAX_AGE_MILLIS}"/>
                    <heap-memory size="${SMALL_ENTITY_MAX_CAPACITY}" size-unit="ENTRIES"/>
                </local-cache>
                <local-cache name="medium-cache">
                    <transaction mode="NONE"/>
                    <expiration lifespan="${CACHE_MAX_AGE_MILLIS}"/>
                    <heap-memory size="${MEDIUM_ENTITY_MAX_CAPACITY}" size-unit="ENTRIES"/>
                </local-cache>
                <local-cache name="large-cache">
                    <transaction mode="NONE"/>
                    <expiration lifespan="${CACHE_MAX_AGE_MILLIS}"/>
                    <heap-memory size="${LARGE_ENTITY_MAX_CAPACITY}" size-unit="ENTRIES"/>
                </local-cache>
                <local-cache name="immutable-entity">
                    <transaction mode="NONE"/>
                    <expiration max-idle="120000"/>
                </local-cache>
                <local-cache name="local-query">
                    <expiration max-idle="300000"/>
                </local-cache>
                <local-cache name="timestamps">
                    <transaction mode="NONE"/>
                </local-cache>
                <local-cache name="pending-puts">
                    <transaction mode="NONE"/>
                    <expiration max-idle="60000"/>
                </local-cache>
            </cache-container>

Entity itself:

@Entity
@Cache(usage=CacheConcurrencyStrategy.TRANSACTIONAL)
@GenericGenerator(name = HibernateSequenceCache.NAME,
strategy = HibernateSequenceCache.STRATEGY,
parameters = { @Parameter(name=HibernateSequenceCache.SEQUENCE, value=DbSequenceSpecs.SEQUENCE_SITEID) }
    )
public class ConfigSite implements Serializable {
    private static final long serialVersionUID = 1l;
    @Id
    @GeneratedValue(generator = HibernateSequenceCache.NAME)
    private Long siteID;
    private String siteName;
}

pom.xml:

    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-core</artifactId>
      <version>5.3.28.Final</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.infinispan</groupId>
      <artifactId>infinispan-core</artifactId>
      <version>13.0.10.Final</version>
      <scope>provided</scope>
    </dependency>

I've tried to invalidate with Hibernate itself:

    @PersistenceUnit(unitName = EJBFactory.PERSISTENCE_UNIT)
    SessionFactory factory;

    public void invalidateEntityCache(String jndiName, Object primaryKey) {
        Cache cache = factory.getCache();
        cache.evictEntityData(jndiName, (Serializable) primaryKey);
    }

As a primary key I pass a Long value of Object id, jndiName is a full class name, like "com.entity.ejb3.ConfigSite". Unfortunatly, invalidation doesn't work (cache regions are created and filled with keys and values). I debugged a lot and observed that: evictEntityData() to create key hibernate uses this approach:

public Object generateCacheKey(
            Object id,
            EntityPersister rootEntityDescriptor,
            SessionFactoryImplementor factory,
            String tenantIdentifier) { some impl }

final Object key = cacheAccess.generateCacheKey( identifier, entityDescriptor, sessionFactory, null );

i don't know why but it passes tenantId as null and eviction fails On the other hand during session.find(Class entityClass, Object primaryKey) during second lvl cache check

entity = loadFromSecondLevelCache( event, persister, keyToLoad ); --->
final Object ce = getFromSharedCache( event, persister, source );

it uses another approach and put tenantId in key and needed entity can be found:

final Object ck = cache.generateCacheKey(
                event.getEntityId(),
                persister,
                factory,
                source.getTenantIdentifier()
        );

Do you guys have any ideas how this problem can be solved? (looks like tenantId is missed) May be I need extend hibernate classes and override some hibernate methods? or upgrade hibernate version where this problem is solved. Any suggestions are welcome and I will check them.

Thanks in advance!

UPDATE I achieved cache invalidation through InfinispanCacheManager:

public void invalidateEntityCache(String jndiName, Object primaryKey) {
    SessionFactoryImplementor factoryImpl = (SessionFactoryImplementor) factory;
    String tenantIdentifier = UserContext.getMyContextName();
    EntityPersister persister = factoryImpl.getMetamodel().entityPersister(jndiName);
    EmbeddedCacheManager cacheManager = InfinispanCacheManagerProvider.getCacheManager();
    jndiName = CACHE_PREFIX + jndiName;
    if (cacheManager.cacheExists(jndiName)) {
        Cache<Object,Object> infinispanCache = cacheManager.getCache(jndiName);
        Object entityKey = DefaultCacheKeysFactory.staticCreateEntityKey(primaryKey, persister, factoryImpl, tenantIdentifier);
        infinispanCache.remove(entityKey);

    }
}

But still expect to do it with hibernate

Upvotes: 1

Views: 194

Answers (0)

Related Questions