Spring Session with JDBC – 使用JDBC的Spring Session

最后修改: 2018年 7月 27日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this quick tutorial, we’ll learn how to use the Spring session JDBC to persist session information to a database.

在这个快速教程中,我们将学习如何使用Spring session JDBC将会话信息持久化到数据库中。

For demonstration purposes, we’ll be using an in-memory H2 database.

出于示范目的,我们将使用内存中的H2数据库。

2. Configuration Options

2.配置选项

The easiest and fastest way to create our sample project is by using Spring Boot. However, we’ll also show a non-boot way to set things up.

创建我们的示例项目的最简单、最快的方法是使用Spring Boot。然而,我们也将展示一种非启动的方式来设置东西。

Hence, you don’t need to complete both sections 3 and 4. Just pick one depending on whether or not we are using Spring Boot to configure Spring Session.

因此,你不需要同时完成第3和第4部分。只需根据我们是否使用Spring Boot来配置Spring Session,选择一个。

3. Spring Boot Configuration

3.Spring Boot配置

First, let’s look at the required configuration for Spring Session JDBC.

首先,让我们看看Spring Session JDBC的必要配置。

3.1. Maven Dependencies

3.1.Maven的依赖性

First, we need to add these dependencies to our project:

首先,我们需要向我们的项目添加这些依赖项。

<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>1.4.197</version>
    <scope>runtime</scope>
</dependency>

Our application runs with Spring Boot, and the parent pom.xml provides versions for each entry. The latest version of each dependency can be found here: spring-boot-starter-webspring-boot-starter-test, spring-session-jdbc, and h2.

我们的应用程序使用Spring Boot运行,父pom.xml为每个条目提供版本。每个依赖项的最新版本可以在这里找到。spring-boot-starter-web, spring-boot-starter-test, spring-session-jdbc, and h2.

Surprisingly, the only configuration property that we need to enable Spring Session backed by a relational database is in the application.properties:

令人惊讶的是,我们需要的唯一配置属性是在application.properties中启用由关系型数据库支持的Spring Session:

spring.session.store-type=jdbc

4. Standard Spring Config (no Spring Boot)

4.标准Spring配置(无Spring Boot)

Let’s also have a look at the integrating and configuring spring-session without Spring Boot – just with plain Spring.

我们也来看看在没有Spring Boot的情况下整合和配置spring-session的情况–只是用普通的Spring。

4.1. Maven Dependencies

4.1.Maven的依赖性

First, if we’re adding spring-session-jdbc to a standard Spring project, we’ll need to add spring-session-jdbc and h2 to our pom.xml (last two dependencies from the snippet in the previous section).

首先,如果我们将spring-session-jdbc添加到标准的Spring项目中,我们需要将spring-session-jdbch2添加到我们的pom.xml(上一节中的片段的最后两个依赖项)。

4.2. Spring Session Configuration

4.2.Spring会话配置

Now let’s add a configuration class for Spring Session JDBC:

现在让我们为Spring Session 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);
    }
}

As we can see, the differences are minimal. Now we have to define our EmbeddedDatabase and PlatformTransactionManager beans explicitly – Spring Boot does it for us in the previous configuration.

正如我们所看到的,这些差异是最小的。现在我们必须明确定义我们的EmbeddedDatabasePlatformTransactionManagerbeans–Spring Boot在之前的配置中为我们做了这些。

The above ensures that  Spring bean by the name of springSessionRepositoryFilter is registered with our Servlet Container for every request.

上述内容确保了名为springSessionRepositoryFilter的Spring Bean在我们的Servlet Container中为每个请求注册。

5. A Simple App

5 一个简单的应用程序

Moving on, let’s look at a simple REST API that saves demonstrates session persistence

接着,让我们看看一个简单的REST API,它保存演示了会话持久性

5.1. Controller

5.1.控制器

First, let’s add a Controller class to store and display information in the HttpSession:

首先,让我们添加一个Controller类来存储和显示HttpSession中的信息。

@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;
    }
}

6. Testing Our Implementation

6.测试我们的实施

Now that we have an API with a GET and POST method, let’s write tests to invoke both methods.

现在我们有了一个具有GET和POST方法的API,让我们来写测试来调用这两种方法。

In each case, we should be able to assert that the session information is persisted in the database. To verify this, we’ll query the session database directly.

在每一种情况下,我们都应该能够断言会话信息是在数据库中持久存在的。为了验证这一点,我们将直接查询会话数据库。

Let’s first set things up:

首先让我们把事情安排好。

@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);
    }
}

Note the use of @FixMethodOrder(MethodSorters.NAME_ASCENDING) to control the order of test case executionRead more about it here.

注意使用@FixMethodOrder(MethodSorters.NAME_ASCENDING)来控制测试案例的执行顺序阅读更多关于它的内容这里

Let’s begin by asserting that the session tables are empty in the database:

让我们首先断言,数据库中的会话表是空的。

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

Next, we test the GET endpoint:

接下来,我们测试GET端点。

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

When the API is invoked for the first time, a session is created and persisted in the database. As we can see, there is only one row in the SPRING_SESSION table at this point.

当API第一次被调用时,一个会话被创建并持久化在数据库中。我们可以看到,此时在SPRING_SESSION表中只有一行。

Finally, we test the POST endpoint by providing a favorite color:

最后,我们通过提供一个喜欢的颜色来测试POST端点。

@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 = getSessionAttributeBytesFromDatabase();
    
    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));
}

As expected, SPRING_SESSION_ATTRIBUTES table persists the favorite color. Notice that we have to deserialize the contents of ATTRIBUTE_BYTES to a list of String objects since Spring does object serialization when persisting session attributes in the database.

正如预期的那样,SPRING_SESSION_ATTRIBUTES表持久化了最喜欢的颜色。请注意,我们必须将ATTRIBUTE_BYTES 的内容反序列化为String对象的列表,因为Spring在数据库中持久化会话属性时进行对象序列化。

7. How Does It Work?

7.它是如何工作的?

Looking at the controller, there’s no indication of database persisting the session information. All the magic is happening in one line we added in the application.properties.

看一下控制器,没有迹象表明数据库在持久化会话信息。所有的魔法都发生在我们在application.properties中添加的一行中。

That is, when we specify spring.session.store-type=jdbc, behind the scenes, Spring Boot will apply a configuration that is equivalent to manually adding @EnableJdbcHttpSession annotation.

也就是说,当我们指定spring.session.store-type=jdbc时,在幕后,Spring Boot将应用一种配置,相当于手动添加@EnableJdbcHttpSession注解

This creates a Spring Bean named springSessionRepositoryFilter that implements a SessionRepositoryFilter.

这将创建一个名为springSessionRepositoryFilter的Spring Bean,实现a SessionRepositoryFilter

Another key point is that the filter intercepts every HttpServletRequest and wraps it into a SessionRepositoryRequestWrapper.

另一个关键点是,过滤器拦截每一个HttpServletRequest,并将其包装成一个SessionRepositoryRequestWrapper

It also calls the commitSession method to persist the session information.

它还调用commitSession方法来持久化会话信息。

8. Session Information Stored in H2 Database

8.保存在H2数据库中的会话信息

By adding the below properties, we could take a look at the tables where the session information is stored from the URL –  http://localhost:8080/h2-console/:

通过添加以下属性,我们可以从URL–http://localhost:8080/h2-console/中查看存储会话信息的表格。

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

spring session jdbc
spring session jdbc

9. Conclusion

9.结语

Spring Session is a powerful tool for managing HTTP sessions in a distributed system architecture. Spring takes care of the heavy lifting for simple use cases by providing a predefined schema with minimal configuration. At the same time, it offers the flexibility to come up with our design on how we want to store session information.

Spring Session是一个强大的工具,用于管理分布式系统架构中的HTTP会话。Spring通过提供一个预定义的模式和最小的配置,为简单的用例处理了繁重的工作。同时,它还提供了灵活性,让我们可以自己设计如何存储会话信息。

Finally, for managing authentication information using Spring Session you can refer to this article – Guide to Spring Session.

最后,关于使用Spring Session管理认证信息,你可以参考这篇文章 – Guide to Spring Session

As always, you can find the source code over on Github.

一如既往,你可以在Github上找到源代码