Skip to content

Commit 470c080

Browse files
committed
Añade mapeo con XML (JPA)
1 parent 579cf57 commit 470c080

File tree

6 files changed

+244
-11
lines changed

6 files changed

+244
-11
lines changed

source/05.orm/04.mapeo.rst

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ Fundamentalmente sirven para:
1515
- Establecer restricciones sobre los valores.
1616
- Definir relaciones entre tablas.
1717

18+
.. seealso:: Bajo este epígrafe se verá cómo definir el mapeo mediante
19+
anotaciones. Hay otra estrategia que consiste en un utilizar archivos |XML|
20+
aparte. Se tratará más adelante en :ref:`mapeo mediante configuración
21+
<xml-orm-mapping>`.
22+
23+
1824
.. _orm-mapping-bas:
1925

2026
Anotaciones básicas
@@ -23,6 +29,8 @@ Ya introdujimos las anotaciones más básicas al presentar el :ref:`ejemplo de
2329
anotación de la clase Centro <orm-bas-map>`:
2430

2531
.. literalinclude:: files/Centro.java
32+
:class: toggle
33+
:caption: Clase Centro
2634
:language: java
2735
:start-at: @Entity
2836

@@ -257,6 +265,7 @@ equivalente al ``CHECK`` de |SQL|), que requieren importar la librería
257265
private int cantidad
258266
259267
@Positive
268+
@NotNull
260269
private BigDecimal precio;
261270
262271
@Min(value = 18, message = "La entrada está vetada a menores de edad")
@@ -267,6 +276,16 @@ equivalente al ``CHECK`` de |SQL|), que requieren importar la librería
267276
268277
.. note:: \"*message*\" permite personalizar el mensaje de error.
269278

279+
.. note:: ``@NotNull`` impide que el valor sea nulo a nivel de aplicación,
280+
mientras que el atributo ``nullable`` de ``@Column`` define la restrucción en
281+
la definición del esquema (a nivel de base de datos).
282+
283+
.. note:: Aunque hemos relacionado estas restricciones con el ``CHECK`` de
284+
|SQL|, la validación que nos ofrece |JPA| no se basa en usarlo (o sea, en
285+
incluir en la definición de las tablas las cláusulas ``CHECK``
286+
apropiadas), sino en que la propia aplicación de Java haga las comprobaciones
287+
antes de aceptar el valor del atributo.
288+
270289
Relaciones
271290
==========
272291
Las relaciones entre las entidades también se significan mediante anotaciones.
@@ -289,6 +308,8 @@ Hay tres tipos de relaciones binarias en el modelo relacional:
289308
clase ``Estudiante``:
290309

291310
.. literalinclude:: files/Estudiante.java
311+
:class: toggle
312+
:caption: Clase Estudiante
292313
:language: java
293314
:start-at: @Entity
294315
:emphasize-lines: 13-15, 56-62
@@ -442,4 +463,5 @@ Hay tres tipos de relaciones binarias en el modelo relacional:
442463
.. |JPA| replace:: :abbr:`JPA (Java Persisten API)`
443464
.. |SQL| replace:: :abbr:`SQL (Structured Query Language)`
444465
.. |ORM| replace:: :abbr:`ORM (Object-Relational Mapping)`
466+
.. |XML| replace:: :abbr:`XML (eXtensible Markup Language)`
445467
.. _Hibernate: https://www.hibernate.org

source/05.orm/15.extra.rst

Lines changed: 122 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ se encargará de propagar la operación en cascada.
103103

104104
.. caution:: En SQLite la integridad referencial está deshabilitada por defecto.
105105

106-
.. rubric:: Hibernate
106+
.. rubric:: |JPA|
107107

108108
.. caution:: Por lo general, las operaciones en cascada se definen de padre a
109109
hijo cuando las relaciones son bidireccionales. Cuando las relaciones son
@@ -353,9 +353,130 @@ Tanto en |JPQL| como en *Criteria API*, los *JOIN* a secas respetan este
353353
comportamiento derivado de las anotaciones, mientras que los *FETCH JOIN*
354354
fuerzan siempre la carga inmediata.
355355

356+
.. _xml-orm-mapping:
357+
358+
Mapeo mediante archivos
359+
=======================
360+
Aunque, como ya hemos visto, el mapeo puede realizarse mediante :ref:`anotaciones al
361+
propio código de las clases del modelo <orm-mapping>`, también es posible no
362+
añadir las anotaciones al código Java y crear archivos |XML| aparte que
363+
sustituyan por completo a estas anotaciones. Para entender cómo crear estos
364+
archivos es indispensable que entendamos que las anotaciones eran de dos
365+
naturalezas distintas:
366+
367+
* Las que definían propiamente el esquema (como ``@Id`` o ``@Colum``).
368+
* Las que incorporaban restricciones a los valores (como ``@Positive``).
369+
370+
En los archivos ambas deben realizarse por separado, así que las trataremos bajo
371+
epígrafes distintos:
372+
373+
Definición del esquema
374+
----------------------
375+
Antes de crear el archivo para *definir el esquema* debemos indicar en la
376+
configuración de la unidad de persistencia definida en
377+
:file:`META-INF/persistence.xml` cuál será tal archivo:
378+
379+
.. code-block:: xml
380+
381+
<persistenceo-unit name="MiUnidadP" transaction-type="RESOURCE_LOCAL">
382+
<!-- Pueden definirse varios <mapping-file> -->
383+
<mapping-file>META-INF/schema.xml</mapping-file>
384+
385+
<!-- ... -->
386+
387+
<!-- Es bastante probable que no necesitemos declarar las clases con <class> -->
388+
389+
</presistence-unit>
390+
391+
Hecho lo cual, podemos definir :file:`META-INF/schema.xml` con la definición del
392+
esquema:
393+
394+
.. literalinclude:: files/schema.xml
395+
:class: toggle
396+
:caption: Definición del esquema SQL
397+
:language: xml
398+
399+
En el archivo se ha definido una relación *unidirección*. Podríamos hacerla
400+
*bidireccional* descomentando las líneas correspondientes en la definición de la
401+
entidad padre (aunque deberíamos haber definido el atributo ``estudiantes`` en
402+
la clase ``Centro``).
403+
404+
Definición de restricciones
405+
---------------------------
406+
Para definir restricciones **a nivel de aplicación** debemos escribir el archivo
407+
``META-INF/validation``:
408+
409+
.. code-block:: xml
410+
:emphasize-lines: 9
411+
412+
<?xml version="1.0" encoding="UTF-8"?>
413+
<validation-config
414+
xmlns="http://xmlns.jcp.org/xml/ns/validation/configuration"
415+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
416+
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/validation/configuration
417+
http://xmlns.jcp.org/xml/ns/validation/validation-configuration-2.0.xsd"
418+
version="2.0">
419+
420+
<!-- Podemos añadir más elementos <constraint-mapping> para repartir
421+
la configuración en varios archivos. -->
422+
<constraint-mapping>META-INF/constraints.xml</constraint-mapping>
423+
</validation-config>
424+
425+
Los archivos donde propiamente se definen las restricciones tienen esta pinta:
426+
427+
.. code-block:: xml
428+
:caption: Restricciones a nivel de aplicación
429+
:class: toggle
430+
431+
<?xml version="1.0" encoding="UTF-8"?>
432+
<constraint-mappings
433+
xmlns="http://xmlns.jcp.org/xml/ns/validation/mapping"
434+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
435+
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/validation/mapping
436+
http://xmlns.jcp.org/xml/ns/validation/validation-mapping-2.0.xsd"
437+
version="2.0">
438+
<bean class="edu.acceso.ejemplo.modelo.Centro" ignore-annotations="true">
439+
<field name="nombre">
440+
<constraint annotation="jakarta.validation.constraints.NotNull">
441+
<message>El nombre no puede ser nulo</message>
442+
</constraint>
443+
<constraint annotation="jakarta.validation.constraints.Size">
444+
<message>El nombre debe tener entre 2 y 100 caracteres</message>
445+
<element name="min">2</element>
446+
<element name="max">100</element>
447+
</constraint>
448+
</field>
449+
<field name="titularidad">
450+
<constraint annotation="jakarta.validation.constraints.NotNull">
451+
<message>La titularidad debe estar definida</message>
452+
</constraint>
453+
</field>
454+
</bean>
455+
456+
<bean class="edu.acceso.ejemplo.modelo.Estudiante" ignore-annotations="true">
457+
<field name="nombre">
458+
<constraint annotation="jakarta.validation.constraints.NotNull">
459+
<message>El nombre no puede ser nulo</message>
460+
</constraint>
461+
</field>
462+
<field name="centro">
463+
<constraint annotation="jakarta.validation.constraints.NotNull">
464+
<message>El centro no puede ser nulo</message>
465+
</constraint>
466+
</field>
467+
</bean>
468+
</constraint-mappings>
469+
470+
Podemos definir otras restricciones:
471+
472+
* ``jakarta.validation.constraints.Positive``
473+
* ``jakarta.validation.constraints.PositiveOrZero``
474+
* etc.
475+
356476
.. |JPQL| replace:: :abbr:`JPQL (Java Persistence Query Language)`
357477
.. |SGBD| replace:: :abbr:`SGBD (Sistema Gestor de Bases de Datos)`
358478
.. |ORM| replace:: :abbr:`ORM (Object-Relational Mapping)`
359479
.. |JPA| replace:: :abbr:`JPA (Java Persistence API)`
360480
.. |SQL| replace:: :abbr:`SQL (Structured Query Language)`
481+
.. |XML| replace:: :abbr:`XML (eXtensible Markup Language)`
361482
.. _Hibernate: https://hibernate.org/

source/05.orm/files/JpaBackend.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,27 @@
1212
import jakarta.persistence.EntityTransaction;
1313
import jakarta.persistence.Persistence;
1414

15+
/**
16+
* Clase de utilidad para gestionar múltiples instancias de {@link EntityManagerFactory}.
17+
* Permite crear instancias a partir del nombre de la unidad de persistencia y un mapa de propiedades,
18+
* y recuperarlas posteriormente mediante un índice.
19+
* También proporciona métodos para ejecutar transacciones de forma sencilla.
20+
*/
1521
public class JpaBackend {
1622

23+
/**
24+
* Lista de claves hash que representan las instancias creadas.
25+
* Se utiliza una lista para mantener el orden de creación y permitir la recuperación por índice.
26+
*/
1727
private static ArrayList<Integer> keys = new ArrayList<>();
28+
/**
29+
* Mapa que asocia las claves hash con las instancias de EntityManagerFactory.
30+
*/
1831
private static Map<Integer, EntityManagerFactory> instances = new HashMap<>();
1932

33+
/**
34+
* Constructor privado para evitar instanciación.
35+
*/
2036
private JpaBackend() { super(); }
2137

2238
/**
@@ -55,6 +71,12 @@ public static int createEntityManagerFactory(String persistenceUnit) {
5571
return createEntityManagerFactory(persistenceUnit, null);
5672
}
5773

74+
/**
75+
* Devuelve un objeto EntityManagerFactory generado anteriormente.
76+
* @param index El índice de la instancia a recuperar.
77+
* @return La instancia de EntityManagerFactory correspondiente al índice.
78+
* @throws IllegalArgumentException Si el índice está fuera de rango.
79+
*/
5880
public static EntityManagerFactory getEntityManagerFactory(int index) {
5981
if(index < 1 || index > keys.size()) throw new IllegalArgumentException("Índice fuera de rango");
6082

@@ -71,6 +93,7 @@ public static EntityManagerFactory getEntityManagerFactory(int index) {
7193
/**
7294
* Devuelve un objeto EntityManagerFactory generado anteriormente. Sólo funciona si se generó uno.
7395
* @return El objeto resultante.
96+
* @throws IllegalStateException Si no hay ninguna instancia o si hay varias.
7497
*/
7598
public static EntityManagerFactory getEntityManagerFactory() {
7699
EntityManagerFactory instance = null;
@@ -99,6 +122,13 @@ public static void reset() {
99122
}
100123

101124
// Transacciones.
125+
/**
126+
* Ejecuta una acción dentro de una transacción, devolviendo un resultado.
127+
* @param <T> El tipo de resultado de la acción.
128+
* @param index El índice de la instancia a utilizar.
129+
* @param action La acción a ejecutar.
130+
* @return El resultado de la acción.
131+
*/
102132
public static <T> T transactionR(Integer index, Function<EntityManager, T> action) {
103133
EntityManagerFactory emf = index != null?getEntityManagerFactory(index):getEntityManagerFactory();
104134
try(EntityManager em = emf.createEntityManager()) {
@@ -116,17 +146,32 @@ public static <T> T transactionR(Integer index, Function<EntityManager, T> actio
116146
}
117147
}
118148

149+
/**
150+
* Versión sin índice de {@link #transactionR(Integer, Function)} para cuando sólo hay una instancia.
151+
* @param <T> El tipo de resultado de la acción.
152+
* @param action La acción a ejecutar.
153+
* @return El resultado de la acción.
154+
*/
119155
public static <T> T transactionR(Function<EntityManager, T> action) {
120156
return transactionR(null, action);
121157
}
122158

159+
/**
160+
* Ejecuta una acción dentro de una transacción, sin devolver resultado.
161+
* @param index El índice de la instancia a utilizar.
162+
* @param action La acción a ejecutar.
163+
*/
123164
public static void transaction(Integer index, Consumer<EntityManager> action) {
124165
transactionR(index, em -> {
125166
action.accept(em);
126167
return null;
127168
});
128169
}
129170

171+
/**
172+
* Versión sin índice de {@link #transaction(Integer, Consumer)} para cuando sólo hay una instancia.
173+
* @param action La acción a ejecutar.
174+
*/
130175
public static void transaction(Consumer<EntityManager> action) {
131176
transaction(null, action);
132177
}

source/05.orm/files/schema.xml

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<entity-mappings version="2.2"
3+
xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence/orm
6+
http://xmlns.jcp.org/xml/ns/persistence/orm_2_2.xsd">
7+
8+
<entity class="edu.acceso.ejemplo.modelo.Centro" access="FIELD">
9+
<table name="Centro"/>
10+
<attributes>
11+
<id name="id">
12+
<generated-value strategy="IDENTITY"/>
13+
</id>
14+
<basic name="nombre">
15+
<!-- El atributo name permite que la columna en la BD se llame
16+
de distinta forma -->
17+
<column name="nombre" nullable="false" length="255" />
18+
</basic>
19+
<basic name="titularidad">
20+
<!-- El Enum se almacenará como un número -->
21+
<enumerated>ORDINAL</enumerated>
22+
</basic>
23+
<!--
24+
<one-to-many name="estudiantes" target-entity="educ.acceso.ejemplo.modelo.Estudiante" mapped-by="centro">
25+
<cascade>
26+
<cascade-type>ALL</cascade-type>
27+
<cascade>
28+
</one-to-many>
29+
-->
30+
</attributes>
31+
</entity>
32+
33+
<entity class="edu.acceso.ejemplo.modelo.Estudiante" access="FIELD">
34+
<table name="Estudiante"/>
35+
<attributes>
36+
<id name="id">
37+
<!-- o SEQUENCE si queremos que se genere automáticamente -->
38+
<generated-value strategy="IDENTITY"/>
39+
</id>
40+
<basic name="nombre">
41+
<column nullable="false" length="255"/>
42+
</basic>
43+
<basic name="nacimiento" />
44+
<!-- Optional permite valores nulos para el atributo (datos en la aplicación) -->
45+
<many-to-one name="centro" target-entity="edu.acceso.ejemplo.modelo.Centro" fetch="EAGER" optional="false">
46+
<!-- nullable permite valores nulos en el esquema -->
47+
<join-column nullable="false"/>
48+
</many-to-one>
49+
</attributes>
50+
</entity>
51+
</entity-mappings>
-9.7 KB
Binary file not shown.

source/99.ejercicios/05.orm.rst

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,9 @@
1212

1313
Debe resolver **dos** veces el ejercicio usando dos estrategias distintas:
1414

15-
a. Modifique el código del programa para acomodarse a la |API| de |JPA|.
16-
#. Respete escrupulosamente el programa ya escrito y limítese a:
15+
a. Libremente con |JPA|.
16+
#. Respetando escrupulosamente el código con que lo resolvió en la unidad
17+
anterior, es decir, mediante el patrón de diseño |DAO|.
1718

18-
i. Anotar las clases del modelo para que pueda interpretarlas el |ORM|.
19-
#. Escriba la traducción entre la |API| que usó en el ejercicio citado y
20-
la de |JPA|, a fin de que no haya que cambiar ninguna línea del
21-
programa (salvo las importaciones, claro está).
22-
23-
.. |JDBC| replace:: :abbr:`JDBC (Java DataBase Connectivity)`
2419
.. |JPA| replace:: :abbr:`JPA (Java Persistent API)`
25-
.. |API| replace:: :abbr:`API (Application Programming Interface)`
26-
.. |ORM| replace:: :abbr:`ORM (Object-Relational Mapping)`
20+
.. |DAO| replace:: :abbr:`DAO (Data Access Object)`

0 commit comments

Comments
 (0)