Hibernate y JPA
Diseño Web

¿Para qué sirve?

  • Disminuye la cantidad de código usado.
  • Simplifica la interacción con la base de datos.
  • Optimiza el acceso a la base de datos.

ORM

Object Relational Mapping. Se encarga de transformar las tablas en clases, los campos en propiedades y viceversa.

Clave primaria

Usando Hibernate:

  • es fundamental que las tablas tengan clave primaria.
  • no es posible modificar claves primarias.

Instalación

  1. Descargamos las librerías de Hibernate: http://hibernate.org/orm/releases/
  2. Creo una carpeta lib en mi proyecto.
  3. De entre los ficheros descargados, copio el contenido de la carpeta lib/required en la carpeta lib en mi proyecto.
  4. Copio el driver de mysql en la carpeta lib de mi proyecto.
  5. Refresco el proyecto para que coja los cambios realizados.
  6. Añado al java build path del proyecto los jar que están dentro de la carpeta lib, así como el driver MySQL.
  7. Creamos la base de datos. En el siguiente paso estableceremos los datos de conexión a la misma.
  8. Creamos el fichero hibernate.cfg.xml en la carpeta src y le pegamos el siguiente código:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.isolation">2</property>
<property name="hibernate.connection.password">pp</property>
<property name="hibernate.connection.pool_size">10</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost/prueba</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.current_session_context_class">managed</property>
<property name="hibernate.dialect">org.hibernate.dialect.MySQL57Dialect</property>
<property name="hibernate.show_sql">true</property>
<property name="hbm2ddl.auto">update</property>
</session-factory>
</hibernate-configuration>
  • Creamos el fichero HibernateUtil.java para establecer la conexión
    public class HibernateUtil {
    	private static StandardServiceRegistry registry;
    	private static SessionFactory sessionFactory;
    
    	public static SessionFactory getSessionFactory() {
    		if (sessionFactory == null) {
    			try {
    				registry = new StandardServiceRegistryBuilder().configure().build();
    				MetadataSources sources = new MetadataSources(registry);
    				sources.addAnnotatedClass(Alumno.class);
    				Metadata metadata = sources.getMetadataBuilder().build();
    				sessionFactory = metadata.getSessionFactoryBuilder().build();
    			} catch (Exception e) {
    				e.printStackTrace();
    				if (registry != null) {
    					StandardServiceRegistryBuilder.destroy(registry);
    				}
    			}
    		}
    		return sessionFactory;
    	}
    }
  • Creamos el paquete entity (pojos de hibernate) y una clase de prueba
    import javax.persistence.Entity;
    import javax.persistence.Id;
    
    @Entity
    public class Alumno {
    	@Id
    	@GeneratedValue(strategy=GenerationType.IDENTITY)//Para generar números autoincrementados
    	private int id;
    	private String nombre;
    	private int edad;
    
    	//Tener en cuenta que si definimos un constructor, este debe ser público
    	public int getId() {
    		return id;
    	}
    
    	public void setId(int id) {
    		this.id = id;
    	}
    
    	public String getNombre() {
    		return nombre;
    	}
    
    	public void setNombre(String nombre) {
    		this.nombre = nombre;
    	}
    
    	public int getEdad() {
    		return edad;
    	}
    
    	public void setEdad(int edad) {
    		this.edad = edad;
    	}
    
    }
  • Errores de puertos

    Si al arrancar el servidor de apache y el servidor de mysql tuviese algún conflicto de puertos...

    • Debo comprobar aplicaciones en la barra de tareas que sean susceptibles de estar usando alguno de los puertos que necesito (Mysql Monitor, etc.)
    • En windows, ejecutando services.msc puedo ver todos los servicios que se están ejecutando en el sistema. Debo detener (si estuviesen) los de:
      • VM Ware
      • Servicio de Publicación World Wide Web
      • Skype

    Transacciones

    Conjunto de operaciones contra la base de datos que se realizan de forma atómica (o todas o ninguna).

    ¿Cuando nos interesa crear una transacción? Cuando modificamos la base de datos (y por tanto deseamos poder hacer rollback()). Para una consulta no es necesario. Crear una transacción consume recursos.

    • Un objeto Session Hibernate representa una única unidad-de-trabajo y es abierta por un ejemplar de SessionFactory. Se deben cerrar las sesiones cuando se haya completado todo el trabajo de una transación.
    • En caso de fallo, lo que hay dentro del beginTransaction y el commit no se ejecuta y se salta a un catch.
    • Siempre es más rápido hacer dos operaciones en una sola transacción que dos operaciones en dos transacciones.
    Session session = HibernateUtil.getSessionFactory().openSession();
    Transaction transaction = session.beginTransaction();
    Alumno al = new Alumno();
    al.setNombre("Juan");
    al.setEdad(10);
    		
    session.save(al);
    transaction.commit();
    session.close();

    setTimeout()

    sesion.beginTransaction().setTimeout(400);
    • Si pasados 400 milisegundos la base de datos no devuelve nada, se arrojará una UnCaught Eception (una excepción para la que no es estrictamente neceario tener un try catch; al contrario que las Caught Exception).
    • Este timeout se puede gestionar a nivel de aplicación o a nivel de base de datos.

    Métodos de la clase Session

    session.save(persona) // guarda un objeto como registro en la base de datos.
    session.refresh(persona) // en nos permitirá conocer la id del objeto insertado, suponiendo que la id sea autoincrementada.
    session.update(persona);
    session.saveOrUpdate(persona);
    session.get(Persona.class, id) // me permite recuperar datos.
    // el método load lo usamos sólo para borrar un paciente. ya que nos devuelve un paciente sólo con la clave primaria seteada. Para recuperar todos los valores usaremos get.
    Persona personaABorrar = (Persona) session.load(Persona.class, idPaciente);
    session.delete(personaABorrar);

    Claves foráneas

    El siguiente código SQL genera una relación 1 a muchos:

    
    ALTER TABLE libro ADD CONSTRAINT fk_autor FOREIGN KEY ( id_autor ) REFERENCES autor( id )

    El código para generar una relación uno a uno sería igual, pero cambiando los nombres de los campos a los que hago referencia:

    ALTER TABLE autor ADD FOREIGN KEY ( id ) REFERENCES padre_autor (id)

    Nota: Para asignar las foreing keys es necesario que:

    • los tipos de datos de los campos relacionados coincidan.
    • Las tablas deberían estar vacías, u obtendremos un error del tipo "Table already exists".
    • El motor de las tablas sea InnoDB.
    diagrama hibernate

    Carga perezosa

    Es una estrategia que consiste en demorar la carga de un objeto hasta que este sea requerido.

    Es decir, la carga del objeto se realiza de manera explícita cuando este es invocado.

    Lazy loading se utiliza en aquellos casos en los que la aplicación necesita acceder solo a una parte de un objeto.

    En general la carga perezosa sólo se utiliza en las colecciones 1-n y n-n. Está por defecto a lazy true. Es posible usar usar la cargas perezosas en relaciones 1-1 pero no es sencillo ni frecuente.

    Ejercicio autor

    Establecer la relación 1-n en los pojos

    @Entity
    public class Autor {
    	@Id
    	@GeneratedValue(strategy=GenerationType.IDENTITY)	
    	private int id;
    
    	private String nombre;
    	
    	@OneToMany(cascade=CascadeType.ALL, fetch = FetchType.EAGER, mappedBy="autor") //eager = carga ansiosa
    	private List<Libro> libros;
    @Entity
    public class Libro {
    	@Id
    	@GeneratedValue(strategy=GenerationType.IDENTITY)
    	private int id;
        
    	private String titulo;
    
    	@ManyToOne
    	private Autor autor;

    Insertar un autor

    Session session = HibernateUtil.getSessionFactory().openSession();
    Transaction transaction = session.beginTransaction();
    Autor autor = new Autor();
    autor.setNombre("Juan");
    		
    session.save(autor);
    transaction.commit();
    session.close();

    Insertar un autor y sus libros

    Session session = HibernateUtil.getSessionFactory().openSession();
    Transaction transaction = session.beginTransaction();
    Autor autor = new Autor();
    List<Libro> libros = new ArrayList<Libro>();
    libros.add(new Libro(autor, "titulo1"));
    libros.add(new Libro(autor, "titulo2"));
    autor.setNombre("Juan");
    autor.setLibros(libros);
    session.save(autor);
    transaction.commit();
    session.close();

    session.flush()

    Este método se asegura de que la información ha sido persistida en la base de datos, de forma que ya sea posible cerrar con seguridad la session.

    Este método es ejecutado automáticamente por session.close(), no obstante es recomendable que siempre lo ejecutemos explícitamente justo antes.

    Puede ser útil hacer un flush para recuperar la id autoincrementada de un objeto recien insertado. Ejemplo:

    session.save(persona);
    session.flush();
    id = persona.getId();

    Criteria

    Se utiliza para hacer consultas

    Métodos de la clase Criteria

    Session session = HibernateUtil.getSessionFactory().openSession();
    		
    		CriteriaBuilder builder = session.getCriteriaBuilder();
    		CriteriaQuery<Autor> criteria = builder.createQuery(Autor.class);
    		Root<Autor> root = criteria.from(Autor.class);
    		criteria.select(root).where(builder.equal(root.get("nombre"), "Juan"));
    		TypedQuery<Autor> q=session.createQuery(criteria);
    				
    		//List<Autor> list = q.getResultList();
    		Autor cicloResultado = q.getSingleResult();

    Nota: Utilizando Criteria no es necesario hacer commit(), ya que la llamada a este método es para materializar cambios en la base de datos.

    Ejercicio 5 libros

    Insertar 5 libros en la base de datos. Dos registros deben tener el mismo título (''titulo1'').

    Utilizar la clase criteria para mostrar los libros cuyo título contenga la palabra ''titulo1''. Así es como ejecutaremos una sentencia de tipo like:

    criteria.select(root).where(builder.like(root.<String>get("titulo"), "%titulo1%"));

    Hacer una nueva consulta, ordenando la salida por el título, e ir recorriéndola imprimiendo los títulos y las id con System.out.println();

    criteria.orderBy(builder.asc(root.get("titulo")));

    ID:1 Título:titulo1
    ID:5 Título:titulo1
    ID:6 Título:titulo2
    ID:2 Título:titulo3
    ID:4 Título:titulo4
    ID:3 Título:titulo5

    HQL

    Definir consultas
    //recupero una colección de objetos
    Query<Libro> hqlQuery1 = session.createQuery("FROM  Libro");
    //recupero un String en vez de un objeto
    Query<String> hqlQuery2 = session.createQuery("SELECT v.matricula FROM Vehiculo v");
    //Consulta ordenada
    Query<Libro> hqlQuery3 = session.createQuery("FROM Libro ORDER BY id");
    //Consulta con condiciones
    Query<Libro> hqlQuery4 = session.createQuery("FROM Libro WHERE precio>10 AND anio_publicacion>1984");
    
    Ejecutar consulta
    List<Libro> libros = hqlQuery.list();
    Libro libro = hqlQuery.getSingleResult();
    
    Eliminar un registro
    Query hqlQuery = session.createQuery("DELETE FROM Paciente WHERE id=(:condicion)");
    hqlQuery.setParameter("condicion", 4);
    hqlQuery.executeUpdate();

    Evitar inyección SQL

    Se debe evitar crear consultas HQL concatenando Strings:
    String queryString = "from item i where i description like ' " + search + " ' ";
    
    
    En cambio es aconsejable usar:
    String queryString = "FROM Libro WHERE titulo LIKE (:condicion)";
    hqlQuery.setParameter("condicion", "%tit%");
    List result2 = hqlQuery2.list();

    Consulta relacionada

    
    Evaluar si una propiedad del bean es igual a cierto valor
    (no funciona si la propiedad es una lista)
    String queryString = "FROM Libro l WHERE l.autor.nombre LIKE (:condicion)";
    
    Evaluar si uno de los los elementos de la propiedad del bean consultado es igual a cierto valor
    String queryString = " SELECT a FROM Autor a JOIN a.libros l WHERE l.titulo='titulox11'";
    Query hqlQuery = session.createQuery(queryString);

    Nota: La siguiente línea es equivalente a la línea en verde (pero utilizando join):

    
    String queryString = "select l from Libro l join l.autor a where a.nombre='nombre1'";
    

    Las palabras escritas en rojo son propiedades de las clases, no son campos de la base de datos.

    Ejercicio

    Dada la siguiente estructura:

    diagrama ejercicio siniestro vehículo

    Ejecutar las siguientes consultas

    1. Listar las matrículas de todos los vehículos.
    2. Listar la matrícula y la marca de todos los vehículos ordenados por número de ruedas.
    3. Listar la matrícula de todos los vehículos que tengan más de dos asientos y más de dos ruedas.
    4. Listar la matrícula de todos los vehículos que tengan más de dos asientos o más de dos ruedas.
    5. Listar la matrícula de todos los vehículos que tengan una matrícula de menos de seis caracteres (where length(v.matricula)<?).
    6. Listar la matrícula de todos los vehículos que hayan tenido un siniestro con perdida mayor de 1000 euros.
    7. Listar la matrícula de todos los vehículos que hayan tenido un siniestro con perdida mayor de 1000 euros. Mostrar a cuanto ascendió la pérdida.
      Almacenar el resultado de la la consulta en:
      • Una lista de listas de dos elementos; el primero será un bean de Vehículo y el segundo un Integer. La siguiente línea me devuelve una lista de listas de dos elementos; el primero será un bean y el segundo un String.
        select new List(v, s.perdida) from Vehiculo... 
      • Una lista de beans VehiculoSiniestro, que tendrán cada uno dos propiedades, Vehiculo e Integer. Habrá que crear la clase VehiculoSiniestro con su correspondiente constructor.
        select new com.pablomonteserin.main.VehiculoSiniestro(v, s.perdida)...

      Cómo no resolver este ejercicio:

      select v.matricula, v.marca from Vehiculo v order by v.ruedas

      Si ejecutamos esta consulta y pretendemos recorrerla, estaríamos recorriendo una lista de un array de Objects:

      List <Object[]>result = hqlQuery.list();
      Iterator<Object[]> it = result.iterator();
      while(it.hasNext()){
      	Object[] obj =  it.next();				 
      	System.out.println("Matricula: "+ obj[0]);
      	System.out.println("Marca: "+ obj[1]);

    Ejercicio

    Dada la siguiente estructura:

    diagrama ejercicio profesor curso

    Ejecutar las siguientes consultas

    1. Listar los nombres de todos los profesores.
    2. Listar el nombre y el apellido de todos los profesores ordenados por edad.
      Cómo no resolver este ejercicio:
      select p.nombre, p.apellido from Profesor v order by p.edad

      Si ejecutamos esta consulta y pretendemos recorrerla, estaríamos recorriendo una lista de un array de Objects:

      List <Object[]>result = hqlQuery.list();
      Iterator<Object[]> it = result.iterator();
      while(it.hasNext()){
      	Object[] obj =  it.next();				 
      	System.out.println("Nombre: "+ obj[0]);
      	System.out.println("Apellido: "+ obj[1]);
    3. Listar el nombre de todos los profesores que tengan más de treinta años y tengan más de 5 años de experiencia.
    4. Listar el nombre de todos los profesores que tengan más de treinta años o tengan más de 5 años de experiencia.
    5. Listar el nombre de todos los profesores que tengan un nombre de menos de seis caracteres (where length(p.nombre)<?).
    6. Listar el nombre de todos los profesores que hayan impartido un curso de más de 500 euros.
    7. Listar el nombre de todos los profesores que hayan impartido un curso de más de 500 euros. Mostrar cuanto ćostó dicho curso.
      Almacenar el resultado de la la consulta en:
      • Una lista de listas de dos elementos; el primero será un bean de Profesor y el segundo un Integer. La siguiente línea me devuelve una lista de listas de dos elementos; el primero será un bean y el segundo un String.
        select new List(p, c.precio) from Profesor... 
      • Una lista de beans ProfesorCurso, que tendrán cada uno dos propiedades, Profesor e Integer. Habrá que crear la clase ProfesorCurso con su correspondiente constructor.
        select new com.pablomonteserin.main.ProfesorCurso(p, c.precio)...

    Crear una relación n a n

    Una relación n a n son dos relaciones 1 a n vinculadas a una misma tabla auxiliar. En MySQL WorkBench, podemos establecer dicha relación desde el panel Models → flechita → Create EER Model from Database, de forma que se creará automáticamente la tabla intermedia.

    Ejercicio Alumno

    La aplicación consta de 3 clases: Main, Alumno y Servicio.

    La clase Alumno tendrá tres propiedades: id(int), nombre (String) y edad (int).

    Al arrancar la aplicación desde el Main se nos preguntará por la operación que deseamos hacer:

    • Insertar un nuevo alumno. Esta opción nos solicita el nombre y la edad del alumno que queremos insertar.
    • Eliminar un alumno. Esta opción nos solicita la id del alumno que deseamos eliminar.
    • Modificar los datos de un alumno. Esta opción nos solicita primero la id del alumno para poder identificarlo y luego su nuevo nombre y edad.
    • Mostrar todos los alumnos almacenados. Esta opción recorre el ArrayList de alumnos e imprime los nombres de cada uno.

    La clase servicio tendrá los métodos nuevoAlumno, eliminarAlumno, modificarAlumno y getAlumnos que realizarán las 4 operaciones antes citadas. Haremos las operaciones contra una base de datos utilizando Hibernate.

    JPA (Java Persistence)

    Es una especificación standard de la industria del desarrollo con Java para manipular una base de datos relacional utilizando la programación orientada a objetos como aproximación.

    Al ser una especificación, por sí sola no puede manipular la base de datos. Es un conjunto de interfaces que deben ser implementadas.

    Main.javapublic class Main {
        public static void main(String[] args){
            EntityManagerFactory emf = Persistence.createEntityManagerFactory("TEST_PERSISTENCE_JPA");
            EntityManager em = emf.createEntityManager();
          
            Customer customer=new Customer("Pablo", "Monteserín");        
            Address address=new Address("Calle Falsa 123");
            customer.setAddress(address);
            
            try {
                em.getTransaction().begin();
                em.persist(customer);
                em.getTransaction().commit();
    
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                em.close();
            }
        }
    }
    src/main/resources/META-INF/persistence.xml<?xml version="1.0" encoding="UTF-8"?>
    <persistence version="2.1"
    	xmlns="http://xmlns.jcp.org/xml/ns/persistence"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
    	<persistence-unit name="TEST_PERSISTENCE_JPA"
    		transaction-type="RESOURCE_LOCAL">
    		<class>com.pablo.clase.persistence.model.Customer</class>
    		<class>com.pablo.clase.persistence.model.Address</class>
    
    		<properties>
    			<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />
    			<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/prueba-jpa?serverTimezone=UTC" />
    			<property name="javax.persistence.jdbc.user" value="pm" />
    			<property name="javax.persistence.jdbc.password" value="pp" />
    
    			<property name="hibernate.dialect" value="org.hibernate.dialect.MySQL8Dialect" />
    
    			<!-- hace cambios -->
    			<property name="hibernate.hbm2ddl.auto" value="update" />
    
    			<!-- si los cambios los fastidian todo, no hace cambios -->
    			<!-- <property name="hibernate.hbm2ddl.auto" value="validate" /> -->
    
    			<!-- <property name="hibernate.hbm2ddl.auto" value="create-drop" /> -->
    			<property name="show_sql" value="true" />
    		</properties>
    	</persistence-unit>
    </persistence>
    ./pom.xml<?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.pablo.clase</groupId>
        <artifactId>solo-jpa</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <properties>
            <hibernate.version>5.4.2.Final</hibernate.version>
            <spring.version>5.1.7.RELEASE</spring.version>
            <jstl.version>1.2</jstl.version>
            <servletapi.version>4.0.1</servletapi.version>
        </properties>
    
    <dependencies>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>${hibernate.version}</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.16</version>
        </dependency>
    </dependencies>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <source>8</source>
                        <target>8</target>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </project>

    Otras operaciones

    Consulta de un solo objeto

    TypedQuery<Usuario> query = em.createQuery("SELECT u from Usuario u WHERE u.nombre=:name AND u.pass=:password", Usuario.class);
    query.setParameter("name", nombre);
    query.setParameter("password", password);

    Update

    em.merge(usuario);

    Remove

    Invitado invitado = em.find(Invitado.class, id);
    em.remove(invitado);

    Consulta de un listado de objetos

    Query query = em.createQuery("SELECT c from Customer");
    List<Customer> result=query.getResultList();
    icono de mandar un mailSOPORTE Usuarios Premium
    Pablo Monteserín
    contacta conmigoPablo Monteserín

    Para dudas técnicas sobre los ejercicios de mis cursos es necesario tener una cuenta premium activa. Para cualquier otra cosa, puedes usar el formulario de la página de contacto.