Demo project for Spring Boot, Ehcache, h2 db, delegation with BeanPostProcessor.
In this application delegation is used along with spring boot by overriding BeanPostProcessor interface's postProcessAfterInitialization() method.
@EnableJpaRepositories annotation is used on main class to Enable H2 DB related configuration, which will read properties from application.properties file to perform read operations.
EhcacheDelegationConfig class is used as delegation, which wraps SuperHeroRepository inside EhcacheSuperHeroRepositoryImpl if EhCache bean is configured in classpath.
Cache will be checked if and only if Ehcache bean is configured else EhcacheSuperHeroRepositoryImpl will be skipped.
- IntelliJ IDEA or Eclipse/STS (or any preferred IDE) with embedded Maven
- Maven (version >= 3.6.0)
- Postman (or any RESTful API testing tool), even any web browser would work as we are going to test readonly REST APIs.
GOTO > ~/absolute-path-to-directory/spring-boot-h2-ehcache-delegation
and try below command in terminal
mvn spring-boot:runit will run application as spring boot application
or
mvn clean installit will build application and create jar file under target directory
Run jar file from below path with given command
java -jar ~/path-to-spring-boot-h2-ehcache-delegation/target/spring-boot-h2-ehcache-delegation-0.0.1-SNAPSHOT.jar
Or
run main method from
SpringBootH2EhcacheDelegationApplication.javaas spring boot application.
-
Need to add below
JPA&H2dependencies to enable H2 DB related config in pom.xml.
Lombokdependency is to get rid of boiler-plate code.
AOPfor aspect related features to print logs using@LogObjectBefore&@LogObjectAftercustom annotations.
Ehcachefor enabling in memory custom Ehcache in the application<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId> </dependency> -
Reading H2 DB related properties from application.properties file and configuring JPA connection factory for H2 database.
src/main/resources/application.properties
spring.datasource.url=jdbc:h2:mem:sampledb spring.datasource.driverClassName=org.h2.Driver spring.datasource.username=sa spring.datasource.password=password spring.jpa.database-platform=org.hibernate.dialect.H2Dialect spring.h2.console.enabled=true # This will enable circular dependencies which we are using for delegation spring.main.allow-circular-references=true -
Below model classes which we will store in H2 DB and perform read operations.
SuperHero.java@Data @AllArgsConstructor @NoArgsConstructor @Builder @Entity @Table public class SuperHero implements Serializable { @Id @GeneratedValue private int id; private String name; private String superName; private String profession; private int age; private boolean canFly; // Constructor, Getter and Setter }The annotations are added in this controller to print log smartly with the help of Aspect (LoggerAspect class from aop package).
@LogObjectBefore - annotation created to print log before method execution
@LogObjectAfter - annotation created to print the returned value from method -
In SuperHeroController.java class, we have exposed 3 endpoints for basic READ operations
- GET All Super Heroes
- GET by ID
- GET by ID IN
@RequiredArgsConstructor @RestController @RequestMapping("/super-heroes") public class SuperHeroController { private final SuperHeroService superHeroService; @LogObjectAfter @GetMapping public ResponseEntity<List<?>> findAll(); @LogObjectBefore @LogObjectAfter @GetMapping("/{id}") public ResponseEntity<?> findById(@PathVariable Integer id); @LogObjectBefore @LogObjectAfter @GetMapping("/in") public ResponseEntity<List<?>> findByIdIn(@RequestParam List<Integer> ids); }In SuperHeroServiceImpl.java, we are injecting SuperHeroRepository interface using constructor injection for read operation.
In SuperHeroRepository.java, we are extending
JpaRepository<Class, ID>interface which has read related methods by putting query inside@Queryannotation, which we are overriding in EhcacheSuperHeroRepositoryImpl class.public interface SuperHeroRepository extends JpaRepository<SuperHero, Integer> { @Query("SELECT s FROM SuperHero s WHERE s.id = ?1") Optional<SuperHero> findById(Integer id); @Query("SELECT s FROM SuperHero s") List<SuperHero> findAll(); @Query("SELECT s FROM SuperHero s where s.id in (:ids)") List<SuperHero> findByIdIn(List<Integer> ids); } -
This is the most important class in spring delegation pattern and in this application also. This will override the default behaviour of spring JPA by returning
EhcacheSuperHeroReoisitoryImplclass implementation by overridingBeanPostProcessor'spostProcessAfterInitialization()method instead of JPA repository if and only ifEhcache bean is present in classpathelse default JPA repository will be in a picture.
In simple words ifEhcachebean is configured in application then first SuperHero data will be found in cache and then in DB and will be added in cache after fetching from DB.
@ConditionalOnBean(Ehcache.class) will check Ehcache bean is present in application only then this call will get configured.
@ConditionalOnClass(Ehcache.class) will check Ehcache class is present in application only then this call will get configured. This will prevent from gettingnet.sf.ehcache.Ehcacheclass not found exception.@Slf4j @Configuration @RequiredArgsConstructor @ConditionalOnBean(Ehcache.class) @ConditionalOnClass(Ehcache.class) public class EhcacheDelegationConfig implements BeanPostProcessor { private final Ehcache ehcache; @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof SuperHeroRepository) { log.info("*** Ehcache delegation with EhcacheSuperHeroRepositoryImpl class implementation in use ***"); return new EhcacheSuperHeroRepositoryImpl((SuperHeroRepository) bean, ehcache); } return bean; } } -
This is optional class. If we configure this class in application then
Ehcachewill be enabled, alsoEhcacheSuperHeroRepositoryImpl's implementation will be in use.
If we delete this class or comment the content of it thenBeanPostProcessor'spostProcessAfterInitialization()method will not delegate theEhcacheSuperHeroRepositoryImplclass and requests will directly reach to DB without checking any cache data.@EnableCaching @Configuration public class EhcacheCacheConfig { @Bean public CacheManager getCustomCacheManager() { CacheManager cacheManager = CacheManager.create(getEhCacheConfiguration()); cacheManager.addCache(ehcache()); return cacheManager; } @Primary @Bean public Ehcache ehcache() { CacheConfiguration cacheConfig = new CacheConfiguration("my-cache", 1000) .memoryStoreEvictionPolicy(MemoryStoreEvictionPolicy.LRU) .eternal(false) .timeToLiveSeconds(3600) .timeToIdleSeconds(3600); return new Cache(cacheConfig); } private net.sf.ehcache.config.Configuration getEhCacheConfiguration() { net.sf.ehcache.config.Configuration configuration = new net.sf.ehcache.config.Configuration(); DiskStoreConfiguration diskStoreConfiguration = new DiskStoreConfiguration(); diskStoreConfiguration.setPath("java.io.tmpdir"); configuration.addDiskStore(diskStoreConfiguration); return configuration; } }
-
GET Mapping http://localhost:8080/super-heroes - Get all Super Heroes from DB. If found no record, will add mock data from
MockDataHelperUtilclass to DB.GET Mapping http://localhost:8080/super-heroes/1 - Get Super Hero by ID
GET Mapping http://localhost:8080/super-heroes/in?ids=1,2,3 - Get all Super Heroes who has id 1, 2 or 3