Skip to content

Commit 71a9360

Browse files
committed
Update Hibernate cache configuration and add integration tests for second-level cache
Signed-off-by: aazizali <[email protected]>
1 parent 16252ae commit 71a9360

File tree

11 files changed

+304
-8
lines changed

11 files changed

+304
-8
lines changed

data/synapse-data-postgres/pom.xml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,16 +56,22 @@
5656
<dependency>
5757
<groupId>org.ehcache</groupId>
5858
<artifactId>ehcache</artifactId>
59+
<classifier>jakarta</classifier>
5960
</dependency>
6061
<dependency>
61-
<groupId>org.hibernate</groupId>
62+
<groupId>org.hibernate.orm</groupId>
6263
<artifactId>hibernate-jcache</artifactId>
6364
</dependency>
6465
<!-- persistence -->
6566
<dependency>
6667
<groupId>jakarta.persistence</groupId>
6768
<artifactId>jakarta.persistence-api</artifactId>
6869
</dependency>
70+
<dependency>
71+
<groupId>org.springframework.boot</groupId>
72+
<artifactId>spring-boot-starter-test</artifactId>
73+
<scope>test</scope>
74+
</dependency>
6975
</dependencies>
7076

7177
<build>

data/synapse-data-postgres/src/main/java/io/americanexpress/synapse/data/postgres/config/BasePostgresDataConfig.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
* <code>BasePostgresDataConfig</code> class is used to hold the common configuration for all data-postgres modules.
3131
*
3232
* @author Gabriel Jimenez
33+
* @author Aziz Ali
3334
*/
3435
@Configuration
3536
@EnableTransactionManagement
@@ -62,7 +63,6 @@ public DataSource dataSource() {
6263
HikariDataSource dataSource = DataSourceBuilder.create().type(HikariDataSource.class).build();
6364
dataSource.setSchema(environment.getRequiredProperty("spring.jpa.properties.hibernate.default_schema"));
6465
dataSource.setLeakDetectionThreshold(2000);
65-
dataSource.setDataSourceProperties(additionalHibernateSpringProperties());
6666
return dataSource;
6767
}
6868

@@ -81,7 +81,8 @@ private Properties additionalHibernateSpringProperties() {
8181
properties.setProperty("hibernate.cache.use_query_cache", "true");
8282
properties.setProperty("hibernate.cache.provider_class", "org.ehcache.jsr107.EhcacheCachingProvider");
8383
properties.setProperty("hibernate.cache.region.factory_class", "org.hibernate.cache.jcache.internal.JCacheRegionFactory" );
84-
properties.setProperty("hibernate.javax.cache.uri", environment.getProperty("hibernate.javax.cache.uri", "classpath:ehcache.xml"));
84+
properties.setProperty("hibernate.javax.cache.uri",
85+
environment.getProperty("hibernate.javax.cache.uri", "classpath://ehcache.xml"));
8586
return properties;
8687
}
8788

@@ -95,6 +96,7 @@ public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
9596
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
9697
entityManagerFactoryBean.setDataSource(dataSource());
9798
entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
99+
entityManagerFactoryBean.setJpaProperties(additionalHibernateSpringProperties());
98100
setPackagesToScan(entityManagerFactoryBean);
99101
return entityManagerFactoryBean;
100102
}

data/synapse-data-postgres/src/main/resources/ehcache.xml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,10 @@
99
<service>
1010
<jsr107:defaults default-template="default"/>
1111
</service>
12-
1312
<cache-template name="default">
1413
<expiry>
15-
<ttl unit="seconds">5</ttl>
14+
<ttl unit="minutes">10</ttl>
1615
</expiry>
17-
<heap unit="entries">100</heap>
16+
<heap>1000</heap>
1817
</cache-template>
1918
</config>
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/*
2+
* Copyright 2020 American Express Travel Related Services Company, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
package io.americanexpress.synapse.data.postgres.config;
15+
16+
import io.americanexpress.synapse.data.postgres.entity.Product;
17+
import jakarta.persistence.EntityManager;
18+
import jakarta.persistence.EntityManagerFactory;
19+
import jakarta.persistence.PersistenceUnit;
20+
import org.hibernate.engine.spi.SessionFactoryImplementor;
21+
import org.hibernate.stat.Statistics;
22+
import org.junit.jupiter.api.BeforeEach;
23+
import org.junit.jupiter.api.Test;
24+
import org.springframework.boot.test.context.SpringBootTest;
25+
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
26+
27+
/**
28+
* Integration tests for verifying the second-level cache behavior in the Postgres data module.
29+
* <p>
30+
* Tests include cache put, hit, and eviction scenarios using the {@link Product} entity.
31+
* The tests are run with the {@link DataSampleConfig} configuration and use Hibernate statistics
32+
* to assert cache operations.
33+
* </p>
34+
*
35+
* @author Aziz Ali
36+
*/
37+
@SpringBootTest(classes = {DataSampleConfig.class})
38+
class BasePostgresDataConfigTest {
39+
40+
@PersistenceUnit
41+
private EntityManagerFactory emf;
42+
43+
private Statistics stats;
44+
private Product product1, product2, product3;
45+
46+
@BeforeEach
47+
void setUp() {
48+
SessionFactoryImplementor sfi = emf.unwrap(SessionFactoryImplementor.class);
49+
stats = sfi.getStatistics();
50+
stats.clear();
51+
stats.setStatisticsEnabled(true);
52+
53+
EntityManager em = emf.createEntityManager();
54+
em.getTransaction()
55+
.begin();
56+
product1 = new Product();
57+
product1.setProductName("Cached Product1");
58+
product2 = new Product();
59+
product2.setProductName("Cached Product2");
60+
product3 = new Product();
61+
product3.setProductName("Cached Product3");
62+
em.persist(product1);
63+
em.persist(product2);
64+
em.persist(product3);
65+
em.flush();
66+
em.getTransaction()
67+
.commit();
68+
em.close();
69+
}
70+
71+
@Test
72+
void testSecondLevelCachePutOnFind() {
73+
stats.clear();
74+
findAllProducts();
75+
assertThat(stats.getSecondLevelCachePutCount())
76+
.as("Expected three puts on first find")
77+
.isEqualTo(3);
78+
}
79+
80+
@Test
81+
void testSecondLevelCacheHitOnFind() {
82+
// Populate cache
83+
findAllProducts();
84+
stats.clear();
85+
// Access again for hits
86+
findAllProducts();
87+
assertThat(stats.getSecondLevelCacheHitCount())
88+
.as("Expected three hits from 2LC")
89+
.isEqualTo(3);
90+
assertThat(stats.getSecondLevelCachePutCount())
91+
.as("No new puts expected on cache hit")
92+
.isZero();
93+
}
94+
95+
@Test
96+
void testSecondLevelCacheMissAfterEviction() throws InterruptedException {
97+
// Populate cache
98+
findAllProducts();
99+
// Wait for TTL eviction (3 seconds as per ehcache.xml)
100+
Thread.sleep(5000);
101+
stats.clear();
102+
findAllProducts();
103+
assertThat(stats.getSecondLevelCacheMissCount())
104+
.as("Expected three misses after TTL eviction")
105+
.isEqualTo(3);
106+
assertThat(stats.getSecondLevelCachePutCount())
107+
.as("Expected three puts to reload into 2LC after eviction")
108+
.isEqualTo(3);
109+
}
110+
111+
private void findAllProducts() {
112+
EntityManager em = emf.createEntityManager();
113+
em.getTransaction()
114+
.begin();
115+
em.find(Product.class, product1.getId());
116+
em.find(Product.class, product2.getId());
117+
em.find(Product.class, product3.getId());
118+
em.getTransaction()
119+
.commit();
120+
em.close();
121+
}
122+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2020 American Express Travel Related Services Company, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
package io.americanexpress.synapse.data.postgres.config;
15+
16+
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
17+
import org.springframework.context.annotation.Configuration;
18+
import org.springframework.context.annotation.PropertySource;
19+
import org.springframework.core.env.Environment;
20+
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
21+
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
22+
23+
/**
24+
* Configuration class for the sample Postgres data module.
25+
* <p>
26+
* Loads properties from {@code kocho-application.properties}, enables JPA repositories,
27+
* and configures entity scanning for the {@code io.americanexpress.synapse.data.postgres.entity} package.
28+
* </p>
29+
*
30+
* @author Aziz Ali
31+
*/
32+
@Configuration
33+
@PropertySource("classpath:/kocho-application.properties")
34+
@EnableJpaRepositories(basePackages = "io.americanexpress.synapse.data.postgres.entity")
35+
@EnableAutoConfiguration
36+
public class DataSampleConfig extends BasePostgresDataConfig {
37+
38+
static final String PACKAGE_NAME = "io.americanexpress.synapse.data.postgres";
39+
40+
public DataSampleConfig(Environment environment) {
41+
super(environment);
42+
}
43+
44+
@Override
45+
protected void setPackagesToScan(LocalContainerEntityManagerFactoryBean entityManagerFactoryBean) {
46+
entityManagerFactoryBean.setPackagesToScan(PACKAGE_NAME + ENTITY_PACKAGE_NAME);
47+
}
48+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2020 American Express Travel Related Services Company, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
package io.americanexpress.synapse.data.postgres.entity;
15+
16+
import jakarta.persistence.Cacheable;
17+
import jakarta.persistence.Column;
18+
import jakarta.persistence.Entity;
19+
import org.hibernate.annotations.Cache;
20+
import org.hibernate.annotations.CacheConcurrencyStrategy;
21+
22+
/**
23+
* Entity representing a product in the database.
24+
* <p>
25+
* This class is cacheable and uses Hibernate's second-level cache with read-write concurrency.
26+
* Inherits common entity fields from {@link BaseEntity}.
27+
* </p>
28+
*
29+
* @author Aziz Ali
30+
*/
31+
@Entity
32+
@Cacheable
33+
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
34+
public class Product extends BaseEntity {
35+
36+
@Column(name = "product_name")
37+
private String productName;
38+
39+
public String getProductName() {
40+
return productName;
41+
}
42+
43+
public void setProductName(String title) {
44+
this.productName = title;
45+
}
46+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
SET
2+
SCHEMA_SEARCH_PATH TO synapse;
3+
insert into product (id, product_name, created_date_time, last_modified_date_time, created_by,
4+
last_modified_by, version)
5+
values (1, 'Synapse', now(),
6+
7+
insert into product (id, product_name, created_date_time, last_modified_date_time, created_by,
8+
last_modified_by, version)
9+
values (2, 'Revenge of Synapse', now(),
10+
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<config
3+
xmlns:jsr107='http://www.ehcache.org/v3/jsr107'
4+
xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
5+
xmlns='http://www.ehcache.org/v3'
6+
xsi:schemaLocation="
7+
http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core-3.0.xsd
8+
http://www.ehcache.org/v3/jsr107 http://www.ehcache.org/schema/ehcache-107-ext-3.0.xsd">
9+
<service>
10+
<jsr107:defaults default-template="default"/>
11+
</service>
12+
13+
<cache-template name="default">
14+
<expiry>
15+
<ttl unit="seconds">3</ttl>
16+
</expiry>
17+
<heap unit="entries">10</heap>
18+
</cache-template>
19+
</config>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#Datasource in memory database H2 connection
2+
spring.datasource.jdbcUrl=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1;MODE=LEGACY
3+
spring.datasource.username=sa
4+
spring.datasource.password=sa
5+
spring.datasource.driver-class-name=org.h2.Driver
6+
hibernate.dialect=org.hibernate.dialect.H2Dialect
7+
#H2 default for unit test and local
8+
spring.jpa.properties.hibernate.default_schema=PUBLIC
9+
#Connection pool
10+
spring.datasource.connectionTimeout=15000
11+
spring.datasource.idleTimeout=300000
12+
spring.datasource.maxLifetime=900000
13+
spring.datasource.maximumPoolSize=3
14+
15+
spring.datasource.initialization-mode=always
16+
17+
hibernate.hbm2ddl.auto=none
18+
hibernate.show_sql=true
19+
spring.profiles.active=test
20+
hibernate.format_sql=true
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
DROP SCHEMA IF EXISTS synapse CASCADE;
2+
CREATE SCHEMA synapse;
3+
4+
SET
5+
SCHEMA_SEARCH_PATH TO synapse;
6+
7+
DROP TABLE IF EXISTS product CASCADE;
8+
9+
/*
10+
* product table create script
11+
*/
12+
create table product
13+
(
14+
id serial PRIMARY KEY NOT NULL,
15+
product_name VARCHAR(150) NOT NULL,
16+
created_by VARCHAR(100),
17+
last_modified_by VARCHAR(100),
18+
created_date_time TIMESTAMP DEFAULT current_timestamp,
19+
last_modified_date_time TIMESTAMP,
20+
version INTEGER NOT NULL
21+
);
22+
23+
COMMIT;

0 commit comments

Comments
 (0)