Spring Framework
Parte 02 – acesso a dados e testes de
integração
H2 Database
• H2 é um SGBDR escrito em Java que pode atuar
tanto no modo embutido como na forma
cliente-servidor.
• Após instalação, executar o script h2.bat, ou
h2.sh, presente no diretório bin. Em seguida, usar
o navegador para acessar o endereço
http://192.168.56.1:8082/.
Dependências
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
</dependency>
Classes de configuração
• Classe AppConfig: configurações gerais da
aplicação que não possuem relação com a parte
web.
• Class AppConfigTest: configurações específicas
para execução de testes automatizados.
Classes de configuração: AppConfig
package cursoSpring.revenda_veiculos.config; import javax.sql.DataSource; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.datasource.DriverManagerDataSource; @Configurationpublic class AppConfig { @Bean
public DataSource dataSource(){
DriverManagerDataSource ds = new DriverManagerDataSource(); ds.setDriverClassName("org.h2.Driver"); ds.setUrl("jdbc:h2:tcp://localhost/~/curso_spring"); ds.setUsername("sa"); ds.setPassword(""); return ds; }
Classes de configuração: AppConfig
package cursoSpring.revenda_veiculos.config; import javax.sql.DataSource; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.datasource.DriverManagerDataSource; @Configurationpublic class AppConfig { @Bean
public DataSource dataSource(){
DriverManagerDataSource ds = new DriverManagerDataSource(); ds.setDriverClassName("org.h2.Driver"); ds.setUrl("jdbc:h2:tcp://localhost/~/curso_spring"); ds.setUsername("sa"); ds.setPassword(""); return ds; }
DriverManagerDataSource é uma implementação de DataSource que deve ser utilizada apenas em tempo
de desenvolvimento. Para produção, deve-se utilizar uma implementação que trabalhe com pool de conexões tal como Apache Commons DBCP ou C3P0.
Classes de configuração: AppConfigTest
package cursoSpring.revenda_veiculos.config;
import org.springframework.context.annotation.Profile; ...
@Configuration
public class AppConfigTest { @Bean
@Profile("test")
public DataSource dataSource(){
DriverManagerDataSource ds = new DriverManagerDataSource(); ds.setDriverClassName("org.h2.Driver"); ds.setUrl("jdbc:h2:tcp://localhost/~/curso_spring_test"); ds.setUsername("sa"); ds.setPassword(""); return ds; }
Crie esta classe na source folder
Entidade genérica
package cursoSpring.revenda_veiculos.dominio; import java.io.Serializable;
public abstract class Entidade implements Serializable{ private Integer id;
public Entidade() {}
public Entidade(Integer id) { super();
this.id = id; }
public Integer getId() { ... }
public void setId(Integer id) { .. } @Override public int hashCode() { ... }
@Override public boolean equals(Object obj) { ... } }
Use o assistente do Eclipse para criar os métodos getId,
Entidade Fabricante
package cursoSpring.revenda_veiculos.dominio; public class Fabricante extends Entidade{
private String descricao; public Fabricante() {}
public Fabricante(Integer id, String descricao) { super(id);
this.descricao = descricao; }
public String getDescricao() { ... }
public void setDescricao(String descricao) { ... } }
FabricanteRepository
package cursoSpring.revenda_veiculos.dominio; import java.util.List;
public interface FabricanteRepository { List<Fabricante> todos();
Fabricante getPorId(Integer idFabricante); Integer inserir(Fabricante f);
void atualizar(Fabricante f);
void excluir(Integer idFabricante); }
Spring JDBC
• Spring oferece classes que, se comparadas ao JDBC puro,
tornam mais simples o código de interação com banco de
dados. Além disso, provê uma hierarquia de exceções não
verificadas que facilitam o tratamento de exceções.
• Algumas classes:
– JdbcTemplate: facilita a manipulação de registros (select,
insert, update e delete).
– SimpleJdbcInsert: facilita a inserção de registros.
– BeanPropertyRowMapper: mapeamento pré-definido entre
atributos de um objeto e as colunas da tabela, facilitando as
operações de consulta.
– EmptyResultDataAccessException: exceção indicativa de que
uma consulta vazia, ou seja, sem registros.
Spring JDBC: hierarquia parcial de
exceções
FabricanteDAO (1)
package cursoSpring.revenda_veiculos.dao; ... import javax.sql.DataSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.simple.SimpleJdbcInsert; import org.springframework.stereotype.Repository; import cursoSpring.revenda_veiculos.dominio.Fabricante; import cursoSpring.revenda_veiculos.dominio.FabricanteRepository; @Repositorypublic class FabricanteDAO implements FabricanteRepository { @Autowired
FabricanteDAO (1)
package cursoSpring.revenda_veiculos.dao; ... import javax.sql.DataSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.BeanPropertyRowMapper; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.simple.SimpleJdbcInsert; import org.springframework.stereotype.Repository; import cursoSpring.revenda_veiculos.dominio.Fabricante; import cursoSpring.revenda_veiculos.dominio.FabricanteRepository; @Repositorypublic class FabricanteDAO implements FabricanteRepository { @Autowired
private DataSource dataSource;
@Autowired indica uma injeção por
tipo. Assim, o contexto do Spring irá buscar um bean do tipo DataSource
para injetar neste atributo.
@Repository marca
FabricanteDAO como um bean
Spring da camada de persistência. Isto faz com que as exceções lançadas por FabricanteDAO sejam encapsuladas em exceções do tipo
DataAccessExcpetion, tornando as
FabricanteDAO (2)
@Override
public List<Fabricante> todos() {
String sql = "select * from FABRICANTES";
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); return jdbcTemplate.query(sql,
BeanPropertyRowMapper.newInstance(Fabricante.class)); }
FabricanteDAO (2)
@Override
public List<Fabricante> todos() {
String sql = "select * from FABRICANTES";
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); return jdbcTemplate.query(sql,
BeanPropertyRowMapper.newInstance(Fabricante.class)); }
O método query varre os registros obtidos na consulta e retorna uma lista parametrizada pelo RowMapper<T> utilizado como parâmetro. BeanPropertyRowMapper é uma implementação disponibilizada pelo Spring que casa o nome de um atributo (e.g,
numeroPlaca) com a coluna de mesmo nome (e.g., NUMEROPLACA
Consulta com JDBC puro
try{
Connection con = getConnection();
String sql = "select * from fabricantes";
PreparedStatement prep = con.prepareStatement(sql); ResultSet rs = prep.executeQuery();
List<Fabricante> fabricantes = new ArrayList<Fabricante>(); while(rs.next()){
Fabricante f = new Fabricante(); f.setId(rs.getInt("ID"); f.setDescricao(rs.getString("DESCRICAO")); fabricantes.add(f); } return fabricantes; }catch(SQLException ex){ ... }
FabricanteDAO (3)
@Override
public Fabricante getPorId(Integer idFabricante) {
String sql = "select * from FABRICANTES where ID = ?"; JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); try{ return jdbcTemplate.queryForObject(sql, BeanPropertyRowMapper.newInstance(Fabricante.class), idFabricante); }catch(EmptyResultDataAccessException ex){ return null; } }
FabricanteDAO (4)
@Override
public Integer inserir(Fabricante f) {
SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(dataSource) .withTableName("FABRICANTES")
.usingGeneratedKeyColumns("ID"); Map<String, Object> params = new HashMap<>();
params.put("DESCRICAO", f.getDescricao());
Integer id = jdbcInsert.executeAndReturnKey(params).intValue(); f.setId(id);
return id; }
FabricanteDAO (5)
@Override
public void atualizar(Fabricante f) {
String sql = "update FABRICANTES set DESCRICAO=? where ID=?"; JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.update(sql, f.getDescricao(), f.getId()); }
@Override
public void excluir(Integer idFabricante) {
String sql = "delete from FABRICANTES where ID = ?";
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); jdbcTemplate.update(sql, idFabricante);
} }
Atualizando AppConfig
• Adicionando o pacote
cursoSpring.revenda_veiculos.dao ao contexto
do Spring:
package cursoSpring.revenda_veiculos.config; ... @Configuration @ComponentScan(basePackageClasses={FabricanteDAO.class})public class AppConfig { ...
Testes de integração
• Os testes de integração devem se comunicar com
o banco de dados. Para tal, é preciso que o código
de testes tenha acesso ao contexto do Spring.
• Spring oferece suporte aos frameworks JUnit e
TestNG.
• Vamos utilizar os recursos do Eclipse, ao invés do
Maven, para execução dos testes JUnit. Portanto,
devemos adicionara a biblioteca do JUnit 4
Testes de integração
• Para que uma classe de testes do JUnit tenha
acesso ao contexto do Spring, podemos marcá-la
com @RunWith(SpringJUnit4ClassRunner.class)
ou defini-la como subclasse de
AbstractTransactionalJUnit4SpringContextTests.
• A anotação @ActiveProfiles será utilizada para
indicar o profile utilizado nos testes. Dessa forma,
os testes de integração utilizarão o DataSource
Scripts SQL
• Criar os seguintes scripts em src/test/java:
create-schema.sql
create table FABRICANTES (ID int auto_increment, DESCRICAO varchar(40) unique not null, primary key(ID));
insert into FABRICANTES (ID, DESCRICAO) values (1, 'CHEVROLET/GM'); insert into FABRICANTES (ID, DESCRICAO) values (2, 'FIAT');
insert into FABRICANTES (ID, DESCRICAO) values (3, 'FORD');
insert into FABRICANTES (ID, DESCRICAO) values (4, 'VOLKSWAGEN'); insert into FABRICANTES (ID, DESCRICAO) values (5, 'RENAULT');
drop-schema.sql
FabricanteDAOTest (1)
package cursoSpring.revenda_veiculos.dao; ... import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.AbstractTransactionalJUnit4S pringContextTests; @ContextConfiguration(classes={AppConfig.class, AppConfigTest.class}) @ActiveProfiles("test")public class FabricanteDAOTest extends
AbstractTransactionalJUnit4SpringContextTests{ @Autowired
FabricanteDAOTest (2)
@Before
public void setUp(){
executeSqlScript("classpath:/create-schema.sql", false); }
@After
public void tearDown(){
executeSqlScript("classpath:/drop-schema.sql", false); }
@Test
public void testTodos() {
List<Fabricante> lista = dao.todos(); Assert.assertEquals(5, lista.size()); }
FabricanteDAOTest (3)
@Test
public void testGetPorId_1(){
Fabricante f = dao.getPorId(1);
Assert.assertEquals(1, f.getId().intValue());
Assert.assertEquals("CHEVROLET/GM", f.getDescricao()); }
@Test
public void testGetPorId_2(){
Fabricante f = dao.getPorId(20); Assert.assertNull(f);
}
@Test
public void testInserir(){
Fabricante f = new Fabricante(null, "TESTE"); Integer id = dao.inserir(f);
FabricanteDAOTest (4)
@Test
public void testAtualizar(){ Integer idFabricante = 3;
String novaDescricao = "NISSAN";
Fabricante f = new Fabricante(idFabricante, novaDescricao); dao.atualizar(f);
String descricao = jdbcTemplate.queryForObject(
"select DESCRICAO from FABRICANTES where ID = ?", String.class, idFabricante);
Assert.assertEquals(novaDescricao, descricao); }
@Test
public void testExcluir(){ Integer idFabricante = 2; dao.excluir(idFabricante);
Fabricante f = dao.getPorId(idFabricante); Assert.assertNull(f);
DAO com Hibernate
• Adicionar as dependências.
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
</dependency>
Configurando Hibernate: AppConfig (1)
... import org.springframework.orm.hibernate4.HibernateTransactionManager; import org.springframework.orm.hibernate4.LocalSessionFactoryBean; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManageme nt; @Configuration @ComponentScan(basePackageClasses={FabricanteDAO.class}) @EnableTransactionManagementpublic class AppConfig {
Configurando Hibernate: AppConfig (2)
... @Bean
public LocalSessionFactoryBean sessionFactory(){ LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean(); sessionFactory.setDataSource(dataSource()); sessionFactory.setPackagesToScan ("cursoSpring.revenda_veiculos.dominio"); sessionFactory.setHibernateProperties(hibernateProperties()); return sessionFactory; }
Configurando Hibernate: AppConfig (3)
private Properties hibernateProperties(){ Properties props = new Properties(); props.put("hibernate.dialect", "org.hibernate.dialect.H2Dialect"); props.put("hibernate.show_sql", "true"); props.put("hibernate.format_sql", "true"); return props; } @Bean
public PlatformTransactionManager transactionManager(){ return new HibernateTransactionManager(
sessionFactory().getObject()); }
Scripts SQL
• Adicionar instruções:
create-schema.sql
...
create table MODELOS (ID int auto_increment, DESCRICAO varchar(40) not null, ID_FABRICANTE int not null, primary key(ID), foreign key (ID_FABRICANTE) references FABRICANTES);
insert into MODELOS (ID, DESCRICAO, ID_FABRICANTE) values (1, 'CORSA', 1);
insert into MODELOS (ID, DESCRICAO, ID_FABRICANTE) values (2, 'GOL', 4);
insert into MODELOS (ID, DESCRICAO, ID_FABRICANTE) values (3, 'PALIO', 2);
drop-schema.sql
Atualizando classe Entidade
... import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.MappedSuperclass; @MappedSuperclasspublic abstract class Entidade implements Serializable{ @Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Integer id; ...
Atualizando classe Fabricante
... import javax.persistence.Entity; import javax.persistence.Table; @Entity @Table(name="FABRICANTES")public class Fabricante extends Entidade{ ...
Entidade Modelo (1)
package cursoSpring.revenda_veiculos.dominio; import javax.persistence.Entity; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.Table; @Entity @Table(name="MODELOS")public class Modelo extends Entidade { private String descricao;
@ManyToOne
@JoinColumn(name="ID_FABRICANTE") private Fabricante fabricante;
Entidade Modelo (2)
public Modelo() {}
public Modelo(Integer id, String descricao, Fabricante fabricante) {
super(id);
this.descricao = descricao; this.fabricante = fabricante; }
public String getDescricao() { ... }
public void setDescricao(String descricao) { ... } public Fabricante getFabricante() { ... }
public void setFabricante(Fabricante fabricante) { ... } }
Interface ModeloRepository
package cursoSpring.revenda_veiculos.dominio; import java.util.List;
public interface ModeloRepository { List<Modelo> todos();
Modelo getPorId(Integer idModelo); Integer inserir(Modelo m);
void atualizar(Modelo m);
void excluir(Integer idModelo); }
Classe ModeloDAO (1)
package cursoSpring.revenda_veiculos.dao; import java.util.List; import org.hibernate.Query; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; import cursoSpring.revenda_veiculos.dominio.Modelo; import cursoSpring.revenda_veiculos.dominio.ModeloRepository; @Repositorypublic class ModeloDAO implements ModeloRepository { @Autowired
Classe ModeloDAO (2)
@Override
public List<Modelo> todos() {
Session session = sessionFactory.getCurrentSession(); return session.createQuery("from Modelo").list();
}
@Override
public Modelo getPorId(Integer idModelo) {
Session session = sessionFactory.getCurrentSession(); return (Modelo)session.get(Modelo.class, idModelo); }
@Override
public Integer inserir(Modelo m) {
sessionFactory.getCurrentSession().save(m); return m.getId();
Classe ModeloDAO (3)
@Override
public void atualizar(Modelo m) {
sessionFactory.getCurrentSession().merge(m); }
@Override
public void excluir(Integer idModelo) {
String hql = "delete Modelo where id = :idModelo"; Session session = sessionFactory.getCurrentSession(); Query q = session.createQuery(hql)
.setParameter("idModelo", idModelo); q.executeUpdate();
} }
Classe ModeloDAOTest (1)
package cursoSpring.revenda_veiculos.dao; ... @ContextConfiguration(classes={AppConfig.class, AppConfigTest.class}) @ActiveProfiles("test")public class ModeloDAOTest extends
AbstractTransactionalJUnit4SpringContextTests{ @Autowired
private ModeloDAO dao; @Autowired
Classe ModeloDAOTest (2)
@Test
public void testTodos() {
List<Modelo> lista = dao.todos();
Assert.assertEquals(3, lista.size()); }
@Test
public void testGetPorId_1(){ Integer idModelo = 2; Modelo m = dao.getPorId(idModelo); Assert.assertEquals(idModelo, m.getId()); Assert.assertEquals("GOL", m.getDescricao()); } @Test
public void testGetPorId_2(){ Modelo m = dao.getPorId(20); Assert.assertNull(m);
Classe ModeloDAOTest (3)
@Test
public void testInserir(){
Fabricante f = new Fabricante(1, null); Modelo m = new Modelo(null, "TESTE", f); Integer id = dao.inserir(m);
Assert.assertNotNull(id); }
@Test
public void testAtualizar(){ Integer idModelo = 1;
String novaDescricao = "CELTA";
Fabricante f = new Fabricante(1, null);
Modelo m = new Modelo(idModelo, novaDescricao, f); dao.atualizar(m);
Modelo m2 = (Modelo)sessionFactory.getCurrentSession()
.get(Modelo.class, idModelo); Assert.assertEquals(novaDescricao, m2.getDescricao());
Classe ModeloDAOTest (4)
@Test
public void testExcluir(){ Integer idModelo = 3; dao.excluir(idModelo); Modelo m = dao.getPorId(idModelo); Assert.assertNull(m); } @Before
public void setUp(){
executeSqlScript("classpath:/create-schema.sql", false); }
@After
public void tearDown(){
executeSqlScript("classpath:/drop-schema.sql", false); }