目标
在原有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: 0password: ${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 {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(factory);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序列化方式采用jacksontemplate.setValueSerializer(jackson2JsonRedisSerializer);// hash的value序列化方式采用jacksontemplate.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";@Resourceprivate RedisTemplate<String, City> redisTemplate;@Overridepublic 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);}@Overridepublic City findById(Long id) {String key = HASH_REFERENCE + "_" +id;BoundHashOperations<String, Long, City> boundHashOperations = redisTemplate.boundHashOps(key);return boundHashOperations.get(id);}@Overridepublic 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 {@Resourceprivate CityMapper cityMapper;@Resourceprivate CityDao cityDao;@Overridepublic 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);}@Overridepublic 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);}@Overridepublic 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 {@Resourceprivate 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