====== Mapeo Objeto-Relacional. Hibernate ======
===== Desfase objeto-relacional =====
El //desfase objeto-relacional// surge cuando en el desarrollo de una aplicación con un lenguaje orientado a objetos se hace uso de una base de datos relacional. Hay que tener en cuenta que esta situación se da porque tanto los lenguajes orientados a objetos como las bases de datos relacionales están ampliamente extendidas.
En cuanto al desfase, ocurre que en nuestra aplicación Java (como ejemplo de lenguaje Orientada a Objetos) tendremos, por ejemplo, la definición de una clase cualquiera con sus atributos y métodos:
public class Personaje {
private int id;
private String nombre;
private String descripcion;
private int vida;
private int ataque;
public Personaje(. . .) {
. . .
}
// getters y setters
}
Mientras que en la base de datos tendremos una tabla cuyos campos se tendrán que corresponder con los atributos que hayamos definido anteriormente en esa clase. Puesto que son estructuras que no tienen nada que ver entre ellas, tenemos que hacer el mapeo manualmente, haciendo coincidir (a través de los getters o setters) cada uno de los atributos con cada uno de los campos (y vicerversa) cada vez que queramos leer o escribir un objeto desde y hacia la base de datos, respectivamente.
CREATE TABLE personajes (
id INT PRIMARY KEY AUTO_INCREMENT;
nombre VARCHAR(50) NOT NULL,
descripcion VARCHAR(50),
vida INT DEFAULT 10,
ataque INT DEFAULT 10;
);
Eso hace que tengamos que estar continuamente descomponiendo los objetos para escribir la sentencia ''SQL'' para insertar, modificar o eliminar, o bien recomponer todos los atributos para formar el objeto cuando leamos algo de la base de datos.
===== ¿Qué es el mapeo objeto-relacional? =====
{{ orm.png }}
> Mapeo Objeto-Relacional (ORM)
Por ejemplo, si trabajamos directamente con JDBC tendremos que descomponer el objeto para construir la sentencia ''INSERT'' del siguiente ejemplo
. . .
String sentenciaSql = "INSERT INTO personajes (nombre, descripcion, vida, ataque)" +
") VALUES (?, ?, ?, ?)";
PreparedStatement sentencia = conexion.prepareStatement(sentenciaSql);
sentencia.setString(1, personaje.getNombre());
sentencia.setString(2, personaje.getDescripcion());
sentencia.setInt(3, personaje.getVida());
sentencia.setInt(4, personaje.getAtaque());
sentencia.executeUpdate();
if (sentencia != null)
sentencia.close();
. . .
Si contamos con un framework como //Hibernate//, esta misma operación se traduce en unas pocas líneas de código en las que podemos trabajar directamente con el objeto Java, puesto que el framework realiza el mapeo en función de las anotaciones que hemos implementado a la hora de definir la clase, que le indican a éste con que tabla y campos de la misma se corresponde la clase y sus atributos, respectivamente.
@Entity
@Table(name="personajes")
public class Personaje {
@Id // Marca el campo como la clave de la tabla
@GeneratedValue(strategy = IDENTITY)
@Column(name="id")
private int id;
@Column(name="nombre")
private String nombre;
@Column(name="descripcion")
private String descripcion;
@Column(name="vida")
private int vida;
@Column(name="ataque")
private int ataque;
public Personaje(. . .) {
. . .
}
// getters y setters
}
Así, podemos simplemente establecer una sesión con la Base de Datos y enviarle el objeto, en este caso invocando al método ''save'' que se encarga de registrarlo en la Base de Datos donde convenga según sus propias anotaciones.
. . .
sesion = HibernateUtil.getCurrentSession();
sesion.beginTransaction();
sesion.save(personaje);
sesion.getTransaction().commit();
. . .
===== Hibernate =====
En nuestro caso usaremos //Hibernate// como librería //ORM//, concretamente **Hibernate 5.2**. Habrá que tenerlo en cuenta puesto que algunas clases/métodos pueden variar entre diferentes versiones de este framework, especialmente a la hora de configurar (''hibernate.cfg.xml'') e implementar el gestor de sesiones (''HibernateUtil.java'')
==== Configuración ====
El fichero de configuración de hibernate ''hibernate.cfg.xml'' se debe crear directamente dentro de la carpeta ''src'' del proyecto y el propio framework //Hibernate// será el encargado de leerlo para obtener las sesiones que permitan conectar con la Base de Datos con el código que se implementa en el fichero ''HibernateUtil.java'' que se muestra justo después del primero.
com.mysql.jdbc.Driverjdbc:mysql://localhost:3306/basededatosusuariocontraseñaorg.hibernate.dialect.MySQL55Dialecttrue11005102200
Todas las configuraciones anteriores también se pueden indicar mediante código Java en el siguiente fichero, como veremos más adelante.
public class HibernateUtil {
private static SessionFactory sessionFactory;
private static Session session;
/**
* Crea la factoria de sesiones
*/
public static void buildSessionFactory() {
Configuration configuration = new Configuration();
// La llamada al método configure() carga los parámetros del fichero hibernate.cfg.xml
configuration.configure();
// Se registran las clases que hay que mapear con cada tabla de la base de datos
configuration.addAnnotatedClass(Clase1.class);
configuration.addAnnotatedClass(Clase2.class);
configuration.addAnnotatedClass(Clase3.class);
. . .
//Se crea una SessionFactory a partir del objeto Configuration
ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().applySettings(
configuration.getProperties()).build();
sessionFactory = configuration.buildSessionFactory(serviceRegistry);
}
/**
* Abre una nueva sesión
*/
public static void openSession() {
session = sessionFactory.openSession();
}
/**
* Devuelve la sesión actual
* @return
*/
public static Session getCurrentSession() {
if ((session == null) || (!session.isOpen()))
openSession();
return session;
}
/**
* Cierra Hibernate
*/
public static void closeSessionFactory() {
if (session != null)
session.close();
if (sessionFactory != null)
sessionFactory.close();
}
}
Si no uso un fichero de configuración ''xml'', no necesito llamar al método ''configure()''
=== Configuración con otros SGBD ===
Si tenemos distintos valores de configuración para conectar con diferentes sistemas de bases de datos,
necesitamos diferentes ficheros de configuración (p.e. mysql.cfg.xml, postgre.cfg.xml, mssqlserver.cfg.xml, etc).
Por ejemplo, para conectar con postgreSQL, el fichero de configuración sería:
...
org.postgresql.Driverjdbc:postgresql://localhost:5432/baseDatosusuariopasswordorg.hibernate.dialect.PostgreSQL9Dialect
...
En ellos indicaremos los parámetros y propiedades de la conexión con dicho //sgbd//.
En el método que configura la SessiónFactory deberemos indicar por parámetro el fichero necesario en cada caso:
SessionFactory postgreSF = configuration.configure("postgre.cfg.xml").buildSessionFactory();
SessionFactory msSF = configuration.configure("mssqlserver.cfg.xml").buildSessionFactory();
=== Modificar la configuración en tiempo de ejecución ===
Si deseo cambiar los parámetros de configuración en tiempo de ejecución en lugar de usar un fichero ''xml'' de configuración, también puedo indicárselo mediante el código Java en el fichero ''HibernateUtil.java''. Indico las propiedades con los mismos valores que en ''hibernate.cfg.xml''.
En el siguiente enlace podemos ver el significado de las propiedades que podemos configurar en Hibernate, y también del gestor del pool de conexiones ''C3P0''.
http://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#configurations
Configuration configuracion = new Configuration();
//Propiedades de Hibernate
configuracion.setProperty("hibernate.connection.driver_class", "com.mysql.jdbc.Driver");
configuracion.setProperty("hibernate.connection.url", "jdbc:mysql://localhost/bbdd");
configuracion.setProperty("hibernate.connection.username", "root");
configuracion.setProperty("hibernate.connection.password", "");
configuracion.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL55Dialect");
configuracion.setProperty("Hibernate.show_sql", "true");
//Propiedades del gestor de conexiones C3P0
configuracion.setProperty("hibernate.c3p0.acquire_increment", "1");
configuracion.setProperty("hibernate.c3p0.idle_test_period", "100");
configuracion.setProperty("hibernate.c3p0.max_size", "5");
configuracion.setProperty("hibernate.c3p0.max_statements", "10");
configuracion.setProperty("hibernate.c3p0.min_size", "2");
configuracion.setProperty("hibernate.c3p0.timeout", "200");
=== Crear la estructura de la bbdd desde clases mapeadas ===
Hibernate también permite crear la base de datos en nuestro SGBD cuando ya tengo las clases de Java mapeadas con las anotaciones. Para ello basta con incluir la siguiente propiedad en el fichero de configuración ''hibernate.cfg.xml'':
...
jdbc:mysql://localhost:3306/basededatos?createDatabaseIfNotExist=true
...
update
Puedo utilizar //create// o //update//. //Create// crea la base de datos completa. //Update// la crea si no existe, y si ya existe simplemente modifica los cambios que haya en su estructura.
Si quiero crear las claves ajenas de forma correcta, me interesa que el motor de datos de la base de datos sea //InnoDB//.
==== Pool de conexiones ====
Cuando un programa trabaja con una bbdd se deben abrir y cerrar conexiones con ella, y si el programa permite conectar diversos clientes, nos encontramos con el problema de conectar y desconectar conexiones a la base de datos. Un //pool// de conexiones es un conjunto (pool) de conexiones ya conectadas a la base de datos que puedan ser reutilizadas entre distintas peticiones.
{{:apuntes:bbdd-pooling.gif?500|}}
Por defecto, Hibernate cuenta con un //pool// de conexiones muy simple, pero el mismo Hibernate desaconseja su uso fuera de un entorno de pruebas. En nuestro caso usaremos las librerías [[https://www.mchange.com/projects/c3p0/|C3P0]] por ser ampliamente usadas en diversos proyectos y ser de código abierto. Estas librerías están incluidas en la descarga del paquete completo de Hibernate (//libs->hibernate->optional//):
* c3p0-0.9.5.2.jar
* mchange-commons-java-0.2.11.jar
* hibernate-c3p0-5.2.12.jar
Como hemos visto en el fichero ''hibernate.cfg.xml'', podemos configurar ciertas propiedades de este gestor de conexiones; a continuación se indica el significado de algunas de sus propiedades:
* ''acquireIncrement'' Determina cuantas conexiones intentará adquirir C3P0 cuando se saturen las conexiones del pool.
* ''idleTestPeriod'' Si este numero es mayor que 0, C3P0 hará una prueba en todas las conexiones que estén dormidas pero sigan en el pool cada X segundos para permitir que sigan activas.
* ''max_size'' indica el número máximo de conexiones que podrá tener en cualquier momento el pool. Si no se indica esta propiedad, hibernate no utiliza el connection pool de C3P0.
* ''min_size'' indica el número mínimo de conexiones que podrá tener en cualquier momento el pool
* ''timeout'' es el número de segundos que una conexión puede estar en el pool sin ser utilizada antes de ser descartada o eliminada. Un cero indica que nunca se descartarán.
* ''max_statements'' indica el tamaño del caché de PreparedStatements que guardará C3P0. Un cero indica que el caché está deshabilitado.
==== Mapeo de entidades con clases/atributos Java ====
En el caso de las entidades, se deben anotar tanto la propia clase como cada uno de los atributos (se puede hacer en el atributo o en su getter/setter) para indicar con qué tabla mapearla y cómo mapear los atributos con los campos que corresponda, respectivamente.
Las anotaciones que nos podemos encontrar para anotar una clase Java que debe ser mapeada con una tabla son:
* ''@Entity'' Indica que la clase es una tabla en la base de datos
* ''@Table(name = "nombre_tabla", catalog = "nombre_base_datos")'' Indica el nombre de la tabla y la base de datos a la que pertenece (este último parámetro no es necesario ya que esa información viene en el fichero de configuración)
En el caso de los atributos simples que deben ser mapeados con los campos de la tabla correspondiente:
* ''@Id'' Indica que un atributo es la clave
* ''@GeneratedValue(strategy = GenerationType.IDENTITY)'' Indica que es un valor //autonumérico// (''PRIMARY KEY'' en MySQL, por ejemplo)
* ''@Column(name = "nombre_columna")'' Se utiliza para indicar el nombre de la columna en la tabla donde debe ser mapeado el atributo
@Entity
@Table(name = "actor", catalog = "db_peliculas")
public class Actor {
private Integer id;
private String nombre;
private Date fechaNacimiento;
// Constructor/es
public Actor() { . . .}
. . .
@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "id")
public Integer getId() {
return this.id;
}
public void setId(Integer id) {
this.id = id;
}
@Column(name = "nombre")
public String getNombre() {
return this.nombre;
}
public void setNombre(String nombre) {
this.nombre = nombre;
}
@Column(name = "fecha_nacimiento")
public Date getFechaNacimiento() {
return this.fechaNacimiento;
}
public void setFechaNacimiento(Date fechaNacimiento) {
this.fechaNacimiento = fechaNacimiento;
}
. . .
// El resto de métodos se implementan como siempre
@Override
public String toString() {
return nombre;
}
. . .
}
==== Mapeado de las relaciones con atributos Java ====
Para el mapeo de las relaciones, además de crear el correspondiente objeto que permita mantener la relación entre las clases (de forma bidireccional), éstos atributos deben ser mapeados según convenga. Para todos los casos, en ambos casos se indicará el tipo de relación visto desde el lado correspondiente pero sólo se codificará la información de mapeo en uno de los lados:
* ''@OneToOne'' Indica que el objeto es parte de una relación 1-1
* ''@ManyToOne'' Indica que el objeto es parte de una relación N-1. En este caso el atributo sería el lado 1
* ''@OneToMany'' Indica que el objeto es parte de una relación 1-N. En este caso el atributo sería el lado N
* ''@ManyToMany'' Indica que el objeto es parte de una relación N-M. En este caso se indica la tabla que mantiene la referencia entre las tablas y los campos que hacen el papel de claves ajenas en la base de datos
En el otro lado de la relación indicaremos el tipo de relación acompañado de la anotación ''@MappedBy'' añadiendo el atributo de la otra clase donde se especifica toda la información sobre el mapeo
=== Relaciones 1-1 ===
@Entity
@Table(name = "personajes")
public class Personaje {
. . .
private Arma arma;
. . .
@OneToOne(cascade = CascadeType.ALL)
@PrimaryKeyJoinColumn
public Arma getArma() { return arma; }
. . .
}
@Entity
@Table(name = "armas")
public class Arma {
. . .
private Personaje personaje;
. . .
@OneToOne(cascade = CascadeType.ALL)
@PrimaryKeyJoinColumn
public Personaje getPersonaje() { return personaje; }
. . .
}
=== Relaciones 1-N ===
@Entity
@Table(name = "personajes")
public class Personaje {
. . .
private Arma arma;
. . .
@ManyToOne
@JoinColumn(name="id_arma")
public Arma getArma() { return arma; }
. . .
}
@Entity
@Table(name = "armas")
public class Arma {
. . .
private List personajes;
. . .
@OneToMany(mappedBy = "arma", cascade = CascadeType.ALL)
public List getPersonajes() { return personajes; }
. . .
=== Relaciones N-M ===
@Entity
@Table(name = "enemigos")
public class Enemigo {
. . .
private List armas;
. . .
// Cuando se elimine un personaje se desvínculará el arma pero ésta no se borrará (DETACH)
@ManyToMany(cascade = CascadeType.DETACH)
@JoinTable(name="enemigo_arma",
joinColumns={@JoinColumn(name="id_enemigo")},
inverseJoinColumns={@JoinColumn(name="id_arma")})
public List getArmas() { return armas; }
. . .
}
@Entity
@Table(name = "armas")
public class Arma {
. . .
private List enemigos;
. . .
@ManyToMany(mappedBy = "armas", cascade = CascadeType.DETACH)
public List getEnemigos() { return enemigos; }
. . .
}
{{ youtube>YU-jIpkkjRI }}
> Mapeado de una base de datos usando el pluggin de Hibernate
En el [[http://www.bitbucket.org/sfaci/java-hibernate|repositorio de bitbucket de este tema]] se pueden encontrar proyectos que muestran cómo mapear clases para los 3 tipos de relaciones:
* [[https://bitbucket.org/sfaci/java-hibernate/src/2c71dedf9401d8bf522a488e8ef0d13becc9cbf2/HibernateRelacion1a1/?at=master|HibernateRelacion1a1]] Muestra cómo mapear dos clases cuyas tablas mantienen una relación 1-1
* [[https://bitbucket.org/sfaci/java-hibernate/src/2c71dedf9401d8bf522a488e8ef0d13becc9cbf2/HibernateRelacion1aN/?at=master|HibernateRelacion1aN]] Muestra cómo mapear dos clases cuyas tablas mantienen una relación 1-N
* [[https://bitbucket.org/sfaci/java-hibernate/src/2c71dedf9401d8bf522a488e8ef0d13becc9cbf2/HibernateRelacionNaN/?at=master|HibernateRelacionNaM]] Muestra cómo mapear dos clases cuyas tablas mantienen una relación N-M
==== Operaciones sobre la Base de Datos ====
=== Registrar un objeto ===
Para registrar un nuevo objeto en la Base de Datos necesitamos haber creado previamente la clase y haberla mapeado correctamente con la tabla que le corresponda. Entonces, utilizando la clase ''HibernateUtil'' podremos obtener una sesión (conexión con la Base de Datos) para registrar ese objeto directamente en la Base de Datos de la siguiente forma.
. . .
UnaClase unObjeto = new UnaClase();
. . .
Session sesion = HibernateUtil.getCurrentSession();
sesion.beginTransaction();
sesion.saveOrUpdate(unObjeto);
sesion.getTransaction().commit();
. . .
Hay que tener en cuenta que entre el inicio y cierre de la transacción podemos realizar más de una operación y éstas se ejecutarán como tal. Es la forma correcta en el caso de que queramos registrar más de un objeto cuando éstos estén relacionados de alguna forma y dependan entre ellos. Un caso muy claro sería el del registro de un pedido junto con todas sus líneas de detalle puesto que no tendría sentido registrarlo sin los detalles, por lo que la forma más segura sería darlos de alta dentro de una misma transacción.
. . .
Session sesion = HibernateUtil.getCurrentSession();
sesion.beginTransaction();
sesion.saveOrUpdate(unPedido);
for (DetallePedido detallePedido : detallesDelPedido)
sesion.save(detallePedido);
sesion.getTransaction().commit();
. . .
{{ youtube>fopKZSEMba0 }}
\\
=== Modificar un objeto ===
En el caso de que queramos modificar un objeto, la operación se realiza de la misma forma que para el caso de registrar uno nuevo. Hibernate decide qué hacer (si registrar o modificar) comprobando si el objeto que se le envía tiene un valor válido para el campo ''id''. Así, la única diferencia con el ejemplo anterior es que ahora dispondremos de un objeto que hemos obtenido previamente de la Base de Datos y al que hemos realizado algunas modificaciones (nunca el campo ''id'').
. . .
Session sesion = HibernateUtil.getCurrentSession();
sesion.beginTransaction();
sesion.saveOrUpdate(unObjeto);
sesion.getTransaction().commit();
. . .
=== Eliminar un objeto ===
. . .
Session sesion = HibernateUtil.getCurrentSession();
sesion.beginTransaction();
sesion.delete(unObjeto);
sesion.getTransaction().commit();
. . .
=== Búsquedas ===
Para el caso de las búsquedas se utiliza el lenguaje //HQL// (Hibernate Query Language), muy similar al lenguaje SQL que se usa en las base de datos relacionales, pero en este caso totalmente Orientado a Objetos puesto que en vez de trabajar con las tablas se trabaja con las clases y sus atributos directamente.
* Obtener un **objeto identificado por el id**
. . .
int id = . . .;
Cliente cliente = HibernateUtil.getCurrentSession().get(Cliente.class, id);
. . .
* Obtener **todos los objetos** de una clase
. . .
Query query = HibernateUtil.getCurrentSession().createQuery("FROM Cliente");
ArrayList clientes = (ArrayList) query.list();
. . .
* Obtener objetos de una clase **añadiendo algún criterio de búsqueda**
-> Si el criterio especificado nos **devuelve un solo objeto**:
. . .
String nombre = . . .;
. . .
Query query = HibernateUtil.getCurrentSession().
createQuery("FROM Cliente c WHERE c.nombre = :nombre");
query.setParameter("nombre", nombre);
Cliente cliente = (Cliente) query.uniqueResult();
. . .
-> Si el criterio especificado nos **puede devolver más de un objeto**:
. . .
String ciudad = . . .;
. . .
Query query = HibernateUtil.getCurrentSession().
createQuery("FROM Cliente c WHERE c.ciudad = :ciudad");
query.setParameter("ciudad", ciudad);
ArrayList clientes = (ArrayList) query.list();
. . .
* Obtener objetos de una clase utilizando las **relaciones entre clases**
. . .
Query query = HibernateUtil.getCurrentSession().
createQuery("FROM DetallePedido dp WHERE dp.pedido.numeroPedido = :numeroPedido");
query.setParameter("numeroPedido", numeroPedido);
ArrayList detalles = (ArrayList) query.list();
. . .
* Y también es posible lanzar **consultas directamente en lenguaje SQL**, trabajando entonces directamente con las tablas y campos de la base de datos
. . .
SQLQuery sqlQuery = HibernateUtil.getCurrentSession().
createSQLQuery("SELECT nombre, apellidos FROM clientes WHERE ciudad = :ciudad");
query.setParameter("ciudad", ciudad);
List resultado = query.list();
for (Object objeto : resultado) {
Map fila = (Map) objeto;
String nombre = fila.get("nombre");
String apellidos = fila.get("apellidos");
. . .
}
. . .
{{ youtube>w1-Zm9DCmTY }}
> Operaciones CRUD con hibernate
==== Consideraciones sobre mapeo de relaciones====
Cuando realizamos relaciones bi-direccionales de las clases, desde Java podemos acceder a los elementos relacionados desde cualquier de los dos objetos de una relación. Para ello utilizaremos atributos únicos en el lado de ''@ManytoOne'' y colecciones (List, Set, Map) para el lado de ''@OneToMany'' o ''@ManyToMany''.
Aunque las relaciones sean bidireccionales, hibernate enfoca las relaciones desde una jerarquía //**padre-hijo**//. Para asegurarnos de mantener la relaciones de forma correcta en la base de datos, añadiremos a las clases mapeadas una serie de //métodos utilitarios//.
===Relaciones 1:N===
En las relaciones ''@OneToMany'' y ''@ManyToOne'' es recomendable que contenga algunos métodos utilitarios para mantener las relaciones:
@Entity
@Table(name = "armas")
public class Arma {
. . .
private List personajes;
. . .
@OneToMany(mappedBy = "arma", cascade = CascadeType.ALL)
public List getPersonajes() { return personajes; }
. . .
//Métodos utilitarios
//Añadir/Eliminar un personajes
public void addPersonaje(Personaje personaje){
personajes.add(personaje);
personaje.setArma(this);
}
public void removePersonaje(Personaje personaje){
personaje.setArma(null);
personajes.remove(personaje);
}
//Añadir varios (también puedo tener un método de eliminar varios)
public void addPersonajes(Collection personajes){
for(Personaje personaje : personajes){
personaje.setArma(this);
}
this.parsonajes.addAll(personajes);
}
. . .
}
**Los métodos anteriores también se deben codificar en la clase //Personaje//.**
Además en esta clase //padre//, cuando borramos un arma también se borrarán los personajes relacionados con dicho arma en la base de datos, para mantener la integridad de las claves ajenas (personaje.id_arma). Si queremos que al borrar un arma no se borren los personajes en cascada, primero podemos eliminar las relaciones del arma con los personajes, antes de borrar el arma. Podemos realizarlo con un método utilitario:
. . .
public void removeAllPersonajes(){
//Desvinculo todos los personajes con este arma
for(Personaje personaje : personajes){
persona.setArma(null);
}
personajes.clear(); //Vacio la coleccion
}
. . .
===Relaciones N:M===
En las relaciones @ManytoMany ambas clases funcionan como //padre// y también como //hijo//. También creamos //métodos utilitarios// para matener la integridad de las relaciones en la base de datos.
@Entity
@Table(name = "armas")
public class Arma {
. . .
private Set enemigos;
. . .
@ManyToMany(mappedBy = "armas", cascade = CascadeType.DETACH)
public Set getEnemigos() { return enemigos; }
. . .
//Métodos utilitarios
//Para añadir/eliminar uno
public void addEnemigo(Enemigo enemigo){
enemigo.getArmas().add(this);
enemigos.add(enemigo);
}
public void removeEnemigo(Enemigo enemigo){
enemigo.getArmas.remove(this);
enemigos.remove(enemigo);
}
//Para añadir/eliminar varios
public void addEnemigos(Collection enemigos){
for(Enemigo enemigo : enemigos){
enemigo.getArmas().add(this);
}
this.enemigos.addAll(enemigos);
}
public void removeEnemigos(Collection enemigos){
for(Enemigo enemigo: enemigos){
enemigo.getArmas().remove(this);
}
this.enemigos.removeAll(enemigos);
}
//Eliminar las relaciones con este arma
public void removeAllEnemigos(Collection enemigos){
for(Enemigo enemigo: enemigos){
enemigo.getArmas().remove(this);
}
this.enemigos.clear();
}
. . .
}
**También debemos incluir los métodos en la clase //Enemigo//.**
Otro aspecto a tener en cuenta en las relaciones ''@ManyToMany'' es el uso de colecciones tipo ''Set<>'', ya que favorecen el rendimiento respecto a colecciones de tipo ''List<>''. Hibernate necesita lanzar más consultas para las eliminaciones o inserciones usando una lista, frente a un conjunto.
Del mismo modo, si queremos crear el esquema de la base de datos a partir de las clases mapeadas, **solo usando colecciones tipo ''Set'' se crean las claves primarias de la tabla de relación.**
===== Proyectos de ejemplo =====
* [[https://bitbucket.org/fvaldeon/accesodatosud3/src| Repositorio de bitbucket]] con ejemplos y los proyectos realizados en clase (Fernando)
* [[https://bitbucket.org/sfaci/java-hibernate|Repositorio Bitbucket]] Repositorio con proyectos de ejemplo (Santi Faci)
===== Prácticas =====
* **Práctica 3.1** Desarrollar una aplicación que conecta con una Base de Datos Relacional utilizando una herramienta de mapeo objeto-relacional
----
(c) {{date> %Y}} Santiago Faci y Fernando Valdeón