Mapeo objeto-relacional (relaciones)
En el artículo anterior hablé sobre cómo mapear una simple clase. Bien, ahora vamos a tratar el tema de las relaciones.
Voy a crear una aplicación que tenga que gestionar tres entidades: Alumno, Profesor y Asignatura. Voy a crear una relación entre Alumno y Asignatura: un alumno está matriculado en varias asignaturas, en una asignatura habrá varios alumnos matriculados. Esto es una relación "muchos a muchos" o "N a M". También voy a definir una relación entre Asignatura y Profesor: cada asignatura tiene un profesor titular y cada profesor puede impartir varias asignaturas. Esta última es una relaci?n "1 a N".
En mi modelo de datos las relaciones van a ser bidireccionales. Es decir, desde un objeto Asignatura tendrá un método para acceder a su profesor titular, e inversamente un profesor titular tendrá un método de acceso para obtener las asignaturas en las que es profesor titular.
Cuando un objeto tenga una relación a "muchos" la voy a implementar con un Set. Es decir, con una colección que no puede contener duplicados. He aquí las definiciones de las clases.
Clase Alumno
import java.io.Serializable;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
public class Alumno implements Serializable {
private int nif;
private String nombre;
private String apellidos;
private Date fechaNacimiento;
private Set asignaturasEnLasQueEstaMatriculado = new HashSet();
public Set getAsignaturasEnLasQueEstaMatriculado() {
return Collections.unmodifiableSet(asignaturasEnLasQueEstaMatriculado);
}
public void addAsignatura(Asignatura asignatura) {
asignaturasEnLasQueEstaMatriculado.add(asignatura);
if(!asignatura.getAlumnosMatriculados().contains(this))
asignatura.addAlumno(this);
}
public void removeAsignatura(Asignatura asignatura) {
asignaturasEnLasQueEstaMatriculado.remove(asignatura);
if(asignatura.getAlumnosMatriculados().contains(this))
asignatura.removeAlumno(this);
}
public String getApellidos() {
return apellidos;
}
public void setApellidos(String apellidos) {
this.apellidos = apellidos;
}
/* resto de m?todos get y set */
}
Clase Profesor
import java.io.Serializable;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
public class Profesor implements Serializable {
private int nif;
private String nombre;
private String apellidos;
private Date fechaNacimiento;
private Set asignaturasQueImparte = new HashSet();
public Set getAsignaturasQueImparte() {
return Collections.unmodifiableSet(asignaturasQueImparte);
}
public void addAsignatura(Asignatura asignatura) {
asignatura.setProfesorTitular(this);
if(!asignaturasQueImparte.contains(asignatura))
asignaturasQueImparte.add(asignatura);
}
public void removeAsignatura(Asignatura asignatura) {
asignatura.setProfesorTitular(null);
if(asignaturasQueImparte.contains(asignatura))
asignaturasQueImparte.remove(asignatura);
}
public String getApellidos() {
return apellidos;
}
public void setApellidos(String apellidos) {
this.apellidos = apellidos;
}
/* resto de m?todos get y set */
}
Clase Asignatura
import java.io.Serializable;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class Asignatura implements Serializable {
private String nombre;
private int curso;
private int creditos;
private Profesor profesorTitular;
private Set alumnosMatriculados = new HashSet();
public Set getAlumnosMatriculados() {
return Collections.unmodifiableSet(alumnosMatriculados);
}
public void addAlumno(Alumno alumno) {
alumnosMatriculados.add(alumno);
if(!alumno.getAsignaturasEnLasQueEstaMatriculado().contains(this))
alumno.addAsignatura(this);
}
public void removeAlumno(Alumno alumno) {
alumnosMatriculados.remove(alumno);
if(alumno.getAsignaturasEnLasQueEstaMatriculado().contains(this))
alumno.removeAsignatura(this);
}
public Profesor getProfesorTitular() {
return profesorTitular;
}
public void setProfesorTitular(Profesor profesorTitular) {
if(this.profesorTitular != null)
this.profesorTitular.removeAsignatura(this);
this.profesorTitular = profesorTitular;
if(this.profesorTitular != null)
profesorTitular.addAsignatura(this);
}
/* resto de m?todos get y set */
}
Para mantener la integridad de los datos cuando cambio, por ejemplo, el profesor titular de una asignatura tengo que actualzar la lista de asignaturas del profesor anterior y también actualizar la lista del nuevo profesor.
Para evitar que los Set se modifiquen fuera del objeto los métodos public Set<...> getXXX() devuelven un Set inmodificable. Para a?adir o eliminar objetos a estos Set he implementado métodos removeXXX() y addXXX().
Ahora tengo que crear las tablas correspondientes. Siguiendo la aproximación del artículo anterior voy a crear una tabla por cada clase.
CREATE TABLE alumnos (nif INTEGER NOT NULL,
nombre VARCHAR(50),
apellidos VARCHAR(50),
fecha_nacimiento DATE,
PRIMARY KEY(nif));
CREATE TABLE profesores (nif INTEGER NOT NULL,
nombre VARCHAR(50),
apellidos VARCHAR(50),
fecha_nacimiento DATE,
PRIMARY KEY(nif));
CREATE TABLE asignaturas (id_asignatura INTEGER NOT NULL AUTO_INCREMENT,
nombre VARCHAR(50),
curso INTEGER,
creditos INTEGER,
PRIMARY KEY(id_asignatura));
Debido a que en el objeto Asignatura todos los datos son susceptibles de ser modificados, he creado una columna adicional en la tabla "id_asignatura" que contendrá la clave primaria. El modificador "auto_increment" es específico de MySQL y sirve para indicar que el valor de la columna lo deberá generar la base de datos basándose en una progresión incremental. El valor de la clave primaria lo necesitará usar en la aplicación, así que me creo un campo más en la clase Asignatura y sus correspondientes métodos get y set.
private int idAsignatura;
Los objetos de tipo Asignatura guardan una referencia "profesorTitular" que apunta a un objeto de tipo "Profesor". Como se habló en el artículo anterior los punteros o referencias sólo "viven" durante la ejecución del programa, por lo que las bases de datos usan otro mecanismos. Para referirnos a un objeto de forma unívoca a nivel de la base de datos se usan claves primarias. Por ello vamos a incluir una columna más en la tabla "asignaturas" para que guarde la clave primaria del objeto que sea su profesor titular. Voy a cambiar la sentencia SQL para crear la tabla:
CREATE TABLE asignaturas (id_asignatura INTEGER NOT NULL AUTO_INCREMENT,
nombre VARCHAR(50),
curso INTEGER,
creditos INTEGER,
profesor_titular INTEGER,
PRIMARY KEY(id_asignatura));
De este modo ya tenemos la información suficiente para averiguar los datos del profesor titular de una asignatura. Sólo tendríamos que realizar esta consulta:
SELECT * FROM profesores WHERE nif IN (SELECT profesor_titular FROM asignaturas WHERE id_asignatura = ?)
Con esa sentencia sustituyendo "?" por la clave primaria de una asignatura obtendremos todos los datos del profesor titular. Para obtener las asignaturas en las que un profesor es profesor titular usaremos la sentencia:
SELECT * FROM asignaturas WHERE profesor_titular = ?;
Llegados a este punto ya hemos mapeado una relación "1 a N" o "uno a muchos". Sin embargo, nada nos impide insertar una asignatura cuya columna "profesor_titular" contenga un valor que no exista en la tabla "profesores"; en ese caso la asignatura estará "apuntando" a un registro que no existe. Para evitar esto las bases de datos permiten definir restricciones de "integridad referencial". Para evitar que una asignatura contenga una clave primaria de un profesor que no existe añadimos la restricción de este modo:
ALTER TABLE asignaturas ADD FOREIGN KEY (profesor_titular) REFERENCES profesores (nif);
Con esta restricción se indica que el campo "profesor_titular" de la tabla "asignaturas" debe tener un valor que exista en la tabla "profesores" y en la columna "nif". Si se intenta insertar una asignatura indicando un "profesor_titular" que no existe en la tabla "profesores" se producirá un error. Con esto, nos aseguramos la "integridad referencial" de la base de datos.
MySQL sólo soporta integridad referencial en las tablas de tipo InnoDB. Por lo tanto si añadimos restricciones de claves foráneas en una tabla que no sea InnoDB, MySQL lo ignorará. Para indicar que una tabla sea InnoDB debemos añadir "TYPE = INNODB" al final de la sentencia de creación de la tabla. Ejemplo:
CREATE TABLE asignaturas (id_asignatura INTEGER NOT NULL AUTO_INCREMENT,
nombre VARCHAR(50),
curso INTEGER,
creditos INTEGER,
profesor_titular INTEGER,
PRIMARY KEY(id_asignatura)) TYPO=INNODB;
Ahora tenemos que mapear la relación "muchos a muchos" entre Alumno y Asignatura. Recordemos que "un alumno puede estar matriculado en varias asignaturas" y "una asignatura puede tener varios alumnos matriculados". Para mapear esta relación "muchos a muchos" necesitamos crear una tabla adicional que simplemente contenga dos columnas: una para la clave primaria de una asignatura y otra para la clave primaria de un alumno. Tendremos así una tabla de pares "asignatura"<->"alumno matriculado". Esta es la sentencia de creación de la tabla.
CREATE TABLE matriculas (id_asignatura INTEGER NOT NULL,
nif INTEGER NOT NULL,
PRIMARY KEY(id_asignatura, nif));
En este caso la clave primaria es la conjunción de las dos columnas. De este modo evitamos que un alumno está matriculado dos veces en la misma asignatura ya que la combinación es única por ser clave primaria.
Si queremos obtener la lista de asignaturas en las que un alumno está matriculado relizaremos la siguiente consulta:
SELECT * FROM asignaturas WHERE id_asignatura IN (SELECT id_asignatura FROM matriculas WHERE nif=?)
Para obtener la lista de alumnos matriculados en una asignatura realizaremos la siguiente consulta:
SELECT * FROM alumnos WHERE nif IN (SELECT nif FROM matriculas WHERE id_asignatura=?)
Antes de nada también tenemos que asegurar la integridad referencial en esta tabla. Para evitar que un alumno está matriculado en una asignatura que no existe y viceversa. De modo que añado restricciones a las dos columnas:
ALTER TABLE matriculas ADD FOREIGN KEY (id_asignatura) REFERENCES asignaturas (id_asignatura); ALTER TABLE matriculas ADD FOREIGN KEY (nif) REFERENCES alumnos (nif);
Y esto es todo por hoy. Siguen siendo cosas que todos sabemos, que aprendemos "desde niños" :D. Pero lo bueno vendrá en los siguientes posts. La elección de las clases Profesor y Alumno no ha sido casual. Son clases muy similares, de modo que lo más correcto para ahorrar líneas de código hubiera sido crear una clase madre com?n, probablemente abstracta, llamada Persona y que contuviese los campos comunes (dni, nombre, apellidos, fechaNacimiento). Esto lo explicaré en el siguiente post en el que tratará el mapeo de jerarqu?as de herencia. Esto ya es m?s interesante verdad? También, como he usado java.util.Set explicaré cómo implementar correctamente los métodos hashCode() y equals().
¿Alguna corrección/sugerencia/apunte/crítica/discrepancia?
Un saludete!
Hola!
Encuentro muy interesante tu blog, lo acabo de descubrir, y creo que me sera de mucha ayuda.
He probado tu ejemplo escribiendo un programa que borre alumnosMatriculados de una asignatura. He obtenido un iterator sobre el Set unmodifiable de alumnosMatriculados y he hecho un bucle con un while (iterator.hasNext()). Dentro del bucle voy borrando alumnos segun un filtro utilizando el metodo de removeAlumno y en la segunda vuelta del bucle, me salta una ConcurrentModificationException. Y mi pregunta es, al obtener un Iterator de un Set "unmodifiable", no se suponia que al iterator le daba igual que se modificara la coleccion de por debajo?.
Muchas gracias!
Enviado por JoseV en octubre 17, 2005 a las 11:17 AM GMT+01:00 #
ñññ
Enviado por 192.188.51.158 en junio 07, 2006 a las 03:31 PM GMT+01:00 #
en la likuadora debes meter primero a el azukar y despues al agua para evitar contraciones y agitarlo con un chino quintanaroense para ke tenga mejor consistencia y deliciosura y ¡sabooooooooooor! asi tendras un buen pastel de SW japones...
PD. besos a: maria jesus,lorena,zury,zunne,betty(la llorona), andrea y erika.
Enviado por batman nachu cawix uk en octubre 22, 2006 a las 07:41 PM GMT+01:00 #
Excelente!
Enviado por 200.82.129.2 en diciembre 19, 2006 a las 03:44 PM GMT+01:00 #
MUY BUENA EXPLICACION
Enviado por 200.75.42.178 en abril 16, 2007 a las 10:30 PM GMT+01:00 #
me has salvado, me gustaria tener contacto contigo por correo, pues estudio la carrera de ingenieria en sistemas y pues lo estoy viendo muy dificil, y hay cosas que no se y espero puedas ayudarme, por lo pronto gracias por la inforacion que me ha servido al menos para pasar una evaluacion
Enviado por Daniel en octubre 26, 2008 a las 08:36 PM GMT+01:00 #