Persistência de dados com Hibernate 4 em uma aplicação Spring

Nesse artigo, irei mostrar como criar as classes básicas para configurar a camada de persistência de dados de uma aplicação Spring usando o Hibernate.

Esse artigo será dividido em três partes:

  • Configuração: implementação da classe de configuração do Hibernate, onde residirão os beans usados pelas demais classes para acessar o banco de dados;
  • DAO: implementação das classes que definirão métodos para executar diversas operações no banco de dados usando os beans definidor anteriormente;
  • Entidades: implementação das classes que definirão como os dados serão armazenados no banco de dados.
Recomendo, para melhor organização do código (o que facilita manutenções futuras, organizar suas classes em pacotes. Uma sugestão seria o esquema a seguir:
config
–hibernate
persistence
–model

Configuração

Aqui, veremos como implementar uma classe onde residirão os beans que serão usados no restante de seu projeto para acessar o banco de dados. Daremos a essa classe o nome HibernateConfig.
Em primeiro lugar, usaremos algumas anotações para essa classe que definirão algumas características e propriedades dela:
@Configuration -> diz ao sistema que essa classe terá ajustes que poderão ser usados em outras partes da aplicação.
@EnableTransactionManagement -> diz ao sistema que usaremos o gerenciamento de transações do Spring para executar nossas consultas ao banco de dados.
@PropertySource({ “classpath:hibernate.properties” }) -> diz ao sistema onde encontrar o arquivo de propriedades do hibernate; uma sugestão é colocar esse arquivo na pasta src/ do seu projecto.
O arquivo hibernate.properties possui o seguinte formato:
jdbc.driverClassname=org.postgresql.Driver
jdbc.url=jdbc:postgresql://localhost:5432/maven_test?charSet=LATIN1
jdbc.user=klebermo
jdbc.pass=123
hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
hibernate.show_sql=true
hibernate.hbm2ddl.auto=update
@ComponentScan({ “spring.example.persistence” }) -> por fim, essa anotação indica onde estão as classes Dao do seu projeto.
A classe HibernateConfig terá cinco métodos:
  • public LocalSessionFactoryBean sessionFactory()
  • public DataSource restDataSource()
  • public HibernateTransactionManager transactionManager(SessionFactory sessionFactory)
  • public PersistenceExceptionTranslationPostProcessor exceptionTranslation()
  •  Properties hibernateProperties()
Todas essas classes devem ser anotadas com @Bean, e transactionManager também dever ser anotada como @Autowired.
O primeiro deles, sessionFactory(),  cria uma sessão para acesso ao banco de dados, e configura para essa sessão atributos básicos, como a fonte de dados, localização das entidades e propriedades. Uma implementação básica para essa classe seria:
@Bean
public DataSource restDataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(env.getProperty("jdbc.driverClassname"));
dataSource.setUrl(env.getProperty("jdbc.url"));
dataSource.setUsername(env.getProperty("jdbc.user"));
dataSource.setPassword(env.getProperty("jdbc.pass"));return dataSource;
}
O segundo método,  restDataSource(), abre uma conexão com a fonte dos dados, no caso, um banco de dados criado em um sistema gerenciador a sua escolha, com base nos dados lidos do arquivo hibernate.properties:
@Bean
public DataSource restDataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(env.getProperty("jdbc.driverClassname"));
dataSource.setUrl(env.getProperty("jdbc.url"));
dataSource.setUsername(env.getProperty("jdbc.user"));
dataSource.setPassword(env.getProperty("jdbc.pass"));return dataSource;
}
O terceiro método, transactionManager(), associa à sessão de acesso ao banco de dados o gerenciamento de transações do Spring. Sua implementação seria:
@Bean
@Autowired
public HibernateTransactionManager transactionManager(SessionFactory sessionFactory) {
HibernateTransactionManager txManager = new HibernateTransactionManager();
txManager.setSessionFactory(sessionFactory);return txManager;
}
O quarto método, apenas cria um tradutor de exceções para tratar os erros que podem ocorrer durante o acesso ao banco de dados:
@Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
return new PersistenceExceptionTranslationPostProcessor();
}
O último dos método, faz a leitura das demais propriedades contidas no arquivo hibernate.properties, como hbm2ddl.auto, que define com o Hibernate irá lidar com as entidades (podem cria-las no banco de dados caso não existam ou apenas fazer a validação delas com as tabelas existentes) e dialect, que define o dialeto a ser usado para comunicação com o SGBD:
Properties hibernateProperties() {
return new Properties() {
/**
*
*/
private static final long serialVersionUID = 1L;{
setProperty("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto"));
setProperty("hibernate.dialect", env.getProperty("hibernate.dialect"));
setProperty("hibernate.globally_quoted_identifiers", "true");
}
};
}

Classes DAO

Nessas classes, que devem ser uma para cada tabela do banco de dados, serão implementadas as operações de escrita e consulta  ao banco.
Como todas as classes DAO terão basicamente os mesmos métodos, nesse artigo usaremos a seguinte abordagem:
  • Teremos uma classe genérica contendo todos os métodos de acesso ao banco de dados:
@Repository
public class Dao<E> {
private final E entity;@Autowired
SessionFactory sessionFactory;protected Session getCurrentSession(){
return sessionFactory.getCurrentSession();
}public Dao() {
this.entity = null;
}
public Dao(E entity) {
this.entity = entity;
}
@SuppressWarnings("unchecked")
public Dao(Class<?> classe) {
this.entity = (E) classe;
}
public E getEntity() {
return this.entity;
}
}
  • Para cada tabela do banco de dados, teremos uma classe que segue exatamente a seguinte a estrutura:
@Repository
public class EntidadeDao extends Dao<Entidade> {
public EntidadeDao() {
super(Entidade.class);
}
}
Na nossa classe genérica, teremos no minimo os seguintes métodos, todos anotados com @Transactional:
  • persist() -> adiciona um novo campo a tabela
public boolean persist(E transientInstance) {
try {
sessionFactory.getCurrentSession().persist(transientInstance);
return true;
} catch (RuntimeException re) {
return false;
}
}
  • remove() -> remove um campo da tabela
public boolean remove(E transientInstance) {
try {
sessionFactory.getCurrentSession().delete(transientInstance);
return true;
} catch (RuntimeException re) {
return false;
}
}
  • merge() -> atualiza um campo da tabela
public E merge(E detachedInstance) {
try {
E result = (E) sessionFactory.getCurrentSession().merge(detachedInstance);
return result;
} catch (RuntimeException re) {
return null;
}
}
  • findById() -> retorna um objeto que contém os dados uma entidade (campo de uma tabela do banco de dados) identificado pela chave primária id:
public E findById(int id) {
try {
E instance = (E) sessionFactory.getCurrentSession().get(entity.getClass(), id);
return instance;
} catch (RuntimeException re) {
return null;
}
}
  • findAll() -> retorna um objeto List<> contendo todos os campos de uma tabela
public List<E> findAll() {
try {
List<E> instance = sessionFactory.getCurrentSession().createCriteria(entity.getClass()).list();
return instance;
} catch (RuntimeException re) {
return null;
}
}
  • findByField(String field, String value) -> retorna um objeto que contem os dados de uma entidade (campo de uma tabela do banco de dados) que tenha o campo field com o valor value:
public E findByField(String field, String value) {
try {
String expressao = entity.toString();
String nome_classe = new String();
StringTokenizer st = new StringTokenizer(expressao);
while (st.hasMoreTokens()) {
nome_classe = st.nextToken();
}
String query = "from "+nome_classe+" where "+field+" = :data";Query q = sessionFactory.getCurrentSession().createQuery(query);
q.setParameter("data", value);
E instance = (E) q.uniqueResult();
return instance;
} catch (RuntimeException re) {
return null;
}
}

Entidades

Aqui, definiremos como as tabelas do nosso banco de dados são compostas. A implementação dessas classes é de extrema importância, pois, dependendo do valor do atributo hbm2ddl.auto, o conteúdo dessas classes servirá de base para que o Hibernate crie as tabelas no seus banco de dados.
A definição dessa classe não passa se uma simples classe POJO (Plain Object Java Object), como atributos e métodos getter e setter para cada um desses atributos, e algumas anotações para a classe e para os atributos. Essas anotações são as seguintes:
  • Para a classe:
Duas anotações são usadas:
@Entity e @Table(name=”nome_da_tabela”)
O atributo name da anotação Table é opcional, e define com que nome a entidade deverá ser criado no banco de dados. Caso não seja especificado, a tabela será criado com o nome da classe.
  • Para os atributos
As anotações para os atributos variam dependendo do tipo do atributo. Essas anotações podem ser associadas com o método getter do atributo.
chave primária
@Id
@Column(name = “id”)
@GeneratedValue(strategy=GenerationType.AUTO)
chave estrangeira (relacionamento OneToOne)
@OneToOne
@JoinColumn(name=”nome_da_chave_estrangeira”)
chave estrangeira (relacionamento ManyToMany ou ManyToOne ou OneToMany)
@ManyToMany
@JoinTable(name=”autorizacao_usuario”, joinColumns={@JoinColumn(name=”fk_usuario”)}, inverseJoinColumns={@JoinColumn(name=”fk_autorizacao”)})
@LazyCollection(LazyCollectionOption.FALSE)
O atributo associado a chave estrangeira acima precisam ser do tipo List<> obrigatoriamente.
Os demais atributos devem  ter apenas a anotação @Column. Mais uma vez, o atributo name dessa anotação é opcional, caso não seja especificado, será criado na tabela um campo com o nome do atributo.