Manter, numa base de dados relacional, o estado de uma aplicação orientada a objectos feita em Java, com um mínimo de alterações ao modelo de programação.
O Hibernate é uma framework de open source para persistência de objectos em bases de dados relacionais. Na génese deste tipo de ferramentas está a diferença entre os paradigmas relacional (usado pela maioria dos Sistemas de Gestão de Bases de Dados) e orientado a objectos (usado pelas aplicações Java), e a necessidade de estabelecer uma correspondência entre os dois modelos de dados (mapeamento objecto/relacional). Este mapeamento é posteriormente utilizado pelo Hibernate para suportar a leitura e escrita de objectos em suporte persistente no contexto de uma unidade de trabalho.1
Uma unidade de trabalho regista todas as operações de uma transacção de negócio que podem afectar a base de dados. Quando uma unidade de trabalho termina com sucesso, as operações registadas são propagadas para a base de dados. Se a unidade de trabalho abortou, todas as operações registadas são descartadas, como se a unidade de trabalho nunca tivesse existido.
Nota: Apenas as operações realizadas numa unidade de trabalho activa são executadas no contexto de uma transacção de base de dados. |
O modelo de domínio de uma aplicação é o conjunto de classes (dados e comportamento), e suas relações, que representa os conceitos manipulados pela aplicação. É este conjunto de classes que necessita de ser mantido de forma persistente através de uma framework como o Hibernate.
Qualquer objecto Java com dados e funcionalidade associada, pode ser tornado persistente, não sendo necessário fazer alterações de fundo ao modelo de domínio para suportar a sua persistência. Para tal basta:
É ainda política recomendada a definição de propriedades JavaBeans com a
convenção para os nomes dos métodos acessores (getXXXX
, e isXXXX
para
predicados), modificadores (setXXXX
) associados aos atributos
persistentes dos objectos.
Um objecto no estado transiente (transient) é um objecto instanciado pela aplicação, que não está, nem nunca esteve, associado a uma unidade de trabalho. Os seus dados não estão armazenados de forma persistente nem tem uma identidade persistente associada.
Um objecto está no estado persistente (persistent) quando tem uma identidade persistente associada e os seus dados estão (ou serão) armazenados de forma persistente. Um objecto persistente está associado a uma única unidade de trabalho, garantindo-se que, para essa unidade de trabalho, a identidade persistente é equivalente à identidade Java (referência para o objecto em memória).
Nota: Podem existir múltiplas instâncias do mesmo objecto persistente, desde que em unidades de trabalho diferentes. |
Um objecto destacado (detached) é um objecto que já esteve associado a uma unidade de trabalho, mas a associação já não existe (a unidade de trabalho terminou ou o objecto foi explicitamente destacado). Tem uma identidade persistente, mas não há garantia alguma quanto à relação entre a identidade persistente e a identidade Java. Podem existir múltiplos objectos destacados com a mesma identidade persistente.
Esta correspondência é feita através de meta-informação associada às classes do modelo de domínio, através de anotações e/ou de ficheiros de configuração em XML.
Sempre que um objecto persistente é acedido pela primeira vez, o Hibernate usa a meta-informação associada à sua classe para:
Quando uma unidade de trabalho termina, a meta-informação sobre a correspondência entre o modelo de domínio e a sua representação relacional é utilizada pelo Hibernate para fazer as validações necessárias e gerar as queries SQL que efectuarão as alterações necessárias aos registos da base de dados.
Um mapa de identidades guarda um registo de todos os objectos lidos da base de dados numa dada unidade de trabalho. Sempre que se pretende aceder a um objecto é feita uma pesquisa no mapa e o objecto devolvido. Caso o objecto ainda não tenha sido lido da base de dados, é-o no momento e o mapa é actualizado, sendo o objecto recém-criado o devolvido.
Este padrão é utilizado para garantir que não há múltiplos objectos em memória que representem a mesma informação na base de dados (para uma dada unidade de trabalho). Uma vantagem acrescida é a eliminação de acessos redundantes à base de dados, uma vez que, quando é necessário obter um objecto previamente lido, já não é necessário ler a informação novamente da base de dados.
Uma consequência dos objectos não serem partilhados entre unidades de trabalho é a de que não há acesso concorrente aos objectos em memória. No entanto, ao nível da base de dados continuam a poder ocorrer acessos concorrentes a registos, bastando para tal que duas unidades de trabalho distintas estejam a manipular os mesmos objectos persistentes.
O Hibernate advoga uma política de concorrência optimista, em que se assume que a maioria das transacções de base de dados não entram em conflito com outras transacções, não sendo portanto necessário obter trincos na base de dados. A detecção de conflictos é feita através de um número de versão associado aos objectos persistentes (e gerido automaticamente pelo Hibernate). No commit da transacção é feita a validação do número de versão do objecto modificado contra o número de versão guardado no registo que lhe corresponde na base de dados.
Sem alterações adicionais aos objectos do modelo de domínio, o Hibernate não faz qualquer verificação de conflitos, pelo que, no caso de alteração concorrente de um objecto, será persistido o estado do objecto na última transacção a terminar.
Para que haja detecção automática de conflitos é necessário impor uma regra adicional sobre as classes que podem ser persistidas:
O carregamento tardio de objectos permite ler um objecto a partir do suporte persistente, mas não ler imediatamente os objectos que lhe estão associados. Os objectos associados só serão lidos se/quando acedidos pela primeira vez.
Para suportar o carregamento tardio, é usado o padrão Proxy2, em concreto a implementação já disponível na plataforma Java. Este padrão tem como requisito a classe que se quer carregar tardiamente poder ser substituida por um proxy cuja interface é a mesma da classe original, e cuja implementação se encarrega do carregamento tardio.
Este requisito impõe uma restrição adicional sobre os objectos que podem ser persistidos, caso se pretenda suportar o seu carregamento tardio:
O mapeamento Objecto/Relacional faz-se com recurso a anotações do package
javax.persistence
, pelo que se aconselha a inclusão da linha seguinte na
zona de imports de cada classe anotada:
import javax.persistence.*;
Para identificar uma classe como persistente basta anotá-la com
@Entity
, como se mostra no exemplo:
@Entity public class Airplane { ... }
Por omissão, o Hibernate associa a classe persistente a uma tabela com o
mesmo nome. Caso este comportamento não seja desejável, é possível
definir a tabela onde serão guardados os dados dos objectos usando a
anotação @Table
:
@Entity @Table(name=AIRPLANE_DATA) public class Airplane { ... }
Documentação: http://www.hibernate.org/hib_docs/annotations/reference/en/html/entity.html#entity-mapping-entity
Para definir um atributo como identificador da classe é necessário
anotá-lo com @Id
e, caso se queiram identificadores gerados
automaticamente pelo Hibernate (fortemente recomendado),
@GeneratedValue
. Seguindo as regras previamente descritas, o
atributo não deve ser de um tipo primitivo (por exemplo, Long
) e o
seu nome deve seguir uma convenção (por exemplo, atributos
identificadores têm sempre o nome id
):
@Entity public class Airplane { @Id @GeneratedValue(strategy=GenerationType.AUTO) private Long id; ... }
Documentação: http://www.hibernate.org/hib_docs/annotations/reference/en/html/entity.html#entity-mapping-identifier
Um atributo que represente o número de versão do objecto deve ser
anotado com @Version
. De acordo com as regras previamente descritas,
este atributo deve ser de um tipo numérico não primitivo (por exemplo,
Long
) e o seu nome deve seguir uma convenção (por exemplo, atributos
de versão têm sempre o nome version
):
@Entity public class Airplane { @Id @GeneratedValue(strategy=GenerationType.AUTO) private Long id; @Version private Long version; ... }
Para dar a conhecer ao Hibernate o novo tipo de objectos persistentes é
necessário alterar o ficheiro hibernate.cfg.xml
para que inclua a
classe recém-anotada.
... <hibernate-configuration> <session-factory> ... <mapping class="examples.hibernate.domain.Airplane" /> ... </session-factory> </hibernate-configuration>
Documentação: http://www.hibernate.org/hib_docs/annotations/reference/en/html/ch01.html#setup-configuration
O Hibernate suporta três formas de mapear uma hierarquia de classes num modelo relacional:
TABLE_PER_CLASS
);
SINGLE_TABLE
);
JOINED
).
Para definir qual é o tipo de mapeamento escolhido, utiliza-se a anotação
@Inheritance
na classe de topo da hierarquia.
Nota: A anotação de interfaces não é suportada |
Adicionalmente, o Hibernate permite definir a herança de atributos de uma
super-classe quando esta não é persistente, através da anotação
@MappedSuperclass
. No exemplo que se segue, os valores dos atributos
herdados name
e weight
serão guardados na mesma tabela que os
atributos id
e version
(neste caso, a tabela Airplane
descrita
abaixo) quando uma instância de Airplane
for persistida:
@MappedSuperclass public class Aircraft { private String name; private long weight; .... } @Entity public class Airplane extends Aircraft { @Id @GeneratedValue(strategy=GenerationType.AUTO) private Long id; @Version private Long version; ... }
CREATE TABLE Airplane ( name VARCHAR(255), weight BIGINT NOT NULL, id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, objVersion BIGINT NOT NULL, );
Quando se usa uma estratégia uma tabela por classe, são definidas tantas tabelas quantas as classes da hierarquia. Em cada tabela são definidas colunas que permitem guardar todos os atributos persistentes da classe que lhe está associada (incluindo os herdados).
Esta estratégia impede o uso de alguns dos geradores automáticos de
identificadores que o Hibernate disponibiliza (nomeadamente os AUTO
e
IDENTITY
, que correspondem ao uso de uma coluna identificador gerida
pelo sistema de gestão de base de dados3) dado que o identificador
deve ser único entre as várias tabelas.
Adicionalmente, só são suportadas associações um-para-muitos se estas
forem bidireccionais4. O exemplo que se segue define
uma hierarquia com a classe Airplane
e a sua subclasse Jet
, que define
um atributo adicional turbines
que define o número de turbinas de um
avião a jacto. A estratégia usando uma tabela por classe5 é
apresentada de seguida, juntamente com as tabelas correspondentes:
@Entity @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) public class Airplane { @Id private Long id; @Version private Long version; ... } @Entity public class Jet extends Airplane { private int turbines; ... }
CREATE TABLE Airplane ( id BIGINT NOT NULL PRIMARY KEY, objVersion BIGINT NOT NULL ); CREATE TABLE Jet ( id BIGINT NOT NULL PRIMARY KEY, objVersion BIGINT NOT NULL, turbines INTEGER NOT NULL );
Documentação: http://www.hibernate.org/hib_docs/annotations/reference/en/html/entity.html#d0e808
Utilizando esta estratégia, todos os atributos de todas as classes da hierarquia são mapeados na mesma tabela, sendo as instâncias distinguidas por uma coluna discriminante adicional.
Na classe topo da hierarquia é necessário, além de definir a estratégia
de herança, identificar a coluna discriminante, através da anotação
@DiscriminatorColumn
(permite definir o nome, por omissão DTYPE
, e
o tipo da coluna, por omissão uma String).
Cada classe deve ainda definir o valor da coluna discriminante, através
da anotação @DiscriminatorValue
(por omissão, o valor corresponde ao
nome da classe persistente).
@Entity @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorColumn(name="planeType", discriminatorType=DiscriminatorType.STRING) @DiscriminatorValue("Standard Airplane") public class Airplane { @Id @GeneratedValue(strategy=GenerationType.AUTO) private Long id; @Version private Long version; ... } @DiscriminatorValue("Jet Aircraft") @Entity public class Jet extends Airplane { private int turbines; ... }
CREATE TABLE Airplane ( id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, planeType VARCHAR(255) DEFAULT NULL, objVersion BIGINT NOT NULL, turbines INTEGER NOT NULL );
Documentação: http://www.hibernate.org/hib_docs/annotations/reference/en/html/entity.html#d0e829
Nesta estratégia é definida uma tabela para cada classe da hierarquia, que contém apenas os atributos definidos nessa classe. Para se obterem as instâncias de uma dada classe é necessário fazer um join da tabela que representa a classe e de todas as tabelas que representam as classes acima na hierarquia.
As anotações @PrimaryKeyJoinColumn
e @PrimaryKeyJoinColumns
são
usadas para definir, nas subclasses, a(s) chave(s) primária(s) a
utilizar para efectuar o join. Por omissão, são utilizados os mesmos
nomes de chave primária.
No exemplo que se segue todas as classes da hierarquia usam a estratégia
uma tabela por subclasse. Entre as classes Airplane
e Jet
é feito
um join usando os mesmos nomes para as chaves primárias. Entre as
classes Airplane
e RocketAircraft
é feito um join usando a
condição Airplane.id = RocketAircraft.planeId
.
@Entity @Inheritance(strategy = InheritanceType.JOINED) public class Airplane { @Id @GeneratedValue(strategy=GenerationType.AUTO) private Long id; @Version private Long version; ... } @Entity public class Jet extends Airplane { private int turbines; ... } @Entity @PrimaryKeyJoinColumn(name="planeId") public class RocketAircraft extends Airplane { private String rocketType; ... }
CREATE TABLE Airplane ( id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, objVersion BIGINT NOT NULL ); CREATE TABLE Jet ( id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, turbines INTEGER NOT NULL ); CREATE TABLE RocketAircraft ( planeId BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, rocketType VARCHAR(255) );
Documentação: http://www.hibernate.org/hib_docs/annotations/reference/en/html/entity.html#d0e865
Todos os atributos não estáticos e não transientes de um objecto persistente são por omissão persistentes, excepto se anotados com
@Transient
. É possível identificar explicitamente um atributo como
persistente utilizando a anotação @Basic
(que permite, adicionalmente,
definir a politica de carregamento de atributos).
O Hibernate suporta directamente o mapeamento de todos os tipos primitivos do Java, suas classes wrapper e qualquer classe serializável.
Valores Enum
podem ser representados numa coluna quer como ordinais (é
guardado o ordinal do tipo enumerado), quer como texto (é guardada a
representação textual do tipo). Por omissão é utilizada a representação ordinal para enumerados, mas pode definir-se explicitamente o tipo de
representação (ORDINAL
ou STRING
) através da anotação @Enumerated
.
Em Java a precisão de um atributo temporal não está definida, pelo que a
mesma classe (java.util.Date
) pode representar uma data, um instante ao
longo do dia ou ambos (um instante numa dada data). A semântica do atributo
temporal é no entanto relevante para a representação em base de dados,
pelo que se pode usar a anotação @Temporal
para a definir (DATE
para
uma data, TIME
para um instante ao longo do dia e TIMESTAMP
para um
instante numa dada data). Por omissão, o Hibernate assume a precisão máxima para atributos temporais.
Caso se pretenda guardar uma sequência de bytes ou caracteres de grande dimensão
pode usar-se a anotação @Lob
do Hibernate.
@Entity public class Airplane { ... private static int airplaneCount; // transient property private transient double weightInPounds; // transient property @Transient private String currentPilot; // transient property private long wingSpan; // persistent property @Basic private long length; // persistent property @Enumerated(EnumType.STRING) private AirplaneState state; // enum persisted as String in database @Temporal(TemporalType.DATE) private Date lastMaintenance; // persistent property @Lob private String specs; // persistent property persisted as a CLOB ... }
CREATE TABLE Airplane ( ... wingSpan BIGINT NOT NULL, length BIGINT NOT NULL, state VARCHAR(255), lastMaintenance DATE, specs TEXT ... );
No exemplo anterior, os valores dos atributos airplaneCount
(atributo
de classe), weightInPounds
(um atributo transiente) e currentPilot
(anotado com @Transient
) não serão guardados na base de dados.
wingSpan
e length
são atributos persistentes que serão guardados na
base de dados. O atributo maintenanceStatus
é um enumerado, e será
guardada na base de dados a sua representação textual. Finalmente o
atributo lastMaintenance
representa o dia da última manutenção, pelo que
apenas será guardada na base de dados a informação relativa à data (não
sendo relevante o instante em que ocorreu).
Documentação: http://www.hibernate.org/hib_docs/annotations/reference/en/html/entity.html#d0e304
Por omissão, os atributos persistentes são guardados em colunas com o nome do atributo. Caso tal não seja desejado, pode utilizar-se a
anotação @Column
para definir explicitamente o nome da coluna onde o
valor do atributo será guardado (no exemplo, a coluna chamar-se-á
WING_SPAN
):
@Entity public class Airplane { ... @Column(name="WING_SPAN") private long wingSpan; ... }
Documentação: http://www.hibernate.org/hib_docs/annotations/reference/en/html/entity.html#entity-mapping-property-column
Quando existem associações entre um (ou mais) objecto(s) persistente(s) e
um outro objecto persistente, um dos objectos (objecto origem) guarda
uma referência para o outro (objecto destino). No modelo relacional
isto equivale à tabela que corresponde ao objecto origem ter uma coluna
com uma chave estrangeira para a tabela que corresponde ao objecto
destino, cuja declaração é suportada pela anotação @JoinColumn
.
Por omissão é gerada uma coluna cujo nome é composto pelo nome do
atributo, no objecto origem, que mantém a referência para o objecto
destino, seguido do carácter _
(underscore), seguido do nome do
atributo identificador da classe destino.
Para criar uma associação um-para-um entre dois objectos persistentes
usa-se a anotação @OneToOne
no atributo que guarda a referência.
@Entity public class Airplane { ... @OneToOne @JoinColumn(name="engineNumber") private Engine engine; ... } @Entity public class Engine { @Id private Long id; ... }
CREATE TABLE Airplane ( ... engineNumber BIGINT UNIQUE REFERENCES Engine (id), ... ); CREATE TABLE Engine ( id BIGINT NOT NULL PRIMARY KEY, ... );
Nota: No exemplo, o uso da anotação @JoinColumn
define explicitamente
o nome da coluna que guarda a chave de estrangeira como engineNumber
.
Documentação: http://www.hibernate.org/hib_docs/annotations/reference/en/html/entity.html#d0e998
Uma associação muitos-para-um define-se através da anotação @ManyToOne
no atributo que guarda a referência.
@Entity public class Airline { @Id private Long id; ... } @Entity public class Airplane { ... @ManyToOne private Airline airline; ... }
CREATE TABLE Airline ( id BIGINT NOT NULL PRIMARY KEY, ... ); CREATE TABLE Airplane ( ... airline_id BIGINT REFERENCES Airline (id), ... );
Nota: Na ausência da anotação @JoinColumn
, a coluna que guarda a
chave estrangeira tem o valor de omissão: airline_id
.
Documentação: http://www.hibernate.org/hib_docs/annotations/reference/en/html/entity.html#d0e1136
Quando uma associação é navegável nos dois sentidos, ambos os lados da
associação guardam uma referência para o outro lado da associação. Em
Hibernate, todas as anotações que definem associações podem receber um
parâmetro mappedBy
, que define o nome do atributo, no objecto
responsável pela associação, onde a associação está definida.
Mantendo o exemplo da associação um-para-um, introduzir bidireccionalidade
corresponde a estabelecer a relação um-para-um no sentido inverso (de
Engine
para Airplane
), indicando que Airplane
é responsável pela
actualização do estado da associação:
@Entity public class Airplane { ... @OneToOne @JoinColumn(name="engineNumber") private Engine engine; ... } @Entity public class Engine { @Id private Long id; @OneToOne(mappedBy="engine") private Airplane airplane; ... }
Nota: A bidireccionalidade de uma associação não traz implicação alguma no modelo relacional correspondente.
Exemplo: associação um-para-um entre Airplane
e Engine
.
Uma colecção corresponde a uma associação entre um (ou mais) objecto(s) e um conjunto arbitrário de objectos. O Hibernate permite mapear as várias semânticas disponibilizadas na plataforma Java.
Documentação: http://www.hibernate.org/hib_docs/annotations/reference/en/html/entity.html#entity-mapping-association-collections
Tendo uma associação um-para-muitos unidireccional, tipicamente faz sentido o elemento unitário ser o responsável pela relação, pelo que o modelo relacional necessita de uma tabela intermédia para representar a relação.
A tabela intermédia define-se através da anotação @JoinTable
, que
recebe como parâmetros o nome da tabela (name
), e o nome das colunas
que guardam chaves estrangeiras para cada uma das tabelas relacionadas:
joinColumns
define a(s) coluna(s) que guarda(m) a chave estrangeira
para a tabela que representa o objecto responsável;
inverseJoinColumns
que define o nome da(s) coluna(s) que guarda(m) a
chave estrangeira para a tabela que representa o objecto elemento da
colecção.
@Entity public class Airline { @Id private Long id; @OneToMany @JoinTable( name = "Fleet", joinColumns = @JoinColumn(name = "airlineCode"), inverseJoinColumns = @JoinColumn(name = "planeNumber") ) private Set<Airplane> airplanes; ... } @Entity public class Airplane { @Id private Long id; ... }
CREATE TABLE Airline ( id BIGINT NOT NULL PRIMARY KEY, ... ); CREATE TABLE Airplane ( id BIGINT NOT NULL PRIMARY KEY, ... ); CREATE TABLE Fleet ( airlineCode BIGINT NOT NULL REFERENCES Airline (id), planeNumber BIGINT NOT NULL UNIQUE REFERENCES Airplane (id) PRIMARY KEY (airlineCode, planeNumber), );
Caso se omita a anotação @JoinTable
, ou algum dos seus parâmetros:
name
é definido como o nome da classe responsável, seguido do
carácter _
(underscore), seguido do nome da classe elemento da
colecção;
joinColumns
é definido como o nome da classe responsável, seguido do
carácter _
(underscore), seguido do nome da coluna que guarda a sua
chave primária;
inverseJoinColumns
é definido como o nome do atributo que define a
associação na classe responsável, seguido do carácter _
(underscore), seguido do nome da coluna que guarda a chave primária
da classe elemento.
Numa associação um-para-muitos bidireccional, a classe elemento é
normalmente a responsável pela associação, pelo que corresponde a
introduzir bidireccionalidade numa associação muitos-para-um (utilizando
a anotação @OneToMany
com o parâmetro mappedBy
).
Mantendo o exemplo da relação muitos-para-um, introduzir
bidireccionalidade corresponde a estabelecer a associação um-para-muitos
no sentido inverso (de Airline
para Airplane
), indicando que
Airplane
é responsável pela actualização do estado da associação:
@Entity public class Airline { @Id private Long id; @OneToMany(mappedBy="airline") private Set<Airplane> airplanes; ... } @Entity public class Airplane { ... @ManyToOne private Airline airline; ... }
Nota: A bidireccionalidade de uma associação muitos-para-um não trás implicação alguma no modelo relacional correspondente.
Documentação: http://www.hibernate.org/hib_docs/annotations/reference/en/html/entity.html#entity-mapping-association-collection-onetomany
Exemplo: associação um-para-muitos entre Airline
e Airplane
.
Uma associação muitos-para-muitos corresponde sempre, no modelo relacional, a definir uma tabela intermédia para estabelecer a relação entre as tabelas que representam os dois lados da associação.
Para identificar uma associação deste tipo utiliza-se a anotação
@ManyToMany
, podendo ser definida explicitamente a tabela intermédia,
como já descrito nas relações um-para-muitos unidireccionais.
@Entity public class Airplane { @Id private Long id; @ManyToMany private Set<Passenger> passengers; ... } @Entity public class Passenger { @Id private Long id; ... }
CREATE TABLE Airplane ( id BIGINT NOT NULL PRIMARY KEY, ... ); CREATE TABLE Passenger ( id BIGINT NOT NULL PRIMARY KEY, ... ); CREATE TABLE Airplane_Passenger ( Airplane_id BIGINT NOT NULL REFERENCES Airplane (id), passengers_id BIGINT NOT NULL REFERENCES Passenger (id) PRIMARY KEY (Airplane_id, passengers_id), );
De forma semelhante ao que se faz nas restantes associações, introduzir
bidireccionalidade corresponde a anotar, na classe que não é responsável
pela relação, o atributo que representa a associação com @ManyToMany
parametrizado com mappedBy
:
@Entity public class Airplane { ... @ManyToMany private Set<Passenger> passengers; ... } @Entity public class Passenger { @Id private Long id; @ManyToMany(mappedBy="passengers") private Set<Airplane> airplanes; ... }
Nota: A bidireccionalidade de uma associação não trás implicação alguma no modelo relacional correspondente.
Documentação: http://www.hibernate.org/hib_docs/annotations/reference/en/html/entity.html#eentity-mapping-association-collection-manytomany
Exemplo: associação muitos-para-muitos entre Airplane
e Passenger
.
Uma vez definido o mapeamento do modelo de domínio no modelo relacional correspondente, é agora necessário descrever o ciclo de vida de um objecto persistente, desde a sua criação até à sua destruição. A documentação do Hibernate tem um resumo sumário de como criar (e ler) um objecto persistente.
Uma unidade de trabalho é representada por uma instância de Session
. Para
se obter uma instância de Session
utiliza-se uma fábrica SessionFactory
fornecida pelo Hibernate e previamente configurada. Esta configuração é
tipicamente feita numa classe auxiliar, cujo código não repetimos aqui. O
método getCurrentSession
de uma SessionFactory
devolve a unidade de
trabalho corrente, isto é, a instância de Session
associada ao fio de
execução em que o método é invocado (e criando uma nova caso não haja
nenhuma).
É através de uma Session
que se pode iniciar uma transacção de base de
dados. Quando a transacção termina (com ou sem sucesso) o Hibernate termina
também a unidade de trabalho corrente. Quaisquer operações que envolvam
comunicação com a base de dados (independentemente de se uma leitura ou
escrita) têm de ser realizadas no contexto de uma transacção.
Assume-se que os restantes exemplos desta secção se executam no contexto de uma transacção obtida segundo o seguinte padrão:
Session session = factory.getCurrentSession(); // obtain/start unit of work Transaction tx = null; try { tx = session.beginTransaction(); // start transaction ... // do some work tx.commit(); // commit transaction & end unit of work } catch (RuntimeException ex) { if (tx != null) tx.rollback(); // abort transaction throw ex; }
Um objecto recém-criado encontra-se no estado transiente, sendo
necessário associá-lo a uma unidade de trabalho do Hibernate para o tornar
persistente. Esta associação é feita através do método save
de uma
instância de Session
:
... Airplane airplane = new Airplane("Air Force 1", ...); session.save(airplane); // make object persistent ...
Nota: Uma vez que foi definido um gerador automático de identificadores para
Airplane
, só após a invocação do método save
será atribuido o
identificador à instância do objecto.
Documentação: http://www.hibernate.org/hib_docs/v3/reference/en/html/objectstate.html#objectstate-makingpersistent
O Hibernate permite obter objectos persistentes através de dois mecanismos complementares:
As secções seguintes detalham cada um destes casos.
Nota: Num programa orientado a objectos as associações entre objectos são o mecanismo natural para obter referências para outros objectos. Embora se possam invocar aspectos de eficiência de execução para justificar o carregamento de objectos através do atributo identificador ou de pesquisa na base de dados, os custos a nível de manutenção do código aumentam substancialmente visto haver uma maior dependência do mecanismo de persistência/acesso. Por esta razão, o uso dos mecanismos de acesso a objectos fornecidos pelo Hibernate deve ser bem ponderado (idealmente apenas os objectos raiz do grafo de objectos precisam de ser carregados directamente). |
Para obter um objecto persistente do qual sabemos previamente o
identificador podemos usar o método load
de uma instância de Session
.
O método load
recebe como argumentos a classe a ler e o identificador a
utilizar e devolve um novo objecto persistente dessa classe, com os dados
obtidos da base de dados.
... Long id = new Long(554); // load does not accept primitive types Airplane plane = (Airplane) session.load(Airplane.class, id); ...
Nota: Se não existir um registo na tabela Airplane
da base de dados
com o identificador 554
, o método load
lança uma excepção.
Documentação: http://www.hibernate.org/hib_docs/v3/reference/en/html/objectstate.html#objectstate-loading
Quando se pretendem obter vários objectos que partilham uma dada característica (ou o identificador do objecto que pretendemos não é conhecido a priori), o Hibernate disponibiliza duas formas de pesquisa: uma linguagem de pesquisa (HQL) e uma interface programática (Query By Criteria & Example).
Para fazer uma pesquisa utilizando a interface programática basta criar
uma nova instância de Criteria
(através de uma instância de Session
),
parametrizada para a classe a pesquisar, e invocar um dos métodos list
ou
uniqueResult
para obter os resultados da pesquisa.
Adicionar uma restrição à pesquisa faz-se através do método add
, que
recebe como argumento a restrição (as restrições possíveis são tipicamente
obtidas através da classe Restrictions). Por fim é possível ordenar os resultados através do método addOrder
que recebe como argumento o tipo de ordenação desejado.
List<Airplane> planesForMaintenance = session.createCriteria(Airplane.class) .add( Restrictions.eq("state", "Maintenance due") ) .addOrder( Order.asc("lastMaintenance") ) .addOrder( Order.asc("name") ) .list();
Neste exemplo, pretende-se a lista de todos os aviões que necessitam de manutenção, ordenados por data da última manutenção e nome do avião.
Documentação: http://www.hibernate.org/hib_docs/v3/reference/en/html/objectstate.html#objectstate-querying
Ao manipular um objecto persistente no contexto de uma unidade de trabalho, as alterações serão automaticamente detectadas e persistidas quando a unidade de trabalho terminar.
Documentação: http://www.hibernate.org/hib_docs/v3/reference/en/html/objectstate.html#objectstate-modifying
Um objecto persistente pode ser tornado transiente (eliminado da base de
dados) através do método delete
na classe Session
. É no entanto
importante referir que as referências em memória se mantêm, pelo que se
mantém a necessidade de eliminar o objecto (agora transiente) do grafo de
objectos da aplicação6.
... // obtain plane to remove Airplane plane = (Airplane) session.load(Airplane.class, new Long(554)); // obtain the only airline available Airline airline = (Airline) session.createCriteria(Airline.class).uniqueResult(); airline.removeAirplane(plane); // remove all references to plane session.delete(plane); // make object transient ...
No exemplo, existe uma única companhia aérea (singleton), ao qual pertence o avião que pretendemos remover. Para eliminar o avião é necessário:
Documentação: http://www.hibernate.org/hib_docs/v3/reference/en/html/objectstate.html#objectstate-deleting
Este exercício utiliza o ImportAnt, nomeadamente os fragmentos relativos a
Hibernate e aplicações de linha de comandos. Para classes de domínio
recém-anotadas, é necessária actualizar o ficheiro de configuração do Hibernate. O ImportAnt simplifica este processo, permitindo definir no
build.xml
as classes persistentes. Para o fazer é necessário redefinir,
no build.xml
, o alvo -replace-hibernate-custom-tokens(dir)
7.
O alvo generate-db-schema
, fornecido pelo fragmento hibernate.xml
, deve
ser utilizados para gerar um novo esquema de base de dados com base na
informação das classes anotadas.
O alvo run
, fornecido pelo fragmento console-app.xml
é utilizado para
executar uma aplicação de linha de comandos. É possível passar argumentos à
aplicação através da propriedade run.args
. Esta propriedade pode
definir-se directamente na linha de comandos através da opção -D
do Ant:
$ ant -Drun.args=Create run
Complete o mapeamento objecto/relacional e garanta que as operações fornecidas pelas aplicações utilizam adequadamente a base de dados.
1 O Hibernate pode ser utilizado quer quando já existe um modelo relacional com tabelas definidas (permitindo gerar classes a partir das tabelas); quer quando se parte do modelo de objectos (permitindo gerar as tabelas a partir das classes anotadas).
2 "Design Patterns: Elements of Reusable Object-Oriented Software", E. Gamma, R. Helm, R. Johnson e J. Vlissides, Addison-Wesley Professional, 2004.
3 Por exemplo, chaves primárias com a propriedade AUTO_INCREMENT
(no
MySQL) ou IDENTITY
(em SQL Server).
4 Porque não é necessária uma tabela intermédia para estabelecer a relação, em que uma das colunas precisaria de ser chave estrangeira para várias tabelas diferentes.
5 No exemplo, não é definido nenhum gerador, pelo que terá de ser a própria aplicação a gerir os identificadores, garantindo a sua unicidade.
6 Eliminar um objecto corresponde a eliminar todas as referências para ele, de modo a que o Garbage Collector do Java possa libertar a memória que está associada ao objecto.
7 Durante o processo de build da aplicação esta informação será utilizada
para actualizar o ficheiro hibernate.cfg.xml
.
Date: 2008/03/05 10:34:36 AM