Creación de una aplicación MVC con Spring Framework: un tutorial para principiantes

Publicado: 2022-03-11

A menudo se dice que Java es demasiado complicado y tarda demasiado en crear aplicaciones simples. No obstante, Java proporciona una plataforma estable con un ecosistema muy maduro a su alrededor, lo que lo convierte en una excelente opción para desarrollar software robusto.

Spring Framework, uno de los muchos marcos poderosos en el ecosistema de Java, viene con una colección de modelos de programación y configuración con el objetivo de simplificar el desarrollo de aplicaciones comprobables y de alto rendimiento en Java.

marco de primavera

En este tutorial, asumiremos el desafío de crear una aplicación simple que actuará como una base de datos de desarrolladores de software que utilizan Spring Framework y la API de persistencia de Java (JPA).

La aplicación sigue una arquitectura MVC estándar. Tendrá un controlador (clase ContractsController), vistas (basadas en plantillas de Thymeleaf) y un modelo (un objeto de mapa de Java). En aras de la simplicidad, utilizaremos una base de datos en memoria detrás de JPA para conservar los datos mientras se ejecuta la aplicación.

Introducción al tutorial de Spring Framework

Para construir una aplicación basada en Spring, necesitaremos usar una de las siguientes herramientas de construcción:

  • Experto
  • gradle

En este tutorial, usaremos Maven. Si no está familiarizado con ninguna de estas herramientas, una manera fácil de comenzar es descargar Spring Tool Suite. La suite está dedicada a Spring Framework y viene con su propio IDE basado en Eclipse.

En Spring Tool Suite, creamos un nuevo proyecto seleccionando "Proyecto de inicio de Spring" en el menú "Archivo> Nuevo".

Tutorial de Spring Framework

Una vez que se haya creado un nuevo proyecto, necesitaremos editar el archivo de configuración de Maven, “ pom.xml ”, y agregar las siguientes dependencias:

 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-commons</artifactId> </dependency>

Estas dependencias enumeradas cargarán Spring Boot Web, Thymeleaf, JPA y H2 (que servirá como nuestra base de datos en memoria). Todas las bibliotecas necesarias se extraerán automáticamente.

Clases de entidad

Para poder almacenar información sobre los desarrolladores y sus habilidades, necesitaremos definir dos clases de entidad: " Desarrollador " y " Habilidad ".

Ambos se definen como clases simples de Java con algunas anotaciones. Al agregar "@Entity" antes de las clases, hacemos que sus instancias estén disponibles para JPA. Esto facilitará el almacenamiento y la recuperación de instancias del almacén de datos persistente cuando sea necesario. Además, las anotaciones “@Id” y “@GeneratedValue” nos permiten indicar el campo ID único de la entidad y generar su valor automáticamente al almacenarlo en la base de datos.

Como un desarrollador puede tener muchas habilidades, podemos definir una relación simple de muchos a muchos usando la anotación "@ManyToMany".

Desarrollador

 @Entity public class Developer { @Id @GeneratedValue(strategy=GenerationType.AUTO) private long id; private String firstName; private String lastName; private String email; @ManyToMany private List<Skill> skills; public Developer() { super(); } public Developer(String firstName, String lastName, String email, List<Skill> skills) { super(); this.firstName = firstName; this.lastName = lastName; this.email = email; this.skills = skills; } public long getId() { return id; } public void setId(long id) { this.id = id; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public List<Skill> getSkills() { return skills; } public void setSkills(List<Skill> skills) { this.skills = skills; } public boolean hasSkill(Skill skill) { for (Skill containedSkill: getSkills()) { if (containedSkill.getId() == skill.getId()) { return true; } } return false; } }

Habilidad

 @Entity public class Skill { @Id @GeneratedValue(strategy=GenerationType.AUTO) private long id; private String label; private String description; public Skill() { super(); } public Skill(String label, String description) { super(); this.label = label; this.description = description; } public long getId() { return id; } public void setId(long id) { this.id = id; } public String getLabel() { return label; } public void setLabel(String label) { this.label = label; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } }

Repositorios

Con JPA podemos definir una interfaz DeveloperRepository muy útil y una interfaz SkillRepository, que permiten operaciones CRUD sencillas. Estas interfaces nos permitirán acceder a desarrolladores y habilidades almacenados a través de simples llamadas a métodos, tales como:

  • “respository.findAll()”: devuelve todos los desarrolladores
  • “repository.findOne(id)”: devuelve el desarrollador con la ID dada

Para crear estas interfaces, todo lo que necesitamos hacer es extender la interfaz CrudRepository.

Repositorio de desarrolladores

 public interface DeveloperRepository extends CrudRepository<Developer, Long> { }

Repositorio de habilidades

 public interface SkillRepository extends CrudRepository<Skill, Long> { public List<Skill> findByLabel(String label); }

JPA proporcionará automáticamente la funcionalidad para el método adicional " findByLabel " declarado aquí.

Controlador

A continuación, podemos trabajar en el controlador para esta aplicación. El controlador mapeará los URI de solicitud para ver plantillas y realizar todo el procesamiento necesario en el medio.

 @Controller public class DevelopersController { @Autowired DeveloperRepository repository; @Autowired SkillRepository skillRepository; @RequestMapping("/developer/{id}") public String developer(@PathVariable Long id, Model model) { model.addAttribute("developer", repository.findOne(id)); model.addAttribute("skills", skillRepository.findAll()); return "developer"; } @RequestMapping(value="/developers",method=RequestMethod.GET) public String developersList(Model model) { model.addAttribute("developers", repository.findAll()); return "developers"; } @RequestMapping(value="/developers",method=RequestMethod.POST) public String developersAdd(@RequestParam String email, @RequestParam String firstName, @RequestParam String lastName, Model model) { Developer newDeveloper = new Developer(); newDeveloper.setEmail(email); newDeveloper.setFirstName(firstName); newDeveloper.setLastName(lastName); repository.save(newDeveloper); model.addAttribute("developer", newDeveloper); model.addAttribute("skills", skillRepository.findAll()); return "redirect:/developer/" + newDeveloper.getId(); } @RequestMapping(value="/developer/{id}/skills", method=RequestMethod.POST) public String developersAddSkill(@PathVariable Long id, @RequestParam Long skillId, Model model) { Skill skill = skillRepository.findOne(skillId); Developer developer = repository.findOne(id); if (developer != null) { if (!developer.hasSkill(skill)) { developer.getSkills().add(skill); } repository.save(developer); model.addAttribute("developer", repository.findOne(id)); model.addAttribute("skills", skillRepository.findAll()); return "redirect:/developer/" + developer.getId(); } model.addAttribute("developers", repository.findAll()); return "redirect:/developers"; } }

La asignación de URI a métodos se realiza a través de simples anotaciones "@RequestMapping". En este caso, cada método del controlador se asigna a un URI.

El parámetro de modelo de estos métodos permite pasar datos a la vista. En esencia, estos son mapas simples de claves de valores.

Cada método de controlador devuelve el nombre de la plantilla de Thymeleaf que se usará como vista o una URL en un patrón específico ("redireccionar: ”) para redirigir. Por ejemplo, los métodos "developer" y "_developersList_" devuelven el nombre de una plantilla, mientras que "developersAdd" y "developersAddSkill" devuelven direcciones URL a las que redirigir.

Dentro del controlador, las anotaciones “@Autowired” asignan automáticamente una instancia válida de nuestro repositorio definido en el campo correspondiente. Esto permite el acceso a datos relevantes desde dentro del controlador sin tener que lidiar con una gran cantidad de código repetitivo.

Puntos de vista

Finalmente, necesitamos definir algunas plantillas para que se generen las vistas. Para esto estamos usando Thymeleaf, un motor de plantillas simple. El modelo que usamos en los métodos de controlador está disponible directamente dentro de las plantillas, es decir, cuando ingresamos un contrato en la clave " contrato " en un modelo, podremos acceder al campo de nombre como "contrato.nombre" desde dentro de la plantilla.

Thymeleaf contiene algunos elementos y atributos especiales que controlan la generación de HTML. Son muy intuitivos y sencillos. Por ejemplo, para completar el contenido de un elemento span con el nombre de una habilidad, todo lo que necesita hacer es definir el siguiente atributo (suponiendo que la clave " habilidad " esté definida en el modelo):

 <span th:text="${skill.label}"></span>

De manera similar, para establecer el atributo “ href ” de un elemento ancla, se puede usar el atributo especial “ th:href ”.

En nuestra aplicación, necesitaremos dos plantillas simples. Para mayor claridad, omitiremos todos los atributos de estilo y clase (es decir, los de Bootstrap) aquí en el código de la plantilla incrustada.

Lista de desarrolladores

 <!DOCTYPE HTML> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>Developers database</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> </head> <body> <h1>Developers</h1> <table> <tr> <th>Name</th> <th>Skills</th> <th></th> </tr> <tr th:each="developer : ${developers}"> <td th:text="${developer.firstName + ' ' + developer.lastName}"></td> <td> <span th:each="skill,iterStat : ${developer.skills}"> <span th:text="${skill.label}"/><th:block th:if="${!iterStat.last}">,</th:block> </span> </td> <td> <a th:href="@{/developer/{id}(id=${developer.id})}">view</a> </td> </tr> </table> <hr/> <form th:action="@{/developers}" method="post" enctype="multipart/form-data"> <div> First name: <input name="firstName" /> </div> <div> Last name: <input name="lastName" /> </div> <div> Email: <input name="email" /> </div> <div> <input type="submit" value="Create developer" name="button"/> </div> </form> </body> </html>

Detalles del desarrollador

 <!DOCTYPE HTML> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>Developer</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> </head> <body> <h1>Developer</h1> Name: <b th:text="${developer.firstName}" /> <b th:text="${developer.lastName}" /><br/> Email: <span th:text="${developer.email}" /><br/> Skills: <span th:each="skill : ${developer.skills}"> <br/>&nbsp;&nbsp;<span th:text="${skill.label}" /> - <span th:text="${skill.description}" /> </span> <form th:action="@{/developer/{id}/skills(id=${developer.id})}" method="post" enctype="multipart/form-data" > <select name="skillId"> <option th:each="skill : ${skills}" th:value="${skill.id}" th:text="${skill.description}">Skill</option> </select> <input type="submit" value="Add skill"/> </form> </body> </html>

Ejecutando el Servidor

Spring contiene un módulo de arranque. Esto nos permite iniciar el servidor fácilmente desde la línea de comandos como una aplicación Java de línea de comandos:

 @SpringBootApplication public class Application implements CommandLineRunner { @Autowired DeveloperRepository developerRepository; @Autowired SkillRepository skillRepository; public static void main(String[] args) { SpringApplication.run(Application.class, args); } }

Dado que estamos utilizando una base de datos en memoria, tiene sentido arrancar la base de datos con algunos datos predefinidos en el lanzamiento. De esa manera tendremos al menos algunos datos en la base de datos cuando el servidor esté funcionando.

 @Override public void run(String... args) throws Exception { Skill javascript = new Skill("javascript", "Javascript language skill"); Skill ruby = new Skill("ruby", "Ruby language skill"); Skill emberjs = new Skill("emberjs", "Emberjs framework"); Skill angularjs = new Skill("angularjs", "Angularjs framework"); skillRepository.save(javascript); skillRepository.save(ruby); skillRepository.save(emberjs); skillRepository.save(angularjs); List<Developer> developers = new LinkedList<Developer>(); developers.add(new Developer("John", "Smith", "[email protected]", Arrays.asList(new Skill[] { javascript, ruby }))); developers.add(new Developer("Mark", "Johnson", "[email protected]", Arrays.asList(new Skill[] { emberjs, ruby }))); developers.add(new Developer("Michael", "Williams", "[email protected]", Arrays.asList(new Skill[] { angularjs, ruby }))); developers.add(new Developer("Fred", "Miller", "[email protected]", Arrays.asList(new Skill[] { emberjs, angularjs, javascript }))); developers.add(new Developer("Bob", "Brown", "[email protected]", Arrays.asList(new Skill[] { emberjs }))); developerRepository.save(developers); }

Conclusión

Spring es un framework versátil que permite construir aplicaciones MVC. La creación de una aplicación sencilla con Spring es rápida y transparente. La aplicación también se puede integrar fácilmente con una base de datos usando JPA.

El código fuente de todo este proyecto está disponible en GitHub.