Creating a CacheManager from an XMLConfiguration using a StatisticsService - ehcache

I know how to create a CacheManager using an XMLConfiguration in org.ehcache:ehcache:3.8.1:
import org.ehcache.config.Configuration;
import org.ehcache.xml.XmlConfiguration;
import org.ehcache.config.builders.CacheManagerBuilder;
.
.
.
URL myUrl = CacheUtil.class.getResource("/my-config.xml");
Configuration xmlConfig = new XmlConfiguration(myUrl);
cacheManager = CacheManagerBuilder.newCacheManager(xmlConfig);
cacheManager.init();
I also know how to create a CacheManager with a StatisticsService:
StatisticsService statisticsService = new DefaultStatisticsService();
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
.using(statisticsService)
.build();
cacheManager.init();
But how do I create a CacheManager from an XMLConfiguration using a StatisticsService?

Here is a sample code that demonstrates the usage of basic JCache configuration APIs:
CachingProvider provider = Caching.getCachingProvider();
CacheManager cacheManager = provider.getCacheManager();
MutableConfiguration<Long, String> configuration =
new MutableConfiguration<Long, String>()
.setTypes(Long.class, String.class)
.setStoreByValue(false)
.setExpiryPolicyFactory(CreatedExpiryPolicy.factoryOf(Duration.ONE_MINUTE));
Cache<Long, String> cache = cacheManager.createCache("jCache", configuration);
cache.put(1L, "one");
String value = cache.get(1L);
Retrieves the default CachingProvider implementation from the application’s classpath. This method will work if and only if there is only one JCache implementation jar in the classpath. If there are multiple providers in your classpath then use the fully qualified name org.ehcache.jsr107.EhcacheCachingProvider to retrieve the Ehcache caching provider. You can do this by using the Caching.getCachingProvider(String) static method instead.
Retrieve the default CacheManager instance using the provider.
Create a cache configuration using MutableConfiguration
with key type and value type as Long and String respectively…​
configured to store the cache entries by reference(not by value)…​
and with an expiry time of one minute defined for entries from the moment, they are created.
Using the cache manager, create a cache named jCache with the configuration created in step <3>
Put some data into the cache.
Retrieve the data from the same cache.

There is a constructor inside the class EhcacheManager that takes 2 arguments:
public EhcacheManager(Configuration config, Collection<Service> services)
You could use it as follows:
URL myUrl = CacheUtil.class.getResource("/my-config.xml");
Configuration xmlConfig = new XmlConfiguration(myUrl);
StatisticsService statisticsService = new DefaultStatisticsService();
Set<Service> services = new HashSet<>();
services.add(statisticsService);
cacheManager = new EhcacheManager(xmlConfig, services);

Related

Leverage Spring boot Redis Auto configure logic for RedisConnectionFactory

Spring boot auto configures RedisConnectionFactory if spring-data-redis exists on classpath and RedisConnectionFactory is initialized in LettuceConnectionConfiguration if Lettuce-core available on classpath.
I've only one Redis store as of now, so leveraging Spring boot auto configuration.
Now I'm adding two redis stores, one redis store used as default and other is used when specified with parameter cacheManager = "secondayCacheManager" in #Cacheable annotation so, application should've capability to cache/cache-get on both redis stores.
To configure both Redis Stores, we've to configure both the primary and secondary RedisConnectionFactory and cacheManager using custom configuration. (because spring doesn't auto configure RedisConnectionFactory if it already exists in any custom configuration)
Now the above is custom configuration and missing lot of logic that is happening while configuring RedisConnectionFactory in LettuceConnectionConfiguration.
Auto configure logic for LettuceConnectionConfiguration is package private so, cannot be called directly from custom configuration.
We would like to leverage the auto configure logic in
LettuceConnectionConfiguration while configuring the custom
RedisConnectionFactory for both primary and secondary redis caches.
Is there a way to achieve this?
Reason being we would like keep the redis connection configurations as it is done by spring boot auto configure.
Currently using below code to configure both the primary and secondary RedisConnectionFactory with Pool configuration and some code copy pasted from LettuceConnectionConfiguration class.
public static LettuceConnectionFactory buildLettuceConnectionFactory(RedisProperties properties, ClientResources clientResources) {
RedisStandaloneConfiguration standaloneConfiguration = new RedisStandaloneConfiguration(properties.getHost(), properties.getPort());
standaloneConfiguration.setDatabase(properties.getDatabase());
if (properties.getPassword() != null) {
standaloneConfiguration.setPassword(RedisPassword.of(properties.getPassword()));
}
if (properties.getUsername() != null) {
standaloneConfiguration.setUsername(properties.getUsername());
}
LettucePoolingClientConfiguration poolingClientConfiguration = LettucePoolingClientConfiguration.builder()
.poolConfig(buildGenericObjectPoolConfig(properties))
.shutdownTimeout(properties.getLettuce().getShutdownTimeout())
.clientOptions(createClientOptions(properties))
.clientResources(clientResources)
.build();
LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(
standaloneConfiguration, poolingClientConfiguration);
lettuceConnectionFactory.afterPropertiesSet();
return lettuceConnectionFactory;
}
private static GenericObjectPoolConfig buildGenericObjectPoolConfig(RedisProperties properties) {
RedisProperties.Pool pool = properties.getLettuce().getPool();
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
if (Objects.nonNull(pool)) {
poolConfig.setMaxIdle(pool.getMaxIdle());
poolConfig.setMinIdle(pool.getMinIdle());
poolConfig.setMaxTotal(pool.getMaxActive());
poolConfig.setMaxWaitMillis(pool.getMaxWait().toMillis());
}
return poolConfig;
}
private static ClientOptions createClientOptions(RedisProperties properties) {
ClientOptions.Builder builder = initializeClientOptionsBuilder(properties);
Duration connectTimeout = properties.getConnectTimeout();
if (connectTimeout != null) {
builder.socketOptions(SocketOptions.builder().connectTimeout(connectTimeout).build());
}
return builder.timeoutOptions(TimeoutOptions.enabled()).build();
}
private static ClientOptions.Builder initializeClientOptionsBuilder(RedisProperties properties) {
if (properties.getCluster() != null) {
ClusterClientOptions.Builder builder = ClusterClientOptions.builder();
Refresh refreshProperties = properties.getLettuce().getCluster().getRefresh();
Builder refreshBuilder = ClusterTopologyRefreshOptions.builder()
.dynamicRefreshSources(refreshProperties.isDynamicRefreshSources());
if (refreshProperties.getPeriod() != null) {
refreshBuilder.enablePeriodicRefresh(refreshProperties.getPeriod());
}
if (refreshProperties.isAdaptive()) {
refreshBuilder.enableAllAdaptiveRefreshTriggers();
}
return builder.topologyRefreshOptions(refreshBuilder.build());
}
return ClientOptions.builder();
}

How to get all data from Java Spring Cache

i need toknow how to retrieve or where to see al data stored in my cache.
#Configuration
#EnableCaching
public class CachingConf {
#Bean
public CacheManager cacheManager() {
Caffeine<Object, Object> cacheBuilder = Caffeine.newBuilder()
.expireAfterWrite(10, TimeUnit.SECONDS)
.maximumSize(1000);
CaffeineCacheManager cacheManager = new CaffeineCacheManager("hr");
cacheManager.setCaffeine(cacheBuilder);
return cacheManager;
}
}
private final CacheManager cacheManager;
public CacheFilter(CacheManager cacheManager) {
this.cacheManager = cacheManager;
}
#Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
final var cache = cacheManager.getCache("hr");
......
I want to somehow see all data in my cache stored but the cache does not have get all or something like tht.Any advices guys?
The spring cache abstraction does not provide a method to get all the entries in a cache. But luckily they provide a method to get the underlying native cache abstraction which is Caffeine cache in your case.
The Caffeine cache has a method called asMap() to return a map view containing all the entries stored in the cache.
So combining them together will give you the following :
var cache = cacheManager.getCache("hr");
com.github.benmanes.caffeine.cache.Cache<Object, Object> nativeCache = (com.github.benmanes.caffeine.cache.Cache<Object, Object>)cache.getNativeCache();
ConcurrentMap<K, V> map = nativeCache.asMap();
//Loop through the map here to access all the entries in the cache
Please note that it is a quick and effective fix but it will make your codes couple to Caffeine . If you mind , you can configure the spring cache to use JCache and configure JCache to use Caffeine cache (see this) . As JCache API implements Iterable<Cache.Entry<K, V>>, it allow you to iterate all of its entries :
var cache = cacheManager.getCache("hr");
javax.cache<Object, Object> nativeCache = (javax.cache<Object, Object>)cache.getNativeCache();
for(Cache.Entry<Object,Object> entry : nativeCache){
//access the entries here.
}

Configure mapper-locations in spring-mybatis with classpath:*

So I want this to work
#Bean
#ConfigurationProperties("datasource.mybatis-factory")
public SqlSessionFactoryBean sqlSessionFactoryBean() {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource());
return sqlSessionFactoryBean;
}
with property (among others)
datasource.mybatis-factory.mapper-locations=classpath*:sqlmap/*.xml
However, it fails even though the files are there:
Caused by: java.io.FileNotFoundException: class path resource [classpath*:sqlmap/*.xml] cannot be opened because it does not exist
Looking at setMapperLocations() I didn't do anything wrong, they clearly want me to use classpath*:...:
/**
* Set locations of MyBatis mapper files that are going to be merged into the {#code SqlSessionFactory} configuration
* at runtime.
*
* This is an alternative to specifying "<sqlmapper>" entries in an MyBatis config file. This property being
* based on Spring's resource abstraction also allows for specifying resource patterns here: e.g.
* "classpath*:sqlmap/*-mapper.xml".
*
* #param mapperLocations
* location of MyBatis mapper files
*/
public void setMapperLocations(Resource... mapperLocations) {
this.mapperLocations = mapperLocations;
}
Looking further down the code there's just this:
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
xmlMapperBuilder.parse();
There is no code that would convert the classpath*:sqlmap/*.xml into openable resources or at least I don't see it. Or what am I missing here?
Work around:
What I have now and is working (note that I don't use datasource.mybatis-factory.mapper-locations as that would again overwrite what I set):
#Bean
#ConfigurationProperties("datasource.mybatis-factory")
public SqlSessionFactoryBean sqlSessionFactoryBean(
#Value("${datasource.mybatis-factory.mapper-location-pattern}") String mapperLocations) {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource());
sqlSessionFactoryBean.setMapperLocations(findMapperLocations(mapperLocations));
return sqlSessionFactoryBean;
}
private Resource[] findMapperLocations(String resourcePaths) {
PathMatchingResourcePatternResolver patternResolver = new PathMatchingResourcePatternResolver();
return Stream.of(resourcePaths.split(","))
.map(LambdaExceptionUtilities.rethrowFunction(patternResolver::getResources))
.flatMap(Stream::of)
.toArray(Resource[]::new);
}
with property
datasource.mybatis-factory.mapper-location-pattern=classpath*:sqlmap/*.xml
So: what is missing here to make it work without the work around? How do XMLs on the classpath find the way into MyBatis? Maybe something Spring-Bootish missing?
I ran into the same issue recently. I believe this is what what you're looking for:
#Bean
#ConfigurationProperties("datasource.mybatis-factory")
public SqlSessionFactoryBean sqlSessionFactoryBean(
#Value("${datasource.mybatis-factory.mapper-location-pattern}") String mapperLocations) {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource());
sqlSessionFactoryBean.setMapperLocations(
new PathMatchingResourcePatternResolver().getResources("classpath*:sqlmap/*.xml")
);
return sqlSessionFactoryBean;
}
Basically what you need is this line of code in your #Bean definition above:
sqlSessionFactoryBean.setMapperLocations( new PathMatchingResourcePatternResolver().getResources("classpath*:sqlmap/*.xml")
  );
Note: the method name is getResources (the plural) and not getResource
Feel free to replace the hard coded value of classpath*:sqlmap/*.xml with the #Value("datasource.mybatis-factory.mapper-location-pattern") injected value instead.
Because you're using MyBatis with Spring, the issue here is not so much a MyBatis issue, as much as it is a Spring issue. More specifically, the wildcard feature that you want to use to load multiple resources, namely, classpath*:sqlmap/*.xml is specific to Spring and not MyBatis.
I know, that the way it's documented in the MyBatis-Spring docs may lead you to believe that it's a MyBatis feature that let's you do this type of wildcard Resource loading, but it's not. Here's the relevant part of the MyBatis-Spring doc (source: https://mybatis.org/spring/factorybean.html#properties):
The mapperLocations property takes a list of resource locations. This property can be used to specify the location of MyBatis XML mapper files. The value can contain Ant-style patterns to load all files in a directory or to recursively search all paths from a base location.
However, sadly the docs only provide a Spring example based on XML and not Java configuration. If you read the Java Docs docs for SqlSessionFactoryBean, you'll find the following (source: https://mybatis.org/spring/apidocs/org/mybatis/spring/SqlSessionFactoryBean.html#setMapperLocations(org.springframework.core.io.Resource...)):
public void setMapperLocations(org.springframework.core.io.Resource... mapperLocations)
Set locations of MyBatis mapper files that are going to be merged into the
SqlSessionFactory configuration at runtime.
This is an alternative to specifying "<sqlmapper>" entries in an MyBatis config file.
This property being based on Spring's resource abstraction also allows for
specifying resource patterns here: e.g. "classpath*:sqlmap/*-mapper.xml".
Parameters:
mapperLocations - location of MyBatis mapper files
So, the setMapperLocations method needs one or more org.springframework.core.io.Resource object(s). So, using Spring ClassPathResource will not work here because ClassPathResource expects only a single resource. What you need to use instead is Spring's PathMatchingResourcePatternResolver class. See: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/io/support/PathMatchingResourcePatternResolver.html
You may also find this Stack Overflow post useful: How to use wildcards when searching for resources in Java-based Spring configuration?
I hope this helps!
your property should be like this.
if you use default configuration,
mybatis.mapper-locations: classpath*:sqlmap/**/*.xml
if you use your own as you mention above,
datasource.mybatis-factory.mapper-locations= classpath*:sqlmap/**/*.xml
this is a working example code, you can get an idea from this.
#Configuration
#MapperScans(
{
#MapperScan(
basePackages = {"com.example.seeker.repository"},
sqlSessionFactoryRef = "sqlSessionFactorySeeker",
sqlSessionTemplateRef = "sqlSessionTemplateSeeker"
)
}
)
public class SeekerDataSourceConfig {
#Autowired
Environment environment;
#Bean(value = "dsSeeker")
#ConfigurationProperties(prefix = "spring.datasource.seeker")
DataSource dsSeeker() {
return DataSourceBuilder.create().build();
}
#Qualifier("dsSeeker")
#Autowired
private DataSource dsSeeker;
#Bean(value = "sqlSessionFactorySeeker")
public SqlSessionFactory sqlSessionFactorySeeker() {
SqlSessionFactory sessionFactory = null;
try {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
PathMatchingResourcePatternResolver pathM3R = new PathMatchingResourcePatternResolver();
bean.setMapperLocations(pathM3R.getResources("classpath*:sqlmap/**/*.xml"));
bean.setDataSource(dsSeeker);
sessionFactory = bean.getObject();
} catch (Exception e) {
e.printStackTrace();
}
return sessionFactory;
}
#Bean(value = "sqlSessionTemplateSeeker")
public SqlSessionTemplate sqlSessionTemplateSeeker() {
return new SqlSessionTemplate(sqlSessionFactorySeeker());
}
#Bean(name = StaticResource.TxManager.TX_MANAGER_SEEKER)
public PlatformTransactionManager txManager() {
return new DataSourceTransactionManager(dsSeeker);
}
}
property file
spring.datasource.seeker.jdbc-url=jdbc:mysql://*********
spring.datasource.seeker.username=seeker***
spring.datasource.seeker.password=ewfky4eyrmggxbw6**
spring.datasource.seeker.driver-class-name=com.mysql.cj.jdbc.Driver
if you want to using with yml file you can add in the following path your application.yml file
mybatis:
mapperLocations: classpath:sql/*.xml
config-location: classpath:config/mybatis.xml

Include newly added data sources into route Data Source object without restarting the application server

Implemented Spring's AbstractRoutingDatasource by dynamically determining the actual DataSource based on the current context.
Refered this article : https://www.baeldung.com/spring-abstract-routing-data-source.
Here on spring boot application start up . Created a map of contexts to datasource objects to configure our AbstractRoutingDataSource. All these client context details are fetched from a database table.
#Bean
#DependsOn("dataSource")
#Primary
public DataSource routeDataSource() {
RoutingDataSource routeDataSource = new RoutingDataSource();
DataSource defaultDataSource = (DataSource) applicationContext.getBean("dataSource");
List<EstCredentials> credentials = LocalDataSourcesDetailsLoader.getAllCredentails(defaultDataSource); // fetching from database table
localDataSourceRegistrationBean.registerDataSourceBeans(estCredentials);
routeDataSource.setDefaultTargetDataSource(defaultDataSource);
Map<Object, Object> targetDataSources = new HashMap<>();
for (Credentials credential : credentials) {
targetDataSources.put(credential.getEstCode().toString(),
(DataSource) applicationContext.getBean(credential.getEstCode().toString()));
}
routeDataSource.setTargetDataSources(targetDataSources);
return routeDataSource;
}
The problem is if i add a new client details, I cannot get that in routeDataSource. Obvious reason is that these values are set on start up.
How can I achieve to add new client context and I had to re intialize the routeDataSource object.
Planning to write a service to get all the client context newly added and reset the routeDataSource object, no need to restart the server each time any changes in the client details.
A simple solution to this situation is adding #RefreshScope to the bean definition:
#Bean
#Primary
#RefreshScope
public DataSource routeDataSource() {
RoutingDataSource routeDataSource = new RoutingDataSource();
DataSource defaultDataSource = (DataSource) applicationContext.getBean("dataSource");
List<EstCredentials> credentials = LocalDataSourcesDetailsLoader.getAllCredentails(defaultDataSource); // fetching from database table
localDataSourceRegistrationBean.registerDataSourceBeans(estCredentials);
routeDataSource.setDefaultTargetDataSource(defaultDataSource);
Map<Object, Object> targetDataSources = new HashMap<>();
for (Credentials credential : credentials) {
targetDataSources.put(credential.getEstCode().toString(),
(DataSource) applicationContext.getBean(credential.getEstCode().toString()));
}
routeDataSource.setTargetDataSources(targetDataSources);
return routeDataSource;
}
Add Spring Boot Actuator as a dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
Then trigger the refresh endpoint POST to /actuator/refresh to update the DataSource (actually every refresh scoped bean).
So this will depend on how much you know about the datasources to be added, but you could set this up as a multi-tenant project. Another example of creating new datasources:
#Autowired private Map <String, Datasource> mars2DataSources;
public void addDataSourceAtRuntime() {
DataSourceBuilder dataSourcebuilder = DataSourcebuilder.create(
MultiTenantJPAConfiguration.class.getclassloader())
.driverclassName("org.postgresql.Driver")
.username("postgres")
.password("postgres")
.url("Jdbc: postgresql://localhost:5412/somedb");
mars2DataSources("tenantX", datasourcebuilder.build())
}
Given that you are using Oracle, you could also use its database change notification features.
Think of it as a listener in the JDBC driver that gets notified whenever something changes in your database table. So upon receiving a change, you could reinitialize/add datasources.
You can find a tutorial of how to do this here: https://docs.oracle.com/cd/E11882_01/java.112/e16548/dbchgnf.htm#JJDBC28820
Though, depending on your organization database notifications need some extra firewall settings for the communication to work.
Advantage: You do not need to manually call the REST Endpoint if something changes, (though Marcos Barberios answer is perfectly valid!)

Spring Boot Apache CXF JAX-RS service context path / base URI

I configure my JAXRS Server in Spring Boot like so:
JAXRSServerFactoryBean factoryBean = new JAXRSServerFactoryBean();
factoryBean.setBus(this.bus);
factoryBean.setFeatures(singletonList(swagger2Feature()));
factoryBean.setServiceBeans(Arrays.asList(blah(), blah2(), blah3()));
factoryBean.setAddress("/api/v1/"); // HERE
List<Object> providers = new ArrayList<>();
providers.add(new JacksonJaxbJsonProvider());
factoryBean.setProviders(providers);
BindingFactoryManager manager = factoryBean.getBus().getExtension(BindingFactoryManager.class);
JAXRSBindingFactory restFactory = new JAXRSBindingFactory();
restFactory.setBus(factoryBean.getBus());
manager.registerBindingFactory(JAXRSBindingFactory.JAXRS_BINDING_ID, restFactory);
return factoryBean.create();
However, the URLs always require /services in front, which is a nuisance (but not the end of the world). Is there any way I can remove /services and just get it deployed to the root context?
If you have not created your own CxfServlet bean you can set the path by setting cxf.path property in your application.properties file
cxf.path=/
Another way is to override ServletRegistrationBean.
#Bean
public ServletRegistrationBean cxfServletRegistration() {
String urlMapping = "/*";
ServletRegistrationBean registration = new ServletRegistrationBean(
new CXFServlet(), urlMapping);
registration.setLoadOnStartup(-1);
return registration;
}

Resources