flowchart LR
A["Objecte Llibre<br/>titol, isbn, anys"] -->|"guardar directament"| B[("BDOO<br/>llibres.odb")]
B -->|"recuperar directament"| C["Objecte Llibre<br/>reconstruït tal qual"]
20 Bases de dades orientades a objectes
Objectius
- Explicar què és una base de dades orientada a objectes (BDOO) i com es diferencia del model relacional.
- Entendre el desajust objecte-relacional i per què les BDOO l’eviten.
- Comparar una BDOO amb una base de dades relacional en termes de model, consultes, maduresa i rendiment.
- Conèixer ObjectDB, una BDOO per a Java, i configurar-la amb Maven.
- Definir una entitat amb
@Entity,@Idi@GeneratedValue. - Guardar, cercar, actualitzar i esborrar objectes amb
EntityManager. - Fer consultes amb JPQL i modelar relacions 1:N entre objectes.
Als capítols anteriors hem fet sobreviure les dades de dues maneres. Amb serialització (Capítol 18) guardàvem objectes Java tal qual, en binari, però sense poder cercar-hi ni compartir-los. Amb JDBC (Capítol 19) teníem una base de dades relacional de debò, però cada objecte s’havia de trencar en files i columnes i traduir-lo amb SQL.
En aquest capítol veiem la tercera via: una base de dades orientada a objectes, que guarda els objectes directament, amb els seus atributs, mètodes i relacions, i els consulta sense fer-los passar per taules.
20.1 Què és una base de dades orientada a objectes?
Una base de dades orientada a objectes (BDOO) emmagatzema objectes com a unitats. Si guardes un objecte Llibre que té un atribut Autor, la BDOO guarda el llibre i la referència a l’autor, tal com eren a la memòria.
La idea és que el programa treballi sempre amb objectes, tant mentre són a la memòria com quan estan desats al disc. La base de dades no es nota: és com si la memòria del programa s’estengués més enllà de l’execució.
Exemples de BDOO per a Java: ObjectDB, db4o, Versant. En aquest capítol farem servir ObjectDB.
La serialització (Capítol 18) ja guardava objectes directament. La diferència és que una BDOO afegeix el que la serialització no pot:
- Cercar dins de les dades sense carregar-les totes.
- Compartir les dades entre diversos programes o usuaris.
- Fer consultes combinant condicions (com
WHEREen SQL).
La BDOO és, per dir-ho així, una serialització amb motor de consultes.
20.2 El desajust objecte-relacional
Recorda el problema que vam deixar apuntat a Capítol 19: quan guardes objectes en una base de dades relacional, has de traduir cada classe a una taula, cada objecte a una fila i cada atribut a una columna. Aquesta traducció manual es coneix com a desajust d’impedància (o object-relational impedance mismatch).
flowchart LR
O["Objecte Llibre<br/>+ objecte Autor dins"] -->|"trencar en"| T["Taula LLIBRE<br/>+ taula AUTOR + clau forana"]
T -->|"tornar a muntar<br/>amb SQL"| O2["Objecte Llibre<br/>reconstruït"]
Amb una BDOO no cal trencar res: el que guardes és el que tens a la memòria. Un Llibre amb el seu Autor dins es guarda sencer i es recupera sencer.
20.3 BDOO vs base de dades relacional
Abans d’escriure codi, fixem bé les diferències.
| Aspecte | BD orientada a objectes | BD relacional |
|---|---|---|
| Unitat d’emmagatzematge | Objecte (amb atributs i relacions) | Fila d’una taula |
| Model | El mateix que el codi: classes, herència | Taules, files i columnes |
| Llenguatge de consulta | JPQL / consultes sobre objectes | SQL (estàndard) |
| Desajust amb el codi OO | Cap: guardes objectes tal qual | Cal traduir objecte ↔︎ taula |
| Relacions | Referència directa entre objectes | Claus foranes i JOIN |
| Maduresa i suport | Nínxol, poques eines | Enorme: molts SGBD, eines, comunitat |
| Escalabilitat i concurrència | Limitada | Molt provada |
| Exemples | ObjectDB, db4o, Versant | SQLite, MySQL, PostgreSQL, Oracle |
Una BD relacional és com guardar els llibres en fitxes d’un arxivador: cada fitxa (fila) té camps fixos (columnes) i has de fer còpies quan un llibre parla d’un autor.
Una BDOO és com posar els llibres sencers en prestatgeries i, dins de cada llibre, a la contraportada, hi ha la foto de l’autor i la seva biografia. Recuperes el llibre i ja hi tens tot el que portava a dins.
20.4 ObjectDB: la primera configuració
ObjectDB és una base de dades orientada a objectes escrita en Java i pensada per a Java. Té una llicència free per a ús acadèmic i de desenvolupament, i funciona com a fitxer local (un .odb), com SQLite però amb objectes.
Per configurar-la amb Maven cal afegir dues coses al pom.xml:
- El repositori d’ObjectDB, perquè el jar no és a Maven Central.
- La dependència
com.objectdb:objectdb.
<repositories>
<repository>
<id>objectdb</id>
<url>https://m2.objectdb.com</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>com.objectdb</groupId>
<artifactId>objectdb</artifactId>
<version>2.9.5</version>
</dependency>
</dependencies>Com amb SQLite, la base de dades sencera és un únic fitxer (per exemple llibres.odb). No cal instal·lar cap servidor. Això la fa ideal per aprendre i per a aplicacions d’un sol usuari o d’un sol procés.
20.5 La connexió: EntityManagerFactory i EntityManager
ObjectDB usa l’API JPA (Java Persistence API). Les dues classes clau són:
EntityManagerFactory: la “fàbrica” que gestiona les connexions. Es crea una vegada ambPersistence.createEntityManagerFactory(...).EntityManager: la “connexió” cap a una base de dades concreta. Amb ella es guarden i es consulten objectes.
La manera més directa d’obrir una base de dades és passar-li la ruta del fitxer com a paràmetre:
import javax.persistence.*;
public class ProvaConnexio {
public static void main(String[] args) {
// La base de dades és el fitxer llibres.odb
EntityManagerFactory emf =
Persistence.createEntityManagerFactory("$objectdb/db/llibres.odb");
EntityManager em = emf.createEntityManager();
System.out.println("Base de dades oberta correctament!");
em.close(); // Tanquem la connexió...
emf.close(); // ...i la fàbrica
}
}Si la base de dades no existeix, ObjectDB la crea automàticament. Fixa’t que em i emf són recursos que cal tancar, igual que una Connection amb JDBC.
L’API JPA fa servir la paraula entitat per referir-se a la classe que es pot guardar a la base de dades. És el mateix concepte que “objecte persistent”. No et confonguis: una entitat és una classe Java normal, però amb unes quantes anotacions que la marquen com a persistible.
20.6 Definir una entitat: @Entity, @Id, @GeneratedValue
Perquè ObjectDB pugui guardar objectes d’una classe, aquesta ha de ser una entitat. S’aconsegueix amb anotacions:
@Entity: marca la classe com a entitat (persistible).@Id: indica quin atribut és la clau de l’objecte (l’identificador únic).@GeneratedValue: fa que la clau es generi automàticament en guardar.
import java.io.Serializable;
import javax.persistence.*;
@Entity
public class Llibre implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue
private long id; // La clau: la genera ObjectDB
private String titol;
private String isbn;
private int anyPublicacio;
public Llibre() {} // Constructor buit: JPA el necessita
public Llibre(String titol, String isbn, int anyPublicacio) {
this.titol = titol;
this.isbn = isbn;
this.anyPublicacio = anyPublicacio;
}
public long getId() { return id; }
public String getTitol() { return titol; }
@Override
public String toString() {
return titol + " (" + anyPublicacio + ") ISBN " + isbn;
}
}Perquè ObjectDB pugui reconstruir els objectes:
- Ha d’haver-hi un constructor sense arguments (buit). Si en declares d’altres, cal que el buit existeixi (pot ser
publicoprotected). - És recomanable que implementi
Serializablei declari elserialVersionUID(ja ho vam veure a Capítol 18). - Els atributs que es guarden han de ser accessibles (o tenir getters/setters) i de tipus persistible: primitius,
String, col·leccions o altres entitats.
20.7 Guardar objectes: la transacció
Guardar objectes amb ObjectDB segueix sempre el mateix esquema de tres passos:
- Obrir una transacció amb
em.getTransaction().begin(). - Guardar l’objecte amb
em.persist(...). - Confirmar-la amb
em.getTransaction().commit().
EntityManager em = emf.createEntityManager();
// 1. Comencem la transacció
em.getTransaction().begin();
// 2. Guardem un objecte
Llibre llibre = new Llibre("El nom del vent", "978-8401352836", 2007);
em.persist(llibre);
// 3. Confirmem els canvis
em.getTransaction().commit();
System.out.println("Llibre guardat amb id " + llibre.getId());
em.close();Fixa’t que, després de persist, l’objecte ja té l’id assignat per la base de dades.
Si crides em.persist(...) sense begin() i commit(), els canvis no es desen. La transacció és el contracte que li diu a ObjectDB “tot això ho vull confirmar d’un cop”. Fes servir sempre el patró begin → operacions → commit.
20.8 Cercar objectes per clau: find
Per recuperar un objecte a partir de la seva clau (l’id), fem servir em.find(Classe, clau):
Llibre llibre = em.find(Llibre.class, 1);
if (llibre != null) {
System.out.println("Trobat: " + llibre);
} else {
System.out.println("No hi ha cap llibre amb aquest id.");
}find retorna l’objecte reconstruït tal com el vam guardar, o null si no existeix. No hi ha SQL, no hi ha traducció: tornes a tenir el mateix objecte Java.
flowchart LR
A["em.find(Llibre.class, 1)"] --> B[("llibres.odb")]
B -->|"objecte reconstruït"| C["Llibre amb tots els seus camps"]
20.9 Consultes amb JPQL
Per trobar objectes que compleixin condicions, ObjectDB fa servir JPQL (Java Persistence Query Language). És molt semblant a SQL, però:
- Consultes classes i atributs, no taules i columnes.
- El resultat són objectes, no files.
Una consulta que retorna una llista d’objectes es fa amb createQuery(...).getResultList():
import javax.persistence.*;
import java.util.*;
// Tots els llibres
TypedQuery<Llibre> q1 = em.createQuery("SELECT l FROM Llibre l", Llibre.class);
List<Llibre> tots = q1.getResultList();
// Llibres publicats a partir d'un any (paràmetre amb :nom)
TypedQuery<Llibre> q2 = em.createQuery(
"SELECT l FROM Llibre l WHERE l.anyPublicacio >= :any ORDER BY l.titol",
Llibre.class);
q2.setParameter("any", 2000);
List<Llibre> recents = q2.getResultList();
for (Llibre l : recents) {
System.out.println(l);
}| SQL (relacional) | JPQL (objectes) |
|---|---|
SELECT * FROM llibre |
SELECT l FROM Llibre l |
WHERE any_publicacio >= 2000 |
WHERE l.anyPublicacio >= 2000 |
ORDER BY titol |
ORDER BY l.titol |
| Retorna files (resultat pla) | Retorna objectes Llibre |
Les claus del JPQL són les mateixes de SQL (SELECT, FROM, WHERE, ORDER BY, GROUP BY…), però sempre operant sobre objectes i els seus atributs (puntet, com al codi Java).
20.10 Actualitzar i esborrar objectes
Actualitzar és senzill: modifiques l’objecte dins d’una transacció i el “retornes” amb em.merge(...).
em.getTransaction().begin();
Llibre l = em.find(Llibre.class, 1);
l.setTitol("El nom del vent (edició especial)");
em.merge(l); // Desem els canvis
em.getTransaction().commit();Esborrar, amb em.remove(...) (cal passar-li l’objecte, que podem obtenir amb find):
em.getTransaction().begin();
Llibre l = em.find(Llibre.class, 1);
if (l != null) {
em.remove(l); // L'eliminem de la base de dades
}
em.getTransaction().commit();20.11 Relacions entre objectes: llibres i autors
El punt fort de les BDOO són les relacions: un objecte pot tenir una referència a un altre i tot es guarda junt. Amb JPA es marquen amb anotacions:
@ManyToOne: molts llibres → un autor (al costat del llibre).@OneToMany: un autor → molts llibres (al costat de l’autor).
@Entity
public class Llibre {
@Id
@GeneratedValue
private long id;
private String titol;
private int anyPublicacio;
@ManyToOne
private Autor autor; // Referència a l'autor
}@Entity
public class Autor {
@Id
@GeneratedValue
private long id;
private String nom;
@OneToMany(mappedBy = "autor", cascade = CascadeType.ALL)
private List<Llibre> llibres = new ArrayList<>();
}Amb cascade = CascadeType.ALL, en guardar l’autor es guarden també els seus llibres automàticament. Recorda com de feixuc era fer això amb taules i claus foranes a Capítol 19: aquí només assignes la referència i la base de dades ho guarda tot.
flowchart LR
A["Objecte Autor<br/>nom + llista de Llibre"] -->|"una peça"| B[("BDOO<br/>llibres.odb")]
B -->|"una peça"| A2["Objecte Autor<br/>amb els seus llibres dins"]
A Capítol 19, per relacionar un llibre amb el seu autor havíem de crear la taula autor, afegir-hi la columna autor_id com a clau forana i fer un JOIN per consultar-los junts.
Amb ObjectDB, la relació és una referència de Java com qualsevol altra. El programa no es diferencia gens d’un programa que treballa només amb memòria.
20.12 Avantatges i inconvenients
Per decidir quan fer servir una BDOO, val la pena tenir-ho clar:
| Avantatges | Inconvenients |
|---|---|
| Sense desajust d’impedància: objectes tal qual | Tecnologia de nínxol: poques eines i comunitat |
| Relacions directes entre objectes | Menys madura en escalabilitat i concurrència |
| Coherent amb l’orientació a objectes del codi | Sense estàndard de consulta universal (JPQL és de facto) |
| Fàcil d’aprendre si ja saps POO | La majoria d’empreses demanen SQL |
| Ideal per a prototips i models complexos | Millor opció professional: BDR + ORM (com Hibernate) |
Al món professional, la base de dades relacional és l’estàndard, i el desajust objecte-relacional es resol amb frameworks ORM (Hibernate, JPA) que automatitzen la traducció objecte ↔︎ taula.
Les BDOO com ObjectDB brillen quan el model d’objectes és complex (herències, relacions profundes) i la consulta és secundària. Per aprendre, però, són una eina perfecta per entendre què significa persistir objectes sense les distraccions de SQL.
20.13 Errors típics
- Oblidar
@Entitya la classe → ObjectDB no sap que és persistible i llança excepcions enpersist. - No tenir constructor sense arguments → error en reconstruir l’objecte.
- Oblidar
begin()/commit()→ els canvis no es desen. - No tancar
emiemf→ es queden recursos oberts. - Confondre
persist(guardar un objecte nou) ambmerge(desar els canvis d’un objecte existent). - En JPQL, escriure noms de taules i columnes de SQL (
FROM llibre WHERE any_publicacio) en lloc de classes i atributs (FROM Llibre l WHERE l.anyPublicacio). - No afegir el repositori
https://m2.objectdb.comalpom.xml→ Maven no troba la dependència.
20.14 Per practicar
- Crea la classe
Soci(nom, número de carnet, any d’alta) com a entitat amb@Entity,@Idi@GeneratedValue, i un constructor buit. - Escriu un programa que obri la base de dades
socis.odb, guardi tres socis dins d’una transacció i en mostri elsidassignats. - Recupera un soci amb
find, modifica-li el nom ambmergei comprova-ho tornant-lo a cercar. - Fes una consulta JPQL que retorni els socis donats d’alta a partir de l’any 2020, ordenats per nom.
- Explica amb les teves paraules, comparant amb Capítol 19, què estalvies quan la classe
Socité un atributList<Prestec> prestecs.
20.15 Resum
- Una BDOO guarda objectes directament, amb atributs i relacions, i evita el desajust objecte-relacional de les taules.
- ObjectDB és una BDOO per a Java (un fitxer
.odb, sense servidor) que es configura afegint el repositori i la dependència alpom.xml. - Una classe persistible és una entitat:
@Entity, amb@Idi@GeneratedValue, constructor buit iSerializable. - El
EntityManagerés la connexió:persistguarda,findcerca per clau,mergeactualitza iremoveesborra, sempre dins d’una transacció (begin→ operacions →commit). - Les consultes es fan amb JPQL, que és com SQL però sobre classes i atributs, i retorna objectes.
- Les relacions (1:N, N:M) són referències de Java directes:
@ManyToOnei@OneToManyambcascade. - Al món professional domina la BDR amb ORM; les BDOO són una alternativa de nínxol, però excel·lents per aprendre persistència orientada a objectes.