martes, diciembre 19, 2006

 

Por favor, no más SQL, los ORM's se inventaron hace tiempo...

Ahora que ya capté tu atención, aclararemos que el título de este post no es cierto del todo :). El SQL todavía tiene su lugar, y es un lugar importante, pero estoy convencido de que el 90% del código que se escribe hoy en día debería emplear un ORM y no SQL "a pelo". Más aún, los ORMs decentes te permitirán bajarte al nivel del SQL en ese 10% de los casos.

Por supuesto, parto de la base de que ya hemos superado el pleistoceno y estamos programando en un lenguaje orientado a objetos (de nuevo, no todos los desarrollos del mundo deben usar la OOP, pero casi), usando herencia, encapsulación, definiendo interfaces, programando consumiendo dichas interfaces ,etc.

Bien, ¿cuántas veces hemos escrito la típica aplicación de gestión con Clientes, Pedidos, etc.? Seguro que muchos de nosotros todavía hacemos algo del tipo:

public class Cliente{
String nombre;
Calendar fechaNacimiento;
String telefono;
...
}

public class GestorCliente{

//bdd: simplificación de la API JDBC
public void nuevoCliente(Cliente c){
bdd.execute("INSERT INTO clientes VALUES(nombre,fechaNacimiento,telefono) ("+ Sql.escapa(c.nombre) +","+ c.fechaNacimiento +","+...);
}

public void modificaCliente(Cliente c){
bdd.execute("UPDATE clientes SET nombre="+ Sql.escapa(c.nombre) +","+ ...);
}

public Cliente obtenCliente(int id){
ResultSet rs = bdd.consultar("SELECT nombre,fechaNacimiento,telefono FROM clientes WHERE id="+ id);
if (!rs.next()) throw new RuntimeException("Cliente no encontrado");
Cliente c = new Cliente(rs.getString(1),rs.getDate(2),rs.getString(3));
return c;
}

public void eliminarCliente(int id){
bdd.execute("DELETE FROM clientes WHERE id="+ id);
}

}

Además tendremos una serie de scripts SQL para la creación de las tablas en la base de datos:

clientes.sql:

CREATE TABLE clientes{
id INTEGER NOT NULL PRIMARY KEY auto_increment,
nombre VARCHAR(255) NOT NULL,
telefono VARCHAR(100) NOT NULL,
fhnac DATETIME NOT NULL
}

¿Os dais cuenta de cuántas veces hemos tenido que escribir cada campo de la entidad "Cliente"? ¡Seis! ni más ni menos que seis veces. Esto no es duplicación de código, esto es "sextuplicación"! :)

Existe un concepto acuñado por Dave Thomas y Andy Hunt denominado DRY, es decir, "no te repitas". Cuando escribes algo más de una o dos veces piensa que posiblemente algo puede mejorarse en tu código.

Bien, con un ORM como Hibernate toda esta requeterepetición de identificadores se ve reducida a la mínima expresión:

@Entity
public class Cliente implements Serializable {
Long id;
String nombre;
Date fechaNacimiento;
String telefono;

@Id
public Long getId() { return id; }

public void setId(Long id) { this.id = id; }

//getters y setters del resto de propiedades
}

Ahora, para crear un cliente basta con hacer:

Cliente c = new Cliente("luis",new Date(),"600111222333");
session.save(c);

Para modificar un cliente:

session.update(c);

Para obtener un cliente por id:

c = session.get(Cliente.class, id);

Para eliminar un cliente:

session.delete(c);

¿Bastante mejor no? Incluso el propio Hibernate se puede encargar de crear el esquema en la base de datos, por lo que también nos libramos de los scripts SQL para la generación de las tablas.

Ahora bien, no nos engañemos. Hibernate es una solución compleja a un problema complejo, que es la unión del mundo relacional y el OOP. Como tal, su uso no es algo trivial. La gestión de las sesiones, asociaciones entre clases etc. requiere de un poco de práctica y de lectura de documentación con sosiego, pero os lo puedo asegurar, merece la pena superar esta pequeña curva de aprendizaje inicial. Hibernate no sólo hará que escribáis código de forma mucho más productiva y menos aburrida, sino que os proporcionará una visión más clara de lo que deben ser las transacciones y las conversaciones que la aplicación mantiene con la base de datos.

Ánimo y, ¡adios al aburrido SQL! (siii.... en el 90% de los casos).

Comments:
Hola Andrés:

Gracias por hablar de Hibernate. Creo que es una gran herramienta.

Por ahora solo soy estudiante (pronta a terminar mi carrera eso si :) ) y he usado Hibernate para una aplicación web sobre una BD MySql, usando a Tomcat como contenedor de servlets. Fui aprendiendo poco a poco a realizar todos los mapeos para los distintos tipos de asociaciones y relaciones, errando de vez en cuando en las sintaxis, pero al final logré desarrollar mi aplicación generando la BD de forma automática.

La única duda que me quedó fue con manejar la asociación many-to-many bidireccional usando dos one-to-many. Lei la documentación y no encontré un ejemplo de como hacer eso, y pensé "bueno, debe ser directo", y creé una clase para la tabla de asociación, para tratarla como una entidad mas del modelo.

Para graficarte mejor, digamos que tenia una entidad Alumno y una entidad Ramo. Estas tenian una asociación many-to-many, por lo cual agregué a la clase RamoAlumno para la tabla de asociación o entidad intermedia. Al separar esto en dos one-to-many tenia:

Alumno --- one-to-many --- RamoAlumno
Ramo --- one-to-many --- RamoAlumno

Quitando los parentesis de las etiquetas < cambiandolas por * (porque o si no, no puedo postear esto... )

En el mapeo de Alumno y Ramo declaré la colección de RamoAlumno, así:

*class name="encuestas.Ramo" table="RAMO"*
...
*set name="Ramo_alumno"
lazy="true"
inverse="true"
cascade="delete"*
*key>
*column name="alumnoID" />
*/key>
*one-to-many class="encuestas.RamoAlumno"/>
*/set>
*/class>
*/hibernate-mapping>

y en RamoAlumno tenía

*class name="encuestas.RamoAlumno" table="RAMO_ALUMNO">
*id name="id" type="long" column="ID">
*generator class="native"/>
*/id>
*property name="estado"
type="string"
not-null="true"
>
*column name="ESTADO"/>
*/property>
...
*many-to-one
name="alumno"
class="encuestas.Persona">
*column name="alumnoID" not-null="true" />
*/many-to-one>

*many-to-one
name="ramo"
class="encuestas.Ramo"
>
*column name="ramoID" not-null="true" />
*/many-to-one>
*/class>
*/hibernate-mapping>

Además de otras propiedades de la asociación.

Bueno, pasó que compile esto en el Ant, resultó bien, pero al hacer las asociaciones solo me resultaban las del lado de Alumno, y la asociación de Ramo se iba a nulo al parecer, pues no hacia nada, se me bloqueba todo.

Por ejemplo en el siguiente código:

Alumno al = (Alumno) sess.load(Alumno.class, id_alumno);
Ramo r = (Ramo) sess.load(Ramo.class, id_ramo);
RamoAlumno rp = new RamoAlumno();
rp.setEstado(estado);
rp.setAlumno(al);
rp.setRamo(r);
r.getRamoAlumno().add(rp); ---->crash!!
al.getRamoAlumno().add(rp);
sess.save(rp);

Bueno, al final comenté la linea que provocaba el colapso, pero obviamente tampoco podía manejar la asociación en el sentido de Ramo hacia RamoAlumno.

Al final encontré un código de ejemplo que usaba dos one-to-many, pero no le daba una clave propia a la clase de la entidad intermedia, si no que usaba clave compuesta (composite-id), de forma que las claves de Alumno y Ramo que eran FK, formaran una PK compuesta. Así me resultó bien el manejo bidireccional.

Supongo que si no encontré ningún ejemplo que hiciera lo que estaba haciendo yo en principio (con la clave pk independiente en la clase de la tabla de asociación) y usando dos one-to-many, es que eso estuvo mal siempre.

Pensé que no era necesario usar composite-id para manejar la asociación, pero al final solo así logré q funcionara.

Quisiera saber si es así como tu manejas estas relaciones también.

Bueno, un poco largo mi post. Gracias por leerlo.

Cordialmente me despido.

Paola.
 
No es necesario emplear un composite-id ni nada por el estilo.

Te recomendaría que probaras a poner

cascade="all"

en vez de

cascade="delete"


Si esto no resuelve el problema postea los POJOs e intentaré echarte un cable :)

Saludos
 
Hola Andrés:
He probado lo que me haz dicho y esto no ha resuelto el problema. Aqui te muestro los archivos:

Alumno es una subclase de Persona:
Su mapeo:
*hibernate-mapping>

*class name="encuestas.Persona"
table="PERSONA"
lazy="true"
abstract="true">

*id name="id"
type="long"
column="PERSONA_ID"
access="field">
*generator class="native"/>
*/id>
*property name="rut"
type="long"
not-null="true">
*column name="RUT"/>
*/property>
...
*joined-subclass name="encuestas.Alumno" table="ALUMNO">
*key column="ALUMNO_ID"/>

*property name="password"
type="string"
not-null="true">
*column name="PASSW"/>
*/property>
...
*set name="Ramo_alumno"
lazy="true"
inverse="true"
cascade="all" >
*key>
*column name="alumnoID" />
*/key>
*one-to-many class="encuestas.RamoAlumno"/>
*/set>
*/joined-subclass>
...
*/class>

*/hibernate-mapping>

Su clase:

public class Alumno extends Persona
implements Serializable,Comparable
{

private String password;
private int Agno_ingreso;
private String Estado;
private String Titulo;
private Set Ramo_alumno = new HashSet();

public Alumno() {super();}

public String getPassword() {
return password;
}
...
public Set getRamo_alumno(){
return Ramo_alumno;
}
public void setRamo_alumno(Set ramo_al){
this.Ramo_alumno = ramo_al;
}
public int compareTo(Object o) {
if (o instanceof Alumno) {
return this.getRut().compareTo( ((Alumno)o).getRut() );
}
return 0;
}
}


Para entidad Ramo:

*hibernate-mapping>
*class name="encuestas.Ramo" table="RAMO" lazy="true">

*id name="id"
type="long"
column="RAMO_ID"
unsaved-value="null"
access="field">
*generator class="native"/>
*/id>
...
*set name="ramos_alumno"
lazy="true"
inverse="true"
cascade="all" >
*key>
*column name="ramoID" />
*/key>
*one-to-many class="encuestas.RamoAlumno"/>
*/set>


*/class>

*/hibernate-mapping>
La clase para Ramo:

public class Ramo implements Serializable, Comparable{

private Long id;
private int version = 0;
private String nombre;
private int numero;
private int semestre_correspdte;
private int horas_catedra;
private int horas_lab;
private String tipo;

private Set ramos_alumno = new HashSet();
..
public Ramo() {};
public Long getId(){
return id;
}
...
public Set getRamos_alumno(){
return ramos_alumno;
}
public void setRamos_alumno(Set ramo_al){
this.ramos_alumno = ramo_al;
}
...
}


Para la tabla de asociacion, RamoAlumno:

*hibernate-mapping package="encuestas">
*class name="RamoAlumno" table="RAMO_ALUMNO" mutable="false">
*id name="id" type="long" column="ID">
*generator class="native"/>
*/id>
*property name="dateAdded"
column="ADDED_ON"
type="timestamp"
not-null="true"
access="field"/>

*property name="semestre"
type="string"
not-null="true"
update="false"
access="field">
*column name="SEMESTRE"/>
*/property>
*property name="agno"
type="int"
not-null="true"
update="false"
access="field">
*column name="AGNO"/>
*/property>
*property name="estado"
type="string"
not-null="true"
access="field">
*column name="ESTADO"/>
*/property>
*many-to-one
name="alumno"
class="encuestas.Persona">
*column name="alumnoID" not-null="true" />
*/many-to-one>
*many-to-one
name="ramo"
class="encuestas.Ramo"
>
*column name="ramoID" not-null="true" />
*/many-to-one>
*/class>
*/hibernate-mapping>

Su clase:

public class RamoAlumno implements Serializable/*, Comparable */ {

private Long id;

private Date dateAdded = new Date();
private String semestre;
private int agno;
private String estado;
private Ramo ramo;
private Alumno alumno;

public RamoAlumno() {};

public Long getId(){
return id;
}
public void setId(Long id){
this.id = id;
}

public String getSemestre(){
return semestre;
}
public void setSemestre(String sem){
this.semestre = sem;
}

public int getAgno(){
return agno;
}
public void setAgno(int year){
this.agno = year;
}

public String getEstado(){
return estado;
}
public void setEstado(String est){
this.estado = est;
}

public Alumno getAlumno() { return alumno; }
public void setAlumno(Alumno al){
this.alumno = al;
}
public Ramo getRamo() { return ramo; }
public void setRamo(Ramo ramo){
this.ramo = ramo;
}
public Date getDateAdded() { return dateAdded; }

public int compareTo(Object o) {
// CategorizedItems are sorted by date
if (o instanceof RamoAlumno)
return getDateAdded().compareTo( ((RamoAlumno)o).getDateAdded() );
return 0;
}

public String toString() {
return "Added by: '" + getAlumno() + "', " +
"On Date: '" + getDateAdded();
}

}


Tengo el siguiente código en un jsp:


String estado = request.getParameter("estado");
String sem = "II";
int agno = 2006;
...
Session sess = HibernateUtil.getSessionFactory().getCurrentSession();

sess.beginTransaction();
Alumno p = (Alumno) sess.load(Alumno.class, id_alumno);
p.getId();

Ramo r = (Ramo) sess.load(Ramo.class, id_ramo);
r.getNombre();

RamoAlumno rp = new RamoAlumno();
rp.setSemestre(sem);
rp.setAgno(agno);
rp.setEstado(estado);
rp.setAlumno(p);
rp.setRamo(r);

out.println("constructor ramo_alumno");
p.getRamo_alumno().add(rp);
out.println("asoc Alum");
r.getRamos_alumno().add(rp); --->crash!!
out.println("asoc ramo");
sess.save(rp);

out.println("salvado");
sess.getTransaction().commit();


He creado 2 alumnos y dos Ramos. Al primer Alumno le he asignado bien sus 2 ramos, es decir, se han guardado bien en la base de datos, en la tabla RAMO_ALUMNO, y la asociación con ramo no falla, sin embargo, al querer asignar los ramos al 2do alumno, la línea comentada con crash!!! falla. De ahi en adelante, no se ejecuta nada mas.

Por eso terminé usando composite-id, y no me dio estos resultados extraños.

Ojala puedas resolver esto, que para mi es un misterio.
Gracias
 
Dices que la línea "crash" falla. ¿Con qué excepción? Si usas un contenedor de servlets como el Tomcat o similar la excepción, si no la puedes ver directamente al acceder a la página, puede que esté en alguno de los logs del contenedor...

También, ¿has probado a utilizar

<property name="show_sql">true</property>


en la configuración de Hibernate? Esto te mostrará qué sentencias SQL se están ejecutando...

Saludos.

PD: para publicar codigo HTML/XML en el blog debes "escapar" el texto HTML. Puedes hacerlo de forma rapida y comoda con Quick Scape
 
phentermine us pharmacy no perscriptionmeridia pills wholesale
[url=http://www.bebo.com/buyxanaxonline1]buy xanax witho ut prescription[/url]
 
http://markonzo.edu http://www.rottentomatoes.com/vine/showthread.php?p=17358630 http://jguru.com/guru/viewbio.jsp?EID=1534482 http://www.rottentomatoes.com/vine/showthread.php?p=17358489 siegfried woven http://b4071308.typepad.com/ http://www.rottentomatoes.com/vine/showthread.php?p=17358512 orrmarie unpoetic http://www.freecodesource.com/user/profile-365274.html http://www.rottentomatoes.com/vine/showthread.php?p=17358539 spaying recasting
 
what drugs does depakote er interactgeneric zyban pharmacy online

[url=http://www.bebo.com/buylevitraonline1]buy levitra online from dreampharmaceuticals[/url]
 
Publicar un comentario en la entrada



<< Home

This page is powered by Blogger. Isn't yours?