Introducción
El siguiente artículo detalla la implementación de un proyecto MVC que utiliza Maven para mantener actualizadas las dependencias de Spring e Hibernate. La idea es poder utilizar esta plantilla como punto de partida con todo lo necesario para construir un proyecto web MVC sin tener que comenzar desde cero cada vez. Para las pruebas de la capa DAO se implementan dos clases de prueba -User y Admin- que nos sirven para comprobar la flexibilidad de nuestra capa de acceso a datos. El código completo se puede consultar en mi repositorio público de código.
Dependencias
Dado el número de dependencias de librerías externas que puede llegar a tener un proyecto de estas características, el uso de Maven es la opción más lógica para facilitar el mantenimiento y actualización de las mismas. El fichero pom.xml que acompaña al proyecto nos permite gestionar de forma sencilla las dependencias y la versión de las mismas que necesitemos.
Para Spring [1], incluimos la dependencias señaladas a continuación, respetando el uso de una misma versión en cada una de ellas -5.2.12.RELEASE-:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.2.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.12.RELEASE</version>
</dependency>
Cabe destacar que spring-core contiene los módulos y funcionalidades básicas que utilizan todos los demás componentes de Spring. Por otro lado, spring-context nos permite utilizar Inyección de Dependencias (Dependency Injection) de forma sencilla utilizando Application Context entre otras funcionalidades.
La gestión de beans se realiza desde la clase AppConfig. En ella se definen los distintos objetos utilizados en la ejecución del proyecto como beans tipo prototype; será Spring quien se encarge de la instanciación y manejo en memoria de estos objetos a través de la factory.
public class AppConfig {
public static ApplicationContext factory =
new AnnotationConfigApplicationContext(AppConfig.class);
@Bean
@Scope("prototype")
public User getUser() {
return new User();
}
@Bean
@Scope("prototype")
public Admin getAdmin() {
return new Admin();
}
@Bean
@Scope("prototype")
public SearchParameters getSearchParameters() {
return new SearchParameters();
}
}
Por otro lado, para Hibernate tendríamos:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.1.0.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.4.28.Final</version>
</dependency>
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.3</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-c3p0</artifactId>
<version>5.2.17.Final</version>
</dependency>
En este caso, utilizamos la dependencia hibernate-validator para realizar validaciones en formularios web -consultar el código del proyecto para más detalle-. Por otra parte, la dependencia c3p0 es una forma muy utilizada para crear un Pool de conexiones que permite acceder a BBDD de forma eficiente, realizando operaciones de limpieza y optimizacion. La dependencia hibernate-c3p0 es la versión modificada de Hibernate que, en versiones actuales -Hibernate 5-, sería suficiente para habilitar el uso de c3p0 [2].
La configucación del Pool se puede realizar de forma directa en el archivo xml de configuración:
<bean id="myDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close">
<property name="driverClass" value="com.mysql.jdbc.Driver" />
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/userdb?useSSL=false&serverTimezone=UTC" />
<property name="user" value="USER" />
<property name="password" value="PASS" />
<!-- C3P0 connection pool -->
<property name="minPoolSize" value="2" />
<property name="maxPoolSize" value="10" />
<property name="maxIdleTime" value="20000" />
</bean>
En la dirección jdbcUrl se debe incluir el nombre de la BBDD: «jdbc:mysql://localhost:3306/userdb?useSSL=false&serverTimezone=UTC», en mi caso userdb. Finalmente, en las propiedades user y password se deben introducir las credenciales pertinentes que dependerán del administrado de BBDD.
Implementación DAO genérico para acceso a BBDD
Partiendo de la lógica expuesta en el artículo dedicado a Hibernate, implementamos una capa DAO genérica que permite acceder a distintas clases usando un número mínimo de métodos. La interfaz en este caso es:
public interface DAO {
public void Insert(Object o);
public <T> T selectFirstGenericFromCriteria(Class<T> t, String criteria);
public <T> List<T> selectGenericFromCriteria(Class<T> t, String criteria);
public <T extends IHasIntID> T selectGenericByID(Class<T> t, int ID);
public <T extends IHasIntID> void updateGenericByID(
Class<T> t, int ID, String propertyName, Object value);
public void executeVoidHQLQuery(String query);
}
Y una implementanción sencilla sería:
@Repository
public class AccesoDB implements DAO {
@Autowired
private SessionFactory sessionFactory;
@Transactional
//Example criteria: "from User e where e.age > 20"
//Usar el nombre de la clase, no de la tabla de la DB
public <T> List<T> selectGenericFromCriteria(Class<T> t, String criteria) {
Session session = sessionFactory.getCurrentSession();
Query<T> query = (Query<T>)(session.createQuery(criteria,t));
return query.getResultList();
}
@Transactional
public <T> T selectFirstGenericFromCriteria(Class<T> t, String criteria) {
Session session = sessionFactory.getCurrentSession();
Query<T> query = (Query<T>)(session.createQuery(criteria,t));
return query.getResultList().get(0);
}
...
}
En este caso, cabe señalar el uso de la etiqueta Spring @Repository la cual, citando la documentación original: Indicates that an annotated class is a «Repository», originally defined by Domain-Driven Design (Evans, 2003) as «a mechanism for encapsulating storage,retrieval, and search behavior which emulates a collection of objects». Por otro lado, la etiquiera @Autowired [3] es la forma de inyectar dependencias de forma automática mediante un escaneo previo que realiza Spring señalado en el archivo de configuración:
...
<context:component-scan base-package="com.ggm.spring" />
...
<bean id="sessionFactory"
class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="myDataSource" />
<property name="packagesToScan" value="com.ggm.spring.entity" />
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
<prop key="hibernate.show_sql">true</prop>
</props>
</property>
</bean>
...
Spring MVC
Pasamos a describir brevemente el flujo de ejecución del modelo vista controlador con Spring. En mi implementación, he situado todos los controladores bajo un mismo paquete. El llamado MainController se encarga simplemente de proveer métodos de acceso a index:
@Controller
@RequestMapping()
public class MainController {
@RequestMapping()
public String index(){
return "index";
}
@RequestMapping("/index")
public String goToIndex(){
return "index";
}
}
Para gestionar los Usuarios, he creado un Controlador adicional encargado de las operaciones CRUD de búsqueda y alta de nuevos usuarios:
@Controller
@RequestMapping("/users")
public class UserController {
@Autowired
private DAO dao;
...
//------------ Mostrar todos Usuarios ------------//
@RequestMapping("/userlist")
public String userList(Model model) {
//Get from DAO
List<User> users=
dao.selectGenericFromCriteria(
User.class, "from User");
System.out.println(users.toString());
//Add to model
model.addAttribute("userList", users);
return "userview/userListView";
}
//------------ Nuevo Usuario ------------//
@RequestMapping("/newUserFrom")
public String newUserForm(Model model) {
//Testing Spring prototype
User user =
AppConfig.factory.getBean(User.class);
model.addAttribute("user", user);
return "userview/userRegisterForm";
}
@PostMapping("/processNewUser")
//@Valid para especificar que tenemos validación
public String processNewUser(@Valid @ModelAttribute("user") User userToken,
BindingResult resValidation) {
//Aunque no usemos el user UserToken, hay que rescatarlo.
//Aplicamos la validación. BindingResult sin errores
if(!resValidation.hasErrors()) {
//Guardamos en DB
dao.Insert(userToken);
return "userview/viewRegisterForm";
}
//En caso de errores volver a la página de formulario
return "userview/userRegisterForm";
}
...
}
En este caso, el acceso a los métodos de control de usuario se da con el mapeo señalado al inicio: @RequestMapping(«/users»), de tal forma que si quisieramos tener la lista completa de usuarios accederíamos con /users/userlist. De nuevo, la inyección de dependencias es gestionada de forma automática por Spring mediante @Autowired; de esta forma tenemos una referencia a la capa DAO. Finalmente, para el procesado de nuevos usuarios, utilizamos un validador @Valid para impedir altas con datos erróneos.
De esta forma, y mediante el uso de la clase Model y la etiqueta @ModelAttribute, Spring nos permite realizar de forma sencilla la redirección a las distintas vistas donde se muenstran los datos solictados.
Referencias
Un comentario
Los comentarios están cerrados.