spring-session用mysql实现session共享实践
前段时间,写了篇<spring-session-data-redis解决session共享的问题>文章,介绍了spring-session使用redis存储实现session共享的内部机制,
遇到很多人提问到jdbc的实现或者为什么只有理论没有实践?索性就实践一下jdbc方式。
1.spring-session实现了使用不同存储介质保存共享的session的api
spring-session实现session共享的实现有三种:
jdbc:使用数据库做介质,支持多种数据,如oracle,sqlserver,mysql等,对应的实现类
RedisOperationsSessionRepository
<图一 支持的数据库类型及脚本>
redis:使用redis做存储介质,对应的实现类为:
JdbcOperationsSessionRepository
Hazelcast:使用内存做存储介质,对应的实现类为:
HazelcastSessionRepository
Hazelcast作为一个高度可扩展的数据分发和集群平台,提供了高效的、可扩展的分布式数据存储、数据缓存。Hazelcast是开源的,在分布式技术方面,Hazelcast提供了十分友好的接口供开发者选择,如Map,Queue,ExecutorService, Lock和Jcache。
Hazelcast的稳定性很高,分布式应用可以使用Hazelcast进行存储数据、同步数据、发布订阅消息等。Hazelcast是基于Java开发的,其客户端有Java, C/C++, .NET以及REST。Hazelcast同时也支持memcache协议。它很好的支持了Hibernate,可以很容易的在当今流行的数据库系统中应用。
2.spring-session-jdbc使用mysql保存共享session的实现实践
2.0 准备工作
如<图一 支持的数据库类型及脚本>所示,找到schema-mysql.sql脚本,放入sql-client客户端执行,如sqlyog,红色部分是我自己添加的,为了可以重复执行的。
DROP TABLE IF EXISTS SPRING_SESSION_ATTRIBUTES;
DROP TABLE IF EXISTS SPRING_SESSION;
CREATE TABLE SPRING_SESSION (
PRIMARY_ID CHAR(36) NOT NULL,
SESSION_ID CHAR(36) NOT NULL,
CREATION_TIME BIGINT NOT NULL,
LAST_ACCESS_TIME BIGINT NOT NULL,
MAX_INACTIVE_INTERVAL INT NOT NULL,
EXPIRY_TIME BIGINT NOT NULL,
PRINCIPAL_NAME VARCHAR(100),
CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
) ENGINE=INNODB ROW_FORMAT=DYNAMIC; CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME); CREATE TABLE SPRING_SESSION_ATTRIBUTES (
SESSION_PRIMARY_ID CHAR(36) NOT NULL,
ATTRIBUTE_NAME VARCHAR(200) NOT NULL,
ATTRIBUTE_BYTES BLOB NOT NULL,
CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_PRIMARY_ID, ATTRIBUTE_NAME),
CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_PRIMARY_ID) REFERENCES SPRING_SESSION(PRIMARY_ID) ON DELETE CASCADE
) ENGINE=INNODB ROW_FORMAT=DYNAMIC;
此时创建了两张表
SPRING_SESSION
SPRING_SESSION_ATTRIBUTES
通过PRIMARY_ID关联,执行结果如下:
2.1 使用sts创建spring-boot项目,项目名称session-jdbc,使用jdbc,mysql,web(方便测试)的start,创建完项目如下:
2.2 添加spring-session-jdbc依赖
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-jdbc</artifactId>
</dependency>
2.3 配置jdbc属性application.properties
spring.session.store-type=JDBC
spring.datasource.url=jdbc:mysql://localhost:3306/www?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false
spring.datasource.username=root
spring.datasource.password=wangwei456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.session.jdbc.cleanup-cron=0 * * * * *
注意,新版mysql的url需要增加
serverTimezone选项
而且,driver-class-name也变为com.mysql.cj.jdbc.Driver,否则有报警。
2.4 创建测试controller
package com.example.demo; import javax.servlet.http.HttpSession; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; @RestController
@RequestMapping("/jdbc")
public class TestController {
@RequestMapping("/welcome")
public String welcome(HttpSession httpSession) {
httpSession.setAttribute("hello", "world");
return "hello world !"; }
}
2.5 测试
启动spring-boot项目session-jdbc,访问http://localhost:8080/jdbc/welcome/,返回"hello world".
查看数据库数据信息
SPRING_SESSION表信息
SPRING_SESSION_ATTRIBUTES表信息
2.6 Q&A
2.6.1 session_id是如何产生的?
断点调试进入
此时的cookie和请求的一致。
进入HttpSessionAdapter的setAttribute属性方法,再调用JdbcOperationsSessionRepository的setAttribute方法
@Override
public void setAttribute(String attributeName, Object attributeValue) {
boolean attributeExists = (this.delegate.getAttribute(attributeName) != null);
boolean attributeRemoved = (attributeValue == null);
if (!attributeExists && attributeRemoved) {
return;
}
if (attributeExists) {
if (attributeRemoved) {
this.delta.merge(attributeName, DeltaValue.REMOVED, (oldDeltaValue,
deltaValue) -> (oldDeltaValue == DeltaValue.ADDED) ? null
: deltaValue);
}
else {
this.delta.merge(attributeName, DeltaValue.UPDATED,
(oldDeltaValue,
deltaValue) -> (oldDeltaValue == DeltaValue.ADDED)
? oldDeltaValue
: deltaValue);
}
}
else {
this.delta.merge(attributeName, DeltaValue.ADDED,
(oldDeltaValue, deltaValue) -> (oldDeltaValue == DeltaValue.ADDED)
? oldDeltaValue
: DeltaValue.UPDATED);
}
this.delegate.setAttribute(attributeName, value(attributeValue));
if (PRINCIPAL_NAME_INDEX_NAME.equals(attributeName) ||
SPRING_SECURITY_CONTEXT.equals(attributeName)) {
this.changed = true;
}
}
调用内部类JdbcSession的构造方法
JdbcSession() {
this.delegate = new MapSession();
this.isNew = true;
this.primaryKey = UUID.randomUUID().toString();
}
其中primarykey的产生,是随机产生的,而jdbcSession的一个成员变量delegate是MapSession的实例
/**
* Creates a new instance with a secure randomly generated identifier.
*/
public MapSession() {
this(generateId());
}
而generateId()方法,调用了随机uuid的产生
private static String generateId() {
return UUID.randomUUID().toString();
}
2.6.2 为何和cookie的不一致?
response中返回的cookie值为:NDM1Mjg1YjAtMmVjMi00MmQ0LWJjY2UtYzFjY2U4NzQzNDNm
数据库中sessionId为435285b0-2ec2-42d4-bcce-c1cce874343f
为什么会这样呢?
@Override
public List<String> readCookieValues(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
List<String> matchingCookieValues = new ArrayList<>();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (this.cookieName.equals(cookie.getName())) {
String sessionId = (this.useBase64Encoding
? base64Decode(cookie.getValue())
: cookie.getValue());
if (sessionId == null) {
continue;
}
if (this.jvmRoute != null && sessionId.endsWith(this.jvmRoute)) {
sessionId = sessionId.substring(0,
sessionId.length() - this.jvmRoute.length());
}
matchingCookieValues.add(sessionId);
}
}
}
return matchingCookieValues;
}
从代码中我们可以知道,中间查了一个base64
435285b0-2ec2-42d4-bcce-c1cce874343f base64加密后即为:NDM1Mjg1YjAtMmVjMi00MmQ0LWJjY2UtYzFjY2U4NzQzNDNm
3.总结
1.浏览器第一次请求的时候,服务端创建一个session,并且将名为SESSION的属性作为响应cookie返回浏览器
2. 浏览器保存名称为SESSION的cookie作为唯一标识,下次访问服务端时带上此cookie
3.服务端session中的名为SESSION的属性和浏览器保存的cookie是不一致的,为保密原因,进行了base64加密。
4.spring-session使用了简单的配置,可以实现session持久化到数据库。
参考文献:
【1】https://angelbill3.iteye.com/blog/2342989
spring-session用mysql实现session共享实践的更多相关文章
- Spring Session + Redis实现分布式Session共享
发表于 2016-09-29 文章目录 1. Maven依赖 2. 配置Filter 3. Spring配置文件 4. 解决Redis云服务Unable to configure Redis to k ...
- 基于Spring Boot/Spring Session/Redis的分布式Session共享解决方案
分布式Web网站一般都会碰到集群session共享问题,之前也做过一些Spring3的项目,当时解决这个问题做过两种方案,一是利用nginx,session交给nginx控制,但是这个需要额外工作较多 ...
- 玩转spring boot——负载均衡与session共享
前言 当项目上线后,如果要修复bug或扩充功能,都需要重启tomcat服务.此时,正在使用应用的用户们就需要等待服务器的重启,而这就会造成不好的用户体验.还有,当仅仅只有一台tomcat服务时,如果 ...
- [Java][web]利用Spring随时随地获得Request和Session
利用Spring随时随地获得Request和Session 一.准备工作: 在web.xml中加入 <listener> <listener-class> org.spring ...
- 理解Spring MVC Model Attribute和Session Attribute
作为一名 Java Web 应用开发者,你已经快速学习了 request(HttpServletRequest)和 session(HttpSession)作用域.在设计和构建 Java Web 应用 ...
- Spring Security(13)——session管理
1.1 检测session超时 1.2 concurrency-control 1.3 session 固定攻击保护 Spring Security通过http元素下的子元素s ...
- Session的使用与Session共享问题
Session的使用与Session共享问题 Session方法 getId():获取sessionId,这个id不一定是数字,比方说它用字符串来表示唯一标识,所以它返回值是String; boole ...
- PHP利用MySQL保存session
实现环境: PHP 5.4.24 MySQL 5.6.19 OS X 10.9.4/Apache 2.2.26 一.代码 CREATE TABLE `session` ( `skey` ) CHARA ...
- Lost connection to MySQL server during query,MySQL设置session,global变量及网络IO与索引
Navicat导出百万级数据时,报错:2013 - Lost connection to MySQL server during query 网上一番搜索,修改mysql如下几处配置文件即可: sel ...
- Spring Boot 2 (五):Docker Compose + Spring Boot + Nginx + Mysql 实践
Spring Boot 2 (五):Docker Compose + Spring Boot + Nginx + Mysql 实践 Spring Boot + Nginx + Mysql 是实际工作中 ...
随机推荐
- 15. Life Cycle of the Products 产品的生命周期
15. Life Cycle of the Products 产品的生命周期 (1) We can see how the product life cycle works by looking at ...
- sjms-4 行为型模式
行为型模式 责任链模式 内容:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系.将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止.角色:抽象处理者(Hand ...
- 初学者必看的文章:在VM12中安装 RedHat RHEL7.2 系统的详细步骤:看我如何操纵RHEL系统
一.开始安装 1)新建虚拟机 RHEL7.2 2)成功引导系统--开机出现此画面 Install Red Hat EnterpriseLinux 7.2 安装RHLE7.2 操作系统 Test th ...
- Codeforces Codeforces Round #484 (Div. 2) E. Billiard
Codeforces Codeforces Round #484 (Div. 2) E. Billiard 题目连接: http://codeforces.com/contest/982/proble ...
- 理解特性attribute 和 属性property的区别 及相关DOM操作总结
查一下英语单词解释,两个都可以表示属性.但attribute倾向于解释为特质,而property倾向于解释私有的.这个property的私有解释可以更方便我们下面的理解. 第一部分:区别点 第一点: ...
- C#如何以管理员身份运行程序 转
在使用winform程序获取调用cmd命令提示符时,如果是win7以上的操作系统,会需要必须以管理员身份运行才会执行成功,否则无效果或提示错误. 比如在通过winform程序执行cmd命令时,某些情况 ...
- Java工具eclipse控制台console输出乱码问题
捣鼓了一下午,终于tm解决! 我的是Scanner读入,println打印乱码问题. 首先在cmd窗口运行java,是没有乱码问题的,这证明了在cmd窗口时Scanner输入的和println打印的编 ...
- form编码方式application/x-www-form-urlencoded和multipart/form-data的区别
form元素有个enctype属性,可以指定数据编码方式,有如下三种: 1. application/x-www-form-urlencoded: 表单数据编码为键值对,&分隔 2. mult ...
- poj 1050 最大子矩阵
a11 a12 a13 a14 a15 a21 a22 a23 a24 a25 a31 a32 a33 a34 a35 a41 a42 a43 ...
- pyqt5 graphics view简单使用
Graphics View提供了一个平面,用于管理和交互大量自定义的2D图形图元,以及一个用于可视化图元的视图窗口小部件,支持缩放和旋转. 该框架包括一个事件传播架构,允许场景中图元的精确双精度交互功 ...