Backend SpringBoot
Backend SpringBoot
2
22 Proyecto Sistema de Seguridad Industrial...................................................................23
23 Métodos de Spring Data JPA Query............................................................................24
24 Servicios.....................................................................................................................25
25 JUnit Tests..................................................................................................................25
26 Mockito......................................................................................................................25
Argument Capture.................................................................................................................27
27 Test mockMvc............................................................................................................27
28 Test de Integración.....................................................................................................27
29 Separar los unit test de los test de integración...........................................................28
30 Generalización de Servicios........................................................................................29
31 Definición de templates.............................................................................................30
32 Navegación de Items..................................................................................................30
33 Creación de Items.......................................................................................................32
34 Actualizar Items.........................................................................................................34
35 Eliminar Items............................................................................................................34
36 Subir archivos al servidor...........................................................................................34
37 Mostrar imágenes de la Base de Datos.......................................................................35
38 Manejo de excepciones..............................................................................................36
39 Docker........................................................................................................................37
40 Mysql.........................................................................................................................38
41 MS SQL SERVER..........................................................................................................38
42 Dominio de negocio: Sistema Seguridad Industrial.....................................................39
43 Rubrica Tarea 1..........................................................................................................41
44 Modelo Entidades......................................................................................................42
45 Recursos.....................................................................................................................42
3
1. Ambiente de desarrollo
Adicionar la ruta del directorio "bin" a la variable "PATH" separado por ";"
por ejemplo:
C:\WINDOWS\system32;C:\WINDOWS;"C:\Program Files\Java\jdk1.8.0_171\bin"
4
comprobar ejecutando
java -version
javac -version
Al crear un proyecto java, seleccionar Project SDK con la ruta donde se instaló java jdk.
5
1.3 Instalar Maven
Descargar el archivo compreso binario de https://maven.apache.org/download.cgi
Luego, siga los pasos de instalación mientras instala Git usando el instalador.
Puede encontrar más detalles sobre la instalación de Git en https://git-
scm.com/book/en/v2/Getting-Started-Installing-Git.
Los usuarios Windows es recomendable elegir la opción de GitShell para manejo de consola.
Para configurar su nombre de usuario para ser utilizado por Git, escriba lo siguiente:
git config --global user.name "Tu nombre"
Para configurar su correo electrónico para ser utilizado por Git, escriba lo siguiente:
git config --global user.email [email protected]
Puede verificar su configuración global predeterminada de Git, puede escribir lo siguiente:
git config --list
6
Generar su llave publica:
https://git-scm.com/book/en/v2/Git-on-the-Server-Generating-Your-SSH-Public-Key
2 Inicializador de spring
2.1 Inicializador start.spring.io
http://start.spring.io/
group: com.market
artifact: spring-backend
Tipo Maven
Java: 9
SpringBoot: la ultima disponible
Seleccionar Web: Web, SQL: JPA, H2, PostgreSQL, Template: Thymeleaf, Ops: Actuator.
Generar Proyecto
7
git init
Hacer commit con el mensaje “Initial setup”
Seguir los pasos provistos en el repositorio creado en github, similar a las siguientes líneas
reemplazando el usuario eterceros por el suyo:
Escribir los métodos getters y setters con la ayuda del ide CTRL + N Alt+Ins.
4 JPA
Añadir la anotación @Entity a cada clase de la capa de modelo.
Crear la clase ModelBase y adicionar a esta la anotación @MappedSuperclass.
Puesto que a momento de persistir en la Base de datos es necesario una llave primaria debe
añadirse una propiedad id de tipo Long con sus getters y setters a la clase ModelBase.
Para que JPA lo identifique como una llave debe añadirse la anotación @Id al campo id y la
anotación @GeneratedValue(strategy = GenerationType.IDENTITY) donde se especifica la
estrategia de generación de llave como automática.
Hacer que todas las clases de tipo modelo extiendan de ModelBase.
@CreatedDate
@Temporal(TemporalType.TIMESTAMP)
@Column( nullable = false, updatable = false)
private Date createdOn;
@LastModifiedDate
@Temporal(TemporalType.TIMESTAMP)
@Column(insertable = false)
private Date updatedOn;
8
Añadir el campo requerido version a BaseModel con la anotación @Version
@Version
@Column(nullable = false)
private long version;
Anadir las relaciones JPA en Employee el atributo contracts debe tener la relación:
Es posible ver las tablas creadas en la base de datos por defecto mem:testdb, con el usuario sa
http://localhost:8080/h2-console
para tal efecto adicionar en application.properties la siguiente línea
spring.h2.console.enabled=true
spring.datasource.url=jdbc:h2:mem:testdb
#parse("File Header.java")
#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end
import org.springframework.data.repository.CrudRepository;
import com.sales.market.domain.${NAME};
public interface ${NAME}Repository extends CrudRepository<${NAME},Long>{
}
9
private CategoryRepository categoryRepository;
private SubCategoryRepository subCategoryRepository;
private ItemRepository itemRepository;
private EmployeeRepository employeeRepository;
private PositionRepository positionRepository;
private ContractRepository contractRepository;
@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
initData();
}
categoryRepository.save(eppCategory);
// RES category
Category resourceCategory = new Category();
resourceCategory.setCode("RES");
resourceCategory.setName("RESOURCE");
categoryRepository.save(resourceCategory);
// safety subcategory
SubCategory safetySubCategory = new SubCategory();
safetySubCategory.setCategory(eppCategory);
safetySubCategory.setCode("SAF");
safetySubCategory.setName("SAFETY");
subCategoryRepository.save(safetySubCategory);
subCategoryRepository.save(rawMaterialSubCategory);
10
// Helmet Item
Item helmet = new Item();
helmet.setCode("HEL");
helmet.setName("HELMET");
helmet.setSubCategory(safetySubCategory);
itemRepository.save(helmet);
// ink Item
Item ink = new Item();
ink.setCode("INK");
ink.setName("INK");
ink.setSubCategory(rawMaterialSubCategory);
itemRepository.save(ink);
// John Employee
Employee john = new Employee();
john.setFirstName("John");
john.setLastName("Doe");
// Position
Position position = new Position();
position.setName("OPERATIVE");
positionRepository.save(position);
// contract
Contract contract = new Contract();
contract.setEmployee(john);
contract.setInitDate(new Date(2010, 1, 1));
contract.setPosition(position);
john.getContracts().add(contract);
employeeRepository.save(john);
contractRepository.save(contract);
}
Si no ha inicializado las colecciones definidas en las entidades puede tener errores nullPointer,
en tal caso inicialice las colecciones, siempre con una clase concreta.
6 Auditoria
En la clase ModelBase adicionar la anotación @EntityListeners(AuditingEntityListener.class)
Y crear una clase de configuración PersistenceConfig con las anotaciones:
@Configuration
@EnableJpaAuditing
11
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
9 Data Load
Crear un archivo llamado data.sql en el paquete resources.
INSERT INTO category (code, name, CREATED_ON, VERSION ) VALUES ('10', 'ND','2018-04-10
14:46:19.282', 0);
10 Controladores
Crear las clases controladoras dentro del paquete controller, para cada modelo. Cada
controlador debe tener la anotación @Controller.
Inyectar el repositorio correspondiente en cada controlador
11 Templates
En la carpeta resources, templates crear el template para employee employees.html
Todos los tag html deben estar apropiadamente cerrados por restricciones de thymeleaf.
Adicionar al tag <HTML el atributo:
xmlns:th="http://www.thymeleaf.org"
Crear una tabla en html con 3 elementos thead para mostrar id, first name y last name.
Para llenar los datos de la table crear una fila con un iterador thymeleaf
<table>
<tr>
<th>ID</th>
<th>First Name</th>
<th>Last Name</th>
12
</tr>
<tr th:each="employee:${employees}">
<td th:text="${employee.id}">123</td>
<td th:text="${employee.firstName}">Jhon</td>
<td th:text="${employee.lastName}">Doe</td>
<!--<td>
<table>
<tr th:each="contract:${employee.contracts}">
<td th:text="${contract.id}">123</td>
<td th:text="${contract.initDate}">Jhon</td>
</tr>
</table>
</td>-->
</tr>
</table>
Tarea:
Crear un template para Items donde se vea los datos del ítem, su subcategoría y su categoría.
12 Inyección de dependencia
Crear un proyecto con spring boot initializer, java 8, maven, llamado di-demo. No seleccionar
ninguna característica ya que solo es necesario spring core.
Crear un paquete controller.
Crear una clase llamada MyController en el paquete controller. Adicionar la anotación
@Controller a la clase creada.
Adicionar un método hello que devuelva el Sting “Hello Spring”.
@Controller
public class MyController {
public String hello(){
System.out.println("Hello Spring");
return "Hello Spring";
}
}
En la clase DIDemoApplication asignar la sentencia existente a la variable ctx de tipo Application
Context.
Con el objeto ctx es posible obtener un Bean instanciado por Spring con el método getBean
proveyendo como parámetro el nombre.
13
En la clase DIDemoApplication adicionar las siguientes líneas que permiten a Spring buscar e
inyectar los diferentes tipos de Bean.
System.out.println(ctx.getBean(PropertyBasedController.class).sayHello());
System.out.println(ctx.getBean(GetterBasedController.class).sayHello());
System.out.println(ctx.getBean(ConstructorBasedController.class).sayHello());
12.2 Qualifiers
Algunas veces se tiene mas de una implementación de una interfaz de un servicio que produce
conflicto a Spring debido a que no puede elegir cual implementación es la requerida, por lo cual
se puede hacer uso de los cualificadores.
Crear 2 implementaciones de GreetingService: GetterGreetingServiceImpl y
ConstructorGreetingServiceImpl donde el mensaje será “Hello GetterGreetingServiceImpl” y
“Hello ConstructorGreetingServiceImpl” respectivamente.
Al correr la aplicación habrá conflicto de inyección de dependencias. Con un mensaje similar a:
Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using
@Qualifier to identify the bean that should be consumed
Usaremos @Qualifier para resolver los problemas. Con la ayuda del IDE ir a los controladores
para ver el código marcado en rojo y aceptar la ayuda del IDE para adicionar el cualificador
apropiado.
Alternativamente si el nombre del property coincide con el bean que deseamos inyectar
también se resuelve el conflicto sin necesidad del @Qualifier.
También se puede marcar los Bean con @Primary para que ese sea el bean inyectado en caso
de conflicto, pero es una mala práctica de programación.
12.3 @Profile
Los profiles se usan para seleccionar los Bean a inyectar de acuerdo al profile activo.
Se activa un profile en el archivo application.properties
spring.profiles.active=es
14
Copiar el Servicio GreetingServiceImpl con el nombre SpanishGreetingServiceImpl y que
devuelva el mensaje: Hello SpanishGreetingServiceImpl. Al existir ahora dos Primary habrá
conflicto, para resolver Adicionar la anotación @Profile(“en”) a GreetingServiceImpl y
@Profile(“es”) a SpanishGreetingServiceImpl
Se puede además especificar un Profile por defecto en caso de no haber configurado el profile
activo.
@Profile({"es","default"})
Escribir los métodos beforeInit y afterInit los cuales deben imprimir mensajes con los nombres
de sus métodos.
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof SpringBeanLifeCycleDemo) {
((SpringBeanLifeCycleDemo) bean).afterInit();
}
return bean;
}
15
14 Banner
Para reemplazar el banner por defecto se debe crear un archivo llamado banner.txt en la
carpeta resources. Puedes crear tu banner en http://patorjk.com/software/taag/
15 Component Scan
La clase con la anotación @SpringBootApplication determina el directorio o paquete que
contiene los componentes de Spring, en caso de haber componentes fuera del alcance de esta
debe adicionarse la anotación @ComponentScan(basePackages= {“paquete”, “otropaquete”})
Adicionar la línea
System.out.println(ctx.getBean(Forecast.class).weather());
En DiDemoApplication
Crear una clase llamada BeanConfiguration en el paquete config que tenga la anotación
@Configuration y definir un método que instancie un objeto tipo Forecast, para permitirle ser
un Bean de Spring y poder ser inyectado desde otros componentes.
@Configuration
public class BeanConfiguration {
@Bean
public Forecast forecast(){
return new Forecast();
}
}
16
Adicionar la anotación @ImportResource en la clase de la aplicación
@ImportResource("classpath:beanconfig.xml")
public class DiDemoApplication {
17 Archivos .properties
Mover el paquete services a su localización original.
Eliminar de DiDemoApplication las sentencias que imprimen.
Crear un archivo llamado database.properties
Introducir las líneas:
database.user=root
database.password=saltRootPwd
database.url=10.0.0.115
@PropertySource("classpath:database.properties")
Crear un componente llamado FakeDataSource, con las propiedades user, pwd y url, anotar con
@Value(“${propertyKey}”)
En DiDemoApplication inyectar el bean por medio del contexto e imprimir el usuario
18 Variables de Entorno
IntelliJ provee la facilidad de especificar las variables de entorno. En windows son environment
variables y en osx están en bash_profile. En el Toolbar donde está el botón Run se puede editar
la configuración y esto permitirá adicionar las variables de entorno entre otras funcionalidades.
17
Adicionar la varible de entorno DATABASE_USER=EnvironmentUserValue
Es posible inyectar el entorno.
Adicionar la propiedad Environment env; anotada con @Autowired en FakeDataSource.
Mediante env.getProperty(“nombreVariable”) se puede obtener el valor de las variables de
entorno.
Crear una nueva variable de entorno llamada ENV_USER=EnvUser.
Crear un método con la anotación @PostConstruct y asignar el valor de la nueva variable de
entorno a user.
Correr la aplicación para verificar.
Copiar la clase FakeDataSource con el nombre FakeJMS y cambiar las claves de las propiedades
de acuerdo al archivo de jms.properties.
En la clase PropertyConfig adicionar el nuevo recurso en la anotación correspondiente.
De manera similar a la impresión del database user imprimir el usuario de FakeJMS.
20 YAML
Un archivo ejemplo es el siguiente:
name: Valeria
names:
- Gabriela
- Viviana
- Mariela
pound_sign: "#"
book:
author: Joe Buck
publisher: random house
18
include_new_lines: |
line 1
line 2
line 3
ignore_new_lines: >
this is
just one long
string
21 Profile Properties
Cuando un profile está activo ese busca primero el archivo properties-<profile>.properties.
Copiar application.properties con el nombre application-es.properties y escribir los valores en
español. Fijar el profile a “es”
En YML se genera nuevos profiles en el mismo archivo con un separador:
---
spring:
profiles: es
jms:
user: root JMS ES
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
19
De similar manera para subcategory.
Crear una plantilla para category:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>Category</title>
</head>
<body>
<h1>Category</h1>
<table>
<tr>
<th>ID</th>
<th>Name</th>
<th>Code</th>
</tr>
<tr>
<td th:text="${category.id}">123</td>
<td th:text="${category.name}">Category Name</td>
</tr>
</table>
</body>
</html>
@Controller
@RequestMapping("/categories")
public class CategoryController {
private CategoryRepository categoryRepository;
@RequestMapping
public String getCategories(@RequestParam(value = "code", required = false) String code, Model model) {
model.addAttribute("categories", StringUtils.isEmpty(code) ? categoryRepository.findAll() :
categoryRepository.findByCode(code).get());
return "categories";
}
@RequestMapping("/{id}")
public String getCategoriesById(@PathVariable("id") @NotNull Long id, Model model) {
model.addAttribute("category", categoryRepository.findById(id).get());
return "category";
}
}
24 Servicios
Crear el paquete services dentro de este crear la interfaz CategoryService que implementa
20
Set<Category> getCategories();
Con la ayuda del IDE generar su implementación y luego agregar la anotación @Service a la
implementación. Introducir una propiedad de tipo CategoryRepository y hacer la inyección
correspondiente. La implementación del método de interfaz es:
@Override
public Set<Category> getCategories() {
Set<Category> categories= new HashSet<>();
categoryRepository.findAll().iterator().forEachRemaining(categories::add);
return categories;
}
25 JUnit Tests
Asegurarse o adicionar la dependencia en el archivo pom spring-boot-starter-test.
Con la ayuda del IDE Alt+enter (alternativamente Alt+ins en la clase Category) crear test para
comprobar si es posible obtener el codigo de una categoría mediante su método getter.
26 Mockito
Crear un test para el servicio CategoryServiceImpl con ayuda del plugin testMe del IDE.
Adicionar las siguientes propiedades a la clase:
private static final String OTRA_CAT = "OTRACAT";
when(categoryRepository.findAll()).thenReturn(categorySet);
21
MockitoAnnotations.initMocks(this);
categoryService= new CategoryServiceImpl(categoryRepository);
Verificar el número de veces que el método del mock se llama: “test de caja blanca”
verify(categoryRepository,times(1)).findAll();
CategoryController y el resto de los controlladores deberían en este punto hacer llamadas para
búsqueda no mediante el repositorio sino más bien mediante sus services correspondientes.
Realizar un test para CategoryController de similar manera, notar que necesitara introducir
mock para el servicio y model. Ademas una propiedad tipo lista categoryList que almacenera
posteriormente la respuesta Stub.
Necesitará además indicar al mock de service que hacer cuando sea invocado el método find:
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
categoryList = new ArrayList<>();
categoryList.add(new Category());
when(categoryService.find(any())).thenReturn(categoryList);
}
el método test debería invocar a getCategories del controller, verificar que la respuesta sea
“categories”, verificar que se haya llamado 1 vez al método del servicio y al método
addAttribute de model
@Test
public void testGetCategories() throws Exception {
String result = categoryController.getCategories(null, model);
String expectedView = "categories";
assertEquals(expectedView, result);
assertEquals(expectedView, expectedView);
verify(categoryService,times(1)).find(any());
verify(model,times(1)).addAttribute(eq(expectedView), eq(categoryList));
}
Argument Capture
En el método before adicionar una categoría al set de categories.
Un ejemplo de argument capture es:
@Test
public void testGetCategories() throws Exception {
ArgumentCaptor<List<Category>> argumentCaptor=
ArgumentCaptor.forClass((Class<List<Category>>)categoryList.getClass());
String result = categoryController.getCategories(null, model);
String expectedView = "categories";
assertEquals(expectedView, result);
assertEquals(expectedView, expectedView);
verify(categoryService,times(1)).find(any());
verify(model,times(1)).addAttribute(eq(expectedView), eq(categoryList));
verify(model,times(1)).addAttribute(eq("categories"), argumentCaptor.capture());
List<Category> capturedCategories = argumentCaptor.getValue();
22
assertEquals(capturedCategories.size(),1);
}
27 Test mockMvc
Hace un mock de Mvc, por ejemplo:
@RunWith(MockitoJUnitRunner.class)
public class IndexControllerTest {
@Mock
CategoryService categoryService;
@Mock
Model model;
@InjectMocks
IndexController controller;
@Test
public void testMockMVC() throws Exception {
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
mockMvc.perform(get("/"))
.andExpect(status().isOk())
.andExpect(view().name("index"));
}
}
28 Test de Integración
Los nombres de test de este tipo terminan en IT.
Crear una carpeta de nombre integration. Crear un Test llamado CategoryRepositoryIT. Anotar
con @DataJpaTest y @RunWith(SpringRunner.class), inyectar un CategoryRepository con
@Autowired y crear un método de test llamado tesFindAllCategoriesIT que invoca al método
findAll del repositorio inyectado realizando un assert para verificar el número de categoies. Mas
detalles en el siguiente fragmento de codigo
@Before
public void before() {
initialCount = categoryRepository.findAll().size();
categoryA = new Category();
categoryA.setCreatedOn(new Date());
categoryRepository.save(categoryA);
categoryB = new Category();
categoryB.setCreatedOn(new Date());
categoryRepository.save(categoryB);
}
@Test
public void tesFindAllCategoriesIT() {
Set<Category> categories = new HashSet<>();
23
categoryRepository.findAll().iterator().forEachRemaining(categories::add);
assertEquals(initialCount + 2, categories.size());
}
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<configuration>
<includes>
<include>**/*IT.java</include>
</includes>
<additionalClasspathElements>
<additionalClasspathElement>${basedir}/target/classes</additionalClasspathElement>
</additionalClasspathElements>
<parallel>none</parallel>
</configuration>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
30 Generalización de Servicios
En este punto es importante notar que podemos aprovechar las características de java generics
para generalizar mediante abstracción el comportamiento común. A continuación, se muestra
la propuesta de generalización:
T findById(Long id);
}
@Service
public abstract class GenericServiceImpl<T> implements GenericService<T> {
@Override
public List<T> findAll() {
24
List<T> results = new ArrayList<>();
getRepository().findAll().forEach(results::add);
return results;
}
@Override
public T findById(Long id) {
return getRepository().findById(id).get();
}
@Service
public class EmployeeServiceImpl extends GenericServiceImpl<Employee> implements EmployeeService {
private EmployeeRepository employeeRepository;
@Override
protected CrudRepository<Employee, Long> getRepository() {
return employeeRepository;
}
}
31 Definición de templates
Template para interfaz Service, llámenla SSIService:
/**
* @author: Edson A. Terceros T.
*/
import com.sales.market.domain.${NAME};
25
/**
* @author: Edson A. Terceros T.
*/
import com.sales.market.domain.${NAME};
import com.sales.market.repositories.${NAME}Repository;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Service;
@Service
public class ${NAME}ServiceImpl extends GenericServiceImpl<${NAME}> implements ${NAME}Service {
@Override
protected CrudRepository<${NAME}, Long> getRepository() {
return repository;
}
}
32 Navegación de Items
En GenericServiceImpl modificar el siguiente método asi:
@Override
public T findById(Long id) {
/**
* @author: Edson A. Terceros T.
*/
26
import com.sales.market.services.${NAME}Service;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import javax.validation.constraints.NotNull;
@Controller
@RequestMapping("/${LOWER_NAME}s")
public class ${NAME}Controller {
private ${NAME}Service service;
@RequestMapping
public String get${NAME}s(Model model) {
model.addAttribute("${LOWER_NAME}s", service.findAll());
return "${LOWER_NAME}s";
}
@RequestMapping("/{id}")
public String get${NAME}sById(@PathVariable("id") @NotNull Long id, Model model) {
model.addAttribute("${LOWER_NAME}", service.findById(id));
return "${LOWER_NAME}";
}
}
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>Items</title>
</head>
<body>
<table>
<tr>
<th>ID</th>
<th>Name</th>
<th>Code</th>
<th>View</th>
</tr>
<tr th:each="item:${items}">
<td th:text="${item.id}">123</td>
<td th:text="${item.name}">Jhon</td>
<td th:text="${item.code}">Doe</td>
<td><a href="#" th:href="@{'/items/'+${item.id}}">View</a></td>
27
</tr>
</table>
</body>
</html>
33 Creación de Items
Crear un template llamado itemForm. Para acceder a los atributos usar th:field=”*{propiedad}”
usar un elemento select para mostrar las diferentes sub categorías cada <option> se itera con
th:each=”subCategoryItem : ${subCategories}” además proveer el valor relacionado al id y
como texto mostrar el nombre.
<form th:object="${item}" th:action="@{/items}" METHOD="post">
<div>
<label>Name</label>
<input type="text" th:field="*{name}"></input>
</div>
<div>
<label>Code</label>
<input type="text" th:field="*{code}"></input>
</div>
<div>
<label>SubCategory</label>
<select th:field="*{subCategory}">
<option th:each="type : ${subCategories}"
th:value="${type.id}"
th:text="${type.name}"></option>
</select>
</div>
<div>
<button type="submit" class="btn btn-primary">Submit</button>
28
</div>
</form>
Este formulario debe ser accesible desde una ruta en el controller de ítem, para lo cual será
necesario inyectar además SubCategoryService, proveer una instancia nueva de Item en model
y las sub categrias leidas por el servicio correspondiente.
Devolver el nombre del template con el formulario para item
@RequestMapping("/new")
public String newItem(Model model, Item item) {
Item newItem = new Item();
newItem.setSubCategory(new SubCategory());
model.addAttribute("item", newItem);
model.addAttribute("subCategories", subCategoryService.getSubCategories());
return "itemForm";
}
El template hace una llamada post a la ruta ítems, por lo cual necesitamos una ruta en el
controller de ítem para crear y actualizar ítems. Una vez procesado, debe hacerse un redirect
a /ítems/{id}. Implementar el método save en el service.
@PostMapping
public String saveItem(Model model, Item item) {
Item itemPersisted = service.save(item);
model.addAttribute("item", itemPersisted);
return "redirect:/items/"+itemPersisted.getId();
}
y GenericServiceImpl
@Override
public T save(T model) {
return getRepository().save(model);
}
34 Actualizar Items
Para actualizar ítems se necesita adicionar en el listado
<td><a href="#" th:href="@{'/items/update/'+${item.id}}">Edit</a></td>
@RequestMapping("/update/{id}")
public String updateItem(Model model, @PathVariable String id) {
29
model.addAttribute("item", service.findById(Long.valueOf(id)));
model.addAttribute("subCategories", subCategoryService.findAll());
return "itemForm";
}
35 Eliminar Items
Para eliminar ítems se necesita adicionar en el listado
<td><a href="#" th:href="@{'/items/delete/'+${item.id}}">Delete</a></td>
y la ruta correspondiente en el controller
@RequestMapping(value = "/delete/{id}")
public String deleteItem(Model model, @PathVariable String id) {
itemService.deleteItem(Long.valueOf(id));
return "redirect:/items/";
}
@RequestMapping(value = "/{id}/image")
public String showUploadItemImageForm(Model model, @PathVariable String id) {
Item itemPersisted = itemService.findById(Long.valueOf(id));
model.addAttribute("item",itemPersisted);
return "uploadItemImageForm";
}
este formulario hace una llamada post al controller para almacenar la imagen, para tal motivo
es necesario adicionar una nueva propiedad en Item de tipo byte array
@Lob
private Byte[] image;
30
@PostMapping("/items/{id}/image")
public String potImage(Model model, @PathVariable String id, @RequestParam("imagefile") MultipartFile file) {
service.saveImage(Long.valueOf(id), file);
model.addAttribute("item", service.findById(Long.valueOf(id)));
model.addAttribute("subCategories", subCategoryService.getSubCategories());
return "redirect:/items/update/{id}";
}
@Override
public void saveImage(Long id, MultipartFile file) {
Item itemPersisted = findById(id);
try {
Byte[] bytes = new Byte[file.getBytes().length];
int i = 0;
for (Byte aByte : file.getBytes()) {
bytes[i++] = aByte;
}
itemPersisted.setImage(bytes);
getRepository().save(itemPersisted);
} catch (IOException e) {
logger.error("Error reading file", e);
e.printStackTrace();
}
}
@GetMapping("/{id}/readimage")
public void renderImageFromDB(@PathVariable String id, HttpServletResponse response) throws IOException {
Item itemPersisted = service.findById(Long.valueOf(id));
if (itemPersisted.getImage() != null) {
byte[] byteArray = new byte[itemPersisted.getImage().length];
int i = 0;
31
<div>
<label>Image</label>
<div>
<img th:src="@{'/items/' + ${item.id} + '/readimage'}"
width="200" height="200"/>
</div>
<a href="#" th:href="@{'/items/'+${item.id}+'/image'}">change Image</a>
</div>
que permite llamar al endpoint para renderizar imágenes.
38 Manejo de excepciones
En el paquete exceptions crear la excepción
@ResponseStatus(HttpStatus.NOT_FOUND)
public class NotFoundException extends RuntimeException{
public NotFoundException() {
super();
}
public NotFoundException(String message) {
super(message);
}
public NotFoundException(String message, Throwable cause) {
super(message, cause);
}
}
véase que el estado esta establecido por la anotación @ResponseStatus
En el método findById de generic service arrojar la nueva excepción si el item no fue
encontrado.
throw new NotFoundException(typeName + " id:" + id + " Not Found");
A nivel de aplicación se puede definir un handler de excepciones. Crear una clase llamada
ControllerExceptionHandler anotada con @ControllerAdvice que devuelva ModelAndView con
datos de la excepción y la vista a la que redirige.
32
@ControllerAdvice
public class ControllerExceptionHandler {
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(NumberFormatException.class)
public ModelAndView handleNumberFormat(Exception e){
ModelAndView modelAndView= new ModelAndView();
modelAndView.setViewName("applicationerror");
modelAndView.addObject("exception", e);
return modelAndView;
}
}
crear la pagina applicationerror y probar ver un ítem introduciendo un id de ítem invalido.
39 Docker
Instalar Docker community edition edge. Usuarios Windows necesitan virtual box instalado.
Ejecutar el comando
docker version
docker run hello-world
40 Mysql
En el archivo application.yml en resources escribir la configuración de mysql
spring:
datasource:
url: jdbc:mysql://localhost:3306/mysql
username: root
password: root
jpa:
hibernate:
ddl-auto: create
database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
database: mysql
show-sql: true
33
Adicionar la dependencia maven
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
41 MS SQL SERVER
Ejecutar los siguientes comandos.
docker pull microsoft/mssql-server-linux:2017-latest
PostgreSQL
34
docker run --name postgres -e POSTGRES_PASSWORD=postgres -d -p 5432:5432 postgres:9.6.10-alpine
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.3-1100-jdbc41</version>
</dependency>
spring:
datasource:
username: postgres
password: postgres
url: jdbc:postgresql://localhost:5432/ssi
jpa:
database: POSTGRESQL
generate-ddl: true
hibernate.ddl-auto: update
show-sql: true
https://www.getpostman.com/collections/cb9764af6c5d5bcaa0c9
42 Recursos
Muchos ejemplos de Spring 5
https://github.com/eugenp/tutorials
35
https://www.atlassian.com/dam/jcr:8132028b-024f-4b6b-953e-e68fcce0c5fa/atlassian-git-
cheatsheet.pdf
Libros de Git
https://git-scm.com/book/es/v2
Spring Boot H2
https://www.baeldung.com/spring-boot-h2-database
VIM cheatsheet
https://vim.rtorr.com/
36
DSL online mapeo de entidades
http://www.jhipster.tech/jdl-studio/
JPA locking
https://www.baeldung.com/jpa-optimistic-locking
https://www.baeldung.com/jpa-pessimistic-locking
Futuros completables
https://www.baeldung.com/java-completablefuture
WebClient de Spring 5
https://www.baeldung.com/spring-5-webclient
Rest Template
https://www.baeldung.com/rest-template
Mockito
https://www.baeldung.com/mockito-series
https://www.baeldung.com/mockito-annotations
Inyeccion de arrays
https://www.baeldung.com/spring-inject-arrays-lists
Criteria Query
https://www.baeldung.com/hibernate-criteria-queries
37
Spring Security OAuth 2 Guides
https://www.baeldung.com/spring-security-oauth
Java AWS
https://aws.amazon.com/es/blogs/opensource/java-apis-aws-lambda/
https://www.baeldung.com/java-aws-lambda
https://docs.aws.amazon.com/lambda/latest/dg/welcome.html
Ejemplo MVCMock
https://memorynotfound.com/unit-test-spring-mvc-rest-service-junit-mockito/
Validation
https://www.baeldung.com/spring-custom-validation-message-source
38