Ayuda con metodo guardar generico

Hola gente soy nuevo en esto del mundo de java y quisiera pedirles ayuda con un metodo que estoy creando, les cuento un poco cual es la idea, es hacer un metodo guardar generico, que reciba un object y en el metodo convertirlo en la clase correspondiente y mandarlo a guardar a la persistencia k corresponda asi no multiples metodos guardar en mi fachada, solamente quedaria un metodo solo y este distribuiria la accion de guardar a las clases pertinentes, el problema lo tengo en el casting de object a cliente por ejemplo,
asi mi fachada solo tendria un metodo guardar generico y este delegaria esa responsabilidad a cada clase k le corresponda.
desde ya muchas gracias

Comentarios

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.
Imagen de ezamudio

respuestas

Ya hemos dado algunas respuestas a esa pregunta, en otro hilo.

Y que otro usuario tenga la misma duda en tan poco tiempo, me hace pensar que entonces es encargo escolar, no un proyecto real. Por lo tanto diría que mi respuesta es válida, aunque ahora podría aventarme la versión 2.0:

Primero que nada, se necesitan unas anotaciones. Una para la clase, y otra para los getters. Por el momento olvidémonos de la llave primaria, solamente manejemos el caso de que se puedan saltar algunas propiedades en el INSERT.

De modo que:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Entidad {
  String tabla();
}

Y la de los getters:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Columna {
  String nombre();
  int tipo();
  boolean incluir default true;
}

Entonces para tener una clase que se pueda guardar, la decoramos así:

@Entidad(nombre="tabla")
public class Ejemplo {
  private int clave;
  private String dato1;
  private Date dato2;
  private BigDecimal dato3;

  //Me salto los setters pero deben ir aquí

  //Supongamos que la clave sera auto-asignada por lo tanto no necesitamos mencionarla en el INSERT
  @Columna(incluir=false)
  public int getClave() {
    return clave;
  }
  @Columna(nombre="dato_char", tipo=java.sql.Types.VARCHAR)
  public String getDato1() {
    return dato1;
  }
  @Columna(nombre="dato_fecha", tipo=java.sql.Types.DATE)
  public Date getDato2() {
    return dato2;
  }
  @Columna(tipo=java.sql.Types.DECIMAL)
  public BigDecimal getDato3() {
    return dato3;
  }
}

Y por último, el método para insertar es algo así:

public void insertar(Connection conn, Object obj) throws SQLException {
  //Primero que nada, revisar si tiene la anotación entidad, si no, pues no hay nada que podamos hacer
  Entidad ent = obj.getClass().getAnnotation(Entidad.class);
  if (ent == null) {
    //dar error y adios
    return;
  }
  StringBuilder sb = new StringBuilder("INSERT INTO ");
  sb.append(ent.tabla()).append(" (");
  StringBuilder vals = new StringBuilder();
  ArrayList<Integer> tipos = new ArrayList<Integer>();
  ArrayList<Object> valores = new ArrayList<Object>();
  //Por ahora me voy a saltar TODAS las malditas excepciones de java.lang.reflect que son un montón
  for (Method m : obj.getClass().getMethods()) {
    Columna c = m.getAnnotation(Columna.class);
    if (c != null && c.incluir()) {
      /* construir el nombre de la columna quitando el "get" del metodo si es que no trae nombre */
      String colName = c.nombre() == null ? getName().substring(4) : c.nombre();
      vals.append("?,");
      sb.append(colName).append(',');
      tipos.add(c.tipo());
      valores.append(m.invoke(obj));
    }
  }
  sb.deleteCharAt(sb.length()-1);
  vals.deleteCharAt(sb.length()-1);
  sb.append(") VALUES (");
  sb.append(vals).append(')');
  //Preparamos el statement con todos los parámetros que ya obtuvimos
  PreparedStatement ps = conn.prepareStatement(sb.toString());
  for (int i = 0; i < tipos.length(); i++) {
    if (tipos.get(i).intValue() == 0) {
      ps.setObject(i+1, valores.get(i));
    } else {
      ps.setObject(i+1, valores.get(i), tipos.get(i).intValue());
    }
  }
  ps.executeUpdate();
}

Puedo asegurar que hay algunos bugs en este código, los cuales no me voy a tomar la molestia de buscar. Y falta cachar no sé cuántas excepciones de reflexión. Pero la idea es que ese método puede generar sentencias INSERT para instancias de clases decoradas con las anotaciones definidas para ello.

Imagen de neko069

Y bueno, por decir algo, no

Y bueno, por decir algo, no sería igual de válido que en lugar de una anotación, extraiga las propiedades de los métodos getXXX() directamente, sin hacer la comparación de nombre de la columna??

@neko, Si, pero eso solo

@neko Si, pero eso solo serviría para el caso en que la columna y el atributo se llamen exactamente igual que... para este caso podría servir.

Por cierto en Abril uno de los ingenieros de StackOverflow ( o un equipo no sé ) sustituyó LINQ en el engine de SO por so propio ORM. En StackOverflow tienen muy fuertemente enraizado el síndrome de "no inventado aquí"

Ahi les dejo el link, aunque es de C# esta muy interesante:

http://samsaffron.com/archive/2011/03/30/How+I+learned+to+stop+worrying+...

Moraleja, solo haz tu propio ORM si sabes exactamente lo que estas haciendo.

Imagen de ezamudio

Metadatos

La anotación te da ciertas ventajas sobre el uso puramente del nombre de los métodos:

  1. Puedes tener getters que no quieres usar en el INSERT
  2. Puedes ponerle el tipo SQL a la columna en la anotación
  3. Puedes tener un nombre distinto en la propiedad Java y en la columna (esto es útil porque a veces tienes propiedades que en Java son válidas pero en alguna base de datos son palabras reservadas)

El punto 1 no lo ilustré pero si no lo usara, encontraría el método getClass() por reflexión y no me interesa ese método.

El punto 2 lo ilustro poniéndole tipos a algunas columnas, y el punto 3 lo ilustro poniéndole nombre distinto a las propiedades dato1 y dato2.

Todo esto es solamente una prueba de concepto. Si ves JPA, la anotación @Column es bastante más completa, porque le puedes indicar el nombre de la columna en base de datos, el tipo, la longitud (por ejemplo para los VARCHAR indicar que no pase de 40 caracteres, etc).

O yaaaa deplano sin ninguna

O yaaaa deplano sin ninguna magia pero muy eficiente algo como esto:

    SQLExecutor sqlExecutor = new SQLExecutor();
    Persona persona = new Persona("Juan", 25 );
    sqlExecutor.update(
        "insert into t_persona ( f_nombre_varchar, f_edad_number ) values( ? , ? )",
        persona.getNombre(),
        persona.getEdad()
    );

Donde el sqlExecutor hace el manejo de los recursos y se encarga de obtener la conexión y liberarla.

  public void update( String statement, Object ... args ) {
    Connection c;
    PreparedStatement pstmt;
    try {
      c = null;//getConection();
      pstmt = c.prepareStatement( statement );
      for( Object o : args ) {
         pstmt.setNull( i++, o );
      }
      pstmt.executeUpate();
    } catch( SQLException e ) {
      throws new RuntimeException( e );
    } finally {
      c.close();
      pstmt.close();
  }
Imagen de ezamudio

Limitaciones

Cada propuesta tiene sus limitantes. Las broncas en la práctica vienen cuando resulta que tienes un dato tipo Date que realmente quieres guardar en un campo de base de datos tipo TIME o tipo DATE (pura fecha, sin hora). Sin indicar el tipo de columna pues a ver qué sale... indicándolo ya puedes indicar si quieres TIME, DATE, o TIMESTAMP. Igual con cadenas, puedes indicar si quieres CHAR o VARCHAR o TEXT. Y lo mismo con los enteros (long, int, BigInteger, BigDecimal, float, double en Java) que van a TINYINT, INTEGER, NUMERIC, DECIMAL, BIGINT, etc.

En la propuesta de Oscar se puede hacer alguna inferencia de tipo según el objeto en Java, pero pues es agregarle un montón de if's, y de paso cambiar el pstmt.setNull a pstmt.setObject para que el método update haga algo que no sea insertar puros nulos...

Por cierto Groovy tiene este

Por cierto Groovy tiene este modulo llamado Groovy SQL ( bueno no sé si e le llama módulo, biblioteca o que ) pero puedes hacer cosas como las que dije usando una biblioteca que ya no es de juguete.

El intercambio va entre más flexibilidad/dinamismo menor desempeño.

Aquí algunos ejemplos tomados del articulo: Practically Groovy: JDBC programming with Groovy

def val = sql.execute("select * from word where word_id = ?", [5])

O

def nid = 5
def newSpelling = "Dastardly"
sql.executeUpdate("update word set spelling = ? where word_id = ?", [newSpelling, nid])
Imagen de ezamudio

Clase

Pues es una simple clase, pero bastante completa... de hecho en vez de usar los "?" puedes meter las variables ahí mismo como en cualquier GString y la sustitución de parámetros se hace posteriormente (no se va a crear la cadena vulnerable a inyecciones de SQL). Y puedes usar closures con varios métodos.

String valor1 = 'una cadena normal'
BigDecimal valor2 = 3.141592
Date valor3 = new Date()
sql.execute("INSERT INTO tabla (campo1, campo2, campo3) values (${valor1}, ${valor2}, ${new java.sql.Date(valor3.time)})")
sql.eachRow("SELECT * FROM tabla WHERE campo1=${valor1}") { row->
  if (row.campo2 > 3.14) {
    println "El closure se ejecuta con cada renglón, pero esto se imprime sólo si ${row.campo2} > 3.14"
  }
}

y de paso cambiar el


y de paso cambiar el pstmt.setNull a pstmt.setObject para que el método update haga algo que no sea insertar puros nulos...

Jajaj o para empezar declarar todas las variables para que al menos compile:

pstmt.setNull( i++, o );
Imagen de neko069

Ah va, ya entendí lo que

Ah va, ya entendí lo que dices, y sí, tienes razón, con las anotaciones de JPA puedes hacer un buen de cosas, incluso si todos los getters en tu bean no tienen nada que ver con las propiedades de alguna tabla que elijas ( bueno, suponiendo un caso extremo ) ... lo de Groovy ... bueno, ps ahí si no se que onda, no le he metido mano a Groovy, pero no se ve taaan complejo, así a primera vista, entendí lo que ilustran.... y la moraleja está bien pero...naaaaa, mejor invierto tiempo en configurar algo existente, que aventarme a hacerlo, a menos que me lo pidieran como tarea...