Olá, pessoal! Continuando a nossa série de post sobre o Spring, vamos ver hoje como fazer um simples CRUD com Spring + Hibernate + JSF. O objetivo maior é que quem esteja chegando possa ver como juntar as tecnologias de maneira simples. E o melhor de tudo: veremos como Spring é uma mão na roda de verdade.
A nossa aplicação será super simples. Vamos fazer o cadastramento de carros, e exercitar o CRUD. Para exibição e cadastro dos veículos, vamos fazer uma página JSF da maneira mais simples possível e que seja funcional. A seguir os requisitos:
Requisitos:
- MysqlSQL 5.x
- Hibernate 3.x
- Spring 3.x
- Tomcat 7.x
- JSF 2.x
- Jboss tools
Vou considerar que você vem acompanhando a série de posts Spring e alguns pontos que já foram tratados aqui, não vou explicar novamente. Têm alguma dúvida a respeito? E sobre JSF 2.x também irei considerar que você já brincou com o framework, mesmo que seja na versão anterior. Estou ressaltando isso, para que não ter que entrar nos detalhes de cada ponto, fazendo com que o artigo fique mais objetivo.
Configuração
O primeiro ponto é criar um projeto JSF Project (é preciso ter o jboss tools instalado).
Escolha a opção JSF 2 na tela do assistente e em seguida crie os packages conforme mostrado a seguir. Adicione os .jars na pasta lib do projeto:
Criando o source unit/test
Observe que eu criei um source para os unit tests:
Agora, vamos criar o arquivo de configuração do Spring. Na verdade, teremos dois: um para os unit tests e outro para aplicação. Apenas dupliquei, mas poderíamos otimizar o de unit tests importando apenas o que precisamos a partir do arquivo principal, mas não quis fazer isso por agora; vamos focar no CRUD.
Crie um arquivo springconfiguration.xml dentro de WEB-INF. O nome pode ser qualquer um (normalmente utiliza-se applicaiton-context.xml, mas quis fazer diferente para que você veja que o nome não importa).
Não irei adicionar ao cabeçalho apenas o código, que não tem nada de diferente do que já vimos nos artigos quando vimos hibernate com Spring.
[java]<context:component-scan base-package=“*”/>
<tx:annotation-driven/>
<tx:advice id=“txAdvice”>
<tx:attributes>
<tx:method name=“add*” propagation=“REQUIRED”/>
<tx:method name=“delete*” propagation=“REQUIRED”/>
<tx:method name=“read*” propagation=“REQUIRED”/>
<tx:method name=“*” propagation=“SUPPORTS” read-only=“true”/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:advisor pointcut = “execution(* *..CarDAO.*(..)))” advice-ref=“txAdvice”/>
</aop:config>
<bean class=“org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor”/>
<bean id=“dataSource” class=“org.springframework.jdbc.datasource.DriverManagerDataSource”>
<property name=“driverClassName” value=“com.mysql.jdbc.Driver”/>
<property name=“url” value=“jdbc:mysql://localhost/test”/>
<property name=“username” value=“root”/>
<property name=“password” value=“camilo2593″/>
</bean>
<bean id=“sessionFactory” class=“org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean”>
<property name=“dataSource” ref=“dataSource”/>
<property name=“packagesToScan” value=“br.com.camilolopes.car.domain.bean”/>
<property name=“hibernateProperties”>
<props>
<prop key=“hibernate.dialect”>org.hibernate.dialect.MySQL5InnoDBDialect</prop>
<prop key=“hibernate.hbm2ddl.auto”>update</prop>
</props>
</property>
</bean>
<bean id=“transactionManager” class=“org.springframework.orm.hibernate3.HibernateTransactionManager”>
<property name=“sessionFactory” ref=“sessionFactory”/>
</bean>[/java]
Agora precisamos fazer umas configurações no arquivo web.xml.
[java]
<!– dizendo onde está meu arquivo de configuração –>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/springconfiguration.xml,</param-value>
</context-param>
<!– configurando o context loader do Spring, esse cara permite carregar N arquivos de configuração –>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!– esse cara permite dizer ao JSF que os beans serão gerenciados pelo Spring é requerido ter essa
configuração –>
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>[/java]
Testando
Se você subir aplicação agora, não há nada de especial, apenas vai subir o exemplo que temos no JSF Project, que é criado pelo Jboss tols. Mas é bom testar para garantir que as mudanças que fizemos no web.xml não afetaram nada da aplicação.
Arquivo de configuração do spring para unit tests
Agora vamos criar um arquivo de configuração para executar os unit tests. Mas por que? Simplesmente porque a partir de uma classe de teste você não consegue ler um arquivo que está em web-inf, então você precisa ter o arquivo em source.
Noticia boa: o código é o mesmo que do arquivo anterior, então apenas duplique o XML, o meu chamei de springconfiguration-test.xml e coloquei no JavaSource:
Agora vamos começar a brincadeira, ou seja, desenvolver!
Começaremos pelos testes, claro. Para isso, criaremos uma classe que testará o serviço que implicitamente testa as classes DAO.
Antes, criaremos os testes, pois é importante entender que:
- Após os testes terem sido executados, este deve dar rollback, para que os dados não fiquem no banco;
- Os testes não podem ser dependentes de outro test, ou seja, um @Test não pode depender da execução de outro @Test
Por enquanto, ao criar os unit test é esperado que nem compile, já que não temos nenhuma classe pronta. Mas é isso que queremos: que os testes nos ensinem a desenvolver as regras de negócio que precisamos.
O primeiro teste vai nos permitir testar o salvarOrUpdate da nossa aplicação. Eu poderia ter separado em dois testes: um para save e outro para update, mas não quis entrar em detalhes sobre testes aqui, senão o artigo ficaria duas vezes maior.
[java]
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={“classpath:springconfiguration-test.xml”})
@TransactionConfiguration(transactionManager=”transactionManager”,defaultRollback=true)
@Transactional
public class CarServiceImplTest {
@Autowired
private CarServices carServices;
private Car car;
@Before
public void setUp() throws Exception {
car = new Car();
car.setDescription(“ferrari”);
car.setPriceSale(BigDecimal.ZERO);
car.setYear(“2010″);
}
@Test
public void testSaveOrUpdate() {
try{
carServices.saveOrUpdate(car);
}catch (Exception e) {
fail(“not expected result”);
}
}[/java]
Observe que configuramos o rollback igual a true para tudo, então sempre será feito rollback e nada é inserido no banco. Mas, se você quer inserir no banco para um método especifico, basta anotarmos no método assim: @Rollback(false) //isso faz inserir no banco.
Note: nesse exemplo não estou usando um banco em memória, então é preciso que o Mysql esteja rodando para que os testes possam ser executados. O ideal era fazer os testes rodarem em um banco em memória assim, aí não teríamos essa dependência, pois o banco seria iniciado sempre que os testes fossem executados.
Agora vamos criar as classes que o Eclipse está reclamando. Vamos começar pela entidade Car:
[java]
@Entity(name=”SALE_CARS”)
public class Car implements Serializable{
private static final long serialVersionUID = 2792374994901518817L;
@Id
@GeneratedValue
private Long id;
private String description;
private BigDecimal priceSale;
private String year;
//getters/setters omitidos[/java]
Services
Vamos criar os serviços, mas antes precisamos ter uma interface para os nossos serviços CRUD, então:
[java]
public interface CarServices {
void saveOrUpdate(Car car);
void delete(Car car);
List<Car> listAll();
}[/java]
Agora vamos criar a classe em si:
[java]@Service
public class CarServiceImpl implements CarServices {
@Autowired
private CarDAO carDAO;
public void saveOrUpdate(Car car) {
carDAO.addCar(car);
}
//setters CarDAO omitido. Não é preciso criar get.[/java]
DAO Interface
Também criaremos uma interface para o DAO e em seguida a implementação do método save:
[java]
public interface CarDAO {
void addCar(Car car);
List<Car> readAll();
void deleteCar(Long id);
}
@Repository
public class CarDAOImpl implements CarDAO {
@Autowired
private SessionFactory sessionFactory;
private Session getCurrentSession(){
return sessionFactory.getCurrentSession();
}
public void addCar(Car car) {
getCurrentSession().saveOrUpdate(car);
}
//setters sessionFactory ommitido, não é preciso cria get.[/java]
Claro que os demais métodos da interface estarão sem código por enquanto, pois o objetivo agora é testar ocreate do CRUD.
Testando via Unit Tests
Agora vamos rodar o nosso teste e ver o resultado. Se você não adicionou o JUNIT4 ao seu projeto, o Eclipse vai fazer a sugestão.
Note: Ao rodar o unit tests e você ter a exceção “org.springframework.aop.framework.AopConfigException: Cannot proxy target class because CGLIB2 is not available”, faça o download e adicione a lib ao seu projeto na pasta web-inf/lib.
O resultado
Agora precisamos desenvolver o RUD. A seguir, os testes que foram criados e depois, o código com a resolução:
[java]
@Test
public void testDelete() {
try{carServices.saveOrUpdate(car);
carServices.delete(car);
}catch (Exception e) {
String notExpectedResult = “Not expected result “+ e.getMessage();
fail(notExpectedResult);
}
}
@Test
public void testListAll() {
carServices.saveOrUpdate(car);
assertFalse(carServices.listAll().isEmpty());
}[/java]
Classe DAO
[java]
public List<Car> readAll() {
return getCurrentSession().createCriteria(Car.class).list();
}
public void deleteCar(Long id) {
Criteria criteria = getCurrentSession().createCriteria(Car.class);
criteria.add(Restrictions.eq(“id”, id));
Car car = (Car) criteria.uniqueResult();
getCurrentSession().delete(car);
}[/java]
Classe de Serviço:
[java]<strong></strong>
public void delete(Car car) {
carDAO.deleteCar(car.getId());
}
public List<Car> listAll() {
return carDAO.readAll();
}
public void setCarDAO(CarDAO carDAO) {
this.carDAO = carDAO;
}[/java]
Rodando todos os testes:
Há um detalhe que fiz de propósito. Observe que não tem um teste para validar o update, deixarei esse como motivação para você brincar mais com unit test!
Observe que nosso banco está vazio, ou seja, nada foi adicionado.
JSF
Agora que já sabemos que o nosso CRUD está funcionando, vamos trazer isso para o nosso front-end com JSF.
Crie uma classe controller:
[java]
@Controller
public class CarController {
@Autowired
private CarServices carServices;
private Car car;
private List<Car> listCars;
public CarController() {
car = new Car();
}
public void save(){
carServices.saveOrUpdate(car);
car = new Car();
}
public void delete(){
carServices.delete(car);
car = new Car();
}
public List<Car> getListCars() {
listCars = carServices.listAll();
return listCars;
}
//setters/getters omitidos[/java]
Observe que nosso controller conecta com o nosso service. É só isso que precisamos. Se você já brinca com JSF, nada de especial por aqui.
Criando .xhtml
Na verdade, vamos alterar o index.xhtml que foi criado por default pelo jboss tools:
[java]<html><head><meta http-equiv=“Refresh” content=“0; URL=pages/sales-car.jsf”/></head></html>[/java]
Apenas fizemos um redirecionamento para uma página que vamos criar: sales-car.xhtml.
Crie um xhtml, com suporte facelets e adicione o código a seguir:
[java]<body>
<h:form>
<h:commandLink action=“form-car” value=“::Cadastro”/>
</h:form>
</body>[/java]
Essa página vai levar para a tela de cadastro: form-car.xhtml
Vamos por partes… Primeiro temos o código do form:
[java]
<h:form>
<h:panelGrid columns=“2″>
<h:outputLabel value=“Car Description: “/>
<h:inputText value=“#{carController.car.description}”/>
<h:outputLabel value=“Sale Price: “/>
<h:inputText value=“#{carController.car.priceSale}” converter=“javax.faces.BigDecimal”/>
<h:outputLabel value=“year:”/>
<h:inputText value=“#{carController.car.year}”/>
<h:commandButton value=“Save” actionListener=“#{carController.save}”/>
</h:panelGrid>[/java]
Agora, vamos ter o código de uma tabela que exibe o usuário adicionado e permite editar/deletar:
[java]
<h:dataTable id=“cartable” value=“#{carController.listCars}” var=“car” cellpadding=“10″>
<h:column>
<f:facet name=“header”>
<h:outputText value=“Id”/>
</f:facet>
#{car.id}
</h:column>
<h:column>
<f:facet name=“header”>
<h:outputText value=“Description”/>
</f:facet>
#{car.description}
</h:column>
<h:column>
<f:facet name=“header”>
<h:outputText value=“Sale Price”/>
</f:facet>
<h:outputText value=“#{car.priceSale}”>
<f:convertNumber type=“currency” maxFractionDigits=“3″/>
</h:outputText>
</h:column>
<h:column>
<f:facet name=“header”>
<h:outputText value=“Year”/>
</f:facet>
#{car.year}
</h:column>
<h:column>
<f:facet name=“header”>
<h:outputText value=“Action”/>
</f:facet>
<h:commandLink value=“Delete” action=“#{carController.delete}”>
<f:setPropertyActionListener target=“#{carController.car}” value=“#{car}”/>
</h:commandLink>
<h:commandLink value=” | Edit “>
<f:setPropertyActionListener target=“#{carController.car}” value=“#{car}”/>
</h:commandLink>
</h:column>
</h:dataTable>
</h:form>[/java]
Pronto! Esse é o nosso front-end para testarmos o CRUD. Vamos subir aplicação. Clique com o botão direito no projeto e escolha Run as >> Run on Server
Note: Caso não tenha um servidor selecionado com o projeto, o Eclipse vai solicitar que escolha um (no meu caso escolhi o tomcat e informei o local de instalação). Se der tudo certo você terá a tela a seguir:
Vamos para tela de cadastro clicando no botão “Cadastro”:
Verificando no bd:
Editando:
Resultado da edição:
E no banco:
Ufa! Artigo longo, heim?! E olha que reduzi! Mas vou ficando por aqui. E espero que tenham gostado.
Abraços!