이번에는 SpringBoot와 데이터접근과 관련해서 작성 해 보려고한다 많은 경우에 Spring은 DB에 담긴 데이터를 가져오고 전달하는 역할을 하고있는데 이 때, Redis를 가지고 데이터에 빠르게 접근하는 방법을 정리 해 보려고 한다
Redis는 데이터베이스로도 사용되고, Message Broker로도 사용되지만 Cache Manager에 더 많이 사용된다 한번 만들어보면서 정리 해 보도록 하겠다
Redis 사용 형태
Redis 캐시란
Redis Cache 활성화를 위한 @Annotation
아래는 이해를 돕기 위해서 사용 한 프로젝트 구조를 적어보았다
├── HELP.md ├── README.md ├── build ├── build.gradle ├── docker-compose-database.yml ├── example-docker-data ├── gradle │ └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src ├── main │ ├── java │ │ └── com │ │ └── example │ │ └── springbootredissimplestarter │ │ ├── SpringBootRedisSimpleStarterApplication.java │ │ ├── config │ │ │ └── RedisCacheConfig.java │ │ ├── controller │ │ │ └── OrderController.java │ │ ├── domain │ │ │ └── Order.java │ │ ├── exception │ │ │ ├── OrderNotFoundException.java │ │ │ └── OrderStatusException.java │ │ ├── repository │ │ │ └── OrderRepository.java │ │ └── service │ │ ├── OrderService.java │ │ └── OrderServiceImpl.java │ └── resources │ ├── application.properties │ ├── static │ └── templates └── test └── java └── com └── example └── springbootredissimplestarter └── SpringBootRedisSimpleStarterApplicationTests.java
당연하지만 Spring Boot
와 Redis
를 준비해야 한다.
SpringBoot는 Spring Data Redis
라는 디펜던시를 통해서 Redis와 연결을 지원한다. 이 디펜던시를 통해서 Redis와 소통 할 것이고, Redis 서버를 docker를 사용해서 띄워 사용
하려고 한다
데이터베이스 환경을 먼저 구축하려고 한다. 모두의 컴퓨터환경이 같지 않기때문에, 현재 글 작성일 기준으로 docker-compose
로 Database
를 구성하고자한다.
docker-compose-database.yml
version: "3" services: mysql-docker: image: arm64v8/mariadb ports: - "3306:3306" environment: TZ: Asia/Seoul MYSQL_ROOT_PASSWORD: qwerqwer123 MYSQL_DATABASE: rediswithspring MYSQL_USER: paul MYSQL_PASSWORD: qwerqwer123 container_name: "docker-maria" volumes: - ./example-docker-data/maria:/var/lib/mysql redis-docker: image: redis:latest command: redis-server --port 6379 container_name: "docker-redis" volumes: - ./example-docker-data/redis:/data labels: - "name=redis" - "mode=standalone" ports: - 6379:6379
arm64v8/mariadb
를 사용했는데, 이는 m1에서 mysql을 구동하기 위함이다.mysql_env
로 작성했으며 내용은 아래와 같다(docker-compose에 명시하지않고 env파일로 빼고싶은 사람만 사용하면 될 것 같다)MYSQL_HOST=localhost MYSQL_PORT=3306 MYSQL_ROOT_PASSWORD=qwerqwer123 MYSQL_DATABASE=rediswithspring MYSQL_USER=paul MYSQL_PASSWORD=qwerqwer123
--requirepass
로 비밀번호를 설정 해 주고, --port
로 6379 포트를 열어주었다SpringBoot Starter를 사용해 프로젝트를 시작하고, Dependency 세팅을 해 준다
SpringBoot에게 Redis Cache를 사용 할 것이라고 알려주어야 한다. 위에 적어놓은 어노테이션 중 @EnableCaching
을 스타터 클래스에 적용한다
package com.example.springbootredissimplestarter; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCaching; @SpringBootApplication @EnableCaching public class SpringBootRedisSimpleStarterApplication { public static void main(String[] args) { SpringApplication.run(SpringBootRedisSimpleStarterApplication.class, args); } }
spring.datasource.driver-class-name=org.mariadb.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost:3306/rediswithspring spring.datasource.username=paul spring.datasource.password=qwerqwer123 spring.jpa.database-platform=org.hibernate.dialect.MariaDB103Dialect spring.jpa.show-sql=true spring.jpa.hibernate.ddl-auto=update spring.cache.type=redis spring.cache.redis.cache-null-values=true
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect
RedisCacheConfig.java
를 생성 해 주었다testCacheManger
객체를 앞으로 사용 할 레디스 어노테이션에 명시 해 주어야 한다package com.example.springbootredissimplestarter.config; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.StringRedisSerializer; import java.time.Duration; @Configuration @EnableCaching public class RedisCacheConfig { @Bean public CacheManager testCacheManager(RedisConnectionFactory cf) { RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig() .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())) .entryTtl(Duration.ofMinutes(3L)); return RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(cf).cacheDefaults(redisCacheConfiguration).build(); } }
order 예약어
로 인한 생성오류가 발생한다@Table
)가 필요하다Order.java
를 생성package com.example.springbootredissimplestarter.domain; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.Table; @Data @NoArgsConstructor @AllArgsConstructor @Entity @Table(name = "ORDERS") public class Order { private static final long serialVersionUID = 1L; @Id @GeneratedValue private Integer id; private String orderCode; private String orderObject; private String orderStatus; private Integer orderPrice; }
OrderRepository.java
를 생성package com.example.springbootredissimplestarter.repository; import com.example.springbootredissimplestarter.domain.Order; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface OrderRepository extends JpaRepository<Order, Integer> { }
OrderNotFoundException.java
와 OrderStatusException.java
를 생성// OrderNotFoundException.java package com.example.springbootredissimplestarter.exception; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ResponseStatus; @ResponseStatus(HttpStatus.NOT_FOUND) public class OrderNotFoundException extends RuntimeException { private static final long serialVersionUID = 1L; public OrderNotFoundException(String message) { super(message); } }
// OrderStatusException.java package com.example.springbootredissimplestarter.exception; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.ResponseStatus; @ResponseStatus(HttpStatus.BAD_REQUEST) public class OrderStatusException extends RuntimeException { private static final long serialVersionUID = 1L; public OrderStatusException(String message) { super(message); } }
OrderService.java
인터페이스와 OrderServiceImpl.java
를 생성// OrderService.java - interface package com.example.springbootredissimplestarter.service; import com.example.springbootredissimplestarter.domain.Order; import java.util.List; public interface OrderService { public Order createOrder(Order order); public Order getOrder(Integer orderId); public Order updateOrder(Order order, Integer orderId); public void deleteOrder(Integer orderId); public List<Order> getAllOrders(); }
// OrderServiceImpl.java - implementation package com.example.springbootredissimplestarter.service; import com.example.springbootredissimplestarter.domain.Order; import com.example.springbootredissimplestarter.exception.OrderNotFoundException; import com.example.springbootredissimplestarter.exception.OrderStatusException; import com.example.springbootredissimplestarter.repository.OrderRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CachePut; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import java.util.List; @Service public class OrderServiceImpl implements OrderService { @Autowired private OrderRepository orderRepository; @Override public Order createOrder(Order order) { return orderRepository.save(order); } @Override @Cacheable(value = "Order", key = "#orderId", cacheManager = "testCacheManager") public Order getOrder(Integer orderId) { return orderRepository.findById(orderId).orElseThrow(() -> new OrderNotFoundException("Order Not Found")); } @Override @CachePut(value = "Order", key = "#orderId", cacheManager = "testCacheManager") public Order updateOrder(Order order, Integer orderId) { /* order status 변화주기 status: ready -> processing -> shipped -> delivered */ Order orderObject = orderRepository.findById(orderId).orElseThrow(() -> new OrderNotFoundException("Order Not Found")); if (orderObject.getOrderStatus().equals("ready")) { orderObject.setOrderStatus("processing"); } else if (orderObject.getOrderStatus().equals("processing")) { orderObject.setOrderStatus("shipped"); } else if (orderObject.getOrderStatus().equals("shipped")) { orderObject.setOrderStatus("delivered"); } else { throw new OrderStatusException("Order Status Cannot Change"); } return orderRepository.save(orderObject); } @Override @CacheEvict(value = "Order", key = "#orderId", cacheManager = "testCacheManager") public void deleteOrder(Integer orderId) { Order orderObject = orderRepository.findById(orderId).orElseThrow(() -> new OrderNotFoundException("Order Not Found")); orderRepository.delete(orderObject); } @Override @Cacheable(value = "Order", cacheManager = "testCacheManager") public List<Order> getAllOrders() { return orderRepository.findAll(); } }
OrderController.java
생성package com.example.springbootredissimplestarter.controller; import com.example.springbootredissimplestarter.domain.Order; import com.example.springbootredissimplestarter.service.OrderService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("/order") public class OrderController { @Autowired OrderService orderService; @PostMapping() public Order createOrder(@RequestBody Order order) { return orderService.createOrder(order); } @GetMapping() public ResponseEntity<List<Order>> getOrder() { return ResponseEntity.ok(orderService.getAllOrders()); } @GetMapping("/{id}") public Order getOrder(@PathVariable Integer id) { return orderService.getOrder(id); } @PutMapping("/{id}") public Order updateOrder(@PathVariable Integer id, @RequestBody Order order) { return orderService.updateOrder(order, id); } @DeleteMapping("/{id}") public String deleteOrder(@PathVariable Integer id) { orderService.deleteOrder(id); return "Order with id: " + id + " deleted."; } }
application.properties
의 설정에 따라서 쿼리가 찍히게 된다curl -d '{"orderCode":"AGGEKF123","orderObject":"로제떡볶이","orderStatus":"ready","orderPrice":17000}' -H "Accept: application/json" -H "Content-Type: application/json" -X POST http://localhost:8080/order
curl -X GET http://localhost:8080/order
curl -X GET http://localhost:8080/order/1