Salta el contingut

3. Bases de dades Orientades a Objectes

1. Base de dades Orientades a Objectes. ObjectDB

En aquesta secció, com a SGBD, s'ha escollit ObjectDB, ja que és molt versàtil, gratuït i fins i tot permet incrustar-lo dins dels nostres projectes Java, cosa que permet una gran simplicitat per al desenvolupament de petites aplicacions, gràcies a l'eliminació d'un servidor.

2. Instal·lació

ObjectDB no requereix instal·lació com a tal, ja que tot el seu codi està integrat en una API per accedir a la base de dades empaquetada en un fitxer jar. Des del lloc web oficial podem descarregar el Kit de Desenvolupament d'ObjectDB. En el moment d'escriure aquest llibre, la versió és la 2.9.0. Aquest kit conté, entre altres:

  • Dependències per a projectes Java
  • Utilitats per visualitzar i consultar la base de dades
  • Servidor per a aplicacions distribuïdes
  • Documentació

Un cop descomprimit, només necessitarem la màquina virtual Java instal·lada al nostre sistema per executar tots els elements. Per utilitzar ObjectDB en el nostre projecte hem d'afegir el fitxer objectdb.jar a les dependències del nostre projecte o fer-ho utilitzant el gestor de dependències maven o gradle.

En aquest moment podem connectar-nos a la base de dades, la centralització de la connexió a la base de dades es fa a través d'una instància d'un objecte EntityManagerFactory, del qual podem obtenir diverses instàncies d'un EntityManager.

Des de l'EntityManager podrem dur a terme les operacions típiques de CRUD, tenint en compte que sempre que hi hagi modificacions en ell. Hem de dur a terme l'operació dins d'una transacció per evitar situacions inconsistents en ella. Aquí veiem una possible classe amb l'establiment de la connexió i l'obtenció d'un EntityManager. Aquest codi es pot veure en el fitxer adjunt ConexionOBD.java. Tingueu en compte que les importacions són de javax.persistence:

Java
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.PersistenceException;

public class ConexionOBD {

    private EntityManagerFactory emf;
    private EntityManager em;
    private String nombreBD;

    /**
     * Crea una objeto a la BBDD indicada
     */
    public ConexionOBD(String nombre){
        this.emf=null;
        this.em=null;
        this.nombreBD=nombre;
    }

    /**
     * Método que realiza la conexión
     */
    private void conectar(){  
        emf = Persistence.createEntityManagerFactory(nombreBD);
        try{
            em = emf.createEntityManager();
        }
        catch(PersistenceException ex){
            System.out.println(ex.getMessage());
        }
    }

    /**
     * Metodo que desconecta
     */
    public void desconectar(){  
        this.em.close();
        this.emf.close();
        this.em=null;
        this.emf=null;
    }

    /**
     * 
     * @return El EntityManager sobre el que operar
     */
    public EntityManager getEM(){
        if (em==null)
            conectar();
        return em;
    }
}

3. Persistència de classes

Per persistir un objecte, necessitarem (semblant a Hibernate):

  • Anotar la teva classe i marcar-la com a @Entity
  • Definir un camp com a @Id i opcionalment auto-incrementar amb @GeneratedValue.
  • La resta dels atributs de l'entitat, per defecte, es persisteixen automàticament sense cap tipus d'anotació. En cas de no voler persistir-ne, podem indicar-ho amb @Transient.
Java
1
2
3
4
5
6
@Entity
public classes Alumno{ 
  @Id @GeneratedValue
  private Long idAlumno;
  private String Name;
}

Per emmagatzemar un Alumne, n'hi haurà prou amb crear un Alumne i persistir-lo a la base de dades, com es veu a continuació, assumint l'objecte de tipus conexionDB vist en el fitxer adjunt:

Java
1
2
3
4
5
6
ConexionOBD con= new ConexionOBD("Alumnos.odb");
EntityManager em=con.getEM();
em.getTransaction().begin();
Alumno alu=new Alumno("Antonio Ramos");
em.persist(alu);
em.getTransaction().commit();

Classes incrustades o Components

En Java, de vegades hi ha classes que no tenen existència pròpia, tret que existeixin dins d'una altra classe, com ara una classe Adreça. No té sentit crear un objecte Adreça ad-hoc, en canvi, sí que té sentit crear-lo perquè una Adreça existeixi, per exemple, dins d'un Estudiant.

Per a aquests casos (febles) que existeixen incrustats dins d'altres classes, hem de declarar-los com a incrustables utilitzant l'anotació @Embeddable i marcar-los com a incrustats (@Embedded) dins de la classe en què existeixen.

Java
@Embeddable
public class Direccion{
    ...
}

@Entity
public classes Alumno{
  ...
  @Embedded
  private Direccion direction;
}

// in main
Alumno alu= new Alumno("Joan Gerard");
Direccion d= new Direccion("calvary street");
alu.setDireccion(d);
em.persist(alu);

Una entitat Alumno es guardarà a la base de dades, però la Direccion no existeix com a objecte per si mateix.

4. Relacions

4.1. Relació un a un

La relació més senzilla és un a un, en la qual un objecte conté un altre objecte. La marcarem com ja ho vam fer en Hibernate amb el modificador @OneToOne indicant que el desament és en cascada (cascade=CascadeType.PERSIST).

A partir d'ara, quan desis una instància d'un objecte, la teva pròpia instància de l'objecte relacionat es desarà i s'enllaçarà. L'objecte enllaçat tindrà existència per si mateix (si està marcat com a @Entity). Un exemple bàsic en què una classe té un únic Tutor:

Basat en un exemple en què una Clase (d'un institut) té un Tutor associat, l'exemple serà el següent:

Java
@Entity
public classes Professor{
  ...
}

@Entity
public class Clase{
  ...
  @OneToOne(cascade=CascadeType.PERSIST)
  private Professor tutor;
}


Professor p=new Professor("Pepe");
Clase c= new Clase("2DAM");
c.setTutor(p);
um.persist(c); // when saving the class the tutor is saved

Atenció

Recorda que, de la mateixa manera que en Hibernate, aquesta relació és unidireccional, però es pot fer bidireccional.

4.2. Relació Una a molts

Ara ens referirem a una relació clàssica en la qual un Professor és el tutor de diversos Alumne. Aquestes relacions poden ser unidireccionals o bidireccionals. En aquest exemple ho veurem de manera bidireccional, de tal manera que donat un alumne podem saber qui és el seu tutor i donat un professor podem obtenir els alumnes que tutoritza.

Java
@Entity
public class Alumno{
  ...
  @ManyToOne(cascade=CascadeType.PERSIST)
  private Profesor tutor;

}

@Entity
public class Profesor{
  ...
  @OneToMany(cascade=CascadeType.PERSIST,
             fetch=FetchType.EAGER)
  private List<Alumno> theStudents;
}

Atenció

Recorda que, de la mateixa manera que en Hibernate, la càrrega de les col·leccions es pot fer immediatament, amb una mode EAGER o quan sigui necessari en mode LAZY.

4.3. Molts a molts

En les relacions de molts a molts, podem abordar-les de diverses maneres. Donem l'exemple de l'ensenyament entre Professor i Alumne. Si simplement volem indicar qui ensenya a qui, seria suficient emmagatzemar una col·lecció de professors en cada alumne (els professors que ensenyen a aquest alumne), i simètricament, en cada professor una col·lecció d'alumnes (els alumnes a qui ensenyen). En aquest cas, seria bidireccional, ja que des d'una classe podem navegar a l'altra, quedant així:

Java
@Entity
public classes Alumno{
  ...
  @ManyToMany(cascade=CascadeType.PERSIST, fetch=FetchType.LAZY)
  private Set<Profesor> teachers=new Hash Set<>();
}

@Entity
public classes Profesor{
  ...
  @ManyToMany(cascade=CascadeType.PERSIST, fetch=FetchType.LAZY)
  private Set<Alumno> students=new Hash Set<>();;
}

Atenció

Si necessitem desar qualsevol informació dins d'aquesta relació, com ara la nota que s'ha rebut, l'estudiant, o els incidents publicats, etc., llavors hem de crear una nova classe, que incorporarà els atributs de la relació, i establir relacions un a molts des de cada entitat (Estudiant/Professor) fins a la nova entitat (Ensenyament). Aquesta suposició és la famosa Les relacions N-M generen una taula amb els atributs que posseeixen del model relacional.

5. Consultes

Revisarem com podem carregar les dades que hem desat prèviament a la base de dades. Suposem que tenim una classe Estudiant, mapada amb una entitat i amb un identificador (idStudent). La manera més senzilla de carregar un Estudiant, coneixent el seu id, és el mètode find(class,id), com vam fer a Hibernate:

Java
Student a=em.find(Student.classes,2);

La resta de les càrregues s'han de dur a terme mitjançant consultes, en un llenguatge JPQL (Java Persistence Query Language), novament similar a l'HQL de Hibernate.

Hi ha dues classes: Query i TypedQuery (la segona hereta de la primera), que s'utilitzen normalment en el primer cas quan no coneixem el resultat de la consulta, i en el segon quan coneixem el resultat.

La primera és polimòrfica, de manera que enllaçarà dinàmicament els resultats, i la segona verifica el resultat amb la classe actual. La documentació oficial recomana l'ús de la segona, TypedQuery per a consultes i Query per a actualitzacions i eliminacions.

Java
// creation of Query (q) or TypedQuery (tq)

// unique result

//return a generic object, and we must cast it.
Object q.getSingleResult();   

// return an specified Object
T tq.getSingleResult();

// a result set

//return a List of generic object, and we must cast it.
List q.getResultList(); 

// return a list of specified Object
List<T> tq.getResultList();

// to update or delete
q.executeUpdate();

De manera similar a Hibernate, podem consultar la base de dades de la següent manera:

Java
TypedQuery<Alumno> tq=em.createQuery("Select a from Alumno a where a.ampa=true", Alumno.class);
List<Alumno> alumnosAmpa=tq.getResultList();

o amb paràmetres:

Java
1
2
3
TypedQuery<Alumno> tq=em.createQuery("Select a from Alumno a where a.ampa= :ampa", Alumno.class);
tq.setParameter("ampa", true);
List<Alumno> alumnosAmpa=tq.getResultList();

o crear consultes amb nom específiques:

Java
1
2
3
4
5
6
7
8
9
// on the class
@NamedQueries({
    @NamedQuery(query = "Select a from Alumno a where a.nombre = :name", name = "find alu by name"),
    @NamedQuery(query = "Select a from Alumno a", name = "get all alu")
})

// on main or whatever
TypedQuery<Alumno> tq=em.createNamedQuery("get all alu",Alumno.class);
List<Alumno> alumnosAmpa=tq.getResultList();

5.1. Esborrats i Actualitzacions

Finalment, revisarem les últimes operacions CRUD que ens queden. Les actualitzacions són totalment transparents per a l'usuari, ja que qualsevol modificació que es faci dins del context d'una transacció es desarà automàticament quan es tanqui amb un commit(). Alternativament, es poden realitzar consultes d'actualització.

Per a les eliminacions, si l'objecte s'ha recuperat de la base de dades, i per tant està en la transacció actual, es pot eliminar amb em.remove(object). Es eliminarà de la base de dades quan es faci el commit.