logo

Spring MVC - Spring Data JPA - Hibernate

Introducción

En el pasado pasado tutorial vimos como integrar Spring e Hibernate. Es el momento de dar un paso mas y comprobar como Spring nos hace la vida más fácil a la hora de trabajar con la persistencia de datos, para ello vamos a ver en este tutorial Spring Data JPA.

Os podéis descargar el proyecto de github, Spring Data JPA.

Podemos ver en la estructura de carpetas del proyecto la clase ResourceNotFoundException. Aunque el manejo de excepciones no lo veremos hasta llegar a spring boot, adelantamos aquí una clase para el manejo de excepciones en la capa de servicio de forma que si no se puede llevar a cabo alguna acción (delete, get, edit) el programa avise mediante una página informativa y podamos ir a la página principal y poder seguir "navegando".

El Proyecto

La estructura del proyecto
estructura

Como dije en al capítulo anterior, antes de seguir, recomiendo que os detengáis en el archivo database.properties para estudiarlo un poco. Todas las lineas están comentadas para una mayor compresión del mismo, en cualquier caso, al final del archivo dejo un par de links que me parecen interesantes y explican todo esto.

database.properties

# MySQL configuration
# ==================

# Indica el driver/lib para conectar java a mysql
mysql.driver=com.mysql.jdbc.Driver
# Url donde esta el servicio de tu mysql y el nombre de la base de datos
mysql.url=jdbc:mysql://localhost:3306/testdb
#Usuario para tu base de datos 
mysql.username=root
#Contrasena para tu base de datos 
mysql.password=

# Hibernate configuration
# ======================

# Esta propiedad hace que Hibernate genere el SQL optimizado para la base de datos elegida.
hibernate.dialect=org.hibernate.dialect.MySQLDialect
# Indica que debe mostrar el log de las consultas sql ejecutadas, bueno para depurar
hibernate.show_sql=true
# update the schema (para el desarrollo de la aplicación, no para producción)
hibernate.hbm2ddl.auto=update
# Formatea la sentencia SQL generada para que sea más legible. (Ocupa más espacio en la pantalla).
hibernate.format_sql=true

# C3P0 configuration
# ==================

# Especifica el número mínimo de conexiones que debe mantener en un momento dado. Por defecto, mantendrá al menos tres conexiones.
hibernate.c3p0.min_size=5
# Especifica el número máximo de conexiones que puede mantener en un momento dado. Por defecto, mantendrá un máximo de 15 conexiones.
hibernate.c3p0.max_size=20
# Especifica cuántas conexiones debería intentar adquirir si el grupo se queda sin conexiones disponibles. 
# Por defecto, intentará adquirir tres conexiones nuevas.
hibernate.c3p0.acquire_increment=5
# Especifica el número de segundos que se mantendrá una conexión no utilizada antes de ser descartada. 
# Por defecto, las conexiones nunca expirarán en el pool.
hibernate.c3p0.timeout=1800
# El número de declaraciones preparadas se almacenará en la memoria caché
hibernate.c3p0.max_statements=150
                    
Las dependencias
pom.xml

<dependencies>
  <!-- Spring -->
  <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.1.5.RELEASE</version>
  </dependency>
  <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>${spring.version}</version>
  </dependency>
  <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-orm</artifactId>
      <version>${spring.version}</version>
  </dependency>
  <!-- Spring-data-jpa -->
  <dependency>
      <groupId>org.springframework.data</groupId>
      <artifactId>spring-data-jpa</artifactId>
      <version>2.1.5.RELEASE</version>
  </dependency>
  <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>${spring.version}</version>
      <scope>test</scope>
  </dependency>
  <!-- Hibernate -->
  <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-entitymanager</artifactId>
      <version>${hibernate.version}</version>
  </dependency>
  <!-- hibernate-c3p0 -->
  <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-c3p0</artifactId>
      <version>${hibernate.version}</version>
  </dependency> 
  <!-- Hibernate Validator -->
  <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-validator</artifactId>
      <version>${hibernate.version}</version>
  </dependency>
  <!-- mysql-connector-java -->
  <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.15</version>
      <scope>runtime</scope>
  </dependency>
  <!-- lombok -->
  <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.16.16</version>
      <scope>provided</scope>
  </dependency>
  <!-- javax.servlet -->
  <dependency>   
      <groupId>javax.servlet</groupId>   
      <artifactId>javax.servlet-api</artifactId>    
      <version>3.1.0</version>
      <scope>provided</scope>   
  </dependency>
  <dependency>  
      <groupId>javax.servlet.jps</groupId> 
      <artifactId>javax.servlet.jsp-api</artifactId> 
      <version>2.3.1</version> 
      <scope>provided</scope> 
  </dependency>
  <!-- jstl -->
  <dependency>  
      <groupId>javax.servlet</groupId> 
      <artifactId>jstl</artifactId> 
      <version>1.2</version>  
  </dependency>
  <!-- junit -->
  <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
  </dependency>
</dependencies>
                    
La Configuración

Como siempre configuramos el proyecto con Java Config.

SpringWebAppInitializer.java

 package com.wanchopi.spring.config;


import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

/**
 * Init app
 * @author Wanchopi
 *
 */
public class SpringWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

  @Override
  protected Class<?>[] getRootConfigClasses() {
    return new Class<?>[] { PersistenceJPAConfig.class};
  }

  @Override
  protected Class<?>[] getServletConfigClasses() {
    return new Class<?>[] { WebMvcConfig.class};
  }

  @Override
  protected String[] getServletMappings() {
    return new String[] { "/" };
  }

}                       
                    
WebMvcConfig.java

package com.wanchopi.spring.config;

import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;


/**
 * Configure Spring MVC
 * @author Wanchopi
 *
 */
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.wanchopi.spring")
public class WebMvcConfig implements WebMvcConfigurer {
    
    @Bean(name = "viewResolver")
    public InternalResourceViewResolver getViewResolver() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        
        viewResolver.setPrefix("/WEB-INF/views/");
        viewResolver.setSuffix(".jsp");
        
        return viewResolver;
    }
    
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/public/**").addResourceLocations("/public/"); 
    }
    
    @Bean
    public MessageSource messageSource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        messageSource.setBasename("messages");
        messageSource.setDefaultEncoding("UTF-8");
        //messageSource.setCacheSeconds(1); 
        return messageSource;
    }

}                        
                    
PersistenceJPAConfig.java

package com.wanchopi.spring.config;

import java.util.Properties;

import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@EnableTransactionManagement
@PropertySource({"classpath:database.properties"})
@ComponentScan({"com.wanchopi.spring"})
@EnableJpaRepositories({"com.wanchopi.spring.repository"})
public class PersistenceJPAConfig {
  
  @Autowired
  private Environment env;

  /**
   * 
   */
  public PersistenceJPAConfig() {
    super();
  }
  
  @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        final LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = 
            new LocalContainerEntityManagerFactoryBean();
        entityManagerFactoryBean.setDataSource(dataSource());
        entityManagerFactoryBean.setPackagesToScan(new String[] {
            "com.wanchopi.spring.entity"
        });

        final HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        entityManagerFactoryBean.setJpaVendorAdapter(vendorAdapter);
        entityManagerFactoryBean.setJpaProperties(hibernateProperties());

        return entityManagerFactoryBean;
    }
  
  private Properties hibernateProperties() {
        Properties properties = new Properties();
        // Setting Hibernate properties
        properties.put("hibernate.dialect", env.getRequiredProperty("hibernate.dialect"));
        properties.put("hibernate.show_sql", env.getRequiredProperty("hibernate.show_sql"));
        properties.put("hibernate.format_sql", env.getRequiredProperty("hibernate.format_sql"));
        properties.put("hibernate.hbm2ddl.auto", env.getRequiredProperty("hibernate.hbm2ddl.auto"));
        // Setting C3P0 properties
        properties.put("hibernate.c3p0.min_size", env.getProperty("hibernate.c3p0.min_size"));
        properties.put("hibernate.c3p0.max_size", env.getProperty("hibernate.c3p0.max_size"));
        properties.put("hibernate.c3p0.acquire_increment", env.getProperty("hibernate.c3p0.acquire_increment"));
        properties.put("hibernate.c3p0.timeout", env.getProperty("hibernate.c3p0.timeout"));
        properties.put("hibernate.c3p0.max_statements", env.getProperty("hibernate.c3p0.max_statements"));
        return properties;
    }
  
  @Bean
  public DataSource dataSource() {
    DriverManagerDataSource dataSource = new DriverManagerDataSource();
    dataSource.setDriverClassName(env.getProperty("mysql.driver"));
    dataSource.setUrl(env.getProperty("mysql.url"));
    dataSource.setUsername(env.getProperty("mysql.username"));
    dataSource.setPassword(env.getProperty("mysql.password"));
    return dataSource;
  }
  
  @Bean
    public PlatformTransactionManager transactionManager(final EntityManagerFactory emf) {
        final JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(emf);
        return transactionManager;
    }
  
  @Bean
    public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
        return new PersistenceExceptionTranslationPostProcessor();
    }

}
                    

Como puedes ver, estamos usando la anotación @PropertySource para leer las propiedades JDBC, Hibernate y C3P0 del archivo database.properties, y por otra parte tenemos que anotar la clase con la anotación @EnableTransactionManagement para habilitar la gestión de transacciones.

El Modelo
Employee.java

package com.wanchopi.spring.entity;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import org.hibernate.validator.constraints.Email;

import lombok.Getter;
import lombok.Setter;

/**
 * Employed entity
 * 
 * @author Wanchopi
 *
 */
@Entity
@Table(name = "employees")
public class Employee {
    
    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Getter @Setter
    private long id;
    
    @Column(name = "first_name")
    @NotNull
    @Size(min = 2, max = 50)
    @Getter @Setter
    private String firstName;
    
    @Column(name = "last_name")
    @NotNull
    @Size(min = 2, max = 50)
    @Getter @Setter
    private String lastName;
    
    @Column(name = "email")
    @NotNull
    @Email
    @Getter @Setter
    private String email;

}
                    
El Controlador
EmployeeController.java

package com.wanchopi.spring.controller;

import java.util.List;

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.propertyeditors.StringTrimmerEditor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

import com.wanchopi.spring.entity.Employee;
import com.wanchopi.spring.exception.ResourceNotFoundException;
import com.wanchopi.spring.service.IEmployeeService;

/**
 * Main controller
 * 
 * @author Wanchopi
 *
 */
@Controller
public class EmployeeController {
  
  @Autowired
  private IEmployeeService employeeService;
  
  @InitBinder
    public void initBinder(WebDataBinder dataBinder) {
        StringTrimmerEditor stringTrimmerEditor = new StringTrimmerEditor(true);
        dataBinder.registerCustomEditor(String.class, stringTrimmerEditor);
    }
  
  @RequestMapping("/")
  public String listEmployees(Model model) {
    List<Employee> employees = employeeService.getAllEmployees();
    model.addAttribute("employees", employees);
    return "index";
  }
  
  @GetMapping("/form")
  public String showFormForAdd(Model model) {
    Employee employee = new Employee();
    model.addAttribute("employee", employee);
    return "employee-form";
  }
  
  @PostMapping("/save")
  public String saveEmployee(@Valid @ModelAttribute("employee") Employee employee,
      BindingResult theBindingResult) {
    if (theBindingResult.hasErrors()) {
      return "employee-form";
    } else {
      employeeService.saveEmployee(employee);
      return "redirect:/";
    }
  }
  
  @GetMapping("/delete")
  public String deleteOne(@RequestParam("id") long id) throws ResourceNotFoundException {
    employeeService.deleteEmployee(id);
    return "redirect:/";
  }
  
  @GetMapping("/update")
  public String showFormForUpdate(@RequestParam("id") long id, 
      Model model) throws ResourceNotFoundException {
    Employee employee = employeeService.getEmployeeById(id);
    model.addAttribute("employee", employee);
    return "employee-update-form";
  }
    
  @PostMapping("/search")
  public ModelAndView search(@RequestParam String keyword) throws ResourceNotFoundException {
    ModelAndView mav = new ModelAndView("search");
    
    List<Employee> result = employeeService.search(keyword);
    mav.addObject("result", result);
    
    return mav;
  }
  
  @ExceptionHandler(ResourceNotFoundException.class)
  private String handleResourceNotFoundException(ResourceNotFoundException e) {
    return "error";
  }
  
}
                    

Observamos la inyección de dependencia de una interface, EmployeeService, donde están definidas las operaciones básicas, y como novedad tenemos el método privado handleResourceNotFoundException() anotado con @ExceptionHandle. Los métodos anotados de esta manera se encargarán de manejar las excepciones. En este caso simplemente manda al usuario a una página de error informando de que no se ha podido realizar la acción solicitada.

El Servicio
IEmployeeService.java

package com.wanchopi.spring.service;

import java.util.List;

import com.wanchopi.spring.entity.Employee;

/**
 * Service
 * @author Wanchopi
 *
 */
public interface IEmployeeService {
    
    public List<Employee> getAllEmployees();
    public void saveEmployee(Employee employee);
    public Employee getEmployeeById(long id) throws ResourceNotFoundException;
    public void deleteEmployee(long id) throws ResourceNotFoundException;
    public void deleteAll() throws ResourceNotFoundException;
    public List<Employee> search(String keyword);

}                        
                    

Y la clase que implementa la interface que a su vez presenta una inyección de dependencia de una interface que a su vez es implementada por una clase que es la responsable de proveer las operaciones CRUD, esto es un repositorio. Es en la clase repositorio donde está el trabajo de spring-data-jpa como veremos.

EmployeeService.java

package com.wanchopi.spring.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.wanchopi.spring.entity.Employee;
import com.wanchopi.spring.exception.ResourceNotFoundException;
import com.wanchopi.spring.repository.EmployeeRepository;

/**
 * Class that implements the IEmployeeService interface 
 * @author Wanchopi
 *
 */
@Service
public class EmployeeService implements IEmployeeService {
  
  @Autowired
  private EmployeeRepository employeeRepository;

  @Override
  @Transactional
  public List<Employee> getAllEmployees() {
    List<Employee> employees = employeeRepository.findAll();
    return employees;
  }

  @Override
  @Transactional
  public void saveEmployee(Employee employee) {
    employeeRepository.save(employee);
  }

  @Override
  @Transactional
  public Employee getEmployeeById(long id) throws ResourceNotFoundException {
    return employeeRepository.findById(id).orElseThrow(
        () -> new ResourceNotFoundException(id));
  }

  @Override
  @Transactional
  public void deleteEmployee(long id) throws ResourceNotFoundException {
    employeeRepository.deleteById(id);
  }

  @Override
  @Transactional
  public void deleteAll() throws ResourceNotFoundException {
    employeeRepository.deleteAll();
  }

  @Override
  @Transactional
  public List<Employee> search(String keyword) {
    return employeeRepository.search(keyword);
  }

}                        
                    

Estamos utilizando la anotación @Transactional que se aplica a la capa de servicio para el soporte de transacciones. La anotación @Service se utiliza para anotar las clases de implementación de la capa de servicios.

El Repositoriio
EmployeeRepository.java

package com.wanchopi.spring.repository;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import com.wanchopi.spring.entity.Employee;

/**
 * Repository
 * @author Wanchopi
 *
 */
@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
  
  @Query(value = "SELECT e FROM Employee e WHERE e.firstName LIKE '%' || :keyword || '%'"
      + "OR e.email LIKE '%' || :keyword || '%' OR e.lastName LIKE '%' || :keyword || '%'")
  public List<Employee> search(@Param("keyword") String keyword);

}
                    

Olvidemonos del único método de esta clase (interface) pues este es para un autocompletado que veremos con spring boot Y pongamos que la clase está así:


package com.wanchopi.spring.repository;

import java.util.List;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

import com.wanchopi.spring.entity.Employee;

/**
 * Repository
 * @author Wanchopi
 *
 */
@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> { }
                    

Simplemente anotando la clase con @Repository y extendiendo la interface JpaRepository<T, ID> tenemos todas las operaciones básicas sin añadir ni una sola linea de código.

JpaRepository es una extensión específica del Repositorio del JPA. Contiene el API completo de CrudRepository y PagingAndSortingRepository. Así que contiene el API para operaciones básicas de CRUD y también el API para paginación y clasificación.

Aquí encontrareis mas información al respecto

Las Vistas
index.jsp

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<title>index</title>
<link rel="stylesheet" href="public/css/bootstrap.min.css">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.0/css/all.css">
<link rel="stylesheet" href="public/css/mystyle.css">
</head>
<body>
  <div class="container-fluid" align="center">
    <!-- nav -->
    <nav class="navbar navbar-light bg-light">
      <a href="<c:url value="/"/>"><img alt="logo" src="public/images/spring_50x50.png"></a>
      <a href="form" class="btn btn-outline-success my-2 my-sm-0">Save new employee</a>
    </nav>
    <!-- header -->
    <div class="container">
      <div class="row">
        <div class="col-lg-12" align="center">
          <img alt="logo" src="public/images/logo.png">
        </div>
      </div>
    </div>
    <div class="main">
      <div class="col-lg-9" style="margin-top: 50px;">
        <h2 class="bg-primary text-white">List of employees</h2>
        <table class="table">
          <thead class="thead-dark">
            <tr align="center">
              <th scope="col">ID</th>
              <th scope="col">NAME</th>
              <th scope="col">E-MAIL</th>
              <th scope="col">EDIT</th>
              <th scope="col">DELETE</th>
            </tr>
          </thead>
          <tbody>
            <c:forEach items="${employees}" var="employee">
              <tr align="center">
                <td>${employee.id}</td>
                <td>${employee.firstName} ${employee.lastName}</td>
                <td>${employee.email}</td>
                <td><a href="update?id=${employee.id}"><i class="fa fa-edit"></i></a></td>
                <td><a href="delete?id=${employee.id}"><i class="fa fa-times"></i></a></td>
              </tr>
            </c:forEach>
          </tbody>
        </table>
      </div>
    </div>
  </div>
  <!-- jQuery core JavaScript -->
    <script src="public/js/jquery.min.js"></script>
    <!-- Bootstrap core JavaScript -->
    <script src="public/js/bootstrap.min.js"></script>
</body>
</html>
                    

El index presenta una lista de los empleados con los botones correspondientes para llevar a cabo las operaciones básicas (guardar, borrar, editar).

run app
employee-form.jsp

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<title>employee form</title>
<link rel="stylesheet" href="public/css/bootstrap.min.css">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.0/css/all.css">
<link rel="stylesheet" href="public/css/mystyle.css">
</head>
<body>
  <div class="container-fluid" align="center">
    <!-- nav -->
    <nav class="navbar navbar-light bg-light">
      <a href="<c:url value="/"/>"><img alt="logo" src="public/images/spring_50x50.png"></a>
      <a href="form" class="btn btn-outline-success my-2 my-sm-0">Save new employee</a>
    </nav>
    <div class="col-lg-6" style="margin-bottom: 50px">
      <h2 class="bg-primary text-white">Employee Manager</h2>
    </div>
    <div class="col-lg-6" style="margin-top: 50px;">
      <div class="card" align="left">
        <h5 class="card-title p-3 mb-2 text-light bg-dark">New Employee</h5>
        <div class="card-body">
          <form:form action="save" method="post" modelAttribute="employee" cssClass="form-horizontal">
            <!-- first name -->
              <div class="form-group">
                <label for="firstName" class="col-md-3 control-label font-weight-bold">First Name</label>
                <div class="col-md-9">
                  <form:input path="firstName" type="text" cssClass="form-control" />
                  <form:errors path="firstName" cssClass="errors" />
                </div>
              </div>
              <!-- last name -->
              <div class="form-group">
                <label for="lastName" class="col-md-3 control-label font-weight-bold">Last Name</label>
                <div class="col-md-9">
                  <form:input path="lastName" type="text" cssClass="form-control" />
                  <form:errors path="lastName" cssClass="errors" />
                </div>
              </div>
              <!-- email -->
              <div class="form-group">
                <label for="email" class="col-md-3 control-label font-weight-bold">Email</label>
                <div class="col-md-9">
                  <form:input path="email" type="email" cssClass="form-control" />
                  <form:errors path="email" cssClass="errors" />
                </div>
              </div>
              <div class="form-group">
              <!-- Button -->
              <div class="col-md-offset-3 col-md-9">
                <form:button type="Submit" class="btn btn-primary">Save</form:button>
              </div>
            </div>
          </form:form>
        </div>
      </div>
    </div>
  </div>
  <!-- jQuery core JavaScript -->
    <script src="public/js/jquery.min.js"></script>
    <!-- Bootstrap core JavaScript -->
    <script src="public/js/bootstrap.min.js"></script>
</body>
</html>
                    

Esta vista es un simple formulario para introducir en la base de datos un nuevo empleado.

run app
employee-update-form.jsp

<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<title>employee update form</title>
<link rel="stylesheet" href="public/css/bootstrap.min.css">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.0/css/all.css">
<link rel="stylesheet" href="public/css/mystyle.css">
</head>
<body>
  <div class="container-fluid" align="center">
    <!-- nav -->
    <nav class="navbar navbar-light bg-light">
      <a href="<c:url value="/"/>"><img alt="logo" src="public/images/spring_50x50.png"></a>
      <a href="form" class="btn btn-outline-success my-2 my-sm-0">Save new employee</a>
    </nav>
    <div class="col-lg-10" style="margin-bottom: 100px">
      <h1>Employee Manager</h1>
    </div>
    <div class="col-lg-6" style="margin-top: 50px;">
      <div class="card" align="left">
        <h5 class="card-title p-3 mb-2 text-light bg-dark">Update employee</h5>
        <div class="card-body">
          <form:form action="save" method="post" modelAttribute="employee" cssClass="form-horizontal">
            <!-- ID -->
              <div class="form-group">
                <label for="id" class="sr-only">ID</label>
                <div class="col-md-9">
                  <form:input path="id" type="text" cssClass="form-control" readonly="true"/>
                </div>
              </div>
            <!-- first name -->
              <div class="form-group">
                <label for="firstName" class="col-md-3 control-label font-weight-bold">First Name</label>
                <div class="col-md-9">
                  <form:input path="firstName" type="text" cssClass="form-control" />
                  <form:errors path="firstName" cssClass="errors" />
                </div>
              </div>
              <!-- last name -->
              <div class="form-group">
                <label for="lastName" class="col-md-3 control-label font-weight-bold">Last Name</label>
                <div class="col-md-9">
                  <form:input path="lastName" type="text" cssClass="form-control" />
                  <form:errors path="lastName" cssClass="errors" />
                </div>
              </div>
              <!-- email -->
              <div class="form-group">
                <label for="email" class="col-md-3 control-label font-weight-bold">Email</label>
                <div class="col-md-9">
                  <form:input path="email" type="email" cssClass="form-control" />
                  <form:errors path="email" cssClass="errors" />
                </div>
              </div>
              <div class="form-group">
              <!-- Button -->
              <div class="col-md-offset-3 col-md-9">
                <form:button type="Submit" class="btn btn-primary">Save</form:button>
              </div>
            </div>
          </form:form>
        </div>
      </div>
    </div>
  </div>
    <!-- jQuery core JavaScript -->
    <script src="public/js/jquery.min.js"></script>
    <!-- Bootstrap core JavaScript -->
    <script src="public/js/bootstrap.min.js"></script>
</body>
</html>
                    

Y la última es un formulario para poder editar a los empleados cuando sea necesario.

run app

Herramientas y Software

  • Eclipse Version: 2019-03 (4.11.0)
  • jdk 1.8
  • Maven 3.6
  • Spring MVC 5.1.5
  • Hibernate 5.4.1. Final
  • MySQL 8.0.15
  • bootstrap 4.3.1
  • Apache Tomcat v8.5
anterior

Internacionalización i18n

siguiente

Introducción a Spring Boot