Para estructurar una aplicación Java que gestione bases de datos con JDBC, es fundamental organizar el código siguiendo el patrón modelo vista controlador (MVC). Esto nos permite dividir la aplicación en tres capas bien definidas: la capa de presentación, que contendrá los formularios y la interfaz gráfica; la capa de modelo, que representará las estructuras de datos equivalentes a las tablas de la base de datos; y la capa de controladores, que gestionará la lógica necesaria para que la aplicación funcione correctamente.
En la capa de modelo, nuestro objetivo es crear clases Java que reflejen fielmente las tablas de la base de datos. Por ejemplo, si tenemos una tabla de alumnos, crearemos una clase Alumno con campos que coincidan con las columnas de esa tabla. Es importante que los tipos de datos en Java permitan representar correctamente los valores que pueden venir de la base de datos, incluyendo la posibilidad de valores nulos. Por eso, para campos como el identificador, es preferible usar objetos envolventes como Long en lugar de tipos primitivos long, ya que estos últimos no admiten valores nulos. Esto es útil porque, antes de guardar un objeto en la base de datos, no conocemos su identificador y queremos poder representarlo como null.
Así, la clase Alumno podría definirse con un identificador de tipo Long, un nombre, apellidos y una fecha de nacimiento, usando Date para esta última. Gracias a las herramientas que ofrecen los IDEs como NetBeans o Eclipse, podemos generar automáticamente constructores, getters, setters, y métodos como equals, hashCode y toString, lo que agiliza mucho el desarrollo y mantiene el código limpio.
package makigas.escuela.modelo;
import java.util.Date;
import java.util.Objects;
public class Alumno {
private Long id;
private String nombre;
private String apellidos;
private Date fechaNacimiento;
public Alumno() {
}
public Alumno(Long id, String nombre, String apellidos, Date fechaNacimiento) {
this.id = id;
this.nombre = nombre;
this.apellidos = apellidos;
this.fechaNacimiento = fechaNacimiento;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getNombre() {
return nombre;
}
public void setNombre(String nombre) {
this.nombre = nombre;
}
public String getApellidos() {
return apellidos;
}
public void setApellidos(String apellidos) {
this.apellidos = apellidos;
}
public Date getFechaNacimiento() {
return fechaNacimiento;
}
public void setFechaNacimiento(Date fechaNacimiento) {
this.fechaNacimiento = fechaNacimiento;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Alumno)) return false;
Alumno alumno = (Alumno) o;
return Objects.equals(id, alumno.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
@Override
public String toString() {
return "Alumno{" +
"id=" + id +
", nombre='" + nombre + '\'' +
", apellidos='" + apellidos + '\'' +
'}';
}
}
De forma similar, podemos crear la clase Profesor, que también tendrá un identificador de tipo Long, nombre y apellidos, pero sin fecha de nacimiento. La estructura y generación de métodos será análoga.
Cuando empezamos a modelar tablas que tienen relaciones con otras, como la tabla Asignatura que está relacionada con Profesor mediante una clave foránea, debemos decidir cómo representar esa relación en nuestro modelo. En frameworks ORM como JPA o Hibernate, podríamos tener un campo de tipo Profesor dentro de Asignatura, y el propio framework se encargaría de gestionar la relación y la carga de datos. Sin embargo, trabajando directamente con JDBC, debemos gestionar estas relaciones manualmente.
Para simplificar, podemos optar por almacenar en la clase Asignatura únicamente el identificador del profesor (Long idProfesor) en lugar de un objeto Profesor. Esto implica que la lógica de negocio será responsable de obtener el objeto Profesor correspondiente cuando sea necesario, a partir de ese identificador.
package makigas.escuela.modelo;
import java.util.Objects;
public class Asignatura {
private Long id;
private String nombre;
private Long idProfesor;
public Asignatura() {
}
public Asignatura(Long id, String nombre, Long idProfesor) {
this.id = id;
this.nombre = nombre;
this.idProfesor = idProfesor;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getNombre() {
return nombre;
}
public void setNombre(String nombre) {
this.nombre = nombre;
}
public Long getIdProfesor() {
return idProfesor;
}
public void setIdProfesor(Long idProfesor) {
this.idProfesor = idProfesor;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Asignatura)) return false;
Asignatura that = (Asignatura) o;
return Objects.equals(id, that.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
@Override
public String toString() {
return "Asignatura{" +
"id=" + id +
", nombre='" + nombre + '\'' +
", idProfesor=" + idProfesor +
'}';
}
}
Finalmente, la clase Matricula representa la inscripción de un alumno en una asignatura en un año determinado, con una posible nota. Aquí también tenemos relaciones con otras tablas: el alumno y la asignatura. En este caso, dado que tanto el alumno como la asignatura deben existir para que la matrícula tenga sentido, podemos usar tipos primitivos para sus identificadores, aunque también es válido usar objetos envolventes si queremos permitir valores nulos.
Además, la nota puede no existir, por lo que es recomendable usar un Integer en lugar de un tipo primitivo int para poder representar un valor nulo.
package makigas.escuela.modelo;
import java.util.Objects;
public class Matricula {
private long idAlumno;
private long idAsignatura;
private int anio;
private Integer nota; // Puede ser nulo
public Matricula() {
}
public Matricula(long idAlumno, long idAsignatura, int anio, Integer nota) {
this.idAlumno = idAlumno;
this.idAsignatura = idAsignatura;
this.anio = anio;
this.nota = nota;
}
public long getIdAlumno() {
return idAlumno;
}
public void setIdAlumno(long idAlumno) {
this.idAlumno = idAlumno;
}
public long getIdAsignatura() {
return idAsignatura;
}
public void setIdAsignatura(long idAsignatura) {
this.idAsignatura = idAsignatura;
}
public int getAnio() {
return anio;
}
public void setAnio(int anio) {
this.anio = anio;
}
public Integer getNota() {
return nota;
}
public void setNota(Integer nota) {
this.nota = nota;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Matricula)) return false;
Matricula that = (Matricula) o;
return idAlumno == that.idAlumno &&
idAsignatura == that.idAsignatura &&
anio == that.anio;
}
@Override
public int hashCode() {
return Objects.hash(idAlumno, idAsignatura, anio);
}
@Override
public String toString() {
return "Matricula{" +
"idAlumno=" + idAlumno +
", idAsignatura=" + idAsignatura +
", anio=" + anio +
", nota=" + nota +
'}';
}
}
Con estas clases modelo bien definidas, estamos preparados para avanzar hacia la implementación de los DAO (Data Access Objects), que serán los encargados de gestionar la conexión con la base de datos, ejecutar las consultas SQL y convertir los resultados en objetos de nuestro modelo, o viceversa. Este paso es fundamental para que la aplicación pueda interactuar con la base de datos de forma ordenada y mantenible.