Guía de JPA con Hibernate: Mapeo básico

    Introducción

    La API de persistencia de Java (JPA) es el estándar de persistencia del ecosistema de Java. Nos permite mapear nuestro modelo de dominio directamente a la estructura de la base de datos y luego nos da la flexibilidad de solo manipular objetos en nuestro código. Esto nos permite no especular con los engorrosos componentes JDBC como Connection, ResultSet, etc.

    Haremos una guía completa para usar JPA con Hibernate como su proveedor. En este artículo, exploraremos la configuración y el mapeo básico en Hibernate:

    • Guía de JPA con Hibernate: Mapeo básico (estás aquí)
    • Guía de JPA con Hibernate: mapeo de relaciones
    • Guía de JPA con Hibernate: asignación de herencia (¡próximamente!)
    • Guía de JPA con Hibernate: consultas (¡próximamente!)

    ¿Qué es JPA?

    API de persistencia de Java

    JPA es una API que tiene como objetivo estandarizar la forma en que accedemos a una base de datos relacional desde el software Java utilizando Object Relational Mapping (ORM).

    Fue desarrollado como parte del JSR 220 por un grupo de expertos en software EJB 3.0, aunque no solo se dedica al desarrollo de software EJB.

    JPA no es más que una API y, por lo tanto, no proporciona ninguna implementación, sino que define y estandariza únicamente los conceptos de ORM en Java.

    Por lo tanto, para poder utilizarlo, debemos proporcionar una implementación de la API. Felizmente para nosotros, no estamos atados a escribirlo nosotros mismos, ya existen implementaciones, llamadas proveedores, disponibles:

    Cada proveedor, además de implementar la API, también proporciona algunas características específicas. En este artículo usaremos Hibernate como nuestro proveedor, aunque no veremos sus peculiaridades.

    Mapeo relacional de objetos

    El mapeo relacional de objetos es una técnica que se utiliza para crear un mapeo entre una base de datos relacional y los objetos de un software, en nuestro caso, objetos Java. La idea detrás de esto es dejar de trabajar con cursores o matrices de datos obtenidos de la base de datos, sino obtener directamente objetos que representen nuestro dominio empresarial.

    Para lograrlo, utilizamos técnicas para mapear nuestros objetos de dominio a las tablas de la base de datos para que se llenen automáticamente con los datos de las tablas. Entonces, podemos realizar la manipulación de objetos estándar en ellos.

    Nuestro Ejemplo

    Antes de comenzar, presentaremos el ejemplo que usaremos a lo largo de la serie. La idea es mapear el modelo de una escuela con estudiantes que toman cursos impartidos por profesores.

    Así es como se ve el modelo final:

    Como podemos ver, hay algunas clases con algunas propiedades. Y esas clases tienen relaciones entre ellas. Al final de esta serie, habremos mapeado todas esas clases a las tablas de la base de datos y podremos guardar y recuperar datos de la base de datos usándolas.

    Empezando

    Vayamos directamente al grano con un ejemplo funcional, aunque minimalista. En primer lugar, necesitaremos importar la dependencia JPA / Hibernate . Usando Maven, agreguemos las dependencias necesarias a nuestro pom.xml:

    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-core</artifactId>
        <version>${version}</version>
    </dependency>
    

    También necesitaremos una base de datos para trabajar. H2 es liviano y simple, así que vamos con eso:

    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <version>${version}</version>
    </dependency>
    

    Luego, tendremos que crear un persistence.xmlarchivo en nuestra ruta de clases, bajo un META-INFdirectorio. Este archivo se utiliza para configurar JPA, indicando cuál es el proveedor, qué base de datos vamos a usar y cómo conectarnos, cuáles son las clases a mapear, etc.

    Por ahora, se verá así:

    <?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_2.xsd"
                 version="2.2">
        <persistence-unit name="guide-to-jpa-with-hibernate">
            <class>com.fdpro.clients.Pharos.sh.jpa.domain.Student</class>
    
            <properties>
                <!-- Database configuration -->
                <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
                <property name="javax.persistence.jdbc.url" value="jdbc:h2:mem:"/>
                <property name="javax.persistence.jdbc.user" value="user"/>
                <property name="javax.persistence.jdbc.password" value="password"/>
    
                <!-- Schema configuration -->
                <property name="javax.persistence.schema-generation.database.action" value="create"/>
            </properties>
        </persistence-unit>
    </persistence>
    

    No nos preocuparemos mucho por el significado de todo esto por ahora. Finalmente, vamos a mapear nuestra primera clase Student,:

    @Entity
    public class Student {
        @Id
        private Long id;
    
        public Long id() {
            return id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    }
    

    Esto significa que esta clase será una entidad en nuestra base de datos. Hibernate ahora sabe que debe mapear esta entidad en una tabla de base de datos y que poblaremos instancias de esta clase con los datos de la tabla. El obligatorio @Idservirá como clave principal de la tabla de coincidencias.

    Ahora, veamos cómo manipular esta entidad:

    EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("guide-to-jpa-with-hibernate");
    EntityManager entityManager = entityManagerFactory.createEntityManager();
    
    entityManager.getTransaction().begin();
    
    Student student = new Student();
    student.setId(1L);
    entityManager.persist(student);
    
    entityManager.getTransaction().commit();
    entityManager.clear();
    
    Student foundStudent = entityManager.find(Student.class, 1L);
    
    assertThat(foundStudent).isEqualTo(student);
    
    entityManager.close();
    

    Nuevamente, no nos molestemos con todo aquí, ya que se volverá mucho más simple. Esto es un poco burdo, pero es un enfoque de prueba de concepto para verificar si podemos acceder a la entidad mediante programación.

    Todo lo que tenemos que saber por el momento es que este código nos permite guardar una Studententidad en la base de datos y luego recuperarla. La assertThat()declaración pasa por ser foundStudentgenuinamente la que estamos buscando.

    Eso es todo para nuestros primeros pasos con la API de persistencia de Java. Tendremos la oportunidad de profundizar en los conceptos que usamos aquí en el resto del tutorial.

    Configuración

    Ahora es el momento de profundizar en la API, comenzando con el persistence.xmlarchivo de configuración. Veamos qué tenemos que poner ahí.

    Espacio de nombres, esquema y versión

    En primer lugar, aquí está la etiqueta de apertura:

    <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_2.xsd"
                 version="2.2">
    

    Aquí podemos ver que estamos definiendo el espacio de nombres http://xmlns.jcp.org/xml/ns/persistence, y la ubicación del esquema http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd(observe la versión).

    Además, aunque ya lo mencionamos en la ubicación del esquema, estamos mencionando la versión nuevamente.

    Entonces, aquí estamos trabajando con la versión 2.2de JPA.

    Unidad de persistencia

    Luego, justo después de la etiqueta de apertura, declaramos una <persistence-unit>etiqueta:

    <persistence-unit name="guide-to-jpa-with-hibernate">
    

    Una unidad de persistencia define un conjunto de entidades administradas por una aplicación y que se encuentran en una base de datos determinada. Debe tener un nombre, que se usará más adelante. Toda la siguiente configuración estará dentro de esta unidad de persistencia, ya que se refiere a esa única base de datos.

    Si tuviéramos que tener varias bases de datos diferentes y, por lo tanto, diferentes conjuntos de entidades, tendríamos que definir varias unidades de persistencia, todas con nombres diferentes.

    Clases mapeadas

    Entonces, lo primero que notamos en la unidad de persistencia es una <class>etiqueta con el nombre calificado de nuestra Studentclase:

    <class>com.fdpro.clients.Pharos.sh.jpa.domain.Student</class>
    

    Eso es porque debemos definir manualmente cada clase asignada en el persistence.xmlarchivo.

    Los marcos como Spring simplificaron mucho este proceso al presentarnos la packagesToScanpropiedad, que escanea automáticamente paquetes completos en busca de anotaciones.

    Base de datos

    Después de eso, están las propiedades, comenzando con la configuración de la base de datos:

    <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
    <property name="javax.persistence.jdbc.url" value="jdbc:h2:mem:"/>
    <property name="javax.persistence.jdbc.user" value="user"/>
    <property name="javax.persistence.jdbc.password" value="password"/>
    

    Aquí hay algunas líneas, repasemos una tras otra:

    • javax.persistence.jdbc.driver: El nombre calificado del controlador necesario para comunicarse con la base de datos.
    • javax.persistence.jdbc.url: La URL de la base de datos, aquí indicamos que queremos comunicarnos con una instancia en memoria de H2 .
    • javax.persistence.jdbc.user: El usuario para conectarse a la base de datos. En realidad, no importa lo que pongamos allí, ya que la instancia H2 no tiene un usuario específico. Incluso hubiéramos podido omitir esta línea.
    • javax.persistence.jdbc.password: La contraseña que coincide con el usuario. Lo mismo se aplica aquí para la instancia H2, podemos omitir esto o poner lo que queramos.

    Esquema

    Finalmente, le decimos a JPA que cree nuestro esquema en el inicio. Lo hacemos principalmente porque estamos usando una base de datos en memoria, por lo que el esquema se pierde cada vez que se detiene la base de datos.

    <property name="javax.persistence.schema-generation.database.action" value="create"/>
    

    En una aplicación de producción con una base de datos persistente, probablemente no dependeríamos de este mecanismo para crear nuestro esquema de base de datos.

    Clases de mapeo

    Ahora que se ha cubierto nuestra configuración mínima, vayamos al tema principal: mapeos. Como recordatorio, el mapeo es el mecanismo de vinculación de nuestras clases de Java a las tablas de la base de datos.

    Entonces, lo primero que debemos hacer para asignar una clase a una tabla de base de datos es anotarla con la @Entityanotación:

    @Entity
    public class Student {}
    

    Si nos detenemos allí mismo, a continuación, JPA deducir el nombre de la tabla a partir del nombre de la clase: STUDENT. Las tablas de la base de datos no distinguen entre mayúsculas y minúsculas, pero para mayor claridad usaremos mayúsculas al referirnos a ellas.

    Pero ahora, ¿qué pasa si queremos asignar esa clase a una tabla con un nombre diferente, como STUD? Luego tenemos que usar la @Tableanotación, que toma un atributo de nombre:

    @Entity
    @Table(name = "STUD")
    public class Student {}
    

    Ahora, nuestra clase está asignada a la STUDtabla en lugar de STUDENT. Esto resulta particularmente útil cuando se trabaja con una base de datos heredada, que puede tener nombres de tabla que son abreviaturas o nombres engorrosos. Entonces, podemos dar nombres propios a nuestras clases, incluso si los nombres de las tablas de la base de datos son muy diferentes.

    Campos de mapeo

    Ahora, vamos a mapear nuestros campos a las columnas de la base de datos. Dependiendo de los campos, hay algunas técnicas disponibles.

    Lo esencial

    Comencemos con los fáciles. Hay varios tipos que JPA maneja automáticamente:

    • Primitivos
    • Envoltorios de primitivos
    • String
    • BigInteger, BigDecimal
    • Fechas (aunque su mapeo podría requerir alguna configuración, por lo que tendrán su propia sección)

    Cuando colocamos un campo de uno de esos tipos en nuestras clases, se asignan automáticamente a una columna del mismo nombre.

    Entonces, si tuviéramos que agregar apellidos y nombres a nuestro Student:

    public class Student {
        private String lastName;
        private String firstName;
    }
    

    Luego, estos campos se asignarían a columnas denominadas LASTNAMEy FIRSTNAME, respectivamente.

    Nuevamente, ciertamente nos gustaría personalizar los nombres de nuestras columnas. Para hacer eso tendríamos que usar la @Columnanotación y su nameatributo:

    public class Student {
        private String lastName;
    
        @Column(name = "FIRST_NAME")
        private String firstName;
    }
    

    Así, nuestro firstNamecampo se asigna a una FIRST_NAMEcolumna.

    Veamos si esto funciona recuperando un estudiante de la base de datos. En primer lugar, data.sqlcreemos un archivo de conjunto de datos , que colocaremos en la raíz de nuestra ruta de clases:

    insert into STUD(ID, LASTNAME, FIRST_NAME) values(2, 'Doe', 'John');
    

    Luego, digamos a JPA que cargue este conjunto de datos. Eso se hace usando la javax.persistence.sql-load-script-sourcepropiedad en nuestro persistence.xml:

    <property name="javax.persistence.sql-load-script-source" value="data.sql"/>
    

    Finalmente podemos escribir una prueba afirmando que recuperamos a nuestro alumno y sus datos son correctos:

    Student foundStudent = entityManager.find(Student.class, 2L);
    
    assertThat(foundStudent.id()).isEqualTo(2L);
    assertThat(foundStudent.lastName()).isEqualTo("Doe");
    assertThat(foundStudent.firstName()).isEqualTo("John");
    

    ID

    Ahora, hablemos rápidamente de identificaciones. Hay mucho que decir sobre ellos, aunque aquí solo veremos lo básico. Para declarar una identificación, necesitamos usar la @Idanotación:

    public class Student {
        @Id
        private Long id;
    }
    

    Pero, ¿qué es exactamente una identificación? Es el mapeo de la clave principal de nuestra tabla, es decir, la columna que identifica nuestras filas. A veces, queremos que nuestras claves principales se generen automáticamente. Para hacer eso en JPA, debemos usar la @GeneratedValueanotación junto con la siguiente @Id:

    public class Student {
        @Id
        @GeneratedValue
        private Long id;
    }
    

    Existen múltiples estrategias de generación de valor , que puede especificar configurando la strategybandera:

    @GeneratedValue(strategy = GenerationType.TYPE)
    

    Sin establecer la estrategia, Hibernate elegirá la que mejor se adapte a nuestro proveedor de base de datos.

    fechas

    Mencionamos fechas anteriormente, diciendo que fueron manejadas naturalmente por JPA, pero con algunas peculiaridades.

    Así, en primer lugar, recordemos Java nos proporciona dos representaciones de fecha y hora: El que está en el java.utilpaquete ( Date, Timestamp, etc.) y la del java.timepaquete ( LocalDate, LocalTime, LocalDateTime, etc.).

    Los primeros se manejan mediante el uso de la @Temporalanotación, mientras que los segundos se manejan de forma inmediata, pero solo desde la versión 2.2de JPA. Antes de eso, habríamos tenido que usar convertidores, que veremos más adelante en este artículo para proyectos heredados.

    Comencemos mapeando un Datecampo, digamos la fecha de nacimiento de un estudiante:

    public class Student {
        @Temporal(TemporalType.DATE)
        private Date birthDate;
    }
    

    Podemos notar que la @Temporalanotación toma un argumento de tipo TemporalType. Esto debe especificarse para definir el tipo de columna en la base de datos.

    ¿Tendrá una cita? ¿Un momento? ¿Una fecha y una hora?

    Hay un enumvalor para cada una de estas posibilidades: DATE, TIMEy TIMESTAMP, respectivamente.

    Necesitamos hacer eso porque un Dateobjeto mantiene la fecha y la hora juntas, lo que significa que debemos especificar qué parte de los datos realmente necesitamos.

    La nueva representación de la hora de Java nos lo hizo más fácil, ya que hay un tipo específico para la fecha, la hora y la fecha y la hora.

    Por lo tanto, si queremos usar a en LocalDatelugar de a Date, simplemente podemos mapear el campo sin la @Temporalanotación:

    public class Student {
        private LocalDate birthDate;
    }
    

    Y así de simple, ¡nuestro campo está mapeado!

    Enumeraciones

    Otro tipo de campo que necesita atención específica son los enums. Fuera de la caja, JPA ofrece una anotación para mapear enums – @Enumerated. Esta anotación toma un argumento de tipo EnumType, que es una enumoferta de los valores ORDINALy STRING.

    El primero asigna el enuma un número entero que representa su posición de declaración, lo que hace que luego esté prohibido cambiar el orden de las enumconstantes. Este último usa los enumnombres de las constantes como el valor correspondiente en la base de datos. Con esta solución, no podemos cambiar el nombre de las enumconstantes.

    Además, si estamos trabajando con una base de datos heredada, es posible que nos veamos obligados a usar nombres ya almacenados para nuestras enumconstantes, lo que quizás no queramos si esos nombres no son significativos. La solución entonces sería darle al enumcampo a que representa el valor de la base de datos, dejándonos elegir el nombre de constante que consideremos adecuado y usar un convertidor para mapear el enumtipo. Veremos convertidores en la siguiente sección.

    Entonces, ¿qué dice todo sobre nuestro Studentejemplo? Digamos que queremos agregar género al estudiante, que está representado por un enum:

    public enum Gender {
        MALE,
        FEMALE
    }
    
    public class Student {
        private Gender gender;
    }
    

    Luego, debemos agregar la @Enumeratedanotación a nuestro campo de género para que se asigne:

    public class Student {
        @Enumerated
        private Gender gender;
    }
    

    Pero, ¿qué pasa con el argumento del que hablamos antes? Por defecto, el seleccionado EnumTypees ORDINAL. Es posible que deseemos cambiar eso a STRING:

    public class Student {
        @Enumerated(EnumType.STRING)
        private Gender gender;
    }
    

    Y ahí estamos, los géneros de los estudiantes ahora se asignarán como MALEy FEMALEen la base de datos.

    Convertidores

    Esta sección tratará sobre los convertidores de los que hablamos mucho antes. Los convertidores deben usarse cuando queremos que una columna de la base de datos se asigne a un tipo que JPA no maneja de manera inmediata.

    Digamos, por ejemplo, que tenemos una columna que nos dice si un estudiante quiere recibir el boletín escolar o no, pero los datos almacenados en esta columna son Yy Npara “sí” y “no”, respectivamente. Entonces tenemos múltiples posibilidades:

    • Asigne la columna a a String, pero será complicado de usar en el código.
    • Asigne la columna a algún tipo de YesNo enum, pero eso parece una exageración.
    • Asigne la columna a a Boolean, ¡y ahora estamos llegando a alguna parte!

    Entonces, ¿cómo logramos ese último? Utilizando un convertidor. En primer lugar, debemos crear una YesNoBooleanConverterclase, que implemente la AttributeConverterinterfaz:

    public class YesNoBooleanConverter implements AttributeConverter<Boolean, String> {
        @Override
        public String convertToDatabaseColumn(Boolean attribute) {
            return null;
        }
    
        @Override
        public Boolean convertToEntityAttribute(String dbData) {
            return null;
        }
    }
    

    Notamos entonces que hay dos métodos para implementar. El primero convierte nuestro booleanen un Stringpara almacenarlo en la base de datos, mientras que el otro convierte un valor de la base de datos en un boolean. Implementémoslos:

    public class YesNoBooleanConverter implements AttributeConverter<Boolean, String> {
        @Override
        public String convertToDatabaseColumn(Boolean attribute) {
            return attribute ? "Y" : "N";
        }
    
        @Override
        public Boolean convertToEntityAttribute(String dbData) {
            return dbData.equals("Y");
        }
    }
    

    Aquí, consideramos que nuestra columna siempre tendrá un valor, pase lo que pase, y que este valor siempre será Yo N. Podríamos tener que escribir un poco más de código en casos más complejos (para manejar nullvalores, por ejemplo).

    Ahora, ¿qué hacemos con eso? Mapearemos nuestro campo de estudiantes con una @Convertanotación, que toma nuestra clase como argumento:

    public class Student {
        @Convert(converter = YesNoBooleanConverter.class)
        private boolean wantsNewsletter;
    }
    

    Observe cómo mapeamos nuestro campo como una primitiva boolean, no como un tipo contenedor. Podemos hacer eso porque sabemos que nuestra columna siempre tendrá un valor y que el convertidor que escribimos nunca regresa nullcomo un valor.

    Pero aún no hemos terminado. Aún debemos agregar el convertidor a nuestro persistence.xmlarchivo:

    <class>com.fdpro.clients.Pharos.sh.jpa.domain.converters.YesNoBooleanConverter</class>
    

    Y ahora funciona. Sin embargo, ¿qué podemos hacer si tenemos un montón de columnas sí / no en nuestra base de datos y nos resulta agotador repetir la @Convertanotación para esos tipos todo el tiempo? Luego podemos agregar una @Converteranotación a nuestra YesNoBooleanConverterclase y pasarle el autoApply = trueargumento.

    Entonces, cada vez que tengamos un Stringvalor en la base de datos que queramos mapear como Booleanen nuestro código, se aplicará este convertidor. Vamos a agregarlo:

    @Converter(autoApply = true)
    public class YesNoBooleanConverter implements AttributeConverter<Boolean, String>
    

    Y luego elimine la @Convertanotación de la clase ‘Estudiante’:

    public class Student {
        private boolean wantsNewsletter;
    }
    

    Incrustado

    Finalmente, hablemos de tipos incrustados. ¿Para qué son? Imaginemos que nuestra STUDtabla contiene la información de la dirección de los estudiantes: calle, número y ciudad. Pero, en nuestro código nos gustaría usar un Addressobjeto, haciéndolo reutilizable y, sobre todo, un objeto (¡porque todavía estamos haciendo programación orientada a objetos!).

    Ahora, hagámoslo en el código:

    public class Address {
        private String street;
        private String number;
        private String city;
    }
    
    public class Student {
        private Address address;
    }
    

    Por supuesto, todavía no funcionará así. Debemos decirle a JPA qué tiene que ver con este campo. Para eso son las anotaciones @Embeddabley @Embedded. El primero irá a nuestra Addressclase y el segundo al campo:

    @Embeddable
    public class Address {
        private String street;
        private String number;
        private String city;
    }
    
    public class Student {
        @Embedded
        private Address address;
    }
    

    Veamos nuestro conjunto de datos nuevamente:

    insert into STUD(ID, LASTNAME, FIRST_NAME, BIRTHDATE, GENDER, WANTSNEWSLETTER, STREET, NUMBER, CITY)
        values(2, 'Doe', 'John', TO_DATE('2000-02-18', 'YYYY-MM-DD'), 'MALE', 'Y', 'Baker Street', '221B', 'London');
    

    Ha evolucionado un poco desde el principio. Puede ver aquí que agregamos todas las columnas de las secciones anteriores, así como la calle, el número y la ciudad. Hemos hecho esto como si los campos pertenecieran a la Studentclase, no a la Addressclase.

    Ahora, ¿nuestra entidad todavía está mapeada correctamente? Probémoslo:

    Student foundStudent = entityManager.find(Student.class, 2L);
    
    assertThat(foundStudent.id()).isEqualTo(2L);
    assertThat(foundStudent.lastName()).isEqualTo("Doe");
    assertThat(foundStudent.firstName()).isEqualTo("John");
    assertThat(foundStudent.birthDateAsDate()).isEqualTo(DateUtil.parse("2000-02-18"));
    assertThat(foundStudent.birthDateAsLocalDate()).isEqualTo(LocalDate.parse("2000-02-18"));
    assertThat(foundStudent.gender()).isEqualTo(Gender.MALE);
    assertThat(foundStudent.wantsNewsletter()).isTrue();
    
    Address address = new Address("Baker Street", "221B", "London");
    assertThat(foundStudent.address()).isEqualTo(address);
    

    ¡Sigue funcionando bien!

    Ahora, ¿qué pasa si queremos reutilizar la Addressclase para otras entidades, pero los nombres de las columnas son diferentes? No entremos en pánico, JPA nos tiene cubiertos con la @AttributeOverrideanotación.

    Digamos que las STUDcolumnas de las tablas de la dirección son: ST_STREET, ST_NUMBERy ST_CITY. Puede parecer que nos estamos volviendo creativos, pero seamos honestos, el código heredado y las bases de datos son lugares definitivamente creativos.

    Entonces debemos decirle a JPA que anulamos el mapeo predeterminado:

    public class Student {
        @AttributeOverride(name = "street", column = @Column(name = "ST_STREET"))
        @AttributeOverride(name = "number", column = @Column(name = "ST_NUMBER"))
        @AttributeOverride(name = "city", column = @Column(name = "ST_CITY"))
        private Address address;
    }
    

    Y ahí lo tenemos, nuestro mapeo está arreglado. Debemos tener en cuenta que, desde JPA 2.2, la @AttributeOverrideanotación es repetible.

    Antes de eso, habríamos tenido que envolverlos con la @AttributeOverridesanotación:

    public class Student {
        @AttributeOverrides({
            @AttributeOverride(name = "street", column = @Column(name = "ST_STREET")),
            @AttributeOverride(name = "number", column = @Column(name = "ST_NUMBER")),
            @AttributeOverride(name = "city", column = @Column(name = "ST_CITY"))
        })
        private Address address;
    }
    

    Conclusión

    En este artículo, nos sumergimos en lo que son JPA e Hibernate y su relación. Hemos configurado Hibernate en un proyecto de Maven y nos sumergimos en el mapeo relacional de objetos básico.

    El código de esta serie se puede encontrar en GitHub .

     

    Etiquetas:

    Deja una respuesta

    Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *