目标
在原有SpringBoot项目中,集成Redis,并实现Dao层,Service层,Controller层。
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
使用Spring Boot的Redis依赖。
application-dev.yaml
spring:
redis:
host: ${REDIS_HOST:localhost}
port: ${REDIS_PORT:6379}
database: 0
password: ${REDIS_PASSWORD}
RedisConfig.java
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* @author zyl
*/
@Configuration
public class RedisConfig {
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory();
}
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory());
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.activateDefaultTyping(om.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY);
jackson2JsonRedisSerializer.setObjectMapper(om);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用jackson的序列化方式
template.setHashKeySerializer(jackson2JsonRedisSerializer);
// value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
这里配置使用redis的链接池,以及key,hash key和数据存储使用的序列化方式。
Domain层
City.java
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* @author zyl
*/
@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
public class City implements Serializable {
private Long id;
private String name;
private String state;
private String country;
}
注意:这里要实现Serializable接口,只有这样,我们这里使用的Redis依赖库才能够正常使用。
Dao层
CityDao.java
/**
* @author zyl
*/
public interface CityDao {
void save(City city);
City findById(Long id);
void delete(Long id);
}
上面是Dao的定义层,下面来看看Dao的实现层:
CityDaoImpl.java
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
/**
* @author zyl
*/
@Repository
public class CityDaoImpl implements CityDao {
private static final String HASH_REFERENCE = "City";
@Resource
private RedisTemplate<String, City> redisTemplate;
@Override
public void save(City city) {
Long id = city.getId();
String key = HASH_REFERENCE + "_" + id;
BoundHashOperations<String, Long, City> boundHashOperations = redisTemplate.boundHashOps(key);
boundHashOperations.putIfAbsent(id, city);
boundHashOperations.expire(60, TimeUnit.SECONDS);
}
@Override
public City findById(Long id) {
String key = HASH_REFERENCE + "_" +id;
BoundHashOperations<String, Long, City> boundHashOperations = redisTemplate.boundHashOps(key);
return boundHashOperations.get(id);
}
@Override
public void delete(Long id) {
String key = HASH_REFERENCE + "_" +id;
BoundHashOperations<String, Long, City> boundHashOperations = redisTemplate.boundHashOps(key);
boundHashOperations.delete(id);
}
}
注意这里重点是通过BoundHashOperations类来实现Redis相关对象的操作。BoundHashOperations类是操作Redis的重点类。如果使用通过HashOperations类让redis里面的数据过期处理,有点麻烦,故这里使用BoundHashOperations类处理。
VO层
ApiRes.java
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.http.HttpStatus;
/**
* 通用响应类
* @author zyl
*/
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Data
public class ApiRes<T> {
private int status = HttpStatus.OK.value();
private String message;
private T data;
public static final String SUCCESS_MSG = "操作成功";
public static final String ERROR_MSG = "操作成功";
/**
* 返回成功数据
* @author zyl
* @return 成功消息
*/
public static <T> ApiRes<T> success(T data)
{
return ApiRes.success(SUCCESS_MSG, data);
}
/**
* 返回成功消息
* @author zyl
* @param msg 返回内容
* @return 成功消息
*/
public static <T> ApiRes<T> success(String msg)
{
return ApiRes.success(msg, null);
}
/**
* 返回成功消息
* @author zyl
* @param msg 返回内容
* @param data 数据对象
* @return 成功消息
*/
public static <T> ApiRes<T> success(String msg, T data)
{
return new ApiRes<>(HttpStatus.OK.value(), msg, data);
}
/**
* 返回错误消息
* @author zyl
*/
public static <T> ApiRes<T> error()
{
return ApiRes.error(ERROR_MSG);
}
/**
* 返回错误消息
* @author zyl
* @param msg 返回内容
* @return 警告消息
*/
public static <T> ApiRes<T> error(String msg)
{
return ApiRes.error(msg, null);
}
/**
* 返回错误消息
* @author zyl
* @param msg 返回内容
* @param data 数据对象
* @return 警告消息
*/
public static <T> ApiRes<T> error(String msg, T data)
{
return new ApiRes<>(HttpStatus.INTERNAL_SERVER_ERROR.value(), msg, data);
}
/**
* 返回错误消息
* @author zyl
* @param msg 返回内容
* @param code 响应编码
* @return 警告消息
*/
public static <T> ApiRes<T> error(int code, String msg)
{
return new ApiRes<>(code, msg, null);
}
}
这里定义实现了统一响应VO,下面继续实现定义City的响应VO:
CityRes.java
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author zyl
*/
@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
public class CityRes {
private Long id;
private String name;
private String state;
private String country;
}
City响应VO定义实现。
Service层
CityService.java
/**
* 测试city模板
* @author zyl
*/
public interface CityService {
ApiRes<CityRes> findByState(String state);
ApiRes<CityRes> findById(Long id);
void delete(Long id);
}
CityServiceImpl.java
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* @author zyl
*/
@Service
public class CityServiceImpl implements CityService {
@Resource
private CityMapper cityMapper;
@Resource
private CityDao cityDao;
@Override
public ApiRes<CityRes> findByState(String state) {
City city = cityMapper.findByState(state);
if (city == null) {
throw new HandleException("没有找到城市数据");
}
CityRes cityRes = CityRes.builder().build();
BeanUtils.copyProperties(city, cityRes);
return ApiRes.success(cityRes);
}
@Override
public ApiRes<CityRes> findById(Long id) {
// 先查缓存
City city = cityDao.findById(id);
if (city == null) {
// 查数据库
city = cityMapper.findById(id);
if (city != null) {
// 更新缓存
cityDao.save(city);
}
}
if (city == null) {
throw new HandleException("没有找到城市数据");
}
CityRes cityRes = CityRes.builder().build();
BeanUtils.copyProperties(city, cityRes);
return ApiRes.success(cityRes);
}
@Override
public void delete(Long id) {
cityMapper.delete(id);
cityDao.delete(id);
}
}
这里的HandleException类,是自定义异常处理类,这里就不再给出详细实现了。CityMapper类是mybatis的相关定义实现,这不是这里需要关注的,故也不再给出详细实现了。
Controller层
HelloController.java
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
/**
* 测试模板
* @author zyl
*/
@RefreshScope
@RestController
@RequestMapping("/hello")
public class HelloController {
@Resource
private CityService cityService;
@GetMapping("/city/findById")
public ResponseEntity<ApiRes<CityRes>> cityFindById(@RequestParam Long id) {
return ResponseEntity.ok(cityService.findById(id));
}
@GetMapping("/city/delete")
public ResponseEntity<ApiRes<String>> cityDelete(@RequestParam Long id) {
cityService.delete(id);
ApiRes<String> apiRes = ApiRes.success("删除成功");
return ResponseEntity.ok(apiRes);
}
}
SQL
drop table if exists city;
create table city (id int primary key auto_increment, name varchar(200), state varchar(200), country varchar(200));
insert into city (name, state, country) values ('San Francisco', 'CA', 'US');
insert into city (name, state, country) values ('San Francisco2', 'CA2', 'US2');
测试
这里只看看Redis里面存的数据效果图:
看看postman中的响应:
总结
这里主要使用BoundHashOperations类来处理redis中的对象。为什么不使用HashOperations类?因为不能直接使用HashOperations类对对象进行过期时间设置,而BoundHashOperations类可以这弄。这里值得注意的是,我们只能对Redis里面的整个散列进行过期,不能对散列里面的具体key过期。这一点Redis规范要注意一下。具体如下图:
我们能对1过期,但是不能对2进行过期处理。HashOperations类就是把多个对象存在一个1里面,我们这里为了实现单个对象过期处理,就只能使用BoundHashOperations类,在一个1里面存一个对象,这样实现对一个对象的redis过期。
总的来说,BoundHashOperations类可以自己过期Redis数据,HashOperations类没有直接过期数据方法。
参考:
- Redis hashes
- Spring boot redis javadoc
- Spring Boot Redis CRUD Example