- Presta!
- Então tá.
Hoje vamos fazer um projeto java web completamente gerido pelo gradle. A grande vantagem é aproveitar o script nas ferramentas de integração contínua com pouca ou nenhuma alteração.
Para esta receita precisaremos dos seguintes ingredientes:
- java 8 disponível no path do sistema
- gradle disponível no path do sistema
- um editor de códigos (recomendo o notepad++!)
O passo seguinte é criar a pasta do nosso projeto. O nome será rango-vote-spring. Dentro desta pasta criamos o arquivo build.gradle.
O que é esse arquivo? Para que serve? Como vivem? Vamos tecer um exemplo:
Você pode usar um eclipse pra montar e configurar seu projeto. Ou um netbeans, ou um intellij.
A desvantagem de ferramentas assim é que se a sua equipe for maior que você, você mesmo e você de novo, todo mundo precisa ter um eclipse também.
Mas isso não é problema, alguém diria. E se em sua equipe você tiver um membro responsável unicamente por publicar a aplicação? Ele baixa tudo do zero, roda os testes, publica um pacote e manda email avisando que deu certo.
E se ele não for um ser humano? (pausa dramática)
colegas de equipe como teamcity, jenkins, bamboo, dentre outros, não fazem uso de IDE's. Eles só precisam de um script bem esperto que saiba como compilar tudo, testar e empacotar.
Pulando todas as eras geológicas pelas quais o java passou (até nunca mais, maven!), é onde o gradle entra.
Com ele você pode gerir um projeto java, sem depender de nenhuma outra ferramenta externa.
A melhor parte é que o gradle não exclui a possibilidade de usar uma IDE, na realidade ele possui plugins que permitem o convívio sadio com elas.
Nosso build.gradle vai começar assim:
apply plugin: 'java' apply plugin: 'war' sourceCompatibility = 1.8 targetCompatibility = 1.8 repositories { mavenCentral() }
Note que isto é um arquivo texto, com uma sintaxe própria, mas sem muitos mistérios. Com esta configuração básica temos suporte a criar projetos java para a web.
Este arquivo prevê a existência de uma estrutura de pastas similar ao que o finado maven utilizava. Crie a pasta src dentro de rango-vote-spring, e dentro desta src crie as pastas main e test. cada uma delas terá a pasta java. Se ficou confuso, sua estrutura de pastas deve ser a seguinte:
rango-vote-spring/ src/ main/ java/ test/ java/ build.gradle
Dentro destas pastas 'java' é que a sua estrutura de pacotes começa.
Nosso próximo passo é escolher as tecnologias para o nosso aplicativo. Começemos adicionando o spring-webmvc.
Como estamos usando o gradle para configurar nosso projeto, isso significa adicionar ao nosso build.gradle as dependências:
apply plugin: 'java' apply plugin: 'war' sourceCompatibility = 1.8 targetCompatibility = 1.8 repositories { mavenCentral() } dependencies { compile 'org.springframework:spring-context:4.2.1.RELEASE' compile 'org.springframework:spring-webmvc:4.2.1.RELEASE' compile 'org.springframework:spring-web:4.2.1.RELEASE' compile 'org.springframework:spring-context-support:4.2.1.RELEASE' }
Ao efetuar um "gradle build", a saída será mais ou menos essa:
Vamos adicionar ainda suporte ao hibernate:
apply plugin: 'java' apply plugin: 'war' sourceCompatibility = 1.8 targetCompatibility = 1.8 repositories { mavenCentral() } dependencies { compile 'org.springframework:spring-context:4.2.1.RELEASE' compile 'org.springframework:spring-webmvc:4.2.1.RELEASE' compile 'org.springframework:spring-web:4.2.1.RELEASE' compile 'org.springframework:spring-orm:4.2.1.RELEASE' compile 'org.springframework:spring-tx:4.2.1.RELEASE' compile 'org.springframework.data:spring-data-jpa:1.9.0.RELEASE' compile 'org.hibernate:hibernate-core:5.0.2.Final' compile 'org.hibernate:hibernate-entitymanager:5.0.2.Final' compile 'org.slf4j:slf4j-simple:1.7.12' compile 'org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final' compile 'org.springframework:spring-context-support:4.2.1.RELEASE' }
Toda vez que o build.gradle for alterado, você deve rodar o comando gradle build para que a ferramenta baixe possíveis novos plugins e compile e construa novamente o projeto.
Tratemos do banco de dados agora. Usaremos um postgresql, que já foi debatido aqui no blog algumas outras vezes. Devemos adicionar a dependência ao drive de banco ao build.gradle:
apply plugin: 'java' apply plugin: 'war' sourceCompatibility = 1.8 targetCompatibility = 1.8 repositories { mavenCentral() } dependencies { compile 'org.springframework:spring-context:4.2.1.RELEASE' compile 'org.springframework:spring-webmvc:4.2.1.RELEASE' compile 'org.springframework:spring-web:4.2.1.RELEASE' compile 'org.springframework:spring-orm:4.2.1.RELEASE' compile 'org.springframework:spring-tx:4.2.1.RELEASE' compile 'org.springframework.data:spring-data-jpa:1.9.0.RELEASE' compile 'org.hibernate:hibernate-core:5.0.2.Final' compile 'org.hibernate:hibernate-entitymanager:5.0.2.Final' compile 'org.slf4j:slf4j-simple:1.7.12' compile 'org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final' compile 'org.springframework:spring-context-support:4.2.1.RELEASE' compile 'org.postgresql:postgresql:9.2-1004-jdbc41' }
Note que após ter executado um gradle build, a estrutura de pastas deve estar assim:
rango-vote-spring/ src/ main/ java/ test/ java/ build/ libs/ rango-vote-spring.war .gradle/ build.gradle
Para gerenciar nosso banco de dados precisaremos de uma ferramenta de migração. A selecionada é o flywaydb, pelo simples motivo do liquibase ser muito complicado, sem contar que o flyway me lembra aquela música do Areosmith, ;-)
plugins { id "org.flywaydb.flyway" version "3.2" } apply plugin: 'java' apply plugin: 'war' sourceCompatibility = 1.8 targetCompatibility = 1.8 repositories { mavenCentral() } flyway { driver = 'org.postgresql.Driver' url = 'jdbc:postgresql://localhost:5432/rango-vote' password = 'postgres' user = 'postgres' } dependencies { compile 'org.springframework:spring-context:4.2.1.RELEASE' compile 'org.springframework:spring-webmvc:4.2.1.RELEASE' compile 'org.springframework:spring-web:4.2.1.RELEASE' compile 'org.springframework:spring-orm:4.2.1.RELEASE' compile 'org.springframework:spring-jms:4.2.1.RELEASE' compile 'org.springframework:spring-tx:4.2.1.RELEASE' compile 'org.springframework.data:spring-data-jpa:1.9.0.RELEASE' compile 'org.hibernate:hibernate-core:5.0.2.Final' compile 'org.hibernate:hibernate-entitymanager:5.0.2.Final' compile 'org.springframework.security:spring-security-web:4.0.2.RELEASE' compile 'org.slf4j:slf4j-simple:1.7.12' compile 'org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final' compile 'org.springframework:spring-context-support:4.2.1.RELEASE' compile 'org.postgresql:postgresql:9.2-1004-jdbc41' compile "org.flywaydb:flyway-core:3.2.1" }
A novidade, conforme pode ser visto, é a adição do plugin que o flyway oferece para gerenciar as versões do banco. rodando um gradle tasks na linha de comando poderemos ver a task do flyway:
Agora vamos criar uns migrates. O flyway espera encontrar os scripts na estrutura de pastas abaixo:
rango-vote-spring/ src/ main/ java/ resources/ db/ migration/ V2015.10.13__esquema_inicial.sql test/ java/ build/ libs/ rango-vote-spring.war .gradle/ build.gradle
Onde se lê V2015.10.13__esquema_inicial.sql temos o conteúdo abaixo:
-- -- versão inicial do banco -- desenhado para trabalhar com postgresql -- -- aqui temos os restaurantes create table restaurante( idrestaurante serial primary key, nomerestaurante varchar(255) not null, latrestaurante numeric(9,6) DEFAULT -30.1087957, lngrestaurante numeric(9,6) DEFAULT -51.3169918, zoomrestaurante integer DEFAULT 10, fotorestaurante bytea ); -- temos equipes também create table equipe( idequipe serial primary key, nomeequipe varchar(255) not null ); -- temos membros (o 'profissional faminto' da estória 1) create table membro( idmembro serial primary key, idequipe integer not null, emailmembro varchar(255) not null, hashsenhamembro varchar(255) not null, fotomembro bytea, foreign key (idequipe) references equipe(idequipe) ); -- temos votos create table voto( dtvoto date not null, idrestaurante integer not null, idmembro integer not null, foreign key (idrestaurante) references restaurante(idrestaurante), foreign key (idmembro) references membro(idmembro), primary key (dtvoto,idmembro) ); -- tabela com os ganhadores, pra facilitar a estória 2 -- só um ganhador por dia create table ganhador( dtvitoria date primary key, idrestaurante integer not null, numvotosganhador integer not null, foreign key (idrestaurante) references restaurante(idrestaurante) ); -- tabela com os usuários administradores create table administrador( idadministrador serial primary key, emailadministrador varchar(255) not null, hashsenhaadministrador varchar(255) not null ); -- view com o resultado das votações create view vw_resultado_votacao as select dtvoto,idrestaurante,count(idmembro) as totvotos from voto group by dtvoto,idrestaurante order by totvotos desc limit 3;
Com o comando gradle flywayMigrate teremos o nosso banco de dados atualizado para o migrate mais recente (no caso esse um aí!)
Observe que o artigo não disse, mas os dados de conexão com o banco de dados precisam estar corretos para que o migrate rode.
Aliás, o banco tem que estar em pé, coisa que eu não disse também.
Mas eu acredito em você! Aposto que o banco está de pé e os dados estão corretos!
Dando andamento à montagem do projeto, vamos montar as configurações xml do spring.
O applicationContext.xml (o arquivo onde configuramos um monte de troços do spring) deverá se localizar nesta altura da estrutura de pastas:
rango-vote-spring/ src/ main/ java/ resources/ db/ migration/ V2015.10.13__esquema_inicial.sql webapp/ WEB-INF/ applicationContext.xml test/ java/ build/ libs/ rango-vote-spring.war .gradle/ build.gradle
O conteúdo deverá ser o seguinte:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:task="http://www.springframework.org/schema/task" 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:p="http://www.springframework.org/schema/p" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!-- let's keep this xml as clean as posible --> <mvc:annotation-driven /> <context:annotation-config /> <context:component-scan base-package="rango.vote" /> <mvc:annotation-driven> <mvc:message-converters> <bean class="org.springframework.http.converter.StringHttpMessageConverter"/> <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/> </mvc:message-converters> </mvc:annotation-driven> <!-- suporte in-app para as migrações do esquema de banco --> <jee:jndi-lookup jndi-name="jdbc/rango-vote-ds" id="dataSource" /> <bean id="flyway" class="org.flywaydb.core.Flyway" init-method="migrate"> <property name="dataSource" ref="dataSource"/> </bean> <!-- we'll need tasks to open and close voting --> <task:annotation-driven executor="myExecutor" scheduler="myScheduler"/> <task:executor id="myExecutor" pool-size="5"/> <task:scheduler id="myScheduler" pool-size="10"/> <!-- JPA EntityManager injection support --> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean"> <property name="persistenceUnitName" value="rango-vote-pu" /> </bean> <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="entityManagerFactory" /> </bean> <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" /> <tx:annotation-driven /> <!-- JavaMail support --> <bean id="mailSender" class ="org.springframework.mail.javamail.JavaMailSenderImpl" > <property name="username" value="rangovote6@gmail.com" /> <property name="password" value="********" /> <property name="javaMailProperties"> <props> <prop key="mail.host">smtp.gmail.com</prop> <prop key="mail.smtp.port">587</prop> <prop key="mail.username">rangovote6@gmail.com</prop> <prop key="mail.password">********</prop> <prop key="mail.transport.protocol">smtp</prop> <prop key="mail.smtp.auth">true</prop> <prop key="mail.smtp.starttls.enable">true</prop> <prop key="mail.from.email">rangovote6@gmail.com</prop> </props> </property> </bean> </beans>
Calma que tem mais. Ainda nos resta definir web.xml, DataSource e a PersistenceUnit, tudo xml:
web.xml:
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/app/*</url-pattern> </servlet-mapping> <!-- <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/applicationContext.xml</param-value> </context-param> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> --> </web-app>
persistence.xml:
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd" version="2.1"> <persistence-unit name="rango-vote-pu" transaction-type="RESOURCE_LOCAL"> <non-jta-data-source>java:comp/env/jdbc/rango-vote-ds</non-jta-data-source> <class>rango.vote.model.Administrador</class> <class>rango.vote.model.Restaurante</class> <class>rango.vote.model.Equipe</class> <class>rango.vote.model.Membro</class> <class>rango.vote.model.ResultadoVotacao</class> <class>rango.vote.model.Voto</class> </persistence-unit> </persistence>
context.xml:
<Context> <!-- JDBC DataSource --> <Resource name="jdbc/rango-vote-ds" auth="Container" type="javax.sql.DataSource" driverClassName="org.postgresql.Driver" url="jdbc:postgresql://localhost:5432/rango-vote" username="postgres" password="postgres" maxTotal="20" maxIdle="10" maxWaitMillis="-1"/> </Context>
Os três arquivos podem ser localizados na estrutura de pastas abaixo:
rango-vote-spring/ src/ main/ java/ resources/ META-INF/ persistence.xml db/ migration/ V2015.10.13__esquema_inicial.sql webapp/ WEB-INF/ applicationContext.xml web.xml META-INF/ context.xml test/ java/ build/ libs/ rango-vote-spring.war .gradle/ build.gradle
As localizações parecem meio cabalísticas no começo, mas tratam-se de localidades padrão que quando o projeto é montado, cada uma se dirige a um lugar adequado.
Quase prontos para partir!
O que precisamos agora é de um servidor para rodarmos nosso app. O gradle tem plugin pra isso também:
plugins { id "com.bmuschko.tomcat" version "2.2.2" id "org.flywaydb.flyway" version "3.2" } apply plugin: 'java' apply plugin: 'war' sourceCompatibility = 1.8 targetCompatibility = 1.8 repositories { mavenCentral() flatDir name: 'localRepository', dirs: 'lib' } flyway { driver = 'org.postgresql.Driver' url = 'jdbc:postgresql://localhost:5432/rango-vote' password = 'postgres' user = 'postgres' } tomcat { tomcatRunWar.outputFile = file('tomcat-war.log') tomcatRun.outputFile = file('tomcat.log') } dependencies { def tomcatVersion = '8.0.27' tomcat "org.apache.tomcat.embed:tomcat-embed-core:${tomcatVersion}", "org.apache.tomcat.embed:tomcat-embed-logging-juli:${tomcatVersion}", "org.apache.tomcat.embed:tomcat-embed-jasper:${tomcatVersion}", "org.apache.tomcat:tomcat-dbcp:${tomcatVersion}" providedCompile 'javax.servlet:javax.servlet-api:3.1.0' compile 'org.springframework:spring-context:4.2.1.RELEASE' compile 'org.springframework:spring-webmvc:4.2.1.RELEASE' compile 'org.springframework:spring-web:4.2.1.RELEASE' compile 'org.springframework:spring-orm:4.2.1.RELEASE' compile 'org.springframework:spring-jms:4.2.1.RELEASE' compile 'org.springframework:spring-tx:4.2.1.RELEASE' compile 'org.springframework.data:spring-data-jpa:1.9.0.RELEASE' compile 'org.postgresql:postgresql:9.2-1004-jdbc41' compile 'org.hibernate:hibernate-core:5.0.2.Final' compile 'org.hibernate:hibernate-entitymanager:5.0.2.Final' compile 'org.springframework.security:spring-security-web:4.0.2.RELEASE' compile 'org.slf4j:slf4j-simple:1.7.12' compile 'org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.0.Final' compile "org.flywaydb:flyway-core:3.2.1" compile 'com.fasterxml.jackson.core:jackson-databind:2.6.3' compile 'javax.mail:mail:1.4' compile 'org.springframework:spring-context-support:4.2.1.RELEASE' providedCompile "org.projectlombok:lombok:1.16.6" testCompile 'org.springframework:spring-test:4.2.1.RELEASE' testCompile "junit:junit:4.12" }
Com este plugin poderemos rodar o tomcat e testar nossa aplicação a partir da linha de comando usando o comando gradle tomcatRunWar.
Por fim, nossas classes:
rango-vote-spring/src/main/java/rango/vote/to/VotingStatus.java:
package rango.vote.to; import java.util.Date; import rango.vote.model.Membro; import lombok.Data; @Data public class VotingStatus { private Membro membro; private boolean votingOpen; private Date dia; }
Alguns aficionados dirão: "Mas onde estão seus get/set?" A anotação @Data, um oferecimento do lombok (a lib java mais bonita da cidade!), equivale a fazer os tediodos get/set, equals e hashcode.
A lib é mais antiga que alguns desenvolvedores java, mas em pleno java 8 e avanço do openjdk temos o lombok funcionando bonito.
Agora as classes que fazem a ponte entre o banco de dados e o código java:
rango-vote-spring/src/main/java/rango/vote/model/Membro.java:
package rango.vote.model; import javax.persistence.Id; import javax.persistence.Table; import javax.persistence.Entity; import javax.persistence.ManyToOne; import javax.persistence.JoinColumn; import lombok.Data; @Data @Entity @Table(name="membro") public class Membro { @Id private long idMembro; private String emailMembro; private String hashSenhaMembro; @ManyToOne @JoinColumn(name="idequipe") private Equipe equipe; }
Esta classe java, além da anotação @Data, contém também as anotações @Entity, @Table, entre outras. Estas são anotações do JPA, que ajuda os programadores java a não lidar diretamente com o mapeamento entre os objetos java e as tabelas do banco.
Seguindo.
rango-vote-spring/src/main/java/rango/vote/model/Equipe.java
package rango.vote.model; import javax.persistence.Id; import javax.persistence.Table; import javax.persistence.Entity; import lombok.Data; @Data @Entity @Table(name="equipe") public class Equipe { @Id private long idEquipe; private String nomeEquipe; }
Nessa classe, também uma classe que só serve para espelhar aquelas tabelas criadas lá em cima, alguns podem perguntar: "Cadê o @OneToMany para Membro?"
Em Membro temos um atributo do tipo Equipe anotado com @ManyToOne, denotando que na tabela membro temos uma chave estrangeira para a tabela equipe.
Já a anotação @OneToMany decora coleções. E as coleções não estão aqui por duas razões:
- Não sou obrigado
- Minhas rotas serão transformadas em JSON, logo o mapeamento unidirecional me beneficia.
rango-vote-spring/src/main/java/rango/vote/model/Administrador.java:
package rango.vote.model; import javax.persistence.Id; import javax.persistence.Table; import javax.persistence.Entity; import lombok.Data; @Data @Entity @Table(name="administrador") public class Administrador { @Id private long idAdministrador; private String emailAdministrador; }
rango-vote-spring/src/main/java/rango/vote/model/Restaurante.java:
package rango.vote.model; import javax.persistence.Id; import javax.persistence.Table; import javax.persistence.Entity; import lombok.Data; @Data @Entity @Table(name="restaurante") public class Restaurante { @Id private long idRestaurante; private String nomeRestaurante; }
rango-vote-spring/src/main/java/rango/vote/model/Voto.java:
package rango.vote.model; import java.util.Date; import java.io.Serializable; import javax.persistence.Id; import javax.persistence.Table; import javax.persistence.Entity; import javax.persistence.Temporal; import javax.persistence.ManyToOne; import javax.persistence.JoinColumn; import javax.persistence.TemporalType; import lombok.Data; @Data @Entity @Table(name="voto") public class Voto implements Serializable { private static final long serialVersionUID = 2806425360666625L; @Id @Temporal(value=TemporalType.DATE) private Date dtVoto; @Id @ManyToOne @JoinColumn(name="idmembro") private Membro membro; @ManyToOne @JoinColumn(name="idrestaurante") private Restaurante restaurante; }
rango-vote-spring/src/main/java/rango/vote/model/ResultadoVotacao.java:
package rango.vote.model; import java.util.Date; import java.io.Serializable; import javax.persistence.Id; import javax.persistence.Table; import javax.persistence.Entity; import javax.persistence.Temporal; import javax.persistence.ManyToOne; import javax.persistence.JoinColumn; import javax.persistence.TemporalType; import lombok.Data; @Data @Entity @Table(name="vw_resultado_votacao") public class ResultadoVotacao implements Serializable { private static final long serialVersionUID = 999664253605L; @Id @Temporal(value=TemporalType.DATE) private Date dtVoto; @Id @ManyToOne @JoinColumn(name="idrestaurante") private Restaurante restaurante; private long totVotos; }
Nesta classe temos algo bacana para exemplificar: Não somos obrigados a mapear unicamente tabelas. Podemos mapear visões de banco também. Mais uma vez, os aficionados podem perguntar por que usar uma view e não criteria do hibernate/jpa. a simplicidade da estrutura sql e da classe aqui anotada deixo como resposta. E até mesmo reflexão:
Até que ponto o tal "código de banco portável" é vantagem?
Há um ditado que diz: Se você tem um problema complexo para resolver, resolva um problema simples que faça a mesma coisa. Então você terá resolvido o problema complexo.
Continuando:
rango-vote-spring/src/main/java/rango/vote/RangoVoteRepository.java:
package rango.vote; import java.util.Date; import java.util.List; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; import rango.vote.model.ResultadoVotacao; import rango.vote.model.Restaurante; import rango.vote.model.Membro; import rango.vote.model.Voto; @Repository public class RangoVoteRepository { @PersistenceContext private EntityManager em; public List<Restaurante>listCandidatos(String search) throws Exception { // XXX adicionar detalhes a esta query String q = "select r from Restaurante r where upper(r.nomeRestaurante) like upper(:n)"; return em.createQuery(q,Restaurante.class)// .setParameter("n","%"+search+"%").getResultList(); } public List<Membro>listMembros() throws Exception { return em.createQuery("select m from Membro m",Membro.class).getResultList(); } public Membro login(Membro membro) throws Exception { if(membro==null) return null; String email = membro.getEmailMembro(); String hash = membro.getHashSenhaMembro(); String q = "select m from Membro m where m.emailMembro = :email and m.hashSenhaMembro = :hash"; return em.createQuery(q,Membro.class)// .setParameter("email",email).setParameter("hash",hash).getSingleResult(); } @Transactional public void vote(Voto voto) throws Exception { em.persist(voto); } public List<ResultadoVotacao>listResultadoVotacao(Date dtResultado) throws Exception { String q = "select r from ResultadoVotacao r where r.dtVoto = :dtResultado"; return em.createQuery(q,ResultadoVotacao.class)// .setParameter("dtResultado",dtResultado).getResultList(); } public List<String>listMemberMail() throws Exception { String q = "select m.emailMembro from Membro m"; return em.createQuery(q,String.class).getResultList(); } }
Esta classe é o nosso repositório. Ela sabe como recuperar nossas coleções. A anotação @Transactional indica que temos ali uma operação protegida de qualquer acesso concorrente.
rango-vote-spring/src/main/java/rango/vote/RangoVoteController.java:
package rango.vote; import java.util.List; import java.sql.Date; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.stereotype.Controller; import rango.vote.model.ResultadoVotacao; import rango.vote.model.Restaurante; import rango.vote.to.VotingStatus; import rango.vote.model.Membro; import rango.vote.model.Voto; @Controller @RequestMapping("/controller") public class RangoVoteController { @Autowired private RangoVoteRepository repo; @Autowired private RangoElectionTask task; @RequestMapping("/getCurrentMember") public @ResponseBody Membro getCurrentMember(HttpServletRequest req) throws Exception { return (Membro) req.getSession().getAttribute("logado"); } @RequestMapping("/isVotingOpen") public @ResponseBody String isVotingOpen() throws Exception { return ""+task.isVotingOpen(); } @RequestMapping("/getVotingStatus") public @ResponseBody VotingStatus getVotingStatus(HttpServletRequest req) throws Exception { Membro m = (Membro) req.getSession().getAttribute("logado"); VotingStatus vs = new VotingStatus(); vs.setMembro(m); vs.setVotingOpen(task.isVotingOpen()); vs.setDia(new Date(System.currentTimeMillis())); return vs; } @RequestMapping("/listCandidatos") public @ResponseBody List<Restaurante>listCandidatos(@RequestParam("q") String q) throws Exception { return repo.listCandidatos(q); } @RequestMapping("/listMembros") public @ResponseBody List<Membro>listMembros() throws Exception { return repo.listMembros(); } @RequestMapping("/login") public @ResponseBody Membro login(@RequestBody Membro membro, HttpServletRequest req,HttpServletResponse response) throws Exception { Membro m = repo.login(membro); if(m != null){ m.setHashSenhaMembro(""); req.getSession().setAttribute("logado",m); return m; }else{ response.setStatus(404); return membro; } } @RequestMapping(value="/vote",produces="text/plain") public @ResponseBody String vote(@RequestBody Voto voto,HttpServletResponse response) throws Exception { Date d = new Date(System.currentTimeMillis()); if(task.isVotingOpen()){ voto.setDtVoto(d); repo.vote(voto); return "OK"; }else{ response.setStatus(404); return "voteclosed"; } } @RequestMapping("/listResultadoVotacao") public @ResponseBody List<ResultadoVotacao>listResultadoVotacao() throws Exception { return repo.listResultadoVotacao(new Date(System.currentTimeMillis())); } }
Esta aqui é importante. Isto é um controller do spring, e ele corresponde a endereços no navegador, como pode ser observado nas anotações @RequestMapping.
O spring mvc com pouco ajuste pode trabalhar com modernas estruturas da web, serviços REST, tradução automática de objetos e por aí vai.
Estes serviços são consumidos por uma aplicação angularjs, Mas isso trataremos com mais paciência noutro instante.
Todo o conteúdo deste artigo pode ser melhor estudado neste repositório aqui.
Até outra hora, sem mais.
Nenhum comentário :
Postar um comentário