spring-session用mysql实现session共享实践
liebian365 2024-10-22 15:41 24 浏览 0 评论
上一篇,写了篇<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
相关推荐
- 深度解密epoll 如何工作的?(epoll基本处理流程)
-
epoll...
- 大乐透第19082期:头奖开出7注1000万分落六地 奖池41亿元
-
2019年7月17日晚开奖的体彩超级大乐透第19082期开奖号码为:前区06、18、20、21、31,后区03、04。本期大乐透前区号码五区比为1:0:3:0:1,二区和四区号码没有给出。当期前区和值...
- 【开奖】4月27日周六:福彩、体彩(2021年4月27日体彩开奖结果)
-
4月27日开奖福彩3D第2019110期:61222选5第2019110期:0812202122排列3第19110期:303排列5第19110期:30305大乐透第19047期:0304...
- “红狒狒”落户哈尔滨铁路局(哈尔滨铁路红肠)
-
这几天,“红人”“红狒狒”在牡丹江机务段可引起了不小的轰动,众粉丝争相与其拍照留念,在该段人气爆棚!“红狒狒”到底何许人也?“红狒狒”,中文名:和谐3D型电力机车;绰号:红狒狒、番茄;制造商:大连机...
- 2D、3D、2.5D,做游戏还是搞噱头?玩家都晕了
-
前言游戏类型就像某种潮流,一种流行罢,另一种接棒成为主流。前两年的新作大多以“开放世界”为标签,在追求纯沙盒的过程中打造出一些细致的分类,比如说“类GTA沙盒”。诚然,纯碎的沙盒游戏并不多见,业内只有...
- 《战神4》PC版宣传片发布 GTX 1070即可60帧畅玩
-
在今年10月的时候索尼PlayStation官方正式宣布圣莫尼卡2018年的《战神4》将于2022年1月14日推出PC版本,官方在今天公布了一段PC版宣传片,并且公开了游戏的配置需求。下面让我们一起来...
- 男星深情好丈夫形象崩塌,半夜搂美女坐大腿,举止亲密
-
近日,于晓光被拍到深夜在酒吧玩,结束后与一名女子一起上车离开。上车后,女子直接坐在了他腿上,他也顺势搂着美女,美女满脸笑容地坐在他腿上玩手机离开。可能有人会好奇,于晓光是谁呢?于晓光是韩国艺人秋瓷炫的...
- d3d12dll丢失怎么修复?d3d12dll加载失败怎么解决?
-
d3d12.dll丢失怎么修复?d3d12.dll加载失败怎么解决?很多朋友想要运行游戏的时候都会遇到这个问题,这种情况该怎么办呢?今天系统之家小编给朋友们讲讲具体的解决方法,操作其实还蛮简单的。...
- 许多玩家反馈《生化4RE》PC一直崩溃 无法进入游戏
-
今日(3月24日),卡普空《生化危机4:重制版》正式发售,然而有部分PC玩家遇到了游戏崩溃等问题。很多玩家在贴吧发帖称游戏遇到了严重的崩溃问题,且经常反复,报错代码普遍为FatalD3Derror...
- 微软正式推出适用于WSL Linux的D3D12 GPU视频加速技术
-
今天,微软正式向WindowsSubsystemforLinux(WSL)用户发布了Direct3D12GPU视频加速支持。在微软通过WSL允许在Linux下使用Open...
- 《怪物猎人:崛起》曙光系统报错“Fatal d3d error”的解决办法
-
《怪物猎人:崛起》曙光系统报错“Fatald3derror”的解决办法不少小伙伴反应《怪物猎人:崛起》DLC曙光预载以后打不开游戏,出现了Fatald3derror类似的错误代码,这类问题的解...
- Mac+双屏,前端程序员的专业配置 - Loctek 乐歌 D3D 双屏电脑显示器支架
-
做FE也有一段日子了,电脑屏幕每天在设计稿、浏览器、IDE、即时通讯工具、Terminal、邮箱之间切换。虽然mac的工作区带来了很多灵活,但是依然略显不足。于是入手支架,把公司配的电脑和显示器发挥起...
- RPC 的原理和简单使用(rpc详解)
-
RPC的概念RPC,RemoteProcedureCall,翻译成中文就是远程过程调用,是一种进程间通信方式。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数。在调用的...
- 大厂开源的golang微服务rpc框架 — kitex
-
提前rpc估计所有的开发同学都知道,不知道的也无所谓,毕竟我也好几年没用了,今天带大家在复习一下。RPC(RemoteProcedureCall):远程过程调用,...
- 干货!一文掌握Protobuf所有语言所有用法,快收藏
-
说实话,Protobuf这个库,让人相见时难别亦难,东风无力百花残,每次等到要用它的时候,总感觉还没有完全掌握它的用法,而实际上等去百度或者谷歌的时候,教程都是多么的凌乱不堪。学会它,最直接关系到的,...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- wireshark怎么抓包 (75)
- qt sleep (64)
- cs1.6指令代码大全 (55)
- factory-method (60)
- sqlite3_bind_blob (52)
- hibernate update (63)
- c++ base64 (70)
- nc 命令 (52)
- wm_close (51)
- epollin (51)
- sqlca.sqlcode (57)
- lua ipairs (60)
- tv_usec (64)
- 命令行进入文件夹 (53)
- postgresql array (57)
- statfs函数 (57)
- .project文件 (54)
- lua require (56)
- for_each (67)
- c#工厂模式 (57)
- wxsqlite3 (66)
- dmesg -c (58)
- fopen参数 (53)
- tar -zxvf -c (55)
- 速递查询 (52)