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, é para quem está 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.
Lets go…
Starting…
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 de 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 no blog e alguns pontos que já foi tratado aqui, não estarei explicando novamente, dúvidas? Farei um link para o post referente. E sobre JSF 2.x também irei considerar que vc já brincou com o framework, mesmo que seja na versão anterior. Estou ressaltando isso, para que não entre no detalhes de cada ponto, e que o post fique mais direto e mão na massa.
Configuração
Primeiro ponto é , criar um projeto JSF Project (é preciso ter o jboss tools instalado).
Escolha a opção JSF 2 na tela do assistente. Em seguida crie os packages conforme a seguir, por enquanto eles estarão vazios, mas iremos daqui a pouco colocar carne no esqueleto : ).
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 poderia 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 o cabeçalho apenas o código, que não tem nada de diferente do que já vimos nos posts quando vimos hibernate com Spring.
<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>
Agora precisamos fazer umas configurações no arquivo web.xml
<!– 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>
Testando.
Se você subir aplicação agora, não há nade 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 afetou 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, pq? Simplesmente pq 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 duplique o XML apenas, o meu chamei de springconfiguration-test.xml e coloquei no JavaSource:
Agora vamos começar a brincadeira, ou seja, desenvolver.
Development
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 precisamos entender que é importante entender que:
– após 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 pronto, mas é isso que queremos, que os testes nos ensinem a desenvolver as regras de negocio que precisamos.
O primeiro teste vai nos permitir testar o salvarOrUpdate da nossa aplicação, poderia ter separado em dois testes um para save e outro para update, mas não quis entrar muito detalhes sobre testes aqui, senão o post ia fica 2x maior e sinceramente não curto muito posts grandes.
@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”);
}
}
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, 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:
@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
Services
Vamos criar os serviços, mas antes precisamos ter uma interface para os nossos serviços CRUD então:
public interface CarServices {
void saveOrUpdate(Car car);
void delete(Car car);
List<Car> listAll();
}
Agora vamos criar a classe em si:
@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.
DAO Interface
Também criaremos uma interface para o DAO e em seguida a implementação do método save
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.
Claro que os demais métodos da interface estarão sem código por enquanto, pois o objetivo agora é testar o create 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 http://cglib.sourceforge.net/ 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:
@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());
}
Classe DAO
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);
}
Classe de Serviço:
public void delete(Car car) {
carDAO.deleteCar(car.getId());
}
public List<Car> listAll() {
return carDAO.readAll();
}
public void setCarDAO(CarDAO carDAO) {
this.carDAO = carDAO;
}
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
@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
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:
<html><head><meta http-equiv=“Refresh” content=“0; URL=pages/sales-car.jsf”/></head></html>
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:
<body>
<h:form>
<h:commandLink action=“form-car” value=“::Cadastro”/>
</h:form>
</body>
Essa página vai levar para a tela de cadastro:
form-car.xhtml
Vamos por parte:
Primeiro temos o código do form:
<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>
Agora vamos ter o código de uma tabela que exibi o usuário adicionado e permite editar/deletar :
<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>
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 Edição:
E no banco:
Ufa! Post longo heim, e olha que reduzir. Mas, vou ficando por aqui. E espero que tenham gostado.
Abraços, see ya!!!