[Tutorial] JPA 2.1 - Parte I

Iniciado por Gus Garsaky, Abril 10, 2015, 09:48:35 AM

Tema anterior - Siguiente tema

0 Miembros y 1 Visitante están viendo este tema.

TUTORIAL JPA 2.1



El presente tutorial tiene como finalidad mostrar los aspectos básicos de la especificación de Java JPA (API de Persistencia de Java por sus siglas en inglés). Se cubrirán los aspectos más básicos de ésta API para que el lector se dé cuenta del potencial que nos ofrece y que se puede aplicar en nuestros proyectos. Así mismo,




¿QUÉ ES JPA?


Como en todo proceso de aprendizaje, primero debes saber qué es exactamente el objeto de nuestro estudio. JPA como ya dijimos es el API para la persistencia en Java, pero, ¿qué es en concreto? Bien, JPA es un ORM, aunque no propiamente. Explicaremos esto en detalle a continuación.

La Java Community Process (JCP), es la encargada de las especificaciones en Java. ¿Qué quiero decir con especificaciones? Pues, una especificación no es más que un estándar. La JCP propone un estándar y si por mayoría de votos se acepta la proposición, se designa un equipo experto para que trabaje en ella. Aquí el equipo se encarga de definir la estructura de la especificación, sus características y forma de trabajar. Pero no podemos empezar a trabajar con una especificación si no tiene una implementación. Una implementación es una representación real de dicha especificación. Es como en el mismo lenguaje, una interface vendría a ser la especificación y una clase que implemente dicha interface vendría a ser la implementación o representación. Así mismo, la JCP puede o no realizar la implementación de una especificación, como lo hizo con Servlet, JAXB, JMS, JAAS, y algunos otros.

Comprendido lo anteriormente explicado, se procede a listar las mejores implementaciones de JPA:

• Hibernate
• EclipseLink
• MyBatis

Hibernate y MyBatis se pueden usar nativamente, es decir, sin usar a JPA como interfaz o también con JPA. En éste tutorial se usará Hibernate por ser el más adoptado por los desarrolladores.




PREPARANDO NUESTRO ENTORNO DE TRABAJO


Primero que todo, vamos a disponer del siguiente material:

• No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
• No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
• No tienes permitido ver los links. Registrarse o Entrar a mi cuenta (opcional, porque usaremos Maven).
• No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
• No tienes permitido ver los links. Registrarse o Entrar a mi cuenta (opcional, usaremos Maven).
• No tienes permitido ver los links. Registrarse o Entrar a mi cuenta

Instalación de MySQL como servicio

Nos dirigimos a "C:\Program Files\MySQL\MySQL Server 5.6" y renombramos el archivo "my-default.ini" a "my.ini". Lo editamos y al final agregamos la línea:

Código: php
PERFORMANCE_SCHEMA=0


Ahora, abrimos la terminal como Administrador y nos dirigimos hacia "bin". Aquí ejecutaremos el comando:

Código: php
mysqld –install


Y ya tenemos MySQL instalado como servicio. Si no está corriendo lo iniciamos.



CREACIÓN DE LA BASE DE DATOS



Creación de la base de datos y la tabla Employees

Abrimos MySQL Workbench e iniciamos sesión. En el editor SQL escribimos el siguiente código para crear nuestra base de datos "jpa2_tuto" y nuestra primera tabla "employees":


Abril 10, 2015, 09:49:19 AM #1 Ultima modificación: Abril 10, 2015, 05:53:00 PM por Gus Garsaky


CREACIÓN DEL PROYECTO



Vamos a crear nuestro proyecto en Eclipse. El proyecto será maven para que gestione nuestras dependencias y no tener que preocuparnos por bajar las bibliotecas/librerías. Vamos al menú "File" -> "New" -> "Maven Project". Tildamos la opción create a simple project:


Clic en Next. A continuación llenamos la información de nuestro proyecto, de tal modo que quede así:


Click en finish y ya tenemos listo nuestro proyecto.



AGREGANDO DEPENDENCIAS AL PROYECTO Y CONFIGURACIÓN



Abrimos el archivo pom.xml, que se encuentra en la raíz del proyecto. Éste archivo es el encargado de gestionar las dependencias del proyecto. Si hemos visto PHP, se puede comparar a Composer, PEAR, o RubyGems de Ruby. En éste archivo debemos de especificar las dependencias que requerirá el proyecto y algunas configuraciones de ser necesario. Editamos su contenido, de tal modo que quede así:

Código: xml
<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.company</groupId>
  <artifactId>jpa2-tuto</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>JPA2-Tuto</name>
  <description>Tutorial de JPA 2</description>
 
  <properties>
  <maven_compiler_source>1.8</maven_compiler_source>
  <maven_compiler_target>1.8</maven_compiler_target>
  </properties>
 
  <build>
  <plugins>
  <plugin>
  <artifactId>maven-compiler-plugin</artifactId>
  <version>3.1</version>
  <configuration>
  <source>1.8</source>
  <target>1.8</target>
  </configuration>
  </plugin>
  </plugins>
  </build>
 
  <dependencies>
  <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>4.3.8.Final</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>5.1.3.Final</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate.common</groupId>
            <artifactId>hibernate-commons-annotations</artifactId>
            <version>4.0.5.Final</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate.javax.persistence</groupId>
            <artifactId>hibernate-jpa-2.0-api</artifactId>
            <version>1.0.1.Final</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>4.3.8.Final</version>
        </dependency>
        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>1.1.0.Final</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.34</version>
        </dependency>
  </dependencies>
 
</project>


Primero especificamos que la versión de Java será la 1.8 porque por defecto un proyecto Maven crea una proyecto para la versión 1.5. Más abajo especificamos las dependencias de nuestro proyecto*, es decir, frameworks, bibliotecas/librerías, etc. Como se puede observar, la gran mayoría pertenece a Hibernate, del que ya hablamos anteriormente y al final, agregamos la dependencia MySQL jdbc.
Para actualizar el proyecto Maven con las nuevas dependencias y configuraciones, le damos clic derecho > Maven > Update Project...
Ya tenemos nuestro proyecto con todas las dependencias que necesitamos. Ahora solo queda empezar a programar.







* Para saber que versión o que artifactId poner, debemos ir al repositorio Maven y buscar las dependencias que queramos. Cada dependencia tendrá su código para solo copiar y pegar en nuestro pom.xml



PRIMER CONTACTO CON JPA



Primero, crearemos nuestro archivo persistence.xml, el cual contendrá toda la información sobre nuestra conexión a la base de datos. Creamos un folder dentro de src/main/resources llamado META-INF y dentro de éste folder creamos un fichero xml llamado persistence.xml. Éste fichero tendrá el siguiente aspecto:

Código: xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence 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"
             version="2.1">
             
             <persistence-unit name="JPA2-tuto" transaction-type="RESOURCE_LOCAL">
              <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
             
              <properties>
              <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
              <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/jpa2_tuto"/>
              <property name="javax.persistence.jdbc.user" value="root"/>
              <property name="javax.persistence.jdbc.password" value="toor"/>
              <property name="org.hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect"/>
              <property name="show_sql" value="true"/>
              <property name="format_sql" value="true"/>
              <property name="hbm2ddl.auto" value="auto"/>
              </properties>
             </persistence-unit>
             
</persistence>


Éste fichero especifica la configuración de nuestra conexión con JPA. Aquí describimos el proveedor de la implementación de JPA, el driver del SGBD, el url, el usuario y contraseña, el dialecto, etc. A continuación se describen algunas palabras clave de persistence.xml:

  • transaction-type, indica que las transacciones se harán de forma local. Si tuviésemos un servidor de aplicaciones, las transacciones las manejaría el servidor, y la conexión a nuestra base de datos también. Por lo que dicha conexión estaría siempre disponible para cualquier aplicación que la quiera usar. En nuestro caso, lo haremos de forma local.

  • <provider>, especifica el proveedor de la implementación de JPA (¿recuerdas la charla que tuvimos sobre especificaciones e implementaciones?), en nuestro caso Hibernate.

  • <properties>, almacena un conjunto de propiedades que hacen referencia a nuestra conexión: driver, url, usuario, contraseña, dialecto del SGBD que usaremos (MySQL) y otras propiedades como show_sql que muestra el código SQL generado por Hibernate y format_sql que lo formatea para que sea más amigable a la vista.




    CREACIÓN DE UN DAO GENÉRICO



    Para ahorrar código, haremos uso de un Dao Genérico del cual extenderemos para tener a nuestra disposición todas las acciones de persistencia. Empezaremos por crear nuestra interface GenericDao, la cual especificará que todo DAO tendrá los métodos básicos: create, read, update y delete.

    Código: java
    package model.dao;

    import java.io.Serializable;

    public interface GenericDao<T, PK extends Serializable> {
    T create(T t) throws Exception;
    T read(PK id) throws Exception;
    T update(T t) throws Exception;
    void delete(T t) throws Exception;
    }


    Como se puede observar, ésta interface acepta un tipo de clase y un tipo de PK, que viene a ser el tipo de dato de la llave primaria. Veamos su implementación, GenericDaoJpaImpl, de la cual extenderán todos los DAOs:

    Código: java
    package model.dao;

    import java.io.Serializable;
    import java.lang.reflect.ParameterizedType;

    import javax.persistence.EntityManager;
    import javax.persistence.EntityTransaction;
    import javax.persistence.Persistence;

    public class GenericDaoJpaImpl<T, PK extends Serializable> implements GenericDao<T, PK> {

    protected EntityManager em = Persistence.createEntityManagerFactory("JPA2-tuto").createEntityManager();
    protected Class<T> clazz;

    @SuppressWarnings("unchecked")
    public GenericDaoJpaImpl() {
    ParameterizedType genericSuperclass = (ParameterizedType) getClass()
                 .getGenericSuperclass();
            this.clazz = (Class<T>) genericSuperclass
                 .getActualTypeArguments()[0];
    }

    @Override
    public T create(T t) throws Exception {
    EntityTransaction tx = em.getTransaction();
    tx.begin();
    em.persist(t);
    em.flush();
    tx.commit();
    return t;
    }

    @Override
    public T read(PK id) throws Exception {
    return em.find(clazz, id);
    }

    @Override
    public T update(T t) throws Exception {
    EntityTransaction tx = em.getTransaction();
    tx.begin();
    em.persist(t);
    em.flush();
    tx.commit();
    return t;
    }

    @Override
    public void delete(T t) throws Exception {
    EntityTransaction tx = em.getTransaction();
    tx.begin();
    em.remove(t);
    em.flush();
    tx.commit();
    }

    }


    Analicemos un poco la siguiente línea:

    Código: java
    protected EntityManager em = Persistence.createEntityManagerFactory("JPA2-tuto").createEntityManager();


    Creamos un objeto EntityManager, pero ¿Qué es un EntityManager?. Pues bien, éste objeto es el encargado, como su nombre lo dice, de manejar todas las entidades que existen en el proyecto. Gracias a éste objeto, podemos guardar objetos y convertirlos internamente a SQL transparente para nosotros. El EntityManager es el que hace la "magia".

    La instrucción:

    Código: java
    Persistence.createEntityManagerFactory("JPA2-tuto")


    Crea una fábrica de EntityManager (solo habrá una fábrica por proyecto). Éste método recibe como parámetro el nombre de la unidad de persistencia que se especifica en el fichero persistence.xml. El método concatenado:

    Código: java
    createEntityManager()


    Devuelve un EntityManager para manejar las entidades. Más abajo, tenemos una propiedad Class<T> clazz. Ésta propiedad especificará el tipo de Clase (T) de la entidad en tiempo de ejecución para que el EntityManager sepa con qué entidad está trabajando, además del tipo de dato de la llave primaria (PK). En cada método de persistencia, primero iniciamos obteniendo una transacción:

    Código: java
    EntityTransaction tx = em.getTransaction();


    Toda acción, se debe de realiza en una transacción. Esto es importante, si no, tendrás una excepción. Toda transacción debe de iniciarse llamando al método begin() y finalizar con el método commit().

    A continuación, describiremos los métodos más importantes de EntityManager:

    persist(T t): guarda en la base de datos una entidad. Pero no se ejecutará directamente en la base de datos, si no que internamente es marcado para que al finalizar la transacción se guarde en la base de datos. Con el método flush(), forzamos a que dicha instrucción se guarde inmediatamente.

    find(T, PK id): Busca en la entidad T un registro con el id especificado. Si lo encuentra, lo devuelve, caso contrario, devuelve null.

    remove(PK id): Elimina la entidad con el id especificado de la base de datos.

    flush(): Forza al EntityManager a ejecutar inmediatamente las acciones que se han llevado acabo dentro de la transacción actual.

    Sabiendo cómo funcionan éstos métodos, creo que no es necesario explicar cada método de GenericDaoJpaImpl.



    Creación de un DAO concreto: EmployeeDao



    Dado que todo el trabajo ya lo hace nuestro DAO genérico, no necesitamos especificar nada en nuestros DAOs concretos, tan solo basta extenderlo y llamar al constructor padre para que reciba el tipo de entidad que le pasamos por parámetros al DAO genérico. Veamos:

    Código: java
    package model.dao;

    import model.entities.Employee;

    public class EmployeeDao extends GenericDaoJpaImpl<Employee, Short> {

    public EmployeeDao() {
    super();
    }

    }


    Como se puede observar, extendemos de nuestro DAO genérico pasándole por parámetros nuestra entidad, en este caso, Employee y nuestro tipo de PK, que es en nuestro caso Short. Luego, llamamos al constructor padre para que éste tome los parámetros y se los asigne al elemento T y PK respectivamente. El resultado, un DAO extremadamente pequeño.
    Ahora que ya tenemos nuestro DAO genérico y nuestro DAO concreto para la entidad Employee, procedemos a crear nuestra entidad Employee.



    CREACIÓN DE LA ENTIDAD EMPLOYEE



    Crearemos nuestra entidad Employee:

    Código: java
    package model.entities;

    import java.io.Serializable;
    import java.util.Date;

    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.persistence.Temporal;
    import javax.persistence.TemporalType;

    @Entity
    @Table(name="employees")
    public class Employee implements Serializable {
    private static final long serialVersionUID = -4525913610632158800L;
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name="id_employee")
    @Id
    private Short id;
    @Column(name="names")
    private String names;
    @Column(name="surnames")
    private String surnames;
    @Temporal(TemporalType.DATE)
    @Column(name="birth_date")
    private Date birthDate;
    @Column(name="dni")
    private String dni;
    @Column(name="address")
    private String address;
    @Column(name="home_phone")
    private String homePhone;
    @Column(name="email")
    private String email;
    @Column(name="active")
    private Boolean active;

    public Employee() {}

    public Employee(Short id, String names, String surnames, Date birthDate,
    String dni, String address, String homePhone, String email,
    Boolean active) {
    super();
    this.id = id;
    this.names = names;
    this.surnames = surnames;
    this.birthDate = birthDate;
    this.dni = dni;
    this.address = address;
    this.homePhone = homePhone;
    this.email = email;
    this.active = active;
    }

    public Short getId() {
    return id;
    }

    public void setId(Short id) {
    this.id = id;
    }

    public String getNames() {
    return names;
    }

    public void setNames(String names) {
    this.names = names;
    }

    public String getSurnames() {
    return surnames;
    }

    public void setSurnames(String surnames) {
    this.surnames = surnames;
    }

    public Date getBirthDate() {
    return birthDate;
    }

    public void setBirthDate(Date date) {
    this.birthDate = date;
    }

    public String getDni() {
    return dni;
    }

    public void setDni(String dni) {
    this.dni = dni;
    }

    public String getAddress() {
    return address;
    }

    public void setAddress(String address) {
    this.address = address;
    }

    public String getHomePhone() {
    return homePhone;
    }

    public void setHomePhone(String homePhone) {
    this.homePhone = homePhone;
    }

    public String getEmail() {
    return email;
    }

    public void setEmail(String email) {
    this.email = email;
    }

    public Boolean getActive() {
    return active;
    }

    public void setActive(Boolean active) {
    this.active = active;
    }


    }


    Como se puede observar, nuestra entidad Employee es un POJO simple, pero con algunas novedades. Veamos cuáles son:

  • @Entity: indica que la clase es una entidad.
  • @Table: indica que dicha clase es una tabla en la base de datos. Su nombre es especifica por el atributo name.
  • @Column: indica que dicho atributo es una columna en la tabla.
  • @Temporal: indica que dicha columna es de tipo temporal, de tiempo. El atributo TemporalType indica exactamente que tipo de temporal es.
  • @GeneratedValue: indica que hay una generación de valores. Es usada generalmente para especificar una generación para
  • los IDs. El tipo de generación se especifica a través del atributo GenerationType, que puede tomar valores como AUTO, SEQUENCE, IDENTITY, entre otras.
  • @Id: Indica que dicha columna es la llave primaria.

    Ésta entidad representa a la tabla employees de la base de datos y cada instancia de ésta entidad, representa un registro. Es por ésta razón, que cada vez que creemos un objeto Employee representará un registro de la tabla employees y cuando el método create lo persiste, es transformado a una setencia SQL e insertado en la tabla employees.



    CRUD



    Para probar el funcionamiento de nuestro programa, haremos un CRUD básico.

    INSERCIÓN

    Código: java
    import java.util.logging.Logger;

    import model.dao.EmployeeDao;
    import model.entities.Employee;

    public class Main {

    public static void main(String[] args) throws ParseException {
    try {
    EmployeeDao employeeDao = new EmployeeDao();
    Employee employee = new Employee();
    employee.setNames("Pepito");
    employee.setSurnames("Pérez");
    employee.setBirthDate(new SimpleDateFormat("yyyy-MM-dd").parse("1994-03-20"));
    employee.setDni("12345678");
    employee.setAddress("Av. que te importa #123");
    employee.setEmail("[email protected]");
    employee.setHomePhone("048389483");
    employee.setActive(true);
    employeeDao.create(employee);
    } catch (Exception e) {
    Logger.getLogger(Main.class.getName()).warning("Something was wrong: "+e.getMessage());
    } finally {
    System.exit(0);
    }
    }

    }


    Ésta clase es sumamente sencilla. Solo creamos un EmployeeDao y un objeto tipo Employee que es una entidad como ya hemos comentado. Le asignamos valores a las propiedades de la entidad y llamamos al método create de EmployeeDao para guardar dicha entidad en la base de datos.

    Ahora, verifiquemos que se haya insertado el empleado en la base de datos:


    Efectivamente, se ha registrado el objeto en la base de datos.

    LECTURA

    Código: java
    package main;

    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.logging.Logger;

    import model.dao.EmployeeDao;
    import model.entities.Employee;

    public class Main {

    public static void main(String[] args) throws ParseException {
    try {
    EmployeeDao employeeDao = new EmployeeDao();
    Employee employee = employeeDao.read(new Short("1"));
    // hacer lo que se desea con el empleado recuperado de la bbdd
    } catch (Exception e) {
    Logger.getLogger(Main.class.getName()).warning("Something was wrong: "+e.getMessage());
    } finally {
    System.exit(0);
    }
    }

    }


    ACTUALIZACIÓN

    Código: java
     package main;

    import java.text.ParseException;
    import java.util.logging.Logger;

    import model.dao.EmployeeDao;
    import model.entities.Employee;

    public class Main {

    public static void main(String[] args) throws ParseException {
    try {
    EmployeeDao employeeDao = new EmployeeDao();
    Employee employee = employeeDao.read(new Short("1"));
    employee.setNames("Bugs");
    employee.setSurnames("Bunny");
    employeeDao.update(employee);
    } catch (Exception e) {
    Logger.getLogger(Main.class.getName()).warning("Something was wrong: "+e.getMessage());
    } finally {
    System.exit(0);
    }
    }

    }


    De la misma manera que al eliminar un registro, primero obtenemos el empleado objetivo. En éste caso, le cambiamos las propiedades y llamamos al método update(T t) que internamente hace lo mismo que create(T t), es decir, un em.persist(t) que guardará el mismo objeto pero con las nuevas propiedades, es decir, hará un UPDATE en la tabla.

    Verifiquemos que ha actualizado al empleado:


    ELIMINACIÓN

    Código: java
     package main;

    import java.text.ParseException;
    import java.util.logging.Logger;
    import model.dao.EmployeeDao;
    import model.entities.Employee;

    public class Main {

    public static void main(String[] args) throws ParseException {
    try {
    EmployeeDao employeeDao = new EmployeeDao();
    Employee employee = employeeDao.read(new Short("1"));
    employeeDao.delete(employee);
    } catch (Exception e) {
    Logger.getLogger(Main.class.getName()).warning("Something was wrong: "+e.getMessage());
    } finally {
    System.exit(0);
    }
    }

    }


    Verificamos que ha eliminado al usuario:





    -> Próximo capítulo: Relaciones en JPA + ejemplo funcional.



    By Gus.