Creación de una aplicación con Spring e Hibernate desde 0

5
49315

Creación de una aplicación con Spring e Hibernate desde 0

1. Introducción

Como el título reza, en este tutorial vamos a crear una pequeña aplicación usando Spring e Hibernate partiendo desde 0. Este es un tutorial con un enfoque claramente práctico, con lo cual voy a intentar no entrar en conceptos teóricos ni técnicos. Simplemente crearemos unas entidades, unos DAO, una clase con un método main() que ejecute la aplicación y, como no podia ser de otro modo, juntaremos todas las piezas con Spring haciendo uso de su soporte para Hibernate.

 

Para no complicar mucho el ejemplo voy a crear la típica aplicación que accede a una base de datos con información sobre empresas y personal, y para ello usaremos Hibernate con anotaciones, no con mapeos en ficheros .hbm.xml. La aplicación no tiene apenas funcionalidad y se utilizará por consola, pero servirá para ver cómo podemos integrar Hibernate con anotaciones en un proyecto de Spring. Además veremos que gracias al uso de anotaciones es muy sencillo sustituir Hibernate por cualquier otro motor de persistencia que pueda usarse a través de JPA.

 

En adictosaltrabajo ya vemos publicado otros tutoriales sobre Spring e Hibernate que seguramente os sirvan de referencia o como complemento a éste. Si quieres descargar el código fuente de este tutorial no tienes más que pinchar aqui.

 

2. Entorno

 

3. Modelo de datos

El modelo de datos, como se puede observar, es muy sencillo. Tan solo una relación N:M entre COMPANY y WORKER :

 

 

 

El script de creación para MySQL es el siguiente:

 

CREATE DATABASE autentiaSpringHb;
USE autentiaSpringHb;

CREATE TABLE COMPANY (
id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
name VARCHAR(45) NOT NULL,
description VARCHAR(255) NOT NULL,
PRIMARY KEY(id)
) engine=innodb default charset=utf8 collate=utf8_spanish_ci;

CREATE TABLE WORKER (
id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
COMPANY_id INTEGER UNSIGNED NOT NULL,
name VARCHAR(255) NOT NULL,
surname VARCHAR(255) NOT NULL,
workerType VARCHAR(20) NOT NULL,
PRIMARY KEY(id),
CONSTRAINT fk_person_company FOREIGN KEY(COMPANY_id) REFERENCES COMPANY(id)
) engine=innodb default charset=utf8 collate=utf8_spanish_ci;

 

4. Entidades

Para definir las entidades vamos a utilizar las anotaciones de Hibernate, y gracias a ellas nos ahorraremos los .hbm.xml. Como podreis observar viendo los ejemplos es muy sencillo mapear los atributos de las clases y las propias clases, aparte que hace que sea más fácil mantener las clases ya que no tenemos que mantener, aparte de la clase, un fichero xml.

A continuación pego el código del fichero Company.java

package com.autentia.springhb.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;

@Entity
@Table(name="COMPANY")
public class Company extends AbstractEntity {

	private static final long serialVersionUID = 1460669361717988469L;

	private String name;
	private String description;
	
	@Id @GeneratedValue(strategy = GenerationType.AUTO)	
	@Override
	public Long getId() {
		return id;
	}

	@Column(length=45, nullable=false)
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Column(length=255, nullable=true)
	public String getDescription() {
		return description;
	}

	public void setDescription(String notes) {
		this.description = notes;
	}	
}

Tras ello, el del fichero Worker.java

package com.autentia.springhb.entity;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

@Entity
@Table(name="WORKER")
public class Worker extends AbstractEntity {

	private static final long serialVersionUID = 6430623703467941209L;
	
	private WorkerType workerType = WorkerType.DESARROLLADOR;
	private String name;
	private String surname;
	private Company company;
	
	@Id @GeneratedValue(strategy = GenerationType.AUTO)	
	@Override
	public Long getId() {
		return id;
	}
	
	@Column(length = 20, nullable = false)
	@Enumerated(EnumType.STRING)
	public WorkerType getWorkerType() {
		return workerType;
	}

	public void setWorkerType(WorkerType workerType) {
		this.workerType = workerType;
	}

	@Column(length=255, nullable=false)
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Column(length=255, nullable=false)
	public String getSurname() {
		return surname;
	}

	public void setSurname(String surname) {
		this.surname = surname;
	}
	
	@ManyToOne(targetEntity = Company.class)
	@JoinColumn(name = "COMPANY_id")
	public Company getCompany() {
		return company;
	}

	public void setCompany(Company company) {
		this.company = company;
	}
}

Como habreis podido observar ambas heredan de AbstractEntity, donde se infieren cosas comunes a las clases mencionadas anteriormente:

package com.autentia.springhb.entity;

import java.io.Serializable;

public abstract class AbstractEntity implements Serializable {

	protected Long id;

	// Este metodo es abstracto porque las anotaciones no son heredables 
	public abstract Long getId();

	// Este metodo es protegido para evitar que un prrgramador pueda poner un
	// idenfificador en la instancia, ya que los identitificadores deben ser
	// gestionados por la capa de persistencia
	protected void setId(final Long id) {
		this.id = id;
	}
}

Y para rematar, los trabajadores poseen un tipo de trabajador representado con la siguiente clase enumerada:

package com.autentia.springhb.entity;

public enum WorkerType {
	JEFE, CONSULTOR, DESARROLLADOR, BECARIO
}	

IMPORTANTE: Daros cuenta de que las anotaciones NO POSEEN ninguna dependencia con Hibernate (todas proceden de javax.persistence). Gracias al uso de estas anotaciones, que forman parte de JPA, podremos cambiar hibernate por cualquier otro ORM que funcione bajo JPA de una manera muy sencilla en los ficheros de configuración de Spring.

 

5. DAO

Otra de las partes que vamos a necesitar en nuestra aplicación son los DAO. Para ello vamos a definir unas interfaces con las operaciones que se puede realizar con ellos y tras eso implementaremos esas interfaces usando el soporte que brinda Spring para Hibernate. Las interfaces son muy simples:

Interfaz CompanyDAO:

package com.autentia.springhb.dao;

import java.util.List;

import com.autentia.springhb.entity.Company;

public interface CompanyDao extends GenericDao {

	/**
	 * Recupera todas las empresas
	 */
	public List findAll();

	/**
	 * Recupera una empresa buscandola por su id 
	 */
	public Company findById(final Long id);
}

Interfaz WorkerDAO:

package com.autentia.springhb.dao;

import java.util.List;

import com.autentia.springhb.entity.Worker;

public interface WorkerDao extends GenericDao {

	/**
	 * Recupera todos los trabajadores de todas las empresas
	 */
	public List findAll();

	/**
	 * Recupera un trabajador buscandolo por su id 
	 */
	public Worker findById(final Long id);
}

Interfaz GenericDAO:

package com.autentia.springhb.dao;

import com.autentia.springhb.entity.AbstractEntity;

public interface GenericDao {

	/**
	 * Almacena un objeto en base de datos 
	 */
	public void save(final AbstractEntity object);

	/**
	 * Elimina un objecto de la base de datos
	 */
	public void delete(final AbstractEntity object);

}

Como podeis ver hay una pequeña herencia entre interfaces, ya que tanto WorkerDAO como CompanyDAO heredan de GenericDAO ya que, al fin y al cabo, los métodos save() y delete() borran cualquier tipo de entidad que pueda ser persistida en base de datos.

Una vez comentada la herencia entre interfaces, vamos a por la implementación:

Clase HbSpringCompanyDaoImpl:

package com.autentia.springhb.dao.impl.hbspring;

import java.util.List;

import com.autentia.springhb.dao.CompanyDao;
import com.autentia.springhb.entity.Company;

@SuppressWarnings("unchecked")
public class HbSpringCompanyDaoImpl extends AbstractHbSpringGenericDaoImpl implements CompanyDao {
	
	public List findAll() {
		System.out.println("usando el metodo 'findAll()' de la clase '" + this.getClass().getSimpleName() + " '");
		return getHibernateTemplate().find("from Company");
	}

	public Company findById(Long id) {
		System.out.println("usando el metodo 'findById()' de la clase '" + this.getClass().getSimpleName() + " '");
		return (Company) getHibernateTemplate().get(Company.class, id);
	}
}

Clase HbSpringWorkerDaoImpl:

package com.autentia.springhb.dao.impl.hbspring;

import java.util.List;

import com.autentia.springhb.dao.WorkerDao;
import com.autentia.springhb.entity.Worker;

@SuppressWarnings("unchecked")
public class HbSpringWorkerDaoImpl extends AbstractHbSpringGenericDaoImpl implements WorkerDao {
	
	public List findAll() {
		System.out.println("usando el metodo 'findAll()' de la clase '" + this.getClass().getSimpleName() + " '");
		return getHibernateTemplate().find("from Worker");
	}

	public Worker findById(Long id) {
		System.out.println("usando el metodo 'findById()' de la clase '" + this.getClass().getSimpleName() + " '");
		return (Worker) getHibernateTemplate().get(Worker.class, id);
	}

}

Clase AbstractHbSpringGenericDaoImpl:

package com.autentia.springhb.dao.impl.hbspring;

import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

import com.autentia.springhb.dao.GenericDao;
import com.autentia.springhb.entity.AbstractEntity;

public abstract class AbstractHbSpringGenericDaoImpl extends HibernateDaoSupport implements GenericDao {
	
	public void save(final AbstractEntity object) {	
		System.out.println("usando el metodo 'save()' de la clase '" + this.getClass().getSimpleName() + " '");
		getHibernateTemplate().saveOrUpdate(object);
	}

	public void delete(final AbstractEntity object) {		
		System.out.println("usando el metodo 'delete()' de la clase '" + this.getClass().getSimpleName() + " '");
		getHibernateTemplate().delete(object);
 	}
}

Bueno, como podeis ver tanto los DAO HbSpringCompanyDaoImpl como HbSpringWorkerDaoImpl heredan de AbstractHbSpringGenericDaoImpl, que a su vez hereda de HibernateDaoSupport. ¿Qué conseguimos con esto?. HibernateDaoSupport es una clase de Spring muy simple, y su función es sencillamente dejar más limpio el código de nuestros DAO implementando métodos como el getHibernateTemplate(), que nos permitirá lanzar consultas que usen Hibernate, y otros métodos que permitan la inyección de sesiones de Hibernate desde Spring (en concreto me refiero al método setSessionFactory(), que como ya lo heredamos, no tenemos que implementarlo)

 

6. Configuración de Spring

Tenemos base de datos, tenemos entidades, tenemos DAO, ahora sólo nos falta una cosa, juntarlo todo para poder trabajar con ello. Es aqui donde Spring entra en juego:

xml version="1.0" encoding="UTF-8"?


	
	
	

	
		
			com.mysql.jdbc.Driver
		
		
			jdbc:mysql://localhost/autentiaSpringHb
		
		
			autentia
		
		
			autentia
		
	

	
	
	

	
		
			
		
		
			
				
					org.hibernate.dialect.MySQLDialect
				
			
		
		
			
				
					com.autentia.springhb.entity.Company
				
				
					com.autentia.springhb.entity.Worker
				
			
		
	
	
	
		
	
	
	
	
	

	
		
	

	
		
	
	

Para configurar el datasource indicamos el driver que vamos a usar, cual es la URL de conexión , y cual es el usuario y la clave con las que acceder a la base de datos.

Para configurar Hibernate indicamos el dialecto con el que debe trabajar, cuales son las clases anotadas que debe persistir, y cual es el datasource donde encontrar la conexión con base de datos. Tambien configuraremos el gestor de transacciones.

Finalmente definimos los DAO que vamos a utilizar en la aplicacion y les inyectaremos la factoria de sesiones de Hibernate. Esta factoria es facilitada por Spring, al igual que la clase HibernateDAOSupport.

Si en algún momento quisieramos quitar Hibernate y utilizar otro motor de persistencia deberiamos:

  1. Crear una nueva implementación de los DAO para las interfaces indicadas anteriormente
  2. Cambiar la definición de los DAO que hacemos en Spring (bastaria con cambiar el class=»…»)
  3. Quitar la configuración de Hibernate del fichero de configuración de Spring y configurar el otro motor de persistencia.

Puede parecer mucho, pero si se jerarquizan bien los DAO y se crean pruebas de integración el trabajo de migrar de un motor de persistencia a otro puede ser muy poco, ya que por un lado tendremos que cambiar poco código (si hemos jerarquizado bien) y sabremos que la aplicación funciona en unos momentos (si hemos creado pruebas de integración)

 

7. Ejecución del programa

Para lanzar la aplicación utilizaremos el clásico método main()

package com.autentia.springhb;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.autentia.springhb.dao.CompanyDao;
import com.autentia.springhb.dao.WorkerDao;
import com.autentia.springhb.entity.Company;
import com.autentia.springhb.entity.Worker;
import com.autentia.springhb.entity.WorkerType;

public class AutentiaSpringHb {

	private static String[] files = new String[] {"applicationContextHbSpring.xml"}; 	
	
	public static void main(String[] args) {

		final ApplicationContext context = new ClassPathXmlApplicationContext(files);
		
		final CompanyDao companyDao = (CompanyDao) context.getBean("companyDao");
		final WorkerDao workerDao = (WorkerDao) context.getBean("workerDao");
		
		Company company = new Company();
		company.setName("Autentia Real Business Solutions");
		company.setDescription("somos pocos, somos buenos y nos gusta lo que hacemos");
		companyDao.save(company);
		
		Worker worker_01 = new Worker();
		worker_01.setName("Roberto");
		worker_01.setSurname("Canales");
		worker_01.setWorkerType(WorkerType.JEFE);
		worker_01.setCompany(company);
		workerDao.save(worker_01);
		
		Worker worker_02 = new Worker();
		worker_02.setName("Carlos");
		worker_02.setSurname("García");
		worker_02.setWorkerType(WorkerType.CONSULTOR);
		worker_02.setCompany(company);
		workerDao.save(worker_02);
		
		Worker worker_03 = new Worker();
		worker_03.setName("Alfonso");
		worker_03.setSurname("Blanco");
		worker_03.setWorkerType(WorkerType.BECARIO);
		worker_03.setCompany(company);
		workerDao.save(worker_03);
		
		System.out.println ("Resumen:");
		System.out.println ("· hay " + companyDao.findAll().size() + " empresa/s");
		System.out.println ("· hay " + workerDao.findAll().size() + " empleado/s");		
	}
}

Lo que hacemos paso a paso es:

  1. Creamos un contexto a partir del fichero de configuración de Spring. Como podeis ver es una constante con un array de Strings, donde cada fichero de configuración es un String. Esto es, podeis crear tantos ficheros como querais para configurar vuestra aplicación con Spring, aunque yo sólo haya utilizado uno.
  2. Recuperamos los DAO que vamos a utilizar.
  3. Creamos la empresa Autentia y a 3 empleados de ella y los enlazamos.
  4. Tras eso, buscamos en la base de datos y vemos que se han guardado. Ademas durante la ejecución veremos la salida de los System.out que hemos dejado en los DAO a modo de log.

 

8. Conclusiones

Si mirais otra vez el código vereis que hemos escrito poco código y que éste es muy fácil de escribir y mantener, pero que con él hemos conseguido las 4 operaciones básicas sobre base de datos (crear, leer, actualizar y borrar), aunque en el ejemplo solo creemos entidades.

Tambien hemos visto cómo utilizar Spring para juntar todos estos componentes, qué deberiamos hacer para cambiar de motor de persistencia, y cómo hacer para acceder al contexto de Spring desde una clase propia y, desde ella, recuperar los DAO y poder operar con los beans declarados en Spring.

Espero que os sea de utilidad.

 

5 COMENTARIOS

  1. En una segunda pasada, veo que donde más me cuesta seguir el tutorial es en el uso de herencias e interfaces…. a modo de autocrítica. Por lo demás, lo entiendo bien. Creo que ambas tecnologías, Spring e Hibernate, son el pan nuestro de cada día del desarrollo actual.

    Saludos y gracias,
    Jaime.

  2. Raro encontrar tutoriales tan simples pero con alto impacto de contenido y aprendizaje y además bien explicado. Muchas gracias y felicidades, me ha sido de mucha utilidad.

  3. Buenas tardes, yo tengo una relacion n:m hecha con la anotacion @manytomany como vemos en la explicación. Ahora me surge una duda y es como accedo a la clave de tabla que se crea en BBDD con está relación n:m si la clave de dicha tabla está compuesta por la primarykey de cada una de las tablas implicadas más otro campo de tipo numérico? aparte está nueva tabla que surge de dicha relación también tiene más atributos como un fecha por ejemplo.

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad