1. Introducción

En este post arrancaremos una base de datos en memoria utilizando Spring Data dentro de un microservicio de Spring Boot.

Os podéis descargar el código de ejemplo de mi GitHub aquí.

Tecnologías empleadas:

  • Java 8
  • Gradle 3.1
  • Spring Test 4.3.7.RELEASE
  • Spring Data Jpa 1.11.1
  • h2 1.4.193

3. Ficheros

Añadimos las dependencias org.springframework.boot:spring-boot-starter-data-jpa y com.h2database:h2 al fichero build.gradle

group 'com.jorgehernandezramirez.spring.springboot'
version '1.0-SNAPSHOT'

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.2.RELEASE")
    }
}

apply plugin: 'java'
apply plugin: 'idea'
apply plugin: 'maven'
apply plugin: 'spring-boot'
apply plugin: 'io.spring.dependency-management'

dependencyManagement {
    imports {
        mavenBom 'org.springframework.cloud:spring-cloud-dependencies:Camden.SR6'
    }
}

sourceCompatibility = 1.8

springBoot {
    mainClass = "com.jorgehernandezramirez.spring.springboot.h2.Application"
}

repositories {
    mavenCentral()
}

dependencies {
    compile('org.springframework.boot:spring-boot-starter-web')
    compile('org.springframework.boot:spring-boot-starter-data-jpa')
    compile('com.h2database:h2')
    compile group: 'net.sf.dozer', name: 'dozer', version: dozer_version
    testCompile("org.springframework.boot:spring-boot-starter-test")
}

Mostramos la clase que arranca SpringBoot

package com.jorgehernandezramirez.spring.springboot.h2;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

    public Application(){
        //For Spring
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Configuramos el datasource en el fichero application.yml. Si no se indica la url sobre la que se va arrancar h2 se utiliza jdbc:h2:mem:testdb por defecto.

spring:
   datasource:
      url: jdbc:h2:mem:mybd
      driverClassName: org.h2.Driver
      username: sa
      password:

Creamos las entidades y repositorios que vamos a utilizar

package com.jorgehernandezramirez.spring.springboot.h2.dao.entity;

import javax.persistence.Entity;
import javax.persistence.Id;

@Entity(name = "user")
public class UserEntity {

    @Id
    private String username;

    private String password;

    public UserEntity(){
        //Para JPA
    }

    public UserEntity(String username, String password) {
        this.username = username;
        this.password = password;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        UserEntity that = (UserEntity) o;

        if (username != null ? !username.equals(that.username) : that.username != null) return false;
        return password != null ? password.equals(that.password) : that.password == null;

    }

    @Override
    public int hashCode() {
        int result = username != null ? username.hashCode() : 0;
        result = 31 * result + (password != null ? password.hashCode() : 0);
        return result;
    }

    @Override
    public String toString() {
        return "UserEntity{" +
                "username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}
package com.jorgehernandezramirez.spring.springboot.h2.dao.repository;

import com.jorgehernandezramirez.spring.springboot.h2.dao.entity.UserEntity;
import org.springframework.data.repository.CrudRepository;

import java.util.List;

public interface UserRepository extends CrudRepository<UserEntity, String> {

    List<UserEntity> findByUsername(String username);
}

Mostramos la configuración para spring data. No es necesario hacer uso de las anotaciones @EnableJpaRepositories y @EntityScan ya que por defecto se escanea todos los paquetes que se encuentren bajo la clase Application.java. Sin embargo si queremos acotar dicho escaneo es necesario utilizarlas.

@Configuration
@EnableJpaRepositories(basePackages = "com.jorgehernandezramirez.spring.springboot.h2.dao.repository")
@EntityScan(basePackages = "com.jorgehernandezramirez.spring.springboot.h2.dao.entity")
public class JpaConfiguration {
}

Mostramos las clases que formarán parte de nuestra capa de servicios

package com.jorgehernandezramirez.spring.springboot.h2.service.dto;

import javax.persistence.Id;

public class UserDto {

    @Id
    private String username;

    private String password;

    public UserDto(){
        super();
    }

    public UserDto(String username, String password) {
        this.username = username;
        this.password = password;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        UserDto userDto = (UserDto) o;

        if (username != null ? !username.equals(userDto.username) : userDto.username != null) return false;
        return password != null ? password.equals(userDto.password) : userDto.password == null;
    }

    @Override
    public int hashCode() {
        int result = username != null ? username.hashCode() : 0;
        result = 31 * result + (password != null ? password.hashCode() : 0);
        return result;
    }

    @Override
    public String toString() {
        return "UserDto{" +
                "username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

Configuramos Dozer para el mapeo entre pojos.

package com.jorgehernandezramirez.spring.springboot.h2.configuration;

import org.dozer.DozerBeanMapper;
import org.dozer.Mapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DozerConfiguration {

    @Bean
    public Mapper buildMapper(){
        return new DozerBeanMapper();
    }
}

Api de usuarios en la capa de servicios

package com.jorgehernandezramirez.spring.springboot.h2.service.api;

import com.jorgehernandezramirez.spring.springboot.h2.service.dto.UserDto;

import java.util.List;

/**
 * Api de usuarios
 */
public interface IUserService {

    /**
     * Método que debe devolver la lista de usuarios en el sistema
     * @return
     */
    List<UserDto> getUsers();

    /**
     * Método que debe insertar a un usuario en el sistema.
     * En caso de existir ya el usuario se devuelve la excepción UserAlreadyExistsException
     * @param userDto
     */
    void insertUser(final UserDto userDto);
}

Implementación de usuarios en la capa de servicios

package com.jorgehernandezramirez.spring.springboot.h2.service.impl;

import com.jorgehernandezramirez.spring.springboot.h2.dao.entity.UserEntity;
import com.jorgehernandezramirez.spring.springboot.h2.dao.repository.UserRepository;
import com.jorgehernandezramirez.spring.springboot.h2.service.api.IUserService;
import com.jorgehernandezramirez.spring.springboot.h2.service.dto.UserDto;
import com.jorgehernandezramirez.spring.springboot.h2.service.exception.UserAlreadyExistsException;
import org.dozer.Mapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
public class UserService implements IUserService{

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private Mapper mapper;

    public UserService(){
         //Para Spring
    }

    @Override
    public List<UserDto> getUsers() {
        final List<UserDto> userDtoList = new ArrayList<UserDto>();
        userRepository.findAll().forEach(userEntity -> {
            userDtoList.add(mapper.map(userEntity, UserDto.class));
        });
        return userDtoList;
    }

    @Override
    public void insertUser(final UserDto userDto) {
        if(userRepository.findOne(userDto.getUsername()) != null){
            throw new UserAlreadyExistsException();
        }
        userRepository.save(mapper.map(userDto, UserEntity.class));
    }
}

Excepción UserAlreadyExistsException que se debe lanzar cuando queremos insertar un usuario que ya existe en el sistema

package com.jorgehernandezramirez.spring.springboot.h2.service.exception;

public class UserAlreadyExistsException extends RuntimeException {
}

Controlador donde exponemos nuestros servicios.

package com.jorgehernandezramirez.spring.springboot.h2.controller;

import com.jorgehernandezramirez.spring.springboot.h2.service.api.IUserService;
import com.jorgehernandezramirez.spring.springboot.h2.service.dto.UserDto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private IUserService userService;

    @RequestMapping
    public List<UserDto> getUsers() {
        return userService.getUsers();
    }

    @RequestMapping(method = RequestMethod.POST)
    public void insertUser(@RequestBody UserDto userDto) {
        userService.insertUser(userDto);
    }
}

3. Consola de administración

Al utilizar la dependencia com.h2database:h2 podemos habilitar una consola de administración para gestionar las tablas dentro de nuestra base de datos en memoria.

Añadir servlet

@Configuration
@EnableJpaRepositories(basePackages = "com.jorgehernandezramirez.spring.springboot.h2.dao.repository")
@EntityScan(basePackages = "com.jorgehernandezramirez.spring.springboot.h2.dao.entity")
public class JpaConfiguration {

    @Bean
    public ServletRegistrationBean h2servletRegistration(){
        final ServletRegistrationBean registrationBean = new ServletRegistrationBean( new WebServlet());
        registrationBean.addUrlMappings("/console/*");
        return registrationBean;
    }
}

Consola

Nos logamos con el usuario sa en la url jdbc:h2:mem:mybd

4. Testando la aplicación

Mostramos el test H2Test

package com.jorgehernandezramirez.spring.springboot.h2;

import com.jorgehernandezramirez.spring.springboot.h2.service.dto.UserDto;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;

import static org.junit.Assert.assertEquals;

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT)
public class H2Test {

    @Autowired
    private TestRestTemplate testRestTemplate;

    @Before
    public void initialization(){
        testRestTemplate.postForEntity("/user", new UserDto("jorge", "jorge"), String.class);
    }

    @Test
    public void shouldGetUserList() throws Exception {
        final ResponseEntity<List<UserDto>> userResponse = testRestTemplate.exchange("/user",
                        HttpMethod.GET, null, new ParameterizedTypeReference<List<UserDto>>() {
                        });
        assertEquals(1, userResponse.getBody().size());
    }

    @Test
    public void shouldReturnInternalServerErrorWhenInsertAExistingUser() throws Exception {
        final ResponseEntity<String> responseEntity
                = testRestTemplate.postForEntity("/user", new UserDto("jorge", "jorge"), String.class);
        assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, responseEntity.getStatusCode());
    }
}

4. Testando la aplicación. Arrancando la aplicación

A continuación compilamos nuestros fuentes y arrancamos SpringBoot para testear nuestros controladores

Compilamos

gradle clean build

Arrancamos la aplicación

java -jar [JAR_EN_BUILD_LIBS]

Insertamos un usuario

curl -X POST http://localhost:8080/user -H 'Content-Type: application/json' -d'{"username": "jorge", "password": "1234"}'

Obtenemos los usuarios del sistema

curl -X GET http://localhost:8080/user

Resultado

[{“username”:”jorge”,”password”:”1234″}]