Monday, April 24, 2017

E-Commerce com Servlets, JSP e JPA: parte 1

Com certeza você já utilizou os serviços de alguma loja virtual e, portanto, deve saber como funciona o processo de compra na web. Não importa em qual loja você esteja comprando, todas elas funcionam da mesma forma, isto é, toda especificação para um sistema de e-commerce tem requisitos essenciais que estão presentes em todos eles. Por exemplo, toda loja online deve permitir que o usuário que a visita possa realizar as seguintes ações:
  • Pesquisar por algum produto baseado no nome ou na descrição
  • Navegar através de uma lista de produtos classificados por categoria
  • Selecionar um produto e ver seus detalhes
  • Colocar o produto em um carrinho de compras
  • Ver e alterar os produtos que estão no carrinho de compras
  • Fazer o pedido e finalizar a compra
Ao longo de 3 posts desenvolveremos um projeto com as especificações acima chamado Stark House E-Commerce. Utilizaremos os frameworks JavaEE Servlets, JSP e JPA junto na IDE NetBeans 8. O banco de dados será o PostgreSQL.

JPA: Modelando o negócio e criando o banco de dados

Java Persistence API (JPA) é a especificação padrão Java EE para persistencia de dados. O JPA realiza sozinho todo mapeamento a partir do modelo orientado a objetos e constrói toda base de dados relacional independente do banco dados que a aplicação vá utilizar.

O diagrama de classes abaixo nos mostra como será o modelo de negócios do projeto:


Um cliente pode realizar vários pedidos. Cada pedido é feito por apenas um cliente. Uma categoria tem vários produtos. Cada produto pertence a somente uma categoria. Um pedido pode ter vários produtos e um produto pode estar em vários pedidos.

Produtos e Pedidos representam uma relação muitos-para-muitos. Esse tipo de relação precisa ser normnalizado. Assim, criamos a classe orderProduct que contem a quantidade de itens de um pedido e o subtotal (mais sobre normalização em seguida...).

Codificando o modelo (para clareza, as declarações import foram omitidas)

Abra a IDE NetBeans, crie um novo projeto Java Web, dê o nome de StarkHouseECommerce e finalize. Delete o arquivo index.html que o NetBeans cria automaticamente.
Para utilizar o JPA você precisa ter as seguintes biblioteca no seu classpath, conforme a imagem da janela de projetos do NetBeans:


Você pode baixar as bibliotecas aqui hibernate 4.3.5.Final (todos os arquivos na pasta /lib/required). Também deve usar esta hibernate-entitymanager. E o driver do banco de dados PostgreSQL.

A especificação do JPA nos diz que precisamos ter o arquivo persistence.xml no projeto. É nele que ficam as informações de acesso ao banco de dados. No nosso exemplo, o arquivo deve ser como segue:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" 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">
  <persistence-unit name="ecommerce" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <properties>
      <property name="javax.persistence.jdbc.url" value="jdbc:postgresql://localhost:5432/ecommerce"/>
      <property name="javax.persistence.jdbc.user" value="postgres"/>
      <property name="javax.persistence.jdbc.password" value="postgres"/>
      <property name="javax.persistence.jdbc.driver" value="org.postgresql.Driver"/>           
      <property name="hibernate.show_sql" value="true"/>
      <property name="hibernate.format_sql" value="true"/>
      <property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect"/>
      <property name="hibernate.hbm2ddl.auto" value="update"/>
    </properties>
  </persistence-unit>
</persistence>
No meu banco, usuário e senha são ambos postgres. E a porta geralmente é 5432. Se no seu caso for diferente, apenas faça as adequações. Crie um banco de dados chamado ecommerce.

Criando as Entidades

Classe EClient.java, a entidade que realiza as compras no nosso site.
//imports omitidos 
@Entity
@Table(name = "eclient")
public class EClient implements Serializable{   

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;   //chave primária gerada sequencialmente
    @Column
    private String firstName;   
    @Column
    private String lastName;  
    @Column @Temporal(TemporalType.TIMESTAMP)
    private Date birthDate;   //campo tipo data e tempo
    @Column
    private String creditCard;   
    @Column
    private String email;   
    @Column
    private String login;  

//esta anotação indica uma relação "um-pra-muitos" entre cliente e pedido
    @OneToMany(mappedBy = "client")
    private List<EOrder> orders;

//construtor
    public EClient() {
        orders = new ArrayList<>();
    }
    //getters & setters para cada atributo
}
A classe EOrder que representa os pedidos do nosso sistema:
@Entity
public class EOrder {
   
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;  
    @Column
    @Temporal(TemporalType.TIMESTAMP)
    private Date date;  

    @ManyToOne   //cada pedido pertence a um cliente
    @JoinColumn(name = "CLI_ID")  //equivale a chave estrangeira da classe EClient
    private EClient client;  

    @OneToMany(mappedBy = "eorder")  //um pedido tem muitos produtos
    private List<OrderProduct> orderProduct;

    public EOrder() {
        client = new EClient();
        orderProduct = new ArrayList<>();
    }
//getters e setters omitidos
}
A classe Product:
@Entity
@Table(name = "product")
public class Product {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;   
    @Column
    private String name;   
    @Column
    private Double price;   
    @Column
    private String description;   

    @ManyToOne  //cada produto pertence a uma categoria
    @JoinColumn(name = "CAT_ID", nullable = false) //chave estrangeira de Category
    private Category category;   

    @OneToMany(mappedBy = "product") //um produto está em muitos pedidos
    private List<OrderProduct> orderProduct;

    public Product() {
        category = new Category();
        orderProduct = new ArrayList<>();
    }
//getters e setters omitidos
}
Normalizando a relação Produto e Pedido

De acordo com o nosso digrama de classes, Product e EOrder têm entre si uma relação N para N, isto é, muitos-para-muitos. Esse tipo de relação só existe conceitualmente, na hora de implementar precisamos normaliza-la, tranformando-a em 2 relações 1 para N, uma-para-muitos. Dessa forma, criamos uma classe extra para armazenar a quantidade de produtos de cada pedido, bem como o subtotal (o cliente pode ter ganho um desconto no dia do pedido, mas o preço real do produto continua inalterado na base de dados, por exemplo). Criamos a classe OrderProduct, a qual possui uma referência tanto para Product quanto para EOrder, mais os campos quantity e total.

De acordo com a especificação do JPA, o ID dessa nova entidade deve ficar em uma classe separada, vamos chamá-la de OrderProductId:
@Entity
@IdClass(OrderProductId.class) //indica a classeId de OrderProduct
public class OrderProduct {

    @Id
    @ManyToOne
    @JoinColumn(name = "prd_id")
    Product product;
   
    @Id
    @ManyToOne
    @JoinColumn(name = "ord_id")
    EOrder eorder;
   
    @Column
    private Integer quantity;  //a quantidade de produtos no pedido
    @Column     //valor total deste produto no pedido, geralmente preço x quantidade
    private Double total;  

    //getters e setters omitidos
}
A classe OrderProductId:
public class OrderProductId implements Serializable{
   
    int eorder;
    int product;
    //getters e setters omitidos
//é obrigatório a implementação dos métodos hashCode() e equals() utilizando os dois campos
}
A classe Category:
@Entity
@Table(name = "category")
public class Category {  

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column
    private String name;

    @OneToMany(mappedBy = "category", fetch = FetchType.LAZY)
    private List<Product> products;  //uma categoria tem muitos produtos

    public Category() {
        products = new ArrayList<>();
    }
//getters e setters omitidos
}
Populando o banco de dados

Agora que temos todo o modelo pronto, é hora de popular as tabelas do banco de dados. Não se esqueça de criar um banco de dados chamado ecommerce. O JPA criará todas as tabelas e os relacionamentos para você.

Para preencher as tabelas crie uma classe chamada teste com um método principal e execute-a:
public class teste {

    public static void main(String[] args) {

//"ecommerce" é o nome da unidade de persistencia conforme definido no arquivo persistence.xml
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("ecommerce");
        EntityManager em = emf.createEntityManager();

//criando as categorias
        Category eletronicos = new Category("eletrônicos", null);
        Category suplementos = new Category("suplementos", null);
        Category livros = new Category("livros", null);
       
//criando os produtos e passando a respectiva categoria no construtor
        Product ps4 = new Product("PS4", 566.23,
                "Adquira o PS4™ e prepare-se para a experiência de jogo mais imersiva possível!", eletronicos);
        Product noteDell = new Product("NoteBook Dell", 303.99, "Linux, 15.6, 8GB, i7", eletronicos);
        Product IPhone = new Product("IPhone 5S", 299.99, "16GB, 4G, Explore o mundo do iPhone!", eletronicos);
       
        Product wheyProt = new Product("Whey Protein ON", 32.01, "Whey Protein Isolada ON. Rápida absorção", suplementos);
        Product multiAz = new Product("Multi AZ Final", 11.0, "Multi AZ para homens e mulheres", suplementos);
        Product omega3 = new Product("Omega 3", 65.99, "Puro Omega 3 importado 120 caps", suplementos);
       
        Product javaHowTo = new Product("Java como Programar", 80.15, "Java como Programar, Deitel, Deitel, 1100 páginas", livros);
        Product jsf = new Product("Core JSF", 80.15, "Core JSF, Geary Horstmann, 400 páginas", livros);
        Product cronicasGeloFogo = new Product("Crônicas de Gelo e Fogo", 55.88, "O Festim dos Corvos, RR Martin, 800 páginas", livros);
       
//antes de salvar as categorias e os produtos, precisamos abrir uma transação
        em.getTransaction().begin();      

//persist() é o método de EntityManger para persistir os objetos no banco
        em.persist(eletronicos);
        em.persist(suplementos);
        em.persist(livros);       

        em.persist(ps4);
        em.persist(noteDell);
        em.persist(IPhone);
        em.persist(wheyProt);
        em.persist(multiAz);
        em.persist(omega3);
        em.persist(javaHowTo);
        em.persist(jsf);
        em.persist(cronicasGeloFogo);
       
 //para finalizar confirme a transação e feche os recursos utilizados  
        em.getTransaction().commit();               
        emf.close();
    }
}
Repare como ficou nossa janela de projetos no NetBans. Repare também que no console da IDE o JPA exibe as declarações sql que foram geradas no banco:



O JPA nos auxilia na construção do modelo e no relacionamento com o banco de dados.

O próximo passo na parte 2 deste artigo é criar a interface com o usuário para que ele possa se cadastrar e comprar os produtos registrados em nosso banco de dados. Para nos auxiliar nesse tarefa utilizaremos os frameworks Servlets e Java Server Pages (JSP)!

No comments:

Post a Comment