1. 概述

本文将带你快速掌握如何使用 Spring Session JDBC 模块,把用户的会话(Session)数据持久化到关系型数据库中。

为了演示方便,我们会使用内存中的 H2 数据库。整个过程简洁高效,适合在微服务或分布式架构中实现会话共享。

✅ 核心目标:解决传统 HttpSession 在多实例部署下的数据不一致问题
❌ 常见误区:认为 Spring Session 需要大量配置 —— 其实 Spring Boot 下一行配置就够了


2. 配置方式选择

搭建示例项目最简单的方式是使用 Spring Boot,但我们也提供纯 Spring 的非 Boot 配置方式。

⚠️ 注意:你只需要根据项目实际情况选择其中一种方式即可,不需要两个都做。


3. Spring Boot 方式配置

如果你正在使用 Spring Boot,那配置简直是简单粗暴到极致。

3.1 Maven 依赖

添加以下依赖项:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency> 
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>2.1.214</version>
    <scope>runtime</scope>
</dependency>

📌 提示:Spring Boot 的 parent POM 已经管理了版本。最新版可参考 Maven Central

3.2 application.properties 配置

关键来了 —— 只需在 application.properties 中加这一行:

spring.session.store-type=jdbc

✅ 就这么一行!Spring Boot 会自动完成以下事情:

  • 创建 springSessionRepositoryFilter
  • 启用 JDBC 会话存储
  • 自动执行建表脚本(基于数据库类型)

⚠️ 如果你用的是 MySQL 或 PostgreSQL,表结构会自动适配,但 H2 是最方便的测试选择。


4. 标准 Spring 配置(无 Boot)

如果你没用 Spring Boot,而是纯 Spring 项目,也可以手动配置。

4.1 Maven 依赖

确保 pom.xml 包含:

<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-jdbc</artifactId>
    <version>3.0.0</version>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>2.1.214</version>
    <scope>runtime</scope>
</dependency>

4.2 配置类

创建一个配置类,启用 JDBC 会话支持:

@Configuration
@EnableJdbcHttpSession
public class Config extends AbstractHttpSessionApplicationInitializer {

    @Bean
    public EmbeddedDatabase dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .addScript("org/springframework/session/jdbc/schema-h2.sql")
            .build();
    }

    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

📌 要点说明:

  • @EnableJdbcHttpSession 是核心注解,启用 JDBC 会话持久化
  • EmbeddedDatabase 手动构建内存数据库并执行建表脚本
  • transactionManager 保证会话操作的事务性
  • 继承 AbstractHttpSessionApplicationInitializer 会自动注册 springSessionRepositoryFilter

💡 对比 Boot:少了自动配置,多了显式 Bean 定义,但逻辑完全一致。


5. 简单应用示例

我们来写个简单的 REST 接口,实现“保存用户喜欢的颜色”功能,验证会话是否真的持久化了。

5.1 控制器代码

@Controller
public class SpringSessionJdbcController {

    @GetMapping("/")
    public String index(Model model, HttpSession session) {
        List<String> favoriteColors = getFavColors(session);
        model.addAttribute("favoriteColors", favoriteColors);
        model.addAttribute("sessionId", session.getId());
        return "index";
    }

    @PostMapping("/saveColor")
    public String saveMessage(
        @RequestParam("color") String color, 
        HttpServletRequest request) {
        
        List<String> favoriteColors = getFavColors(request.getSession());
        if (!StringUtils.isEmpty(color)) {
            favoriteColors.add(color);
            request.getSession().setAttribute("favoriteColors", favoriteColors);
        }
        return "redirect:/";
    }

    private List<String> getFavColors(HttpSession session) {
        List<String> favoriteColors = (List<String>) session.getAttribute("favoriteColors");
        if (favoriteColors == null) {
            favoriteColors = new ArrayList<>();
        }
        return favoriteColors;
    }
}

📌 说明:

  • GET /:读取当前会话中的颜色列表
  • POST /saveColor:添加新颜色并重定向
  • 使用 HttpSession API 完全不变 —— 这正是 Spring Session 的优雅之处:对业务代码透明

6. 测试验证

我们通过集成测试验证会话是否真正写入数据库。

6.1 测试类基础结构

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class SpringSessionJdbcApplicationTests {

    @LocalServerPort
    private int port;

    @Autowired
    private TestRestTemplate testRestTemplate;

    private List<String> getSessionIdsFromDatabase() throws SQLException {
        List<String> result = new ArrayList<>();
        ResultSet rs = getResultSet("SELECT * FROM SPRING_SESSION");
        while (rs.next()) {
            result.add(rs.getString("SESSION_ID"));
        }
        return result;
    }

    private List<byte[]> getSessionAttributeBytesFromDb() throws SQLException {
        List<byte[]> result = new ArrayList<>();
        ResultSet rs = getResultSet("SELECT * FROM SPRING_SESSION_ATTRIBUTES");
        while (rs.next()) {
            result.add(rs.getBytes("ATTRIBUTE_BYTES"));
        }
        return result;
    }

    private ResultSet getResultSet(String sql) throws SQLException {
        Connection conn = DriverManager.getConnection("jdbc:h2:mem:testdb", "sa", "");
        Statement stat = conn.createStatement();
        return stat.executeQuery(sql);
    }
}

✅ 使用 @FixMethodOrder(NAME_ASCENDING) 确保测试顺序,避免依赖混乱

6.2 测试步骤

(1) 初始状态:数据库为空

@Test
public void whenH2DbIsQueried_thenSessionInfoIsEmpty() throws SQLException {
    assertEquals(0, getSessionIdsFromDatabase().size());
    assertEquals(0, getSessionAttributeBytesFromDatabase().size());
}

(2) 访问首页:创建会话

@Test
public void whenH2DbIsQueried_thenOneSessionIsCreated() throws SQLException {
    assertThat(this.testRestTemplate.getForObject(
        "http://localhost:" + port + "/", String.class))
        .isNotEmpty();
    assertEquals(1, getSessionIdsFromDatabase().size());
}

✅ 首次请求触发会话创建,SPRING_SESSION 表中出现一条记录

(3) 提交颜色:验证属性持久化

@Test
public void whenH2DbIsQueried_thenSessionAttributeIsRetrieved() throws Exception {
    MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
    map.add("color", "red");
    this.testRestTemplate.postForObject(
        "http://localhost:" + port + "/saveColor", map, String.class);
    
    List<byte[]> queryResponse = getSessionAttributeBytesFromDb();
    assertEquals(1, queryResponse.size());
    
    ObjectInput in = new ObjectInputStream(new ByteArrayInputStream(queryResponse.get(0)));
    List<String> obj = (List<String>) in.readObject();
    assertEquals("red", obj.get(0));
}

⚠️ 注意:ATTRIBUTE_BYTES 存的是序列化后的对象,需要反序列化才能读取


7. 底层原理揭秘

看起来我们没做什么特别的事,但会话却自动存到了数据库。这背后的魔法在哪?

核心机制

当你配置 spring.session.store-type=jdbc 后,Spring Boot 实际上做了等价于以下操作:

@EnableJdbcHttpSession

这会自动注册一个名为 springSessionRepositoryFilter 的过滤器。

请求处理流程

  1. ✅ 每个 HTTP 请求被 springSessionRepositoryFilter 拦截
  2. ✅ 原始 HttpServletRequest 被包装成 SessionRepositoryRequestWrapper
  3. ✅ 调用 getSession() 时,从数据库加载或创建新会话
  4. ✅ 请求结束时,自动调用 commitSession() 将变更写回数据库

💡 你代码中用的还是标准 HttpSession,但背后已是数据库驱动的会话管理。


8. 查看数据库表结构

想亲眼看看会话数据长啥样?开启 H2 控制台即可。

启用 H2 Console

spring.h2.console.enabled=true
spring.h2.console.path=/h2-console

访问:http://localhost:8080/h2-console/
JDBC URL: jdbc:h2:mem:testdb

表结构截图

spring session jdbc
SPRING_SESSION 表:存储会话元数据

spring session jdbc
SPRING_SESSION_ATTRIBUTES 表:存储会话属性(序列化字节)


9. 总结

Spring Session + JDBC 是解决分布式会话问题的轻量级利器,尤其适合中小项目快速落地。

✅ 优势总结

  • 零侵入:业务代码无需修改,仍用 HttpSession
  • 配置极简:Spring Boot 下仅需一行配置
  • 支持多种数据库:H2、MySQL、PostgreSQL、Oracle 等
  • 自动建表:提供标准 schema,开箱即用
  • 扩展性强:可自定义序列化策略、表名、过期策略等

🚀 实际应用场景

  • 多实例部署的 Web 应用
  • 前后端分离项目中的会话保持
  • 需要会话持久化的管理后台

🔗 想进一步了解 Spring Session 在安全认证中的应用?推荐阅读官方指南:Spring Session 完全指南

完整源码已托管至 GitHub:https://github.com/eugenp/tutorials/tree/master/spring-web-modules/spring-session


原始标题:Spring Session with JDBC