Sebastiandg7
Sebastiandg7

Reputation: 1324

JHipster - Problem with Hibernate 2nd cache / ehcache trying to eject JHipster from a JHipster generated project

I'm trying to create a generic spring boot backend template based on a JHipster generated one (because some apps are not going to be maintained by me and other spring devs may have to adapt to jhipster in order to make changes the jhipster way). That said, the process I followed to achieve this was: generate two jhipster projects with the following settings:

{
    "generator-jhipster": {
        "promptValues": {
            "packageName": "com.mypackage.springtemplate",
            "nativeLanguage": "en"
        },
        "jhipsterVersion": "5.7.2",
        "applicationType": "monolith",
        "baseName": "springtemplate",
        "packageName": "com.mypackage.springtemplate",
        "packageFolder": "com/mypackage/springtemplate",
        "serverPort": "8080",
        "authenticationType": "jwt",
        "cacheProvider": "ehcache",
        "enableHibernateCache": true,
        "websocket": "spring-websocket",
        "databaseType": "sql",
        "devDatabaseType": "mysql",
        "prodDatabaseType": "mysql",
        "searchEngine": false,
        "messageBroker": false,
        "serviceDiscoveryType": false,
        "buildTool": "maven",
        "enableSwaggerCodegen": false,
        "jwtSecretKey": "****",
        "clientFramework": "angularX",
        "useSass": true,
        "clientPackageManager": "npm",
        "testFrameworks": [],
        "jhiPrefix": "jhi",
        "otherModules": [],
        "enableTranslation": true,
        "nativeLanguage": "en",
        "languages": ["en", "es"]
    }
}

One of them was for having it as a reference. The second was the project to take jhipster stuff out. The steps I followed were:

  1. Removed jhipster BOM and included the now missing dependencies.
  2. Copied locally the used JHipster framework classes (mostly for config and utils).
  3. Renamed "JHipster" mentions to "Application".
  4. Changed application-*.yml hispter framework references to local ones.

These are the primary structure changes the project had: Primary changes

The thing is when I try to run the project I get the following error with Hibernate 2nd level cache / ehcache:

Caused by: java.lang.IllegalStateException: All Hibernate caches should be created upfront. Please update CacheConfiguration.java to add com.mypackage.springtemplate.domain.User
    at com.mypackage.springtemplate.config.cache.NoDefaultJCacheRegionFactory.createCache(NoDefaultJCacheRegionFactory.java:24)
    at org.hibernate.cache.jcache.JCacheRegionFactory.getOrCreateCache(JCacheRegionFactory.java:190)
    at org.hibernate.cache.jcache.JCacheRegionFactory.buildEntityRegion(JCacheRegionFactory.java:113)
    at org.hibernate.cache.spi.RegionFactory.buildEntityRegion(RegionFactory.java:132)

The application-dev.yml hibernate config is pointing to the new local classes:

jpa:
    database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
    database: MYSQL
    show-sql: true
    properties:
        hibernate.id.new_generator_mappings: true
        hibernate.connection.provider_disables_autocommit: true
        hibernate.cache.use_second_level_cache: true
        hibernate.cache.use_query_cache: false
        hibernate.generate_statistics: true
        hibernate.cache.region.factory_class: com.mypackage.springtemplate.config.cache.BeanClassLoaderAwareJCacheRegionFactory

Cache associated classes remain the same...

BeanClassLoaderAwareJCacheRegionFactory.java

import java.net.URI;
import java.net.URISyntaxException;
import java.util.Objects;
import java.util.Properties;

import javax.cache.CacheException;
import javax.cache.CacheManager;
import javax.cache.spi.CachingProvider;

/**
 * Fixes Spring classloader issues that were introduced in Spring Boot 2.0.3.
 *
 * This allows to use the same classloader for ehcache, both for the Spring Cache abstraction and for the Hibernate
 * 2nd level cache.
 *
 * See https://github.com/jhipster/generator-jhipster/issues/7783 for more information.
 */
public class BeanClassLoaderAwareJCacheRegionFactory extends NoDefaultJCacheRegionFactory {

    private static volatile ClassLoader classLoader;

    @Override
    protected CacheManager getCacheManager(Properties properties) {
        Objects.requireNonNull(classLoader, "Please set Spring's classloader in the setBeanClassLoader " +
            "method before using this class in Hibernate");
        CachingProvider cachingProvider = getCachingProvider( properties );
        String cacheManagerUri = getProp( properties, CONFIG_URI );

        URI uri = getUri(cachingProvider, cacheManagerUri);
        CacheManager cacheManager = cachingProvider.getCacheManager(uri, classLoader);

        // To prevent some class loader memory leak this might cause
        setBeanClassLoader(null);

        return cacheManager;
    }

    private URI getUri(CachingProvider cachingProvider, String cacheManagerUri) {
        URI uri;
        if (cacheManagerUri != null) {
            try {
                uri = new URI(cacheManagerUri);
            }
            catch (URISyntaxException e) {
                throw new CacheException("Couldn't create URI from " + cacheManagerUri, e);
            }
        }
        else {
            uri = cachingProvider.getDefaultURI();
        }
        return uri;
    }

    /**
     * This method must be called from a Spring Bean to get the classloader.
     * For example: BeanClassLoaderAwareJCacheRegionFactory.setBeanClassLoader(this.getClass().getClassLoader());
     *
     * @param classLoader The Spring classloader
     */
    public static void setBeanClassLoader(ClassLoader classLoader) {
        BeanClassLoaderAwareJCacheRegionFactory.classLoader = classLoader;
    }
} 

NoDefaultJCacheRegionFactory.java

import java.util.Properties;
import javax.cache.Cache;

import org.hibernate.cache.jcache.JCacheRegionFactory;
import org.hibernate.cache.spi.CacheDataDescription;

/**
 * Extends the default {@code JCacheRegionFactory} but makes sure all caches already exist to prevent
 * spontaneous creation of badly configured caches (e.g. {@code new MutableConfiguration()}.
 *
 * See http://www.ehcache.org/blog/2017/03/15/spontaneous-cache-creation.html for more information.
 */
@SuppressWarnings("serial")
public class NoDefaultJCacheRegionFactory extends JCacheRegionFactory {

    public static final String EXCEPTION_MESSAGE = "All Hibernate caches should be created upfront. " +
        "Please update CacheConfiguration.java to add";

    @Override
    protected Cache<Object, Object> createCache(String regionName, Properties properties, CacheDataDescription
        metadata) {
        throw new IllegalStateException(EXCEPTION_MESSAGE + " " + regionName);
    }
}

CacheConfiguration.java

import java.time.Duration;

import org.ehcache.config.builders.CacheConfigurationBuilder;
import org.ehcache.config.builders.ExpiryPolicyBuilder;
import org.ehcache.config.builders.ResourcePoolsBuilder;
import org.ehcache.jsr107.Eh107Configuration;
import org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.mypackage.springtemplate.config.properties.ApplicationProperties;

@Configuration
@EnableCaching
public class CacheConfiguration {

    private final javax.cache.configuration.Configuration<Object, Object> jcacheConfiguration;

    public CacheConfiguration(ApplicationProperties applicationProperties) {
        BeanClassLoaderAwareJCacheRegionFactory.setBeanClassLoader(this.getClass().getClassLoader());
        ApplicationProperties.Cache.Ehcache ehcache =
            applicationProperties.getCache().getEhcache();

        jcacheConfiguration = Eh107Configuration.fromEhcacheCacheConfiguration(
            CacheConfigurationBuilder.newCacheConfigurationBuilder(Object.class, Object.class,
                ResourcePoolsBuilder.heap(ehcache.getMaxEntries()))
                .withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofSeconds(ehcache.getTimeToLiveSeconds())))
                .build());
    }

    @Bean
    public JCacheManagerCustomizer cacheManagerCustomizer() {
        return cm -> {
            cm.createCache(com.mypackage.springtemplate.repository.UserRepository.USERS_BY_LOGIN_CACHE, jcacheConfiguration);
            cm.createCache(com.mypackage.springtemplate.repository.UserRepository.USERS_BY_EMAIL_CACHE, jcacheConfiguration);
            cm.createCache(com.mypackage.springtemplate.domain.User.class.getName(), jcacheConfiguration);
            cm.createCache(com.mypackage.springtemplate.domain.Authority.class.getName(), jcacheConfiguration);
            cm.createCache(com.mypackage.springtemplate.domain.User.class.getName() + ".authorities", jcacheConfiguration);
        };
    }
}

Already checked this question but dave's purpose is different from mine. In fact, I'm not doing any changes to jhipster's generated caching implementation.

Is there any thing I'm missing? Is liquibase maybe doing something I missed?

Upvotes: 1

Views: 2690

Answers (1)

Henri
Henri

Reputation: 5731

It is also possible that CacheConfiguration.cacheManagerCustomizer isn't called at all. This will mean you have broken the cache configuration and are probably using no cache or a simple cache, not Ehcache. Please check that javax.cache is correctly in your classpath.

The other possibility is that you are not using the same CacheManage for Hibernate and Spring. Please debug into EhcacheCachingProvider and check the only one CacheManager is created. If that's not the case, you need to look at what's different. It is usually the class loader but the BeanClassLoaderAwareJCacheRegionFactory should normally prevent that.

Upvotes: 1

Related Questions