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 clienteservidor. 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/. 2
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> 3
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. 4
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; @Configuration public 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; 5
Classes de configuração: AppConfig package cursospring.revenda_veiculos.config; import javax.sql.datasource; DriverManagerDataSource é uma implementação de import org.springframework.context.annotation.configuration; DataSource que deve ser utilizada apenas em tempo import org.springframework.jdbc.datasource.drivermanagerdatasource; de desenvolvimento. Para produção, deve-se utilizar uma implementação que trabalhe com pool de @Configuration conexões tal como Apache Commons DBCP ou C3P0. public 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; 6
Classes de configuração: AppConfigTest package cursospring.revenda_veiculos.config; import org.springframework.context.annotation.profile;... @Configuration public class AppConfigTest { Crie esta classe na source folder src/test/java. @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; 7
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, setid, equals e hashcode. 8
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) {... 9
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); 10
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. 11
Spring JDBC: hierarquia parcial de exceções 12
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; @Repository public class FabricanteDAO implements FabricanteRepository { @Autowired private DataSource datasource; 13
FabricanteDAO (1) package cursospring.revenda_veiculos.dao;... @Repository marca import FabricanteDAO javax.sql.datasource; como um bean import Spring org.springframework.beans.factory.annotation.autowired; da camada de persistência. @Autowired indica uma injeção por import org.springframework.dao.emptyresultdataaccessexception; Isto faz com que as exceções tipo. Assim, o contexto do Spring irá import lançadas org.springframework.jdbc.core.beanpropertyrowmapper; por FabricanteDAO sejam buscar um bean do tipo DataSource import encapsuladas org.springframework.jdbc.core.jdbctemplate; em exceções do tipo para injetar neste atributo. import DataAccessExcpetion, org.springframework.jdbc.core.simple.simplejdbcinsert; tornando as import mensagens org.springframework.stereotype.repository; de erro mais claras. import cursospring.revenda_veiculos.dominio.fabricante; import cursospring.revenda_veiculos.dominio.fabricanterepository; @Repository public class FabricanteDAO implements FabricanteRepository { @Autowired private DataSource datasource; 14
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)); 15
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 ou NUM_PLACA). 16
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){... 17
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; 18
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; 19
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); 20
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 {... 21
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 através do build path do projeto. 22
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 definido em AppConfigTest. 23
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 drop table FABRICANTES; 24
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 private FabricanteDAO dao; 25
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()); 26
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); Assert.assertNotNull(id); 27
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); 28
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> 29
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) @EnableTransactionManagement public class AppConfig { 30
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; 31
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()); 32
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 drop table MODELOS;... 33
Atualizando classe Entidade... import javax.persistence.generatedvalue; import javax.persistence.generationtype; import javax.persistence.id; import javax.persistence.mappedsuperclass; @MappedSuperclass public abstract class Entidade implements Serializable{ @Id @GeneratedValue(strategy=GenerationType.AUTO) private Integer id;... 34
Atualizando classe Fabricante... import javax.persistence.entity; import javax.persistence.table; @Entity @Table(name="FABRICANTES") public class Fabricante extends Entidade{... 35
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; 36
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) {... 37
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); 38
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; @Repository public class ModeloDAO implements ModeloRepository { @Autowired private SessionFactory sessionfactory; 39
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(); 40
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(); 41
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 private SessionFactory sessionfactory; 42
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); 43
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()); 44
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); 45
Referências Deinum, Marten et al. Spring Recipes: a problemsolution approach, 3ª ed. Apress, 2014. Johnson, Rod et al. Spring Framework Reference Documentation, 4.2.1 release. Disponível em <http://docs.spring.io/spring/docs/current/spring -framework-reference/html/> Souza, Alberto. Spring MVC: domine o principal framework web Java. São Paulo: Casa do Código, 2015. 46
Instituto Federal de Educação, Ciência e Tecnologia do Rio Grande do Norte Campus Natal Central Diretoria Acadêmica de Gestão e Tecnologia da Informação Curso de formação em Spring Framework 4 Parte 02 acesso a dados e testes de integração Autor: Alexandre Gomes de Lima Natal, outubro de 2015. 47