Skip to main content

Integrasi Redis dalam Spring Boot dengan Spring Data: Membuat RESTFul API CRUD dengan Unit Testing

Jika sebelumnya pada artikel Integrasi Redis Dalam Spring Boot dengan Redis Template kita menggunakan Redis Template untuk berinteraksi antara Spring dan Redis, maka kali ini kita akan menggunakan Spring Data.

Karena kita akan meneruskan dari artikelsebelumnya aka kita akan mengulas sedikit tentang apa saja yang sebelumnya kita lakukan pada Integrasi Redis Dalam Spring Boot dengan Redis Template  di branch "redis" pada repository  https://github.com/yoesoff/YBoilerplate.git.    

Artikel ini memakai branch redis_spring_data dari branch redis

Dengan menggunakan Spring Data Redis dapat mempermudah interaksi dengan Redis dengan lebih otomatis. Dengan Spring Data Redis, kita dapat memanfaatkan repository untuk operasi CRUD, yang mengurangi jumlah kode manual dan menjadikan akses data lebih idiomatis dalam Spring Boot. Berikut adalah langkah-langkahnya:

1. Tambahkan Model Entity

Misalkan kita ingin menyimpan objek sederhana seperti DataObject.

package com.mhyusuf.yboilerplate.entity;

import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;

import java.io.Serializable;

@RedisHash("DataObject")
public class DataObject implements Serializable {
@Id
private String id;
private String value;

// Constructors, getters, and setters
public DataObject() {}

public DataObject(String id, String value) {
this.id = id;
this.value = value;
}

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public String getValue() {
return value;
}

public void setValue(String value) {
this.value = value;
}
}

2. Buat Interface RedisRepository

Pertama, buat repository interface menggunakan Spring Data Redis dengan anotasi @Repository. Anda juga perlu mengatur entitas yang disimpan ke dalam Redis.

Spring Data Redis menyediakan antarmuka CrudRepository yang dapat Anda gunakan untuk mengakses Redis secara otomatis. Buat repository seperti berikut:

package com.mhyusuf.yboilerplate.repository;

import com.mhyusuf.yboilerplate.entity.DataObject;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface DataObjectRepository extends CrudRepository<DataObject, String> {
// Tidak perlu menambahkan metode apapun, cukup menggunakan metode CRUD dasar
}

3. Update RedisService untuk Menggunakan DataObjectRepository

Dengan repository, Anda bisa memodifikasi RedisService untuk menggunakan DataObjectRepository dalam operasi CRUD.

"Opsi lain juga bisa dengan tidak perlu merubah apapun pada RedisService tapi dengan membut service baru bernama RedisSpringDataService."

package com.mhyusuf.yboilerplate.service;

import com.mhyusuf.yboilerplate.entity.DataObject;
import com.mhyusuf.yboilerplate.repository.DataObjectRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class RedisSpringDataService {

private final DataObjectRepository dataObjectRepository;

@Autowired
public RedisSpringDataService(DataObjectRepository dataObjectRepository) {
this.dataObjectRepository = dataObjectRepository;
}

public void saveData(String key, String value) {
dataObjectRepository.save(new DataObject(key, value));
}

public String getData(String key) {
return dataObjectRepository.findById(key)
.map(DataObject::getValue)
.orElse(null);
}

public boolean deleteData(String key) {
if (dataObjectRepository.existsById(key)) {
dataObjectRepository.deleteById(key);
return true;
}
return false;
}

public Iterable<DataObject> getAllData() {
return dataObjectRepository.findAll();
}
}

Selanjutnya kita akan gunakan service yang bernama RedisSpringDataService.

5. Update RedisSpringDataController

Sesuaikan RedisSpringDataController untuk menggunakan metode baru di RedisSpringDataService:

"Saya kali ini tidak mengubah RedisController tapi membuat Controller baru bernama RedisSpringDataController"

package com.mhyusuf.yboilerplate.controller;

import com.mhyusuf.yboilerplate.entity.DataObject;
import com.mhyusuf.yboilerplate.service.RedisSpringDataService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/redisspringdata")
public class RedisSpringDataController {

private final RedisSpringDataService redisService;

@Autowired
public RedisSpringDataController(RedisSpringDataService redisService) {
this.redisService = redisService;
}

@PostMapping("/{key}")
public ResponseEntity<String> createOrUpdateData(@PathVariable String key, @RequestBody String value) {
redisService.saveData(key, value);
return ResponseEntity.ok("Data saved successfully");
}

@GetMapping("/{key}")
public ResponseEntity<String> getData(@PathVariable String key) {
String value = redisService.getData(key);
if (value != null) {
return ResponseEntity.ok(value);
} else {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Data not found");
}
}

@DeleteMapping("/{key}")
public ResponseEntity<String> deleteData(@PathVariable String key) {
if (redisService.deleteData(key)) {
return ResponseEntity.ok("Data deleted successfully");
} else {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Data not found");
}
}

@GetMapping("/all")
public ResponseEntity<Iterable<DataObject>> getAllData() {
return ResponseEntity.ok(redisService.getAllData());
}
}

6. Tambahkan Konfigurasi Spring Data Redis di application.properties

Cek kembali konfigurasi Redis tambahkan jika belum ada atau sesuaikan dengan setup redis anda:

spring.data.redis.host=localhost
spring.data.redis.port=6379

7. Unit Testing

Terakhir, perbarui unit test di RedisControllerTest untuk menguji antarmuka baru berbasis RedisRepository.

"Atau opsi lain adalah dengan membuat Unit Test Baru supaya test lama masih bisa buat belajar, toh service dan controller lama tidak kita hapus maupun ubah"

7.1. RedisSpringDataControllerTest

package com.mhyusuf.yboilerplate;

import com.mhyusuf.yboilerplate.controller.RedisSpringDataController;
import com.mhyusuf.yboilerplate.entity.DataObject;
import com.mhyusuf.yboilerplate.service.RedisSpringDataService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;

import java.util.Collections;
import java.util.Set;

import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@WebMvcTest(RedisSpringDataController.class)
class RedisSpringDataControllerTest {

@Autowired
private MockMvc mockMvc;

@MockBean
private RedisSpringDataService redisService; // Menggunakan @MockBean untuk memock RedisService

@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
}

@Test
void testCreateOrUpdateData() throws Exception {
String key = "testKey";
String value = "testValue";

doNothing().when(redisService).saveData(key, value);

mockMvc.perform(post("/api/redisspringdata/{key}", key)
.content(value)
.contentType(MediaType.TEXT_PLAIN))
.andExpect(status().isOk())
.andExpect(content().string("Data saved successfully"));
}

@Test
void testGetData() throws Exception {
String key = "testKey";
String value = "testValue";

when(redisService.getData(key)).thenReturn(value);

mockMvc.perform(get("/api/redisspringdata/{key}", key))
.andExpect(status().isOk())
.andExpect(content().string(value));
}

@Test
void testGetData_NotFound() throws Exception {
String key = "unknownKey";

when(redisService.getData(key)).thenReturn(null);

mockMvc.perform(get("/api/redisspringdata/{key}", key))
.andExpect(status().isNotFound())
.andExpect(content().string("Data not found"));
}

@Test
void testDeleteData() throws Exception {
String key = "testKey";

when(redisService.deleteData(key)).thenReturn(true);

mockMvc.perform(delete("/api/redisspringdata/{key}", key))
.andExpect(status().isOk())
.andExpect(content().string("Data deleted successfully"));
}

@Test
void testDeleteData_NotFound() throws Exception {
String key = "unknownKey";

when(redisService.deleteData(key)).thenReturn(false);

mockMvc.perform(delete("/api/redisspringdata/{key}", key))
.andExpect(status().isNotFound())
.andExpect(content().string("Data not found"));
}

@Test
void testGetAllData() throws Exception {
// Buat objek DataObject dengan key dan value
DataObject dataObject = new DataObject("testKey", "testValue");
Set<DataObject> dataObjects = Collections.singleton(dataObject);

// Mock service untuk mengembalikan dataObjects
when(redisService.getAllData()).thenReturn(dataObjects);

// Lakukan perform GET request dan validasi respons JSON
mockMvc.perform(get("/api/redisspringdata/all"))
.andExpect(status().isOk());

}
}



7.2 RedisSpringDataServiceTest

package com.mhyusuf.yboilerplate;


import com.mhyusuf.yboilerplate.entity.DataObject;
import com.mhyusuf.yboilerplate.repository.DataObjectRepository;
import com.mhyusuf.yboilerplate.service.RedisSpringDataService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.util.Optional;
import java.util.stream.StreamSupport;
import java.util.List;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

class RedisSpringDataServiceTest {

@Mock
private DataObjectRepository dataObjectRepository;

@InjectMocks
private RedisSpringDataService redisSpringDataService;

@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
}

@Test
void testSaveData() {
// Arrange
ArgumentCaptor<DataObject> captor = ArgumentCaptor.forClass(DataObject.class);
DataObject expectedDataObject = new DataObject("testKey", "testValue");

// Act
redisSpringDataService.saveData("testKey", "testValue");

// Assert
verify(dataObjectRepository, times(1)).save(captor.capture());
DataObject actualDataObject = captor.getValue();

assertEquals(expectedDataObject.getId(), actualDataObject.getId());
assertEquals(expectedDataObject.getValue(), actualDataObject.getValue());
}


@Test
void testGetData() {
String key = "testKey";
DataObject dataObject = new DataObject(key, "testValue");
when(dataObjectRepository.findById(key)).thenReturn(Optional.of(dataObject));

String result = redisSpringDataService.getData(key);

assertNotNull(result);
assertEquals("testValue", result);
verify(dataObjectRepository, times(1)).findById(key);
}

@Test
void testGetDataNotFound() {
String key = "nonExistentKey";
when(dataObjectRepository.findById(key)).thenReturn(Optional.empty());

String result = redisSpringDataService.getData(key);

assertNull(result);
verify(dataObjectRepository, times(1)).findById(key);
}

@Test
void testDeleteData() {
String key = "testKey";
when(dataObjectRepository.existsById(key)).thenReturn(true);

boolean isDeleted = redisSpringDataService.deleteData(key);

assertTrue(isDeleted);
verify(dataObjectRepository, times(1)).deleteById(key);
}

@Test
void testDeleteDataNotFound() {
String key = "nonExistentKey";
when(dataObjectRepository.existsById(key)).thenReturn(false);

boolean isDeleted = redisSpringDataService.deleteData(key);

assertFalse(isDeleted);
verify(dataObjectRepository, times(0)).deleteById(key);
}

@Test
void testGetAllData() {
Iterable<DataObject> dataObjects = List.of(
new DataObject("key1", "value1"),
new DataObject("key2", "value2")
);

when(dataObjectRepository.findAll()).thenReturn(dataObjects);

Iterable<DataObject> result = redisSpringDataService.getAllData();

assertNotNull(result);
assertEquals(2, StreamSupport.stream(result.spliterator(), false).count());
verify(dataObjectRepository, times(1)).findAll();
}
}



Setelah langkah-langkah ini, aplikasi Spring Boot Anda sudah siap untuk menggunakan Redis melalui Spring Data, memungkinkan pengelolaan data yang lebih mudah dan sesuai dengan pola standar Spring Boot.

Terkait Repository dan Kode Ini

Files yang di tambahkan selama artikel diatas dibuat.

Semua kode di atas sudah di push ke Repo


Tambahan Informasi

Pertanyaan: 

Apakah dengan menggunakan Spring Data untuk Redis kali ini kita juga menggunakan JPA (Spesifikasi Java Persistant API)?

Jawab:

Tidak, saat menggunakan Spring Data Redis, Anda tidak menggunakan spesifikasi JPA. JPA (Java Persistence API) adalah standar Java untuk persistence yang digunakan untuk mengelola relational database seperti MySQL, PostgreSQL, dan lainnya. JPA digunakan bersama dengan ORM (Object-Relational Mapping) seperti Hibernate untuk memetakan objek Java ke tabel dalam database relasional.

Sebaliknya, Spring Data Redis adalah modul Spring khusus untuk bekerja dengan database key-value store Redis. Redis tidak memiliki model relasional seperti database SQL, jadi pendekatan dan API yang digunakan berbeda dengan JPA. Spring Data Redis menyediakan API khusus yang cocok untuk operasi berbasis key-value, seperti menyimpan, mengambil, dan menghapus data dari Redis.

Berikut adalah beberapa perbedaan utama:

  1. Model Data:

    • JPA: Menggunakan entitas yang dipetakan ke tabel-tabel di database relasional.
    • Spring Data Redis: Beroperasi dengan struktur data Redis seperti string, hashes, lists, sets, dan sorted sets.
  2. Repository:

    • JPA: Menggunakan JpaRepository atau CrudRepository untuk operasi database.
    • Spring Data Redis: Menggunakan RedisRepository atau langsung berinteraksi dengan RedisTemplate.
  3. Fitur Spesifik:

    • JPA: Mendukung kueri yang kompleks, relasi antar-entitas, dan transaksi database.
    • Spring Data Redis: Fokus pada penyimpanan data cepat dan cache, tanpa dukungan untuk relasi dan transaksi seperti JPA.

Jadi, jika Anda menggunakan Spring Data Redis, Anda tidak menggunakan spesifikasi JPA karena Redis bukan database relasional dan memiliki cara kerja yang berbeda.




Comments

Popular posts from this blog

Numpang Kerja Remote dari Bandung Creative Hub

Semalam kemarin (09 Januari 2019) tidak sengaja kami sekeluarga lewat Bandung Digital Hub saat pulang dari Fish Wow Cheeseee  yang di Jl. Lombok. Bandung Digital Hub ini sendiri berlokasi tidak jauh dari dari tempat kami makan tersebut, yaitu berlokasi di Jl. Laswi No.7, Kacapiring, Batununggal, Kota Bandung, Jawa Barat 40271. Berhubung untuk bulan Januari 2019 ini sedang tidak masuk ke kantor maka saya putuskan untuk besoknya (hari ini 09 Januari 2019) nyoba untuk bekerja remote dari Bandung Digital Hub , apalagi istri yang kebetulan follower pak Ridwan Kamil di Instagram juga Facebook dan tampaknya pernah lihat ulasan mengenai tempat ini sehingga tampak antusias supaya saya datang ketempat ini ini dan mencoba bekerja dari gedung creative hub dan coworking yang keren ini.  Tempat Parkir Masalah utama saat kita datang ke coworking space terutama yang berlokasi di Bandung (atau mungkin kota-kota lainnya) adalah lahan parkir, kadang lahan parkir ...

Numpang Kerja Remote dari Bandung Digital Valley

Satu lagi co-working place  gratisan dan keren yang cukup populer dikota Bandung, co-working place yang juga memberikan fasilitas tempat kerja (co-working place) dan fitur-fitur menarik lainnya,  co-working place keren  ini adalah Bandung Digital Valley atau yang sering disingkat BDV . C o-working place  Bandung Digital Valley ini  merupakan bagian dari Telkom , mulai aktif digunakan dari sekitar tahun 2012 lalu .  Tempat ini biasanya menjadi tempat favorit bagi para pengiat startup, freelancer, dan mahasiswa . Gedung BDV Gedung BDV Gedung BDV Co-working space Bandung Digital Valley ini sendiri berlokasi di Menara Bandung Digital Valley, Jl. Gegerkalong Hilir No.47, Sukarasa, Sukasari, Kota Bandung, Jawa Barat, detailnya bisa dilihat di Google map berikut. Pemandangan jalan setelah pintu satpam. Free Co-working Space Membership Untuk mulai menggunakan fasilitas co-working space ini secara gratis maka yang pe...

Membuat Authentikasi Berbasis Token pada Spring Boot dengan Spring Security dan JWT

Setelah beberapa kali mencari tutorial tentang otentikasi aplikasi web Spring Boot dengan menggunakan JWT yang mudah dipahami akhirnya saya menemukan artikel berbahasa Inggris tapi sangat mudah dipahami  dan diikuti, artikel tersbut berada disini , dengan judul " Spring Boot Token based Authentication with Spring Security & JWT ". Untuk memudahkan orang-orang yang terbiasa membaca artikel dalam bahasa indonesia (termasuk saya sendiri), artikel ini saya buat dan susun ulang (artikel aslinya tidak tertulis dengan runtut dan dapat membuat pemula bingung dengan berbagai error yang muncul) supaya lebih mudah untuk diikuti dan dapat di gunakan bersama. Applikasi yang akan kita buat adalah aplikasi web yang setiap endpoint-nya hanya bisa di akses oleh role tertentu. 1. Tools Yang Diperlukan IntelliJ Idea text editor. Spring Assistant Plugin. Postman. PostgreSQL + DBeaver. Min Java 8 Spring Boot 2.1.8 (dengan Spring Security, Spring Web, Spring Data JPA). jjwt 0.9.1. Maven 3.6.1....