➤ How to Code a Game
➤ Array Programs in Java
➤ Java Inline Thread Creation
➤ Java Custom Exception
➤ Hibernate vs JDBC
➤ Object Relational Mapping
➤ Check Oracle DB Size
➤ Check Oracle DB Version
➤ Generation of Computers
➤ XML Pros & Cons
➤ Git Analytics & Its Uses
➤ Top Skills for Cloud Professional
➤ How to Hire Best Candidates
➤ Scrum Master Roles & Work
➤ CyberSecurity in Python
➤ Protect from Cyber-Attack
➤ Solve App Development Challenges
➤ Top Chrome Extensions for Twitch Users
➤ Mistakes That Can Ruin Your Test Metric Program
Redis Cache with Spring Data JPA | Redis is a open source software which can be used as In memory database and cache manager and message broker (MQ).
In Memory database:- It is like empty memory. We don’t have to write SQL queries, No tables, no sequence, no Joins. Data is stored as plain string format, List, Set or we can perform Hash operations. It includes in-built services for transcation mangement, clear memory, and e.t.c.
To work with redis we have to add the following dependency:- Spring Data Redis (Access + Driver)
.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
In STS, Ecllipse, CTRL + Shift + T, type RedisConnectionFactory. Open RedisConnectionFactory interface and Function-key + F4 it will show implemntation classes.
RedisConnectionFactory has two implementation classes:-
- JedisConnectionFactory
- LettuceConnectionFactory
Among these Jedis is old driver, and Lettuce is new driver.
To create connection between application and redis database, use RedisConnectionFactory(I) which has 2 implemenation classes JedisConnectionFactory and LettuceConnectionFactory. Also provide properties for redis (host and port).
@Configuration
public class AppConfig {
@Bean
public RedisConnectionFactory cf() {
return new LettuceConnectionFactory();
}
}
# redis key-val (properties)
spring.redis.host=localhost
spring.redis.port=6379
Redis as Database Example
Let us see redis as database. While using redis as cache, internally redis will work as database to store the information.
Create one Spring Boot starter project and add the dependencies:- Lombok
, Spring Data Redis (Access + Driver)
In application.properties:-
# redis key-val (properties)
spring.redis.host=localhost
spring.redis.port=6379
Model class (it must implement Serializable interface):-
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student implements Serializable {
private static final long serialVersionUID = 1L;
private Integer stdId;
private String stdName;
private Double stdFee;
}
Spring configration file will contain redis connection and redis template. Configuration:-
@Configuration
public class AppConfig {
// Redis connection
@Bean
public RedisConnectionFactory cf() {
return new LettuceConnectionFactory();
}
// Redis template
@Bean
public RedisTemplate<String, Student> rt() {
RedisTemplate<String, Student> template = new RedisTemplate<>();
template.setConnectionFactory(cf());
return template;
}
}
Define one interface to perform the operation. StudentDaoImpl <– using HashOperations. Here:-
- H (HRef) = String (‘STUDENT’)
- HK (key) = Integer (stdId)
- HV (val) = Student Type
public interface IStudentDao {
void addStudent(Student s);
void modifyStudent(Student s);
Student getOneStudent(Integer id);
Map<Integer, Student> getAllStudent();
void removeStudent(Integer id);
}
@Repository
public class StudentDaoImpl implements IStudentDao {
private final String KEY = "STUDENT";
// ref type, keyType, valType
@Resource(name = "rt")
private HashOperations<String, Integer, Student> opr;
@Override
public void addStudent(Student s) {
// create new record in HashMemory if given id not exist
opr.putIfAbsent(KEY, s.getStdId(), s);
}
@Override
public void modifyStudent(Student s) {
// update data with given ID
opr.put(KEY, s.getStdId(), s);
}
@Override
public Student getOneStudent(Integer id) {
// read one record based on HashRef and ID
return opr.get(KEY, id);
}
@Override
public Map<Integer, Student> getAllStudent() {
// hashRef, get all rows as Map
return opr.entries(KEY);
}
@Override
public void removeStudent(Integer id) {
// hashRef, key
opr.delete(KEY, id);
}
}
Runner class for test:-
@Component
public class RedisOprTest implements CommandLineRunner {
@Autowired
private IStudentDao dao;
@Override
public void run(String... args) throws Exception {
dao.addStudent(new Student(101, "SAM", 500.25));
dao.addStudent(new Student(102, "SYED", 800.25));
dao.addStudent(new Student(103, "Rocco", 600.25));
dao.getAllStudent().forEach((k, v) -> System.out.println(k + "-" + v));
dao.removeStudent(101);
dao.modifyStudent(new Student(103, "Jerry", 600.25));
System.out.println("After remove & modify:-");
dao.getAllStudent().forEach((k, v) -> System.out.println(k + "-" + v));
}
}
Output after running the application:-
103-Student(stdId=103, stdName=Rocco, stdFee=600.25)
102-Student(stdId=102, stdName=SYED, stdFee=800.25)
101-Student(stdId=101, stdName=SAM, stdFee=500.25)
After remove & modify:-
103-Student(stdId=103, stdName=Jerry, stdFee=600.25)
102-Student(stdId=102, stdName=SYED, stdFee=800.25)
Redis as Cache Example
Cache is a temporary memory. It is used between Server (application) and database. This is used to reduce network calls if we trying to fetch same data multiple times from database.
Do not implment cache for all modules. Select modules which are actually mostly used. Example:- In gmail app multiple modules are there like inbox, sent, drafts, and e.t.c. Among them inbox is the mostly used.
Cache supports 3 operations:-
@Cacheable
: Fetch data from database to application and store in cache.@CachePut
: Update data at cache while it is updating in database.@CacheEvict
: Remove data at cache while it is removing in database
Syntax:- @Cache___(value="CACHE-REGION", key="#Variable")
These operations are effected to selected region. Region is a area/memory part of the cache created for one module. Example:- EMP-REG, STD-REG, and e.t.c.
Unlike Hibernate, Spring Data JPA does not support SessionFactory and therefore 1st level cache (Session) and second level cache (Session Factory Cache) is not supported since Spring Boot 2.x. Spring Data JPA works on its own cache managers.
Let us first see application example without using cache
Example Without Cache
Add the following dependencies:-
- Lombok
- Spring Data JPA
- MySQL Driver
- Spring Web
- Spring Boot DevTools
- Spring Boot Actuator
In properties file:-
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.format_sql=true
spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect
management.endpoints.web.exposure.include=*
In Model class:-
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue
private Integer id;
private String empName;
private Double empSal;
}
public interface EmployeeRepository extends JpaRepository<Employee, Integer> { }
public interface IEmployeeService {
public Employee saveEmployee(Employee e);
public Employee updateEmployee(Integer empId, Employee e);
public void deleteEmployee(Integer id);
public List<Employee> getAllEmployees();
public Employee getOneEmployee(Integer id);
}
@Service
public class EmployeeServiceImpl implements IEmployeeService {
@Autowired
private EmployeeRepository employeeRepository;
@Override
public Employee saveEmployee(Employee e) {
return employeeRepository.save(e);
}
@Override
public Employee updateEmployee(Integer empId, Employee e) {
Employee employee = getOneEmployee(empId);
employee.setEmpName(e.getEmpName());
employee.setEmpSal(e.getEmpSal());
return employeeRepository.save(e);
}
@Override
public void deleteEmployee(Integer empId) {
Employee employee = getOneEmployee(empId);
employeeRepository.delete(employee);
}
@Override
public List<Employee> getAllEmployees() {
return employeeRepository.findAll();
}
@Override
public Employee getOneEmployee(Integer empId) {
return employeeRepository.findById(empId)
.orElseThrow(() -> new ResourceNotFoundException("Employee Not Found"));
}
}
@ResponseStatus(value= HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
private static final long serialVersionUID = 1L;
public ResourceNotFoundException(String message) {
super(message);
}
}
@RestController
@RequestMapping("/employee")
public class EmployeeRestContoller {
@Autowired
private IEmployeeService service;
@PostMapping
public ResponseEntity<Employee> saveEmployee(@RequestBody Employee e) {
return new ResponseEntity<Employee>(service.saveEmployee(e), HttpStatus.CREATED);
}
@GetMapping
public ResponseEntity<List<Employee>> getAllEmployees() {
return ResponseEntity.ok(service.getAllEmployees());
}
@GetMapping("/{eid}")
public ResponseEntity<Employee> getOneEmployee(@PathVariable Integer eid) {
return new ResponseEntity<>(service.getOneEmployee(eid), HttpStatus.FOUND);
}
@PutMapping("/{eid}")
public ResponseEntity<Employee> updateEmployee(@PathVariable Integer eid,
@RequestBody Employee employee) {
return new ResponseEntity<>(service.updateEmployee(eid, employee),
HttpStatus.ACCEPTED);
}
@DeleteMapping("/{eid}")
public ResponseEntity<Void> deleteEmployee(@PathVariable Integer eid) {
service.deleteEmployee(eid);
return ResponseEntity.ok().build();
}
}
Created some entries in the database. The findAll() gives (GET – http://localhost:8080/employee):-
[
{
"id": 1,
"empName": "SAM",
"empSal": 200.0
},
{
"id": 2,
"empName": "AJAY",
"empSal": 400.0
},
{
"id": 3,
"empName": "SYED",
"empSal": 600.0
}
]
Whenever we try to fetch the particular employee (GET – {{url}}/employee/1) then each time application make a call to the database through below query:-
Hibernate:
select
e1_0.id,
e1_0.emp_name,
e1_0.emp_sal
from
employee e1_0
where
e1_0.id=?
Introducing Redis Cache to the Application
Step-1:- Add the Spring Data Redis (Access + Driver)
dependency.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
Step-2:- In starter class enable caching.
@SpringBootApplication
@EnableCaching
public class RedisAsCacheDemoApplication {
public static void main(String[] args) {
SpringApplication.run(RedisAsCacheDemoApplication.class, args);
}
}
Step-3: Add redis properties.
spring.cache.type=redis
spring.cache.redis.cache-null-values=true
# time-to-live is in milli second
# 60000 milli second = 1 minute
spring.cache.redis.time-to-live=60000
Step-4:- Add appriorate annotation (@Cacheable
, @CachePut
, @CacheEvict
) to required methods.
@Override
@Cacheable(value="employees", key="#empId")
public Employee getOneEmployee(Integer empId) {
return employeeRepository.findById(empId)
.orElseThrow(() -> new ResourceNotFoundException("Employee Not Found"));
}
Here key and method parameter must have the same name.
Now when we try to fetch the particular employee (GET – {{url}}/employee/1) then for the first time application make a call to the database and store the result into the cache for given time (time-to-live).
We want to enabled caching for getOneEmployee(), updateEmployee(), and deleteEmployee().
@Service
public class EmployeeServiceImpl implements IEmployeeService {
@Autowired
private EmployeeRepository employeeRepository;
@Override
public Employee saveEmployee(Employee e) {
return employeeRepository.save(e);
}
@Override
@CachePut(value = "employees", key = "#empId")
public Employee updateEmployee(Integer empId, Employee e) {
Employee employee = getOneEmployee(empId);
employee.setEmpName(e.getEmpName());
employee.setEmpSal(e.getEmpSal());
return employeeRepository.save(e);
}
@Override
@CacheEvict(value = "employees", key = "#empId")
public void deleteEmployee(Integer empId) {
Employee employee = getOneEmployee(empId);
employeeRepository.delete(employee);
}
@Override
public List<Employee> getAllEmployees() {
return employeeRepository.findAll();
}
@Override
@Cacheable(value = "employees", key = "#empId")
public Employee getOneEmployee(Integer empId) {
return employeeRepository.findById(empId)
.orElseThrow(() -> new ResourceNotFoundException("Employee Not Found"));
}
}
Assume if we have employee with child (like one-to-one, many-to-many, collection type) and we want to delete child also then we have to provide allEntries=true
@CacheEvict(value = "employees", allEntries = true)
We can use actuator to verify the caching:- http://localhost:8080/actuator/caches. Other endpoints of actuator can be found at http://localhost:8080/actuator.
If you enjoyed this post, share it with your friends. Do you want to share more information about the topic discussed above or do you find anything incorrect? Let us know in the comments. Thank you!