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.
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
Arrancamos la aplicación
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
Resultado
[{“username”:”jorge”,”password”:”1234″}]