Durante o desenvolvimento ou manutenção (principalmente) de um projeto de software, seja qual for o estilo, é sempre delicado lidar com os componentes de infra-estrutura responsáveis pela camada de persistência.
Vou descrever alternativas que o
Spring Framework oferece para simplificar esse trabalho, em cenários variados, como o foco em base de dados relacional. Ou seja, um overview das ferramentas do Spring para trabalhar com os componentes de persistência.
Código legado, bem legado...
Projetos Java antigos, desenvolvidos a + de 8 anos, normalmente não utilizam uma solução ORM (Mapeamento Objeto Relacional). Nesses projetos é muito comum o uso de bibliotecas "caseiras", escritas in house, para otimizar o uso do
JDBC.
O
Spring JDBC é um modulo do Spring interessante para esse tipo de cenário. Com ele é possível reduzir consideravelmente o volume de código
JDBC. O principal componente do Spring JDBC é o
JdbcTemplate, ele disponibiliza uma série de métodos para operações CRUD, consultas e comandos em lote. Para tirar proveito do uso contextos, injeção de dependências e inversão de controle, faz todo o sentido trabalhar em conjunto com o Spring Bean. Dessa forma seria possível injetar a referência do JdbcTemplate nos componente
DAO (pattern
Data Access Object).
O código a seguir demonstra um fragmento do
DAO que utiliza o
JdbcTemplate para inserir/atualizar uma entidade (
Mercadoria).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | @Component
public class MercadoriaDAO {
private final static String INSERT_MERCADORIA = "INSERT INTO mercadoria (nome,descricao,preco,quantidade) VALUES (?,?,?,?)" ;
private final static String UPDATE_MERCADORIA = "UPDATE mercadoria SET nome = ?, descricao = ?, preco = ?, quantidade = ? WHERE id = ?" ;
private final static String GET_MERCADORIA_BY_ID = "SELECT * FROM mercadoria WHERE id = ?" ;
private final static String GET_MERCADORIAS_BY_NOME = "SELECT * FROM mercadoria WHERE nome like ?" ;
@Autowired
private JdbcTemplate jdbcTemplate;
public void save(Mercadoria m) {
if (m.getId() == null ) {
jdbcTemplate.update(INSERT_MERCADORIA,
new Object[]{ m.getNome(), m.getDescricao(), m.getPreco() });
} else {
jdbcTemplate.update(UPDATE_MERCADORIA,
new Object[]{ m.getNome(), m.getDescricao(), m.getPreco(), m.getId() });
}
}
...
}
|
Além do método
update também pode ser utilizado para realizar a remoção da entidade. No trecho de código a seguir coloco dois exemplos de consultas, utilizando
JdbcTemplate. Note que na consulta utilizamos o componente
RowMapper, o
MercadoriaRowMapper. O
RowMapper é utilizado pelos métodos
query de
JdbcTemplate, ele lê os dados do
ResultSet e faz a transformação em uma instância de
Mercadoria.
O método
queryForObject é utilizado para retornar uma instância da entidade (ou
null) com filtro por
id, por exemplo. Enquanto o método
query retorna uma lista de objetos que podem ser encontrados de acordo com o SQL.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | ...
private class MercadoriaRowMapper implements RowMapper<Mercadoria> {
public Mercadoria mapRow(ResultSet rs, int row) throws SQLException {
int id = rs.getInt( "id" );
String nome = rs.getString( "nome" );
String descricao = rs.getString( "descricao" );
double preco = rs.getDouble( "preco" );
return new Mercadoria(id, nome, descricao, qtde, preco);
}
}
public Mercadoria findById(Integer id) {
return jdbcTemplate.queryForObject(GET_MERCADORIA_BY_ID,
new Object[] { id }, new MercadoriaRowMapper());
}
public List<Mercadoria> getMercadoriasByNome(String nome) {
return jdbcTemplate.query(GET_MERCADORIAS_BY_NOME,
new Object[] { nome + "%" }, new MercadoriaRowMapper());
}
...
|
Outra estratégia, que não me agrada, seria fazer no
DAO a
Mercadoria uma herança para
JdbcDaoSupport, e acessar o
JdbcTemplate encapsulado nesse componente.
Com Spring JDBC o código
DAO pode ficar bem mais compacto. Para ilustrar isso, compare o
DAO c/ JDBC puro e o
DAO utilizando Spring JDBC. Veja também as
configurações do Spring e o o
pom.xml com as dependências para esses módulos.
Projetos com Hibernate
O Spring também oferece soluções para reduzir o esforço e agregar funcionalidades durante o desenvolvimento de projetos Java utilizando soluções ORM, como
Hibernate ou
JPA. O
Spring ORM é outro módulo da suíte Spring, ele oferece funcionalidades para facilitar o uso de soluções baseadas em Mapeamento Objeto Relacional.
Em versões antigas do
Hibernate, era trabalhoso manter a
Session vinculada ao contexto de execução, por exemplo utilizar a mesma instância em diferentes
DAOs dentro do mesmo fluxo de
request. Por isso o Spring criou o
HibernateTemplate, com a proposta de disponibilizar a
Session corrente ao contexto de execução.
Mas a partir do
Hibernate 3.0.1 com
contextual sessions, isso não é mais necessário. Nas versões mais recentes do Spring é possível usar a
Session no contexto de execução, além de centralizar as configurações e realizar a injeção da
SessionFactory nos
DAOs.
A seguir o código do
DAO da
Mercadoria com Hibernate:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | @Component
public class MercadoriaDAO {
@Autowired
private SessionFactory sessionFactory;
private final Session getCurrentSession(){
return this .sessionFactory.getCurrentSession();
}
public void save(Mercadoria m) {
if (m.getId() == null ) {
this .getCurrentSession().persist(m);
} else {
this .getCurrentSession().merge(m);
}
}
public Mercadoria findById(Integer id) {
return (Mercadoria) this .getCurrentSession().get(Mercadoria. class , id);
}
public List<Mercadoria> getMercadoriasByNome(String nome) {
return this .getCurrentSession()
.createQuery( "from model.Mercadoria m where m.nome like ?" )
.setParameter( 0 , nome+ "%" )
.list();
}
...
}
|
O
spring-config.xml a seguir centraliza no Spring as configurações com banco de dados e do
Hibernate. Nesse exemplo o banco de dados utilizado é o
HSQLDB (local).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | <? xml version = "1.0" encoding = "UTF-8" ?>
xsi:schemaLocation="
< context:component-scan base-package = "." />
< bean id = "sessionFactory" class = "org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean" >
< property name = "dataSource" ref = "ds" />
< property name = "packagesToScan" value = "model" />
< property name = "hibernateProperties" >
< props >
< prop key = "hibernate.dialect" > org.hibernate.dialect.HSQLDialect</ prop >
< prop key = "hibernate.show_sql" >true</ prop >
< prop key = "hibernate.hbm2ddl.auto" >create</ prop >
</ props >
</ property >
</ bean >
< bean id = "ds" class = "org.apache.commons.dbcp.BasicDataSource" destroy-method = "close" >
< property name = "driverClassName" value = "org.hsqldb.jdbcDriver" />
< property name = "url" value = "jdbc:hsqldb:file:mercadoria" />
< property name = "username" value = "sa" />
< property name = "password" value = "" />
</ bean >
...
</ beans >
|
Projetos com JPA++
O Spring também oferece funcionalidades bem interessantes para projetos que utilizam a
Java Persistence API (
JPA), através do módulo
Spring Data JPA. Através da interface
JpaRepository o Spring define os métodos de consulta e CRUD. O desenvolvedor trabalha de forma alto-nível, criando uma interface de persistência, enquanto a implementação fica por conta do próprio Spring. Outro elemento do Spring Data JPA é a anotação
@Query, responsável por informar consultas customizadas via
JPAQL.
Para esse tipo de componente o Spring adota o pattern
Repository, que abstrai uma coleção de objetos com o design mais próximo ao domínio da aplicação do que com o banco de dados. Veja como ficaria a interface
MercadoriaRepository:
1 2 3 4 5 6 | public interface MercadoriaRepository extends JpaRepository<Mercadoria, Integer> {
@Query ( "select m from Mercadoria m where m.nome like ?1" )
List<Mercadoria> getMercadoriasByNome(String nome);
}
|
Uma vez que a interface foi definida, o Spring cria um proxy que implementa os métodos de persistência. Esse proxy será injetado em classe de negócio/controller pelo Spring. Veja o exemplo:
1 2 3 4 5 6 7 8 9 10 11 | @Component
public class MercadoriaService {
@Autowired
private MercadoriaRepository repo;
public void save(Mercadoria m) {
m.save(m);
}
...
}
|
Nesse exemplo demonstrei funcionalidades básicas da interface
JpaRepository, mas existem outrs funcionalidades como o suporte a paginação da consulta SQL, veja esse
exemplo. No github compartilhamos outros dois projetos que utilizam Spring Data JPA, uma
aplicação web e outra
desktop. Ambas fazem uso do
JpaRepository.
Um pouco além: NoSQL
Na verdade esse módulo compõe o
Spring Data, uma solução "guarda-chuva" com o objetivo de unificar e simplicar o armazenamento de dados em bancos relacionais e NoSQL. Abaixo desse projeto existe o módulo
Spring Data MongoDB, responsável por abstrair o acesso ao
MongoDB. Não é o proposito desse post abordar soluções NoSQL, mas para ter uma idéia o trecho de código a seguir demonstra repositório da
Mercadoria (como
Document) em versão
MongoDB:
1 2 3 4 5 6 | public interface MercadoriaRepository extends MongoRepository<User, String> {
@Query ( "{ nome: ?0 }" )
List<User> getMercadoriasByNome(String nome);
}
|
Abaixo do Spring Data ainda existem sub-projetos para
Neo4j,
Apache Hadoop,
REST e outros. É possível saber um pouco mais sobre essa tecnologia, em artigo
introdutório sobre Spring Data no InfoQ Brasil.
Esse é apenas um resumo de algumas soluções oferecidas pelo Spring Framework para resolver questões relacionadas a persistência em projetos Java. Sou da turma que gosta do Spring e de Java EE. Acredito que tirando proveito das melhoras funcionalidades das duas stacks, aumentamos o nosso poder fogo e logo a possibilidade de desenvolver um projeto com sucesso.
http://twitter.com/edermag
http://www.yaw.com.br