En datanucleus existe un mecanismo llamado attach/detach que sirve para acceder a las propiedades de los objetos fuera del contexto de una transacción. Esto es muy útil, por ejemplo, cuando estructuras tu aplicación en librerías o capas.

Supongamos que tenemos la siguiente clase

    @PersistenceCapable
    public class Audiencia {</p><pre><code>    @PrimaryKey
    @Persistent
    Integer id;
    @Persistent
    Juez juez;
    @Persistent
    Date inicio;
    @Persistent
    Date fin;
    @Persistent
    TipoAudiencia tipo;

    public Audiencia(){
    }

    public static List&lt;Audiencia&gt; getAudiencias(Integer idJuez) {
        List&lt;Audiencia&gt; audiencias;
        PersistenceManager pm = PMF.get().getPersistenceManager();
        Transaction tx = pm.currentTransaction();
        try{
            tx.begin();
            String filter = "juez.id == :idJuez";
            Query query = pm.newQuery(Audiencia.class, filter);
            audiencias = (List&lt;Audiencia&gt;) pm.detachCopyAll((List&lt;Audiencia&gt;)query.executeWithArray(idJuez));
            tx.commit();
        }finally{
            if(tx.isActive()){
                tx.rollback();
            }
            pm.close();
        }
        return audiencias;
    }

}
</code></pre><p>

cabe destacar que dentro del contexto de la transacción podríamos leer las propiedades Juez y TipoAudiencia, datanucleus cargaría los objetos relacionados de forma “lazy loading”. Pero si trataramos de leer alguna propiedad fuera de este contexto -es el caso cuando ejecutamos el método getAudiencias desde otra clase- está tendría el valor null. Datanucleus sólo hace el detach de las propiedades en el Default Fetch Group. Para poder utilizar las demás propiedades, podemos especificar un Fetch Group dinámico de la siguiente manera

    public static List getAudiencias(Integer idJuez, String[] fields) {
        List audiencias;
        PersistenceManager pm = PMF.get().getPersistenceManager();
        Transaction tx = pm.currentTransaction();
        try{
            tx.begin();
            FetchGroup fetchGroup = PMF.get().getFetchGroup(Audiencia.class, "fields");
            fetchGroup.addMembers(fields);
            pm.getFetchPlan().addGroup("fields");
            String filter = "juez.id == :idJuez";
            Query query = pm.newQuery(Audiencia.class, filter);
            audiencias = (List) pm.detachCopyAll((List)query.executeWithArray(idJuez));
            tx.commit();
        }finally{
            if(tx.isActive()){
                tx.rollback();
            }
            pm.close();
        }
        return audiencias;
    }

Fuentes:
http://www.datanucleus.org/products/accessplatform/jdo/fetchgroup.html
http://www.datanucleus.org/products/datanucleus/jdo/attachdetach.html
http://www.datanucleus.org/products/datanucleus/jdo/object
lifecycle.html

Un tipo de propiedad que encontramos comunmente en nuestros objetos es el tipo Boolean. Puede ser para describir si un registro esta activo o no, o para marcar una tarea como realizada, etc. En una base de datos no es común encontrar este tipo de dato para una columna. Normalmente lo representamos mediante un Number(‘0’ o ‘1’) o un Char (‘S’ o ‘N’). En nuestro caso utilizamos el Char.

Al utilizar Datanucleus con JDO para mapear la siguiente clase

@PersistenceCapable(table="TC_EMPLEADOS")
public class Empleado {
@PrimaryKey
@Persistent(column="NID")
Integer id;
@Persistent(column="SNOMBRE")
String nombre;
@Persistent(column="BACTIVO")
Boolean activo;
public Empleado() {
}

}

me arrojaba un error parecido al siguiente:

NucleusDatastoreException: Fail to convert to internal representation

El problema es que, por default, Datanucleus trata de mapear la propiedad “activo” a un valor númerico (‘0’ o ‘1’). Para mapear la propiedad a una cadena podemos hacer lo siguiente:

@PersistenceCapable(table="TC_EMPLEADOS")
public class Empleado {

@PrimaryKey
@Persistent(column="NID")
Integer id;

@Persistent(column="SNOMBRE")
String nombre;

@Persistent(column="BACTIVO")
String activo;

public Empleado() {
}

public Boolean getActiv0() {
   return "S".equals(activo);
}

public void setActivo(Boolean activo) {
   this.activo = activo?"S":"N";
}

}

Notesé que utilizamos el tipo String para la propiedad y Boolean para los métodos de acceso a la propiedad.

Fuentes:

http://turbomanage.wordpress.com/2009/12/04/persisting-enums-with-appengine-datastore/

En una aplicación con múltiples módulos, es probable que diferentes módulos accedan a distintas bases de datos o esquemas y con distintas credenciales.

Para configurar multiples conexiones en Datanucleus hay que crear un archivo jdoconfig.xml en la carpeta META-INF del classpath (WEB-INF/classes/META-INF en una aplicación web). Si utilizas Maven crea el archivo en la carpeta src/main/resources/META-INF y Maven copiara el archivo y la carpeta al classpath.


<?xml version="1.0" encoding="UTF-8"?>

<jdoconfig xmlns="http://java.sun.com/xml/ns/jdo/jdoconfig"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="http://java.sun.com/xml/ns/jdo/jdoconfig">

<persistence-manager-factory name="compras">

<property name="javax.jdo.PersistenceManagerFactoryClass"

value="org.datanucleus.api.jdo.JDOPersistenceManagerFactory"/>

<property name="javax.jdo.option.ConnectionDriverName" value="oracle.jdbc.OracleDriver"/>

<property name="javax.jdo.option.ConnectionURL" value="jdbc:oracle:thin:@132.147.0.1:1521:dbcompras"/>

<property name="javax.jdo.option.ConnectionUserName" value="usrcompras"/>

<property name="javax.jdo.option.ConnectionPassword" value="secret"/>

<property name="javax.jdo.option.DetachAllOnCommit" value="true"/>

</persistence-manager-factory>

<persistence-manager-factory name="recursoshumanos">

<property name="javax.jdo.PersistenceManagerFactoryClass"

value="org.datanucleus.api.jdo.JDOPersistenceManagerFactory"/>

<property name="javax.jdo.option.ConnectionDriverName" value="oracle.jdbc.OracleDriver"/>

<property name="javax.jdo.option.ConnectionURL" value="jdbc:oracle:thin:@192.168.0.250:1521:dbrh"/>

<property name="javax.jdo.option.ConnectionUserName" value="usrrh"/>

<property name="javax.jdo.option.ConnectionPassword" value="topsecret"/>

<property name="javax.jdo.option.DetachAllOnCommit" value="true"/>

</persistence-manager-factory>

</jdoconfig>

Para obtener una instancia PersistenceManagerFactory podríamos tener una clase PMF en cada módulo (suponiendo que son proyectos distintos) como la siguiente. Cambiando solamente el nombre de acuerdo al PersistenceManagerFactory que vamos a utilizar. También podrías recibir como parámetro el nombre del PersistenceManagerFactory y almacenarlo en un Map para usos posteriores, esto si los módulos se encuentran en el mismo proyecto.


public class PMF {

private static final PersistenceManagerFactory instance=JDOHelper.getPersistenceManagerFactory("compras");

private PMF() {

}

public static PersistenceManagerFactory get(){

return instance;

}

}

Y lo usarías de la siguiente forma


public static List<Compra> getCompras(){

List<Compra> compras;

PersistenceManager pm = PMF.get().getPersistenceManager();

Transaction tx = pm.currentTransaction();

try{

tx.begin();

Query query = pm.newQuery(Compra.class);

jueces=(List<Compra>) query.execute();

tx.commit();

}finally{

if(tx.isActive()){

tx.rollback();

}

pm.close();

}

return compras;

}

Fuentes:

http://www.datanucleus.org/products/datanucleus/jdo/pmf_named.html
http://stackoverflow.com/questions/1297473/maven-including-a-meta-inf-folder-in-the-classes-folder

Hace poco escribí sobre como buscar/filtrar recursos en el servidor, para ello utilizamos un objeto Criteria -del mismo tipo que el recurso- para crear una consulta JDOQL que nos permitiera obtener los resultados.

En este artículo mostraré una forma de utilizar la clase AdvancedCriteria de SmartGwt para realizar una consulta más compleja. Al igual que la vez anterior, vamos a utilizar el framework Apache CXF para nuestros servicios REST, y Jackson para la serialización y deserialización de los objetos en formato JSON. También utilizamos Datanucleus para la persistencia de datos.

Me base en este artículo que explica cómo integrar SmartGwt con ASP.Net.

Lo primero que vamos a necesitar es una clase AdvancedCriteria del lado del servidor


public class AdvancedCriteria {

static ObjectMapper om;

{

om=new ObjectMapper();

om.configure(DeserializationConfig.Feature.FAIL_ON_UNKNOWN_PROPERTIES, false);

}

public static class JDOQLData{

public String filter="";

public Map<String,Object> params=new HashMap<String, Object>();

public Class<?> type;

&nbsp;

public JDOQLData(Class<?> type) {

this.type=type;

}

}

&nbsp;

String _constructor;

Integer _startRow;

Integer _endRow;

String fieldName;

String operator;

Object value;

AdvancedCriteria[] criteria;

&nbsp;

public AdvancedCriteria() {

}

&nbsp;

public static AdvancedCriteria fromString(String value){

try {

return om.readValue(value, AdvancedCriteria.class);

} catch (Exception e) {

throw new RuntimeException(e);

}

}

&nbsp;

// Getter's y Setter's ...

&nbsp;

public void toJDOQL(JDOQLData data){

if(this.criteria==null){

if(this.operator.equals(OperatorId.EQUALS.getValue())){

data.filter+=this.fieldName+" == ";

}else if(this.operator.equals(OperatorId.NOT_EQUAL.getValue())){

data.filter+=this.fieldName+" != ";

}else if(this.operator.equals(OperatorId.LESS_THAN.getValue())){

data.filter+=this.fieldName+" < ";

}else if(this.operator.equals(OperatorId.GREATER_THAN.getValue())){

data.filter+=this.fieldName+" > ";

}else if(this.operator.equals(OperatorId.LESS_OR_EQUAL.getValue())){

data.filter+=this.fieldName+" <= ";

}else if(this.operator.equals(OperatorId.GREATER_OR_EQUAL.getValue())){

data.filter+=this.fieldName+" >= ";

}

data.params.put(String.valueOf(data.params.size()+1), fixType(getPropertyType(data.type, this.fieldName), this.value) );

data.filter+=":"+data.params.size();

}else{

data.filter+="(";

for(int index=0;index<this.criteria.length;index++){

this.criteria[index].toJDOQL(data);

if(index+1<this.criteria.length){

if(this.operator.equals(OperatorId.AND.getValue())){

data.filter+=" && ";

}else if(this.operator.equals(OperatorId.OR.getValue())){

data.filter+=" || ";

}else if(this.operator.equals(OperatorId.NOT.getValue())){

data.filter+=" ! ";

}

}

}

data.filter+=")";

}

}

&nbsp;

private Object fixType(Class<?> type, Object value) {

return om.convertValue(value, type);

}

&nbsp;

private Class<?> getPropertyType(Class<?> clazz,String property){

try{

LinkedList<String> properties=new LinkedList<String>();

properties.addAll(Arrays.asList(property.split("\\.")));

Field field = null;

while(!properties.isEmpty()){

field = FieldUtils.getField(clazz,properties.removeFirst(),true);

clazz=field.getType();

}

return field.getType();

}catch(Exception e){

throw new RuntimeException(e);

}

}

}

Lo interesante de esta clase es que tiene un método estático fromString(String) que será utilizado por Apache CXF para crear un objeto de esta clase a partir de una cadena. También tiene un método recursivo toJDOQL(JDOQLData) que nos regresa un filtro y el listado de parámetros en un objeto JDOQLData para poder realizar la consulta en datanucleus. El método fixType y getPropertyType son para utilizar el tipo correcto -de acuerdo a la clase- en nuestro listado de parámetros.

Nuestro servicio REST quedaría de la siguiente forma


@GET

public String retrieve(@QueryParam("")Libro criteria,@QueryParam("")AdvancedCriteria advancedCriteria){

try {

List<Libro> libros = null;

if(advancedCriteria.get_constructor()!=null){

libros = Libro.getByAdvancedCriteria(advancedCriteria);

}else{

libros = Libro.getByCriteria(criteria);

}

Map<String, Object> response = new HashMap<String, Object>();

Map<String, Object> body = new HashMap<String, Object>();

body.put("status", 0);

body.put("data", libros);

response.put("response", body);

return json.writeValueAsString(response);

} catch (Exception e) {

log.error("", e);

}

return "{\"response\":{\"status\":-1, \"data\":\"Hubo un problema al buscar los Libros\"}}";

}

El framework Apache CXF se encarga de crear los objetos criteria(Libro) y advancedCriteria. Para saber cuál fue el que recibimos basta con verificar la propiedad _constructor, si es diferente de null sabemos que hemos recibido un AdvancedCriteria.

Finalmente realizamos la consulta en nuestra clase Libro


public static List<Libro> getByAdvancedCriteria(AdvancedCriteria criteria) {

List<Libro> libros;

PersistenceManager pm = PMF.get().getPersistenceManager();

Transaction tx = pm.currentTransaction();

try{

tx.begin();

JDOQLData data=new JDOQLData(Libro.class);

criteria.toJDOQL(data);

Query query = pm.newQuery(Libro.class,data.filter);

libros=(List<Libro>) query.executeWithMap(data.params);

tx.commit();

}finally{

if(tx.isActive()){

tx.rollback();

}

pm.close();

}

return libros;

}

En el cliente, utilizamos la clase AdvancedCriteria de SmartGwt para realizar la consulta


&nbsp;

AdvancedCriteria criteria=new AdvancedCriteria(OperatorId.OR,

new AdvancedCriteria[]{

new AdvancedCriteria("isbn",OperatorId.EQUALS, txtISBN.getValueAsString()),

new AdvancedCriteria(OperatorId.AND, new AdvancedCriteria[]{

new AdvancedCriteria("autor.nombre", OperatorId.EQUALS, "Gabriel García Márquez"),

new AdvancedCriteria("titulo", OperatorId.EQUALS, "Crónica de una muerte anunciada")

})

});

librosListGrid.fetchData(criteria);

&nbsp;

Finalmente al realizar el ‘fetch’ se realiza una petición a nuestro servicio con los siguientes párametros. Los cuales serán transformados a un objeto de nuestra clase AdvancedCriteria


&nbsp;

__gwt_ObjectId:793

operator:or

criteria:{"__gwt_ObjectId":788,"fieldName":"isbn","operator":"equals","value":"978-1400034956"}

criteria:{"__gwt_ObjectId":791,"operator":"or","criteria":[{"__gwt_ObjectId":789,"fieldName":"autor.nombre","operator":"equals","value":"Gabriel García Márquez"},{"__gwt_ObjectId":790,"fieldName":"titulo","operator":"equals","value":"Crónica de una muerte anunciada"}]}

_constructor:AdvancedCriteria

_operationType:fetch

_startRow:0

_endRow:75

_textMatchStyle:exact

_componentId:isc_ListGrid_0

_dataSource:isc_LibrosModule_3_0

isc_metaDataPrefix:_

isc_dataFormat:json

&nbsp;

Fuentes:
http://wiki.smartclient.com/display/Main/Integrating+with+ASP.Net+MVC
http://wiki.smartclient.com/display/Main/5.+Adding+support+for+AdvancedCriteria
http://www.objectdb.com/database/jdo/manual/chapter7#Query_Parameters
http://www.objectdb.com//database/jdo/manual/chapter7
http://stackoverflow.com/questions/935762/how-to-dynamically-build-jdo-queries-on-multiple-parameters

En orientación a objetos la herencia es, después de la agregación o composición, el mecanismo más utilizado para alcanzar algunos de los objetivos más preciados en el desarrollo de software como lo son la reutilización y la extensibilidad. A través de ella los diseñadores pueden crear nuevas clases partiendo de una clase o de una jerarquía de clases preexistente (ya comprobadas y verificadas) evitando con ello el rediseño, la modificación y verificación de la parte ya implementada. La herencia facilita la creación de objetos a partir de otros ya existentes e implica que una subclase obtiene todo el comportamiento (métodos) y eventualmente los atributos (variables) de su superclase.

En nuestros objetos es fácil implementar la herencia, basta con agregar la palabra clave extends y específicar la clase padre. Pero en una base de datos relacional no siempre queda claro cómo debemos implementar la herencia. Datanucleus (JDO) nos ofrece 4 opciones:

  1. Cuando cada clase tiene su propia tabla en la base de datos: new-table
  2. Cuando los campos de la clase padre son almacenados en la tabla del hijo: subclass-table
  3. Cuando los campos del hijo son almacenados en la tabla del padre: superclass-table
  4. A partir de JDO3.1, cuando cada clase tiene su propia tabla y esta contiene todos los campos (incluídos los de la clase padre): complete-table

Supongamos que tenemos la entidad Trabajador de la cual derivan las entidades Empleado y Meritorio. Nuestras clases quedarían de la siguiente manera:

public abstract class Trabajador {
   private String id;
   private String departmento;
}
public class Empleado extends Trabajador {
   private float salario;
}
public class Meritorio extends Trabajador {
   private Date fechaTermino;
}

Las tablas para las distintas estrategias que ofrece Datanucleus (JDO) serían:

new-table

Trabajador
id departamento
Empleado
idTrabajador salario
Meritorio
idTrabajador fechaTermino

subclass-table

Empleado
id departamento salario
Meritorio
id departamento fechaTermino

superclass-table

Trabajador
id departamento tipo salario fechaTermino

complete-table

Trabajador
id departamento
Empleado
id departamento salario
Meritorio
id departamento fechaTermino

Finalmente mostraré cómo anotar nuestras clases para implementar la estrategia superclass-table, que es el caso particular que se me presentó.

@PersistenceCapable
@Discriminator(strategy=DiscriminatorStrategy.VALUE_MAP, column="tipo")
public abstract class Trabajador {
   private String id;
   private String departmento;
}
@PersistenceCapable
@Inheritance(strategy=InheritanceStrategy.SUPERCLASS_TABLE)
@Discriminator(value="EMPLEADO")
public class Empleado extends Trabajador {
   private float salario;
}
@PersistenceCapable
@Inheritance(strategy=InheritanceStrategy.SUPERCLASS_TABLE)
@Discriminator(value="MERITORIO")
public class Meritorio extends Trabajador {
   private Date fechaTermino;
}

Fuentes:

http://www.datanucleus.org/products/datanucleus/jdo/orm/inheritance.html#superclasstable
http://www.datanucleus.org/products/accessplatform/jdo/orm/inheritance.html
https://developers.google.com/appengine/docs/java/datastore/jdo/dataclasses#Inheritance
http://es.wikipedia.org/wiki/Herencia_(programaci%C3%B3n_orientada_a_objetos)

En el diseño de bases de datos relacionales, se llama clave primaria a un campo o a una combinación de campos que identifica de forma única a cada fila de una tabla. No pueden haber dos filas en una tabla que tengan la misma clave primaria.
Un ejemplos de claves primarias es el ISBN (asociado a un libro).

Supongamos que tenemos una entidad Usuario que es identificada por 2 propiedades: id e idJuzgado. Para especificar la llave compuesta en Datanucleus lo haríamos de la siguiente manera:

@PersistenceCapable(objectIdClass=Usuario.ComposedIdKey.class)
public class Usuario {

public static class ComposedIdKey implements Serializable
{
    public Integer id;
    public String idJuzgado;

    public ComposedIdKey ()
    {
    }

    /**
     * Constructor accepting same input as generated by toString().
     */
    public ComposedIdKey(String value)
    {
        StringTokenizer token = new StringTokenizer (value, "::");
        token.nextToken();               // className
        this.id = Integer.parseInt(token.nextToken()); // field1
        this.idJuzgado = token.nextToken(); // field2
    }

    public boolean equals(Object obj)
    {
        if (obj == this)
        {
            return true;
        }
        if (!(obj instanceof ComposedIdKey))
        {
            return false;
        }
        ComposedIdKey c = (ComposedIdKey)obj;
        return id.equals(c.id) && idJuzgado.equals(c.idJuzgado);
    }

    public int hashCode ()
    {
        return this.id.hashCode() ^ this.idJuzgado.hashCode();
    }

    public String toString ()
    {
        // Give output expected by String constructor
        return this.getClass().getName() + "::"  + this.id + "::" + this.idJuzgado;
    }
}

@PrimaryKey
Integer id;
@PrimaryKey
String idJuzgado;
String nombre;
String apellidoPaterno;
String apellidoMaterno;

public Usuario() {
}

public Integer getId() {return id;}
public void setId(Integer id) {this.id = id;}
public String getNombre() {return nombre;}
public void setNombre(String nombre) {this.nombre = nombre;}
public String getApellidoPaterno() {return apellidoPaterno;}
public void setApellidoPaterno(String apellidoPaterno) {this.apellidoPaterno = apellidoPaterno;}
public String getApellidoMaterno() {return apellidoMaterno;}
public void setApellidoMaterno(String apellidoMaterno) {this.apellidoMaterno = apellidoMaterno;}
}

Notesé el uso de la clase Interna Usuario.ComposedIdKey en el parámetro de la anotación @PersistenceCapable y el uso de 2 anotaciones @PrimaryKey en las propiedades correspondientes.

Para realizar la consulta de un Objeto con una clave primaria compuesta debemos utilizar la sobrecarga del método getObjectById de la siguiente forma:


Usuario usuario=null;
 PersistenceManager pm = PMF.get().getPersistenceManager();
 Transaction tx = pm.currentTransaction();
 try{
 tx.begin();
 usuario = (Usuario) pm.getObjectById(new Usuario.ComposedIdKey(Usuario.ComposedIdKey.class.getName()+"::"+id+"::"+idJuzgado), false);
 tx.commit();
 }finally{
 if(tx.isActive()){
 tx.rollback();
 }
 pm.close();
 }

si tratas de utilizar el método getObjectById de la siguiente forma

pm.getObjectById(Usuario.class,new Usuario.ComposedIdKey(Usuario.ComposedIdKey.class.getName()+"::"+id+"::"+idJuzgado));

te arrojará una excepción parecida a la siguiente

javax.jdo.JDOUserException: Unable to create Object Identity for class “server.model.Usuario” since key is of an unsupported type (server.model.Usuario$ComposedIdKey

Fuentes:
http://www.datanucleus.org/products/datanucleus/jdo/primary_key.html
http://www.datanucleus.org/products/datanucleus/jdo/orm/compound_identity.html
http://es.wikipedia.org/wiki/Clave_primaria
http://book.javanb.com/using-and-understanding-java-data-objects/LiB0035.html

Cuando quieres utilizar una tabla existente para algún objeto persistente, puedes utilizar algunas anotaciones para indicarle a Datanucleus que tabla utilizar y a que columnas mapear las propiedades. Por ejemplo:

@PersistenceCapable(schema="CATALOGOS",table="TPRODUCTOS")
public class Producto {

@PrimaryKey
@Persistent(column="SPRODUCT_ID")
String id;
@Persistent(column="SNAME")
String nombre;

public Producto() {
}

public String getId() {
return id;
}

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

public String getNombre() {
return nombre;
}

public void setNombre(String nombre) {
this.nombre = nombre;
}

}

En este ejemplo le indicamos a Datanucleus que queremos utilizar la tabla “TPRODUCTOS” en el esquema “CATALOGOS” para persistir los objetos de tipo Producto. También le indicamos los nombres de las columnas, ya que muchas veces no es el mismo que en nuestros objetos -sobretodo con tablas ya existentes-. Bueno, pues al ejecutar la validación del esquema mediante el comando “mvn datanucleus:schema-validate” me arrojaba la siguiente excepción:

org.datanucleus.store.rdbms.exceptions.WrongPrecisionException: Expected primary key for table CATALOGOS.TPRODUCTOS.SNAME 50 not found in existing keys 256

Al principio pensé que tenía que ver con que la tabla no tuviera un PRIMARY KEY, pero al volver a leer el mensaje me di cuenta que el tipo de la excepción es WrongPrecisionException. Pues resulta que como parte de la validación del esquema se verifican las restricciones de longitud por lo que fue necesario especificarla de la siguiente manera

@Persistent(column="SNAME")
@Column(length=50)
String nombre;

Fuentes:
http://stackoverflow.com/questions/9540470/how-to-create-a-jdo-datanucleus-key-string-field-of-unknown-size
http://www.datanucleus.org/products/accessplatform_3_1/schematool.html#maven2