Sessão de primavera com JDBC

Sessão de primavera com JDBC

*1. Visão geral *

Neste tutorial rápido, aprenderemos como usar o JDBC da sessão Spring para manter as informações da sessão em um banco de dados.

Para fins de demonstração, usaremos um banco de dados H2 na memória.

===* 2. Opções de configuração *

A maneira mais fácil e rápida de criar nosso projeto de amostra é usando Spring Boot. No entanto, também mostraremos uma maneira sem inicialização de configurar as coisas.

Portanto, você não precisa concluir as seções 3 e 4. Basta escolher um, dependendo de estarmos usando o Spring Boot para configurar o Spring Session.

===* 3. Configuração de inicialização por mola *

Primeiro, vamos analisar a configuração necessária para o Spring Session JDBC.

====* 3.1 Dependências do Maven *

Primeiro, precisamos adicionar essas dependências ao nosso projeto:

<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>

Nosso aplicativo é executado com Spring Boot, e o pai pom.xml fornece versões para cada entrada. A versão mais recente de cada dependência pode ser encontrada aqui: https://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22org.springframework.boot%22%20AND%20a%3A%22spring- boot-starter-web% 22 [https://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22org.springframework.boot%22%20AND% 20a% 3A% 22spring-boot-starter-test% 22 [teste de inicialização de inicialização da primavera] https://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22org.springframework. sessão% 22% 20AND% 20a% 3A% 22spring-session-jdbc% 22 [spring-session-jdbc] e h2.

Surpreendentemente,* a única propriedade de configuração que precisamos para ativar o Spring Session suportado por um banco de dados relacional está no _ application.properties *: _

spring.session.store-type=jdbc

*4. Configuração de mola padrão (sem inicialização por mola) *

Vamos também dar uma olhada na sessão de integração e configuração do Spring sem o Spring Boot - apenas com o Spring simples.

====* 4.1 Dependências do Maven *

Primeiro, se estamos adicionando spring-session-jdbc a um projeto padrão do Spring, precisamos adicionar https://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22org.springframework .session% 22% 20AND% 20a% 3A% 22spring-session-jdbc% 22 [spring-session-jdbc] e https://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22com .h2database% 22% 20AND% 20a% 3A% 22h2% 22 [h2] para o nosso pom.xml (últimas duas dependências do snippet na seção anterior).

====* 4.2 Configuração da sessão da primavera *

Agora vamos adicionar uma classe de configuração para 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);
    }
}

Como podemos ver, as diferenças são mínimas. Agora temos que definir nossos EmbeddedDatabase e PlatformTransactionManager beans explicitamente - o Spring Boot faz isso por nós na configuração anterior.

O item acima garante que o Spring bean com o nome springSessionRepositoryFilter seja registrado com nosso Servlet Container para cada solicitação.

===* 5. Um aplicativo simples *

Continuando, vejamos uma API REST simples que salva demonstra a persistência da sessão.

====* 5.1. Controlador *

Primeiro, vamos adicionar uma classe Controller para armazenar e exibir informações na 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. Testando nossa implementação *

Agora que temos uma API com os métodos GET e POST, vamos escrever testes para chamar os dois métodos.

Em cada caso, devemos poder afirmar que as informações da sessão são mantidas no banco de dados. Para verificar isso, consultaremos o banco de dados da sessão diretamente.

Vamos primeiro configurar as coisas:

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

Observe o uso de _ @ FixMethodOrder (MethodSorters.NAME_ASCENDING) _ para controlar a ordem de execução do caso de teste. Leia mais sobre o assunto https://www..com/junit-5-test-order [aqui].

Vamos começar afirmando que as tabelas de sessão estão vazias no banco de dados:

@Test
public void whenH2DbIsQueried_thenSessionInfoIsEmpty()
  throws SQLException {

    assertEquals(
      0, getSessionIdsFromDatabase().size());
    assertEquals(
      0, getSessionAttributeBytesFromDatabase().size());
}

Em seguida, testamos o ponto de extremidade GET:

@Test
public void whenH2DbIsQueried_thenOneSessionIsCreated()
  throws SQLException {

    assertThat(this.testRestTemplate.getForObject(
      "http://localhost:" + port + "/", String.class))
      .isNotEmpty();
    assertEquals(1, getSessionIdsFromDatabase().size());
}

Quando a API é chamada pela primeira vez, uma sessão é criada e persistida no banco de dados. Como podemos ver, há apenas uma linha na tabela SPRING_SESSION neste momento.

Por fim, testamos o terminal POST fornecendo uma cor favorita:

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

Como esperado, a tabela SPRING_SESSION_ATTRIBUTES persiste a cor favorita. Observe que precisamos desserializar o conteúdo de ATTRIBUTE_BYTES para uma lista de objetos String, pois o Spring faz a serialização de objetos ao persistir os atributos da sessão no banco de dados.

===* 7. Como funciona? *

Olhando para o controlador, não há indicação de que o banco de dados persista nas informações da sessão. Toda a mágica está acontecendo em uma linha que adicionamos no application.properties.

Ou seja,* quando especificarmos spring.session.store-type = jdbc, _ nos bastidores, o Spring Boot aplicará uma configuração equivalente a adicionar manualmente a anotação _ @ EnableJdbcHttpSession *.

Isso cria um Spring Bean chamado springSessionRepositoryFilter que implementa a SessionRepositoryFilter.

Outro ponto importante é que o filtro intercepta todos os HttpServletRequest e os agrupa em um SessionRepositoryRequestWrapper.

Ele também chama o método commitSession para manter as informações da sessão.

*8. Informações da sessão armazenadas no banco de dados H2 *

Adicionando as propriedades abaixo, podemos dar uma olhada nas tabelas em que as informações da sessão são armazenadas a partir da URL - http://localhost: 8080/h2-console/:

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

9. Conclusão

O Spring Session é uma ferramenta poderosa para gerenciar sessões HTTP em uma arquitetura de sistema distribuída. A Spring cuida do trabalho pesado para casos de uso simples, fornecendo um esquema predefinido com configuração mínima. Ao mesmo tempo, oferece a flexibilidade de apresentar nosso design sobre como queremos armazenar informações da sessão.

Por fim, para gerenciar informações de autenticação usando o Spring Session, você pode consultar este artigo - https://www..com/spring-session [Guia para o Spring Session].

Como sempre, você pode encontrar o código fonte over no Github.