terça-feira, 19 de março de 2013

Hello Spring --- Episódio II

Para você prosseguir no episódio dois, recomendo ler a parte 1.
Terminou de ler?
Eu espero.
Pronto mesmo? Adiante então!

Para os trabalhos de hoje vamos precisar do seguinte material:

  • Todo o material já utilizado na parte 1
  • Hibernate 4.2
  • H2 Database
Vamos aos downloads:

E agora o Banco de dados:
Prefira o .zip

Resolvidos os downloads, vamos Trabalhar! 

No episódio de hoje vamos fazer uma... Agenda! Rá!

Vamos começar descompactando o banco H2:

Em seguida, copie-o pra dentro do projeto que fizemos no primeiro artigo:


E não esqueça, de adicioná-lo ao build path:


Feito isso, vamos configurar um DataSource, :-)

O Spring tem uma implementação pronta, precisamos apenas configurá-la. Modifique o applicationContext.xml para que ele fique assim:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:context="http://www.springframework.org/schema/context"
 xmlns:aop="http://www.springframework.org/schema/aop" 
 xmlns:jee="http://www.springframework.org/schema/jee" 
 xmlns:tx="http://www.springframework.org/schema/tx"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="
 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd 
 http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd 
 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd 
 http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.2.xsd   
 http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">
 
 <context:annotation-config/>
 <context:component-scan base-package="hellospring.beans"/>
 
 <bean id="dataSource"
  class="org.springframework.jdbc.datasource.DriverManagerDataSource">
  <property name="driverClassName" value="org.h2.Driver" />
  <property name="url" value="jdbc:h2:~/agenda" />
  <property name="username" value="sa" />
  <property name="password" value="sa" />
 </bean>
</beans>

Perfeito né não? Se nosso assunto fosse só JDBC, estaríamos resolvidos aqui. Mas prossigamos.

Uma vez criado o datasource, vamos proceder com a adição do suporte a JPA no projeto. Descompacte o hibernate que baixamos:

Dentro da pasta descompactada você encontrará uma pasta chamada lib. Dentro dela, uma pasta chamada required, e outra chamada jpa. As outras pastas não nos interessam. Copie o conteúdo destas duas pastas para o projeto:

Não esqueça de colocar as bibliotecas no buildpath.

Agora vamos criar umas entidades persistentes aqui, ;-) crie a seguinte classe:

package hellospring.entity;

import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Entity;

@Entity
public class Contato {

 @Id
 @GeneratedValue
 private int id;
 @Column
 private String nome;
 @Column
 private String endereco;
 @Column
 private String telefone;

 public int getId() {
  return id;
 }

 public void setId(int id) {
  this.id = id;
 }

 public String getNome() {
  return nome;
 }

 public void setNome(String nome) {
  this.nome = nome;
 }

 public String getEndereco() {
  return endereco;
 }

 public void setEndereco(String endereco) {
  this.endereco = endereco;
 }

 public String getTelefone() {
  return telefone;
 }

 public void setTelefone(String telefone) {
  this.telefone = telefone;
 }

}

Representa nossa tabela de contatos, da nossa agenda. Crie agora a seguinte classe:

package hellospring.beans;

import hellospring.entity.Contato;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.springframework.stereotype.Component;

@Component
public class Agenda {

 @PersistenceContext
 private EntityManager em;

 public void salvar(Contato c) {
  em.getTransaction().begin();
  em.persist(c);
  em.getTransaction().commit();
 }

 public void excluir(int id) {
  em.getTransaction().begin();
  em.createQuery("delete from contato c where c.id = :id")//
    .setParameter("id", id).executeUpdate();
  em.getTransaction().commit();
 }

 public List<contato> listar() {
  return em.createQuery("select c from contato c", Contato.class)
    .getResultList();
 }
}

Agora devemos retornar à configuração do xml do spring; pois para termos um EntityManager, vamos precisar de um entityManagerFactory. Deixe o applicationContext.xml deste jeito:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:context="http://www.springframework.org/schema/context"
 xmlns:aop="http://www.springframework.org/schema/aop" 
 xmlns:jee="http://www.springframework.org/schema/jee" 
 xmlns:tx="http://www.springframework.org/schema/tx"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="
 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd 
 http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd 
 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd 
 http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.2.xsd   
 http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">
 
 <context:annotation-config/>
 <context:component-scan base-package="hellospring.beans"/>
 
 <bean id="dataSource"
  class="org.springframework.jdbc.datasource.DriverManagerDataSource">
  <property name="driverClassName" value="org.h2.Driver" />
  <property name="url" value="jdbc:h2:~/agenda" />
  <property name="username" value="sa" />
  <property name="password" value="sa" />
 </bean>
 
 <bean id="entityManagerFactory"
 class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
  <property name="dataSource" ref="dataSource" />
  <property name="jpaVendorAdapter">
   <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
    <property name="showSql" value="true" />
    <property name="generateDdl" value="true"/>
   </bean>
  </property>
 </bean>
</beans>

Agora um último detalhe: como estamos lidando com JPA, precisamos definir um persistence.xml e nele declarar as classes mapeadas. crie a pasta META-INF dentro do seu src e nela crie o perssitence.xml:


<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
 xmlns="http://java.sun.com/xml/ns/persistence" 
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
 <persistence-unit name="hello-spring-pu"
  transaction-type="RESOURCE_LOCAL">
  <class>hellospring.entity.Contato</class>
 </persistence-unit>
</persistence>
Tudo limpo até aqui? abaixo um exemplo de saída caso tudo tenha corrido bem (até o momento):

Mar 18, 2013 9:06:12 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@7ea269e2: startup date [Mon Mar 18 21:06:12 GMT-03:00 2013]; root of context hierarchy
Mar 18, 2013 9:06:12 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [applicationContext.xml]
Mar 18, 2013 9:06:13 PM org.springframework.jdbc.datasource.DriverManagerDataSource setDriverClassName
INFO: Loaded JDBC driver: org.h2.Driver
Mar 18, 2013 9:06:13 PM org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean createNativeEntityManagerFactory
INFO: Building JPA container EntityManagerFactory for persistence unit 'hello-spring-pu'
Mar 18, 2013 9:06:13 PM org.hibernate.annotations.common.Version 
INFO: HCANN000001: Hibernate Commons Annotations {4.0.1.Final}
Mar 18, 2013 9:06:13 PM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {4.2.0.Final}
Mar 18, 2013 9:06:13 PM org.hibernate.cfg.Environment 
INFO: HHH000206: hibernate.properties not found
Mar 18, 2013 9:06:13 PM org.hibernate.cfg.Environment buildBytecodeProvider
INFO: HHH000021: Bytecode provider name : javassist
Mar 18, 2013 9:06:13 PM org.hibernate.ejb.Ejb3Configuration configure
INFO: HHH000204: Processing PersistenceUnitInfo [
 name: hello-spring-pu
 ...]
Mar 18, 2013 9:06:13 PM org.hibernate.service.jdbc.connections.internal.ConnectionProviderInitiator instantiateExplicitConnectionProvider
INFO: HHH000130: Instantiating explicit connection provider: org.hibernate.ejb.connection.InjectedDataSourceConnectionProvider
Mar 18, 2013 9:06:14 PM org.hibernate.dialect.Dialect 
INFO: HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
Mar 18, 2013 9:06:14 PM org.hibernate.engine.transaction.internal.TransactionFactoryInitiator initiateService
INFO: HHH000268: Transaction strategy: org.hibernate.engine.transaction.internal.jdbc.JdbcTransactionFactory
Mar 18, 2013 9:06:14 PM org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory 
INFO: HHH000397: Using ASTQueryTranslatorFactory
Mar 18, 2013 9:06:15 PM org.hibernate.tool.hbm2ddl.SchemaUpdate execute
INFO: HHH000228: Running hbm2ddl schema update
Mar 18, 2013 9:06:15 PM org.hibernate.tool.hbm2ddl.SchemaUpdate execute
INFO: HHH000102: Fetching database metadata
Mar 18, 2013 9:06:15 PM org.hibernate.tool.hbm2ddl.SchemaUpdate execute
INFO: HHH000396: Updating schema
Mar 18, 2013 9:06:15 PM org.hibernate.tool.hbm2ddl.SchemaUpdate execute
INFO: HHH000232: Schema update complete
Mar 18, 2013 9:06:15 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@2e4fda99: defining beans [org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.annotation.internalPersistenceAnnotationProcessor,agenda,sampleBean1,sampleBean2,dataSource,entityManagerFactory,org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor]; root of factory hierarchy
Hello!

Você pode modificar a classe Main para executar um pequeno teste:

package hellospring;

import hellospring.beans.Agenda;
import hellospring.beans.SampleBean2;
import hellospring.entity.Contato;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {

 public static void main(String[] args) {
  ApplicationContext ctx = //
  new ClassPathXmlApplicationContext("applicationContext.xml");
  Agenda agenda = ctx.getBean(Agenda.class);
  Contato c = new Contato();
  c.setNome("Fulano");
  c.setEndereco("Rua tal");
  c.setTelefone("12345678");
  agenda.salvar(c);
  System.out.println(agenda.listar());
} }

Perfeito né? ocorre que se você executar esta classe, um erro curioso e não muito óbvio vai ocorrer:

INFO: HHH000232: Schema update complete
Mar 19, 2013 10:48:50 PM org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons
INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@2df0d2a3: defining beans [org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,org.springframework.context.annotation.internalPersistenceAnnotationProcessor,agenda,sampleBean1,sampleBean2,dataSource,entityManagerFactory,org.springframework.context.annotation.ConfigurationClassPostProcessor.importAwareProcessor]; root of factory hierarchy
Hibernate: select contato0_.id as id1_0_, contato0_.endereco as endereco2_0_, contato0_.nome as nome3_0_, contato0_.telefone as telefone4_0_ from Contato contato0_
[]

Percebeu? No código de testes mandamos primeiro salvar, depois listar. Mas ao listar temos um resultado vazio.

Isto ocorre porque o modo transacional não está funcionando. Sim... se não comitou, não salvou, ainda estamos no mundo relacional, ;-)

Para ativar o modo relacional, primeiro modifique seu applicationContext.xml para adicionar o suporte a transações:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:context="http://www.springframework.org/schema/context"
 xmlns:aop="http://www.springframework.org/schema/aop" 
 xmlns:jee="http://www.springframework.org/schema/jee" 
 xmlns:tx="http://www.springframework.org/schema/tx"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="
 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd 
 http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd 
 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd 
 http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.2.xsd   
 http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">
 
 <context:annotation-config/>
 <context:component-scan base-package="hellospring.beans"/>
 
 <bean id="dataSource"
 class="org.springframework.jdbc.datasource.DriverManagerDataSource">
  <property name="driverClassName" value="org.h2.Driver" />
  <property name="url" value="jdbc:h2:~/agenda" />
  <property name="username" value="sa" />
  <property name="password" value="sa" />
 </bean>
 
 <bean id="entityManagerFactory"
 class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
  <property name="dataSource" ref="dataSource" />
  <property name="jpaVendorAdapter">
   <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<!--     <property name="generateDdl" value="true"/> -->
    <property name="showSql" value="true" />
   </bean>
  </property>
  <property name="jpaProperties">
   <props>
    <prop key="hibernate.hbm2ddl.auto">update</prop>
   </props>
  </property>
 </bean>
 
 <bean id="transactionManager" 
 class="org.springframework.orm.jpa.JpaTransactionManager">
  <property name="entityManagerFactory" ref="entityManagerFactory" />
 </bean>

 <tx:annotation-driven transaction-manager="transactionManager" />
 
</beans>

Mas isto não é tudo, um novo erro ocorrerá:

Mar 19, 2013 11:30:49 PM org.springframework.context.support.AbstractApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@397dea61: startup date [Tue Mar 19 23:30:49 GMT-03:00 2013]; root of context hierarchy
Mar 19, 2013 11:30:49 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [applicationContext.xml]
Exception in thread "main" org.springframework.beans.factory.BeanDefinitionStoreException: Unexpected exception parsing XML document from class path resource [applicationContext.xml]; nested exception is java.lang.NoClassDefFoundError: org/aopalliance/intercept/MethodInterceptor
 at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:412)
 at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:334)
 at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:302)
 at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:174)
 at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:209)
 at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:180)
 at org.springframework.beans.factory.support.AbstractBeanDefinitionReader.loadBeanDefinitions(AbstractBeanDefinitionReader.java:243)
 at org.springframework.context.support.AbstractXmlApplicationContext.loadBeanDefinitions(AbstractXmlApplicationContext.java:127)
 at org.springframework.context.support.AbstractXmlApplicationContext.loadBeanDefinitions(AbstractXmlApplicationContext.java:93)
 at org.springframework.context.support.AbstractRefreshableApplicationContext.refreshBeanFactory(AbstractRefreshableApplicationContext.java:130)
 at org.springframework.context.support.AbstractApplicationContext.obtainFreshBeanFactory(AbstractApplicationContext.java:537)
 at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:451)
 at org.springframework.context.support.ClassPathXmlApplicationContext.(ClassPathXmlApplicationContext.java:139)
 at org.springframework.context.support.ClassPathXmlApplicationContext.(ClassPathXmlApplicationContext.java:83)
 at hellospring.Main.main(Main.java:13)
Caused by: java.lang.NoClassDefFoundError: org/aopalliance/intercept/MethodInterceptor
 at java.lang.ClassLoader.defineClass1(Native Method)
 at java.lang.ClassLoader.defineClass(Unknown Source)
 at java.security.SecureClassLoader.defineClass(Unknown Source)
 at java.net.URLClassLoader.defineClass(Unknown Source)
 at java.net.URLClassLoader.access$100(Unknown Source)
 at java.net.URLClassLoader$1.run(Unknown Source)
 at java.net.URLClassLoader$1.run(Unknown Source)
 at java.security.AccessController.doPrivileged(Native Method)
 at java.net.URLClassLoader.findClass(Unknown Source)
 at java.lang.ClassLoader.loadClass(Unknown Source)
 at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
 at java.lang.ClassLoader.loadClass(Unknown Source)
 at org.springframework.transaction.config.AnnotationDrivenBeanDefinitionParser$AopAutoProxyConfigurer.configureAutoProxyCreator(AnnotationDrivenBeanDefinitionParser.java:126)
 at org.springframework.transaction.config.AnnotationDrivenBeanDefinitionParser.parse(AnnotationDrivenBeanDefinitionParser.java:84)
 at org.springframework.beans.factory.xml.NamespaceHandlerSupport.parse(NamespaceHandlerSupport.java:73)
 at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(BeanDefinitionParserDelegate.java:1438)
 at org.springframework.beans.factory.xml.BeanDefinitionParserDelegate.parseCustomElement(BeanDefinitionParserDelegate.java:1428)
 at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.parseBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:185)
 at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:139)
 at org.springframework.beans.factory.xml.DefaultBeanDefinitionDocumentReader.registerBeanDefinitions(DefaultBeanDefinitionDocumentReader.java:108)
 at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.registerBeanDefinitions(XmlBeanDefinitionReader.java:493)
 at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:390)
 ... 14 more
Caused by: java.lang.ClassNotFoundException: org.aopalliance.intercept.MethodInterceptor
 at java.net.URLClassLoader$1.run(Unknown Source)
 at java.net.URLClassLoader$1.run(Unknown Source)
 at java.security.AccessController.doPrivileged(Native Method)
 at java.net.URLClassLoader.findClass(Unknown Source)
 at java.lang.ClassLoader.loadClass(Unknown Source)
 at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
 at java.lang.ClassLoader.loadClass(Unknown Source)
 ... 36 more

Se você ganhou um desses também, leia a stacktrace: ClassNotFoundException. Sim... vamos baixar outro jar, o aopalliance.jar.

Baixe-o, copie e cole no projeto, e adicione no classpath.
Você sabe como fazer... Confio em você!

Depois disso, pra fechar com chave de ouro, modifique o Agenda.java e deixe-o desse jeito:

package hellospring.beans;

import hellospring.entity.Contato;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Component
public class Agenda {

 @PersistenceContext
 private EntityManager em;

 @Transactional
 public void salvar(Contato c) {
  em.persist(c);
 }

 @Transactional
 public void excluir(int id) {
  em.createQuery("delete from Contato c where c.id = :id")//
    .setParameter("id", id).executeUpdate();
 }

 public List listar() {
  return em.createQuery("select c from Contato c", Contato.class)
    .getResultList();
 }
}

Ao executar novamente seu teste, seus dados estarão sendo salvos perfeitamente.
Lindo, não?

E é isso, simples assim.

Não, sério mesmo. Só isso.

Se você olhar, seu esforço foi pouco, para um grande benefício.

E isto conclui a parte 2, now with lasers, ;-)

Vejo vocês depois, até a próxima!

2 comentários :