springboot入门之八 数据缓存之Redis Mongo
1. 整合redis
依赖导入
redis的引用包:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--session管理委托给redis-->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<!--集群管理使用jedis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
1.1 单机redis
配置文件
server:
port: 8081
spring:
redis:
##单机redis
# Redis数据库索引(默认为0)
database : 0
# Redis服务器地址
host : localhost
# Redis服务器连接端口
port : 6379
# Redis服务器连接密码(默认为空)
password :
# 连接超时时间(毫秒)
timeout : 1000
1.2 redis启动器
@EnableCaching
// session托管给redis
// @EnableRedisHttpSession(maxInactiveIntervalInSeconds = 60)
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
public class RedisConfig {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
/**
* 自定义redis序列化
*
* @param connectionFactory
* @return
*/
@Bean(name = "redisTemplate2")
public RedisTemplate<Object, Object> redisTemplate2(RedisConnectionFactory connectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(mapper);
template.setValueSerializer(serializer);
// 使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
1.3 配置redis集群
配置文件
spring:
redis:
# redis.cluster
cluster:
nodes: 192.168.0.8:7004,192.168.0.8:7000,192.168.0.8:7002,192.168.0.8:7003,192.168.0.8:7005,192.168.0.8:7001
增加集群启动器
@Configuration
public class RedisClusterConfig {
@Value("${spring.redis.cluster.nodes}")
private String clusterNodes;
@Bean
public JedisCluster getJedisCluster() {
String[] cNodes = clusterNodes.split(",");
Set<HostAndPort> nodes = new HashSet<>();
// 分割出集群节点
for (String node : cNodes) {
String[] hp = node.split(":");
System.out.println("clusterNodes------------" + hp[0] + "------" + hp[1]);
nodes.add(new HostAndPort(hp[0], Integer.parseInt(hp[1])));
}
// 创建集群对象
return new JedisCluster(nodes, 5000);
}
}
1.4 redisTemplate介绍
RedisTemplate是Spring Data Redis提供给用户的最高级的抽象客户端,用户可直接通过RedisTemplate进行多种操作,那么,我们先来看看RedisTemplate封装了哪些操作。下面这列表是RedisTemplate的继承关系和所有方法(已过滤重载方法,共有81个方法)
(1) 类继承关系
//RedisAccessor是RedisTemplate定义普通属性的基类,不直接使用
//RedisOperations是指定RedisTemplate实现的Redis connection操作的集合接口
//BeanClassLoaderAware是给其实现类是设置类加载器的接口
RedisTemplate<K, V> extends RedisAccessor implements RedisOperations<K, V>, BeanClassLoaderAware
(2) 方法
//配置默认序列化与反序列化工具类
2.afterPropertiesSet
//根据参数执行相关operation操作,例如,事务
3.execute
//执行pipelining流水线相关操作
4.executePipelined
//执行指定connection连接的相关操作
5.executeWithStickyConnection
//执行session内的execute方法
6.executeSession
//创建RedisConnection代理类
7.createRedisConnectionProxy
//connection连接的预处理
8.preProcessConnection
//结果的后处理,默认什么都不做
9.postProcessResult
//是否向RedisCallback暴露本地连接
10.isExposeConnection
//设置是否向RedisCallback暴露本地连接
11.setExposeConnection
//12到26都是设置和获取相关序列化工具类
12.isEnableDefaultSerializer
13.setEnableDefaultSerializer
14.getDefaultSerializer
15.setDefaultSerializer
16.setKeySerializer
17.getKeySerializer
18.setValueSerializer
19.getValueSerializer
20.getHashKeySerializer
21.setHashKeySerializer
22.getHashValueSerializer
23.setHashValueSerializer
24.getStringSerializer
25.setStringSerializer
26.setScriptExecutor
//27到34为私有方法,不对外提供使用
27.rawKey
28.rawString
29.rawValue
30.rawKeys
31.deserializeKey
32.deserializeMixedResults
33.deserializeSet
34.convertTupleValues
//执行事务
35.exec
36.execRaw
//删除操作
37.delete
//接触链接
38.unlink
//查看是否含有指定key
39.hasKey
40.countExistingKeys
//设置过期时间
41.expire
42.expireAt
//转换成字节流并向channel发送message
43.convertAndSend
//获取过期时间
44.getExpire
//根据传入的正则表达式返回所有的key
46.keys
//取消指定key的过期时间
47.persist
//移动指定的key和index到数据库中
48.move
//从键空间随机获取一个key
49.randomKey
//将指定key改成目标key
50.rename
//key不存在时,将指定key改成目标key
51.renameIfAbsent
//设置存储在指定key的类型
52.type
//检索存储在key的值的序列化版本
53.dump
//执行Redis的restore的命令
54.restore
//标记事务阻塞的开始
55.multi
//丢弃所有在multi之后发出的命令
56.discard
//观察指定key在事务处理开始即multi之后的修改情况
57.watch
//刷新先前观察的所有key
58.unwatch
//为key元素排序
59.sort
//关闭客户端连接
60.killClient
//请求连接客户端的相关信息和统计数据
61.getClientList
//更改复制配置到新的master
62.slaveOf
//将本机更改为master
63.slaveOfNoOne
//64到79都是获取相对应的操作
64.opsForCluster
65.opsForGeo
66.boundGeoOps
67.boundHashOps
68.opsForHash
69.opsForHyperLogLog
70.opsForList
71.boundListOps
72.boundSetOps
73.opsForSet
74.opsForStream
75.boundStreamOps
76.boundValueOps
77.opsForValue
78.boundZSetOps
79.opsForZSet
//设置是否支持事务
80.setEnableTransactionSupport
//设置bean的类加载器
81.setBeanClassLoader
(3)功能介绍
spring-data-redis的提供了如下功能:
连接池自动管理,提供了一个高度封装的“RedisTemplate”类
进行了归类封装,将同一类型操作封装为operation接口
ValueOperations:简单K-V操作
SetOperations:set类型数据操作
ZSetOperations:zset类型数据操作
HashOperations:针对map类型的数据操作
ListOperations:针对list类型的数据操作
提供了对key的“bound”(绑定)便捷化操作API,可以通过bound封装指定的key,然后进行一系列的操作而无须“显式”的再次指定Key,即BoundKeyOperations:
BoundValueOperations
BoundSetOperations
BoundListOperations
BoundSetOperations
BoundHashOperations
将事务操作封装,有容器控制。
针对数据的“序列化/反序列化”,提供了多种可选择策略(RedisSerializer)
1.JdkSerializationRedisSerializer:POJO对象的存取场景,使用JDK本身序列化机制,将pojo类通过ObjectInputStream/ObjectOutputStream进行序列化操作,最终redis-server中将存储字节序列。是目前最常用的序列化策略。
2.StringRedisSerializer:Key或者value为字符串的场景,根据指定的charset对数据的字节序列编码成string,是“newString(bytes,charset)”和“string.getBytes(charset)”的直接封装。是最轻量级和高效的策略。
3.JacksonJsonRedisSerializer:jackson-json工具提供了javabean与json之间的转换能力,可以将pojo实例序列化成json格式存储在redis中,也可以将json格式的数据转换成pojo实例。因为jackson工具在序列化和反序列化时,需要明确指定Class类型,因此此策略封装起来稍微复杂。
4.OxmSerializer:提供了将javabean与xml之间的转换能力,目前可用的三方支持包括jaxb,apache-xmlbeans;redis存储的数据将是xml工具。不过使用此策略,编程将会有些难度,而且效率最低;不建议使用。【需要spring-oxm模块的支持】
如果你的数据需要被第三方工具解析,那么数据应该使用StringRedisSerializer而不是JdkSerializationRedisSerializer。
三.具体使用
(1) String类型:
插入操作
UserInfo markIfFirstSync = new UserInfo();
userRedisTemplate.opsForValue().set(CACHE_KEY, markIfFirstSync);
向redis中某个string类型的key下面插入一个对象。
批量插入操作
public Map<String, OrgRelationInfo> mappingRelationRefresh = new HashMap<>();
redisTemplate.opsForValue().multiSet(mappingRelationRefresh);
获取对象操作
userRedisTemplate.opsForValue().get(CACHE_NAME + CACHE_KEY_EXIST_PREFIX);
从redis中获取某个key下面的某个对象,如果不存在就返回null。
//批量获取对象操作
List sourceIdList = new ArrayList<>();
List orgMappingRelationList = redisTemplate.opsForValue().multiGet(sourceIdList);
//从redis中获取多个key下面的多个对象,返回一个List列表,但是即使对应的key下面没有值,这个value也会返回,不过是
//null,因此要判断是否List都为空,不能够用isEmpty直接判断,而应该一个一个的判断是否为空,才能判断整体为空。
(2) List类型
批量插入
//向redis的某个key下面的list列表里面插入一个list列表,不会去重。
List remainOrgNodes = new ArrayList<>();
redisTemplate.opsForList().leftPushAll(CACHE_KEY, remainOrgNodes);
批量取出
//从redis中取出某一个key下面的list列表, 0表示从列表的第0个元素开始取,-1表示直取到倒数第一个元素,也就是整个列表的所有元素都取出来。
List lastRemainOrgNodeList = redisTemplate.opsForList().range(CACHE_NAME + CACHE_REMAIN_KEY_PREFIX, 0, -1);
(3) Hash类型
批量插入
// 向redis中某个key下面插入key,hash的Map。
Map<Long, UserRelationInfo> value = new HashMap<>();
userHashRedisTemplate.opsForHash().putAll(KEY, value );
-单个删除
//从redis中某个key下面删除掉某个hashkey所在的value。
userHashRedisTemplate.opsForHash().delete(key, sourceOrgId);
单个获取
//从redis中某个key下面得到这个key对应的hashkey的value值。前一个key只能是String类型,hashKey可以声明为自己需要的类型。
userHashRedisTemplate.opsForHash().get(Key, hashKey);
批量获取
//从redis中得到某个key下面的所有的hashkey和hashvalue值。
Map<Object, Object> userOrgMap = userHashRedisTemplate.opsForHash().entries(getUserNodeCacheKey(syncUserNode.getSourceId()));
(4) Set类型
单个插入
//向redis的某个key下面的set列表里面插入一个元素,回去重,且无序。
userRoleSetRedisTemplate.opsForSet().add(KEY, cloudtOrgRoleInfo);
批量取出
//从redis的某个key下面得到set集合的所有元素,返回的也是一个Set集合。
cloudtOrgRoleSet = userRoleSetRedisTemplate.opsForSet().members(KEY);
单个删除
//从redis的某个key下面的set集合中删除掉一个元素。
userRoleSetRedisTemplate.opsForSet().remove( KEY, subDeleteOrgRoleUserArray[i]);
1.5 redis实战 缓存用户信息
实现Cookie传值,因为集群开发的话,session值会变,A机 和 B机 的session值一定改变。因此需要种入Cookie值保证用户登录之后不会出现,登录到A机 就只能在A机使用了。一但请求转到B机,就要重新登录了。
写入Cookie
public static void writeLoginToken(HttpServletResponse response, String token) {
Cookie ck = new Cookie(COOKIE_NAME, token);
ck.setPath("/");
ck.setHttpOnly(true);
ck.setMaxAge(EXPIRE); // 保存时间
log.info("write cookie name:{} , cookie vlaue: {}", ck.getName(), ck.getValue());
response.addCookie(ck);
}
读取Cookie
public static String readLoginToken(HttpServletRequest request) {
Cookie[] cks = request.getCookies();
if (cks != null) {
for (Cookie ck : cks) {
log.info("read cookie name:{} , cookie value:{}", ck.getName(), ck.getValue());
if (ck.getName().toString().equals(COOKIE_NAME)) {
return ck.getValue();
}
}
}
return null;
}
删除Cookie(用于登出)
public static void delLoginToken(HttpServletResponse response, HttpServletRequest request) {
Cookie[] cks = request.getCookies();
if (cks != null) {
for (Cookie ck : cks) {
if (ck.getName().toString().equals(COOKIE_NAME)) {
ck.setPath("/");
ck.setMaxAge(0); // 0 = del
log.info("del cookie name : {} ,cookie vlaue: {}", ck.getName(), ck.getValue());
response.addCookie(ck);
return;
}
}
}
}
2.2 得到Cookie值之后只要把Cookie的值放入到redis里面,设置一样的时间值,就实现了集群登录。
// String token = UUID.randomUUID().toString();
String token = session.getId(); // 获取登录 token
Integer expire = CookieUtil.EXPIRE; // 设置时间
// 设置 token 至 redis
redisTemplate.opsForValue().set(
// String TOKEN_PREFIX = "sample_token_%s";
String.format(RedisConfig.TOKEN_PREFIX, token) // key
, sellerInfo.getId().toString() // 关键字 value
, expire, // 时间
TimeUnit.SECONDS); // 格式
// 设置 token 至 cookie
CookieUtil.writeLoginToken(httpServletResponse, token);
2.3结合使用
@Autowired
private StringRedisTemplate redisTemplate;
@RequestMapping("/do-login")
@ResponseBody
public String login(@RequestParam("userName") String userName, @RequestParam("password") String password,
HttpServletResponse httpServletResponse, HttpSession session) {
// 数据库匹配
User sellerInfo = userService.findByUserName(userName); // 通过关键字 查询用户
if (sellerInfo == null) { // 判断用户是否存在
return "用户不存在";
}
// String token = UUID.randomUUID().toString();
String token = session.getId(); // 获取登录 token
Integer expire = CookieUtil.EXPIRE; // 设置时间
// 设置 token 至 redis
redisTemplate.opsForValue().set(
// String TOKEN_PREFIX = "sample_token_%s";
String.format(RedisConfig.TOKEN_PREFIX, token) // key
, sellerInfo.getId().toString() // 关键字 value
, expire, // 时间
TimeUnit.SECONDS); // 格式
// 设置 token 至 cookie
CookieUtil.writeLoginToken(httpServletResponse, token);
return "success";
}
2.4登出
@RequestMapping("/do-logout")
public String logout(HttpServletRequest request, HttpServletResponse response, Map<String, Object> map) {
// cookie查询
String token = CookieUtil.readLoginToken(request);
if (!StringUtils.isEmpty(token)) {
// 清除 redis
redisTemplate.opsForValue().getOperations().delete(String.format(RedisConfig.TOKEN_PREFIX, token));
// del cookie
CookieUtil.delLoginToken(response, request);
}
return "login";
}
用户信息存储,引入数据库,故整合下jpa
整合jpa
配置文件增加jpa
spring:
## jsp
mvc:
view:
prefix: /WEB-INF/jsp/
suffix: .jsp
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3307/springboot?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
hikari: # springboot 2.0 整合了hikari ,据说这是目前性能最好的java数据库连接池
username: root
password:
jpa:
show-sql: true
database: mysql
hibernate:
ddl-auto: update
pom依赖增加jpa
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- jsp -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<!--<scope>provided</scope>-->
</dependency>
model源码
@Entity
@Data
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String userName;
private String passWord;
private Integer age;
}
持久化源码
public interface UserDao extends JpaRepository<User, Integer> {
User findByUserName(String userName);
User findByIdIs(Integer id);
}
启动测试下
输入用户密码登录后查看redis数据
源码地址
https://gitee.com/xiaolaifeng/sample.springboot/tree/master/springboot-teach9
2. 整合mongo
2.1 引入pom依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
2.2 定义model
@Data
@Document(collection = "user")
public class User implements Serializable {
@Id
private Long id;
private String userName;
private String passWord;
private Integer age;
private Date createTime;
}
2.3 编写持久化类
UserDao.java UserDaoImpl.java
public interface UserDao {
User findByUserName(String userName);
User save(User user);
void update(User user);
List<User> findAll();
void delete(Integer id);
}
@Component
public class UserDaoImpl implements UserDao {
@Autowired
private MongoTemplate mongoTemplate;
/*
* 根据姓名查询用户
*/
@Override
public User findByUserName(String userName) {
// 查询的条件
Query query = new Query(Criteria.where("userName").is(userName));
return mongoTemplate.findOne(query, User.class);
}
/**
* 新增信息
*
* @param User
*/
@Override
public User save(User User) {
return mongoTemplate.save(User);
}
/**
* 修改信息
*
* @param User
*/
@Override
public void update(User User) {
// 修改的条件
Query query = new Query(Criteria.where("id").is(User.getId()));
// 修改的内容
Update update = new Update();
update.set("name", User.getUserName());
mongoTemplate.updateFirst(query, update, User.class);
}
/**
* 查询所有信息
*
* @return
*/
@Override
public List<User> findAll() {
return mongoTemplate.findAll(User.class);
}
/**
* 根据id查询所有信息
*
* @param id
*/
@Override
public void delete(Integer id) {
User byId = mongoTemplate.findById(1, User.class);
mongoTemplate.remove(byId);
}
}
2.4 编写test
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringbootTeach9ApplicationTests {
@Autowired
private UserDao userDao;
@Test
public void contextLoads() {}
@Test
public void addUser() {
User user = new User();
user.setId(1l);
user.setUserName("李四");
user.setPassWord("111111");
user = userDao.save(user);
System.out.println(user.getId());
}
/**
* 查询所有信息
*/
@Test
public void findAll() {
List<User> all = userDao.findAll();
System.out.println(all.size());
}
/**
* 修改信息
*/
@Test
public void update() {
User user = new User();
user.setId(1l);
user.setUserName("张三2");
userDao.update(user);
}
/**
* 删除信息
*/
@Test
public void delete() {
userDao.delete(3);
}
}
源码地址 分支mongo
https://gitee.com/xiaolaifeng/sample.springboot/tree/master/springboot-teach9