openplanning

Tạo ứng dụng Web bán hàng với Spring Boot, Hibernate

  1. Tổng quan về ứng dụng
  2. Xem trước ứng dụng
  3. Chuẩn bị database
  4. Tạo dự án Spring Boot
  5. Cấu hình pom.xml
  6. Cấu hình Hibernate
  7. Bảo mật và UserDetailsService
  8. Các lớp Entity
  9. Các lớp DAO
  10. Các lớp Model
  11. Form bean & Validator
  12. PaginationResult & Utils
  13. Controllers
  14. Static & Thymeleaf Templates

1. Tổng quan về ứng dụng

Trong bài viết này, tôi hướng dẫn bạn tạo một ứng dụng web giỏ hàng (shopping cart) với Spring Boot, HibernateThymeleaf.
Ứng dụng được viết dựa trên:
  • Eclipse 4.7 (Oxygen)

  • Spring Boot 2.x

  • Hibernate 5.x

HibernateJPA là 2 công nghệ rất giống nhau. Nếu bạn biết về Hibernate bạn có thể dễ dàng làm việc với JPA và ngược lại. Tuy nhiên JPA không hỗ trợ tốt việc phân trang (Pagination). Trong khi đó phân trang là một tính năng rất cần thiết trong một ứng dụng, về vấn đề này Hibernate hỗ trợ tốt hơn, đó chính là lý do tại sao tôi sử dụng Hibernate cho ứng dụng này.

2. Xem trước ứng dụng

Chức năng mua hàng dành cho người dùng, và không yêu cầu phải đăng nhập.
Chức năng dành cho nhân viên và người quản lý:
Chức năng thêm và sửa thông tin sản phẩm (Chỉ dành cho người quản lý).

3. Chuẩn bị database

MySQL
-- Create table
create table ACCOUNTS
(
  USER_NAME VARCHAR(20) not null,
  ACTIVE    BIT not null,
  ENCRYTED_PASSWORD  VARCHAR(128) not null,
  USER_ROLE VARCHAR(20) not null
) ;

alter table ACCOUNTS
  add primary key (USER_NAME) ;
---------------------------------------

create table PRODUCTS
(
  CODE        VARCHAR(20) not null,
  IMAGE       longblob,
  NAME        VARCHAR(255) not null,
  PRICE       double precision not null,
  CREATE_DATE datetime not null
) ;

alter table PRODUCTS
  add primary key (CODE) ;
---------------------------------------
-- Create table
create table ORDERS
(
  ID               VARCHAR(50) not null,
  AMOUNT           double precision not null,
  CUSTOMER_ADDRESS VARCHAR(255) not null,
  CUSTOMER_EMAIL   VARCHAR(128) not null,
  CUSTOMER_NAME    VARCHAR(255) not null,
  CUSTOMER_PHONE   VARCHAR(128) not null,
  ORDER_DATE       datetime not null,
  ORDER_NUM        INTEGER not null
) ;
alter table ORDERS
  add primary key (ID) ;
alter table ORDERS
  add constraint ORDER_UK unique (ORDER_NUM) ;
---------------------------------------

-- Create table
create table ORDER_DETAILS
(
  ID         VARCHAR(50) not null,
  AMOUNT     double precision not null,
  PRICE      double precision not null,
  QUANITY    INTEGER not null,
  ORDER_ID   VARCHAR(50) not null,
  PRODUCT_ID VARCHAR(20) not null
) ;
--  
alter table ORDER_DETAILS
  add primary key (ID) ;
alter table ORDER_DETAILS
  add constraint ORDER_DETAIL_ORD_FK foreign key (ORDER_ID)
  references ORDERS (ID);
alter table ORDER_DETAILS
  add constraint ORDER_DETAIL_PROD_FK foreign key (PRODUCT_ID)
  references PRODUCTS (CODE);

---------------------------------------  
insert into Accounts (USER_NAME, ACTIVE, ENCRYTED_PASSWORD, USER_ROLE)
values ('employee1', 1,
'$2a$10$PrI5Gk9L.tSZiW9FXhTS8O8Mz9E97k2FZbFvGFFaSsiTUIl.TCrFu', 'ROLE_EMPLOYEE');

insert into Accounts (USER_NAME, ACTIVE, ENCRYTED_PASSWORD, USER_ROLE)
values ('manager1', 1,
'$2a$10$PrI5Gk9L.tSZiW9FXhTS8O8Mz9E97k2FZbFvGFFaSsiTUIl.TCrFu', 'ROLE_MANAGER');

----------------
insert into products (CODE, NAME, PRICE, CREATE_DATE)
values ('S001', 'Core Java', 100, sysdate);

insert into products (CODE, NAME, PRICE, CREATE_DATE)
values ('S002', 'Spring for Beginners', 50, sysdate);

insert into products (CODE, NAME, PRICE, CREATE_DATE)
values ('S003', 'Swift for Beginners', 120, sysdate);

insert into products (CODE, NAME, PRICE, CREATE_DATE)
values ('S004', 'Oracle XML Parser', 120, sysdate);

insert into products (CODE, NAME, PRICE, CREATE_DATE)
values ('S005', 'CSharp Tutorial for Beginers', 110, sysdate);
SQL Server
-- Create table
create table ACCOUNTS
(
  USER_NAME VARCHAR(20) not null,
  ACTIVE    BIT not null,
  ENCRYTED_PASSWORD  VARCHAR(128) not null,
  USER_ROLE VARCHAR(20) not null
) ;
 
alter table ACCOUNTS
  add primary key (USER_NAME) ;
---------------------------------------
 
create table PRODUCTS
(
  CODE        VARCHAR(20) not null,
  IMAGE       image,
  NAME        VARCHAR(255) not null,
  PRICE       double precision not null,
  CREATE_DATE datetime not null
) ;
 
alter table PRODUCTS
  add primary key (CODE) ;
---------------------------------------
-- Create table
create table ORDERS
(
  ID               VARCHAR(50) not null,
  AMOUNT           double precision not null,
  CUSTOMER_ADDRESS VARCHAR(255) not null,
  CUSTOMER_EMAIL   VARCHAR(128) not null,
  CUSTOMER_NAME    VARCHAR(255) not null,
  CUSTOMER_PHONE   VARCHAR(128) not null,
  ORDER_DATE       datetime not null,
  ORDER_NUM        INT not null
) ;
alter table ORDERS
  add primary key (ID) ;
alter table ORDERS
  add constraint ORDER_UK unique (ORDER_NUM) ;
---------------------------------------
 
-- Create table
create table ORDER_DETAILS
(
  ID         VARCHAR(50) not null,
  AMOUNT     double precision not null,
  PRICE      double precision not null,
  QUANITY    INT not null,
  ORDER_ID   VARCHAR(50) not null,
  PRODUCT_ID VARCHAR(20) not null
) ;
--  
alter table ORDER_DETAILS
  add primary key (ID) ;
alter table ORDER_DETAILS
  add constraint ORDER_DETAIL_ORD_FK foreign key (ORDER_ID)
  references ORDERS (ID);
alter table ORDER_DETAILS
  add constraint ORDER_DETAIL_PROD_FK foreign key (PRODUCT_ID)
  references PRODUCTS (CODE);
 
---------------------------------------  
insert into Accounts (USER_NAME, ACTIVE, ENCRYTED_PASSWORD, USER_ROLE)
values ('employee1', 1,
'$2a$10$PrI5Gk9L.tSZiW9FXhTS8O8Mz9E97k2FZbFvGFFaSsiTUIl.TCrFu', 'ROLE_EMPLOYEE');
 
insert into Accounts (USER_NAME, ACTIVE, ENCRYTED_PASSWORD, USER_ROLE)
values ('manager1', 1,
'$2a$10$PrI5Gk9L.tSZiW9FXhTS8O8Mz9E97k2FZbFvGFFaSsiTUIl.TCrFu', 'ROLE_MANAGER');
 
----------------
insert into products (CODE, NAME, PRICE, CREATE_DATE)
values ('S001', 'Core Java', 100, CURRENT_TIMESTAMP  );
 
insert into products (CODE, NAME, PRICE, CREATE_DATE)
values ('S002', 'Spring for Beginners', 50, CURRENT_TIMESTAMP  );
 
insert into products (CODE, NAME, PRICE, CREATE_DATE)
values ('S003', 'Swift for Beginners', 120, CURRENT_TIMESTAMP  );
 
insert into products (CODE, NAME, PRICE, CREATE_DATE)
values ('S004', 'Oracle XML Parser', 120, CURRENT_TIMESTAMP  );
 
insert into products (CODE, NAME, PRICE, CREATE_DATE)
values ('S005', 'CSharp Tutorial for Beginers', 110, CURRENT_TIMESTAMP  );
Postgres
-- Create table
create table ACCOUNTS
(
  USER_NAME VARCHAR(20) not null,
  ACTIVE    BOOLEAN not null,
  ENCRYTED_PASSWORD  VARCHAR(128) not null,
  USER_ROLE VARCHAR(20) not null
) ;

alter table ACCOUNTS
  add primary key (USER_NAME) ;
---------------------------------------

create table PRODUCTS
(
  CODE        VARCHAR(20) not null,
  IMAGE       bytea,
  NAME        VARCHAR(255) not null,
  PRICE       double precision not null,
  CREATE_DATE Timestamp without time zone not null
) ;

alter table PRODUCTS
  add primary key (CODE) ;
---------------------------------------
-- Create table
create table ORDERS
(
  ID               VARCHAR(50) not null,
  AMOUNT           double precision not null,
  CUSTOMER_ADDRESS VARCHAR(255) not null,
  CUSTOMER_EMAIL   VARCHAR(128) not null,
  CUSTOMER_NAME    VARCHAR(255) not null,
  CUSTOMER_PHONE   VARCHAR(128) not null,
  ORDER_DATE       Timestamp without time zone not null,
  ORDER_NUM        INT not null
) ;
alter table ORDERS
  add primary key (ID) ;
alter table ORDERS
  add constraint ORDER_UK unique (ORDER_NUM) ;
---------------------------------------

-- Create table
create table ORDER_DETAILS
(
  ID         VARCHAR(50) not null,
  AMOUNT     double precision not null,
  PRICE      double precision not null,
  QUANITY    INT not null,
  ORDER_ID   VARCHAR(50) not null,
  PRODUCT_ID VARCHAR(20) not null
) ;
--  
alter table ORDER_DETAILS
  add primary key (ID) ;
alter table ORDER_DETAILS
  add constraint ORDER_DETAIL_ORD_FK foreign key (ORDER_ID)
  references ORDERS (ID);
alter table ORDER_DETAILS
  add constraint ORDER_DETAIL_PROD_FK foreign key (PRODUCT_ID)
  references PRODUCTS (CODE);

---------------------------------------  
insert into Accounts (USER_NAME, ACTIVE, ENCRYTED_PASSWORD, USER_ROLE)
values ('employee1', true,
'$2a$10$PrI5Gk9L.tSZiW9FXhTS8O8Mz9E97k2FZbFvGFFaSsiTUIl.TCrFu', 'ROLE_EMPLOYEE');

insert into Accounts (USER_NAME, ACTIVE, ENCRYTED_PASSWORD, USER_ROLE)
values ('manager1', true,
'$2a$10$PrI5Gk9L.tSZiW9FXhTS8O8Mz9E97k2FZbFvGFFaSsiTUIl.TCrFu', 'ROLE_MANAGER');

----------------
insert into products (CODE, NAME, PRICE, CREATE_DATE)
values ('S001', 'Core Java', 100, current_timestamp);

insert into products (CODE, NAME, PRICE, CREATE_DATE)
values ('S002', 'Spring for Beginners', 50, current_timestamp);

insert into products (CODE, NAME, PRICE, CREATE_DATE)
values ('S003', 'Swift for Beginners', 120, current_timestamp);

insert into products (CODE, NAME, PRICE, CREATE_DATE)
values ('S004', 'Oracle XML Parser', 120, current_timestamp);

insert into products (CODE, NAME, PRICE, CREATE_DATE)
values ('S005', 'CSharp Tutorial for Beginers', 110, current_timestamp);
Oracle
-- Create table
create table ACCOUNTS
(
  USER_NAME VARCHAR2(20 CHAR) not null,
  ACTIVE    NUMBER(1) not null,
  ENCRYTED_PASSWORD  VARCHAR2(128 CHAR) not null,
  USER_ROLE VARCHAR2(20) not null
) ;
 
alter table ACCOUNTS
  add primary key (USER_NAME) ;
---------------------------------------
 
create table PRODUCTS
(
  CODE        VARCHAR2(20 CHAR) not null,
  IMAGE       BLOB,
  NAME        VARCHAR2(255 CHAR) not null,
  PRICE       FLOAT not null,
  CREATE_DATE DATE default sysdate not null
) ;
 
alter table PRODUCTS
  add primary key (CODE) ;
---------------------------------------
-- Create table
create table ORDERS
(
  ID               VARCHAR2(50 CHAR) not null,
  AMOUNT           FLOAT not null,
  CUSTOMER_ADDRESS VARCHAR2(255 CHAR) not null,
  CUSTOMER_EMAIL   VARCHAR2(128 CHAR) not null,
  CUSTOMER_NAME    VARCHAR2(255 CHAR) not null,
  CUSTOMER_PHONE   VARCHAR2(128 CHAR) not null,
  ORDER_DATE       TIMESTAMP(6) not null,
  ORDER_NUM        NUMBER(10) not null
) ;
alter table ORDERS
  add primary key (ID) ;
alter table ORDERS
  add constraint ORDER_UK unique (ORDER_NUM) ;
---------------------------------------
 
-- Create table
create table ORDER_DETAILS
(
  ID         VARCHAR2(50 CHAR) not null,
  AMOUNT     FLOAT not null,
  PRICE      FLOAT not null,
  QUANITY    NUMBER(10) not null,
  ORDER_ID   VARCHAR2(50 CHAR) not null,
  PRODUCT_ID VARCHAR2(20 CHAR) not null
) ;
--  
alter table ORDER_DETAILS
  add primary key (ID) ;
alter table ORDER_DETAILS
  add constraint ORDER_DETAIL_ORD_FK foreign key (ORDER_ID)
  references ORDERS (ID);
alter table ORDER_DETAILS
  add constraint ORDER_DETAIL_PROD_FK foreign key (PRODUCT_ID)
  references PRODUCTS (CODE);
 
---------------------------------------  
insert into Accounts (USER_NAME, ACTIVE, ENCRYTED_PASSWORD, USER_ROLE)
values ('employee1', 1,
'$2a$10$PrI5Gk9L.tSZiW9FXhTS8O8Mz9E97k2FZbFvGFFaSsiTUIl.TCrFu', 'ROLE_EMPLOYEE');
 
insert into Accounts (USER_NAME, ACTIVE, ENCRYTED_PASSWORD, USER_ROLE)
values ('manager1', 1,
'$2a$10$PrI5Gk9L.tSZiW9FXhTS8O8Mz9E97k2FZbFvGFFaSsiTUIl.TCrFu', 'ROLE_MANAGER');
 
----------------
insert into products (CODE, NAME, PRICE, CREATE_DATE)
values ('S001', 'Core Java', 100, sysdate);
 
insert into products (CODE, NAME, PRICE, CREATE_DATE)
values ('S002', 'Spring for Beginners', 50, sysdate);
 
insert into products (CODE, NAME, PRICE, CREATE_DATE)
values ('S003', 'Swift for Beginners', 120, sysdate);
 
insert into products (CODE, NAME, PRICE, CREATE_DATE)
values ('S004', 'Oracle XML Parser', 120, sysdate);
 
insert into products (CODE, NAME, PRICE, CREATE_DATE)
values ('S005', 'CSharp Tutorial for Beginers', 110, sysdate);
 
Commit;

4. Tạo dự án Spring Boot

Trên Eclipse tạo một dự án Spring Boot.
Nhập vào:
  • Name: SbHibernateShoppingCart
  • Group: org.o7planning
  • Description: Shopping Cart + Spring Boot + Hibernate + Thymeleaf
  • Package: org.o7plannng.sbshoppingcart
Lựa chọn các công nghệ và thư viện sẽ được sử dụng:
  • Security
  • JPA
  • Thymeleaf
  • Web
  • MySQL
  • SQL Server
  • PosgreSQL
Vì đây là một bài viết hướng dẫn nên tôi sẽ lựa chọn các thư viện cho 4 loại cơ sở dữ liệu thông dụng là Oracle, My SQL, SQL Server, PostgreSQL. Với Oracle chúng ta sẽ khai báo thư viện cho nó ở bước sau (Trong mục cấu hình pom.xml).
Khi bạn lựa chọn JPA, nó đã bao gồm các thư viện của JPA và cả Hibernate.

5. Cấu hình pom.xml

Nếu bạn sử dụng cơ sở dữ liệu Oracle, bạn cần khai báo thư viện cần thiết cho Oracle trong tập tin pom.xml:
** Oracle **
<dependencies>
    .....

     <dependency>
        <groupId>com.oracle</groupId>
        <artifactId>ojdbc6</artifactId>
        <version>11.2.0.3</version>
    </dependency>
    
    .....
</dependencies>

<repositories>
        ....

    <!-- Repository for ORACLE JDBC Driver -->
    <repository>
        <id>codelds</id>
        <url>https://code.lds.org/nexus/content/groups/main-repo</url>
    </repository>
    
    .....
</repositories>
Nếu bạn kết nối vào cơ sở dữ liệu SQL Service, bạn có thể sử dụng một trong 2 thư viện JTDS hoặc Mssql-Jdbc:
** SQL Server **
<dependencies>
       .....

    <dependency>
        <groupId>com.microsoft.sqlserver</groupId>
        <artifactId>mssql-jdbc</artifactId>
        <scope>runtime</scope>
    </dependency>
    
    <dependency>
        <groupId>net.sourceforge.jtds</groupId>
        <artifactId>jtds</artifactId>
        <scope>runtime</scope>
    </dependency>

     .....
</dependencies>
Các thư viện khác:
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity4</artifactId>
</dependency>

<!-- Commons Email validator,... -->
<!-- http://mvnrepository.com/artifact/commons-validator/commons-validator -->
<dependency>
    <groupId>commons-validator</groupId>
    <artifactId>commons-validator</artifactId>
    <version>1.6</version>
</dependency>

<!-- https://mvnrepository.com/artifact/commons-lang/commons-lang -->
<dependency>
    <groupId>commons-lang</groupId>
    <artifactId>commons-lang</artifactId>
    <version>2.6</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.threeten/threetenbp -->
<dependency>
    <groupId>org.threeten</groupId>
    <artifactId>threetenbp</artifactId>
    <version>1.3.6</version>
</dependency>
Nội dung đầy đủ của pom.xml:
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
      http://maven.apache.org/xsd/maven-4.0.0.xsd">
      
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.o7planning</groupId>
    <artifactId>SbHibernateShoppingCart</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>SbHibernateShoppingCart</name>
    <description>Shopping Cart + Spring Boot + Hibernate + Thymeleaf</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.0.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>
        
        <dependency>
            <groupId>com.microsoft.sqlserver</groupId>
            <artifactId>mssql-jdbc</artifactId>
            <scope>runtime</scope>
        </dependency>
        
        <!-- SQLServer JDBC driver (JTDS) -->
        <!-- http://mvnrepository.com/artifact/net.sourceforge.jtds/jtds -->
        <dependency>
            <groupId>net.sourceforge.jtds</groupId>
            <artifactId>jtds</artifactId>
            <scope>runtime</scope>
        </dependency>
                
        <dependency>
            <groupId>com.oracle</groupId>
            <artifactId>ojdbc6</artifactId>
            <version>11.2.0.3</version>
        </dependency>        

        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity4</artifactId>
        </dependency>
        
        <!-- Commons Email validator,... -->
        <!-- http://mvnrepository.com/artifact/commons-validator/commons-validator -->
        <dependency>
            <groupId>commons-validator</groupId>
            <artifactId>commons-validator</artifactId>
            <version>1.6</version>
        </dependency>
        
        <!-- https://mvnrepository.com/artifact/commons-lang/commons-lang -->
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>
        
        <!-- https://mvnrepository.com/artifact/org.threeten/threetenbp -->
        <dependency>
            <groupId>org.threeten</groupId>
            <artifactId>threetenbp</artifactId>
            <version>1.3.6</version>
        </dependency>    
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <repositories>
        
        <!-- Repository for ORACLE JDBC Driver -->
        <repository>
            <id>codelds</id>
            <url>https://code.lds.org/nexus/content/groups/main-repo</url>
        </repository>
    
    </repositories>

</project>

6. Cấu hình Hibernate

application.properties (MySQL)
# ===============================
# DATABASE
# ===============================

spring.datasource.driver-class-name=com.mysql.jdbc.Driver

spring.datasource.url=jdbc:mysql://localhost:3306/mydatabase
spring.datasource.username=root
spring.datasource.password=12345
 


# ===============================
# JPA / HIBERNATE
# ===============================

spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect
spring.jpa.properties.hibernate.current_session_context_class=org.springframework.orm.hibernate5.SpringSessionContext
application.properties (SQL Server + Mssql-Jdbc Driver)
# ===============================
# DATABASE
# ===============================

spring.datasource.driver-class-name=com.microsoft.sqlserver.jdbc.SQLServerDriver

spring.datasource.url=jdbc:sqlserver://tran-vmware-pc\\SQLEXPRESS:1433;databaseName=shoppingcart
spring.datasource.username=sa
spring.datasource.password=12345
 


# ===============================
# JPA / HIBERNATE
# ===============================

spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.SQLServerDialect
spring.jpa.properties.hibernate.current_session_context_class=org.springframework.orm.hibernate5.SpringSessionContext
application.properties (SQL Server + JTDS Driver)
# ===============================
# DATABASE
# ===============================

spring.datasource.driver-class-name=net.sourceforge.jtds.jdbc.Driver

spring.datasource.url=jdbc:jtds:sqlserver://tran-vmware-pc:1433/shoppingcart;instance=SQLEXPRESS
spring.datasource.username=sa
spring.datasource.password=12345
 


# ===============================
# JPA / HIBERNATE
# ===============================

spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.SQLServerDialect
spring.jpa.properties.hibernate.current_session_context_class=org.springframework.orm.hibernate5.SpringSessionContext
application.properties (ORACLE)
# ===============================
# DATABASE
# ===============================

spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver

spring.datasource.url=jdbc:oracle:thin:@tran-vmware-pc:1521:db12c
spring.datasource.username=shoppingcart
spring.datasource.password=12345
 


# ===============================
# JPA / HIBERNATE
# ===============================

spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.Oracle10gDialect
spring.jpa.properties.hibernate.current_session_context_class=org.springframework.orm.hibernate5.SpringSessionContext
application.properties (Postgres)
# ===============================
# DATABASE
# ===============================

spring.datasource.driver-class-name=org.postgresql.Driver

spring.datasource.url=jdbc:postgresql://tran-vmware-pc:5432/shoppingcart
spring.datasource.username=postgres
spring.datasource.password=12345
 


# ===============================
# JPA / HIBERNATE
# ===============================

spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=none
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQL82Dialect
spring.jpa.properties.hibernate.current_session_context_class=org.springframework.orm.hibernate5.SpringSessionContext



# Fix Postgres JPA Error:
# Method org.postgresql.jdbc.PgConnection.createClob() is not yet implemented.
spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults=false
Chú ý: Spring Boot mặc định sẽ tự động cấu hình JPA, và tạo ra các Spring BEAN liên quan tới JPA, các tự động cấu hình này của Spring Boot bao gồm:
  • DataSourceAutoConfiguration
  • DataSourceTransactionManagerAutoConfiguration
  • HibernateJpaAutoConfiguration
Mục đích trong ứng dụng này chúng ta sẽ sử dụng Hibernate, vì vậy chúng ta cần vô hiệu hóa các cấu hình tự động nói trên của Spring Boot.
** SbHibernateShoppingCartApplication **
package org.o7planning.sbshoppingcart;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;

@SpringBootApplication

@EnableAutoConfiguration(exclude = { //  
        DataSourceAutoConfiguration.class, //
        DataSourceTransactionManagerAutoConfiguration.class, //
        HibernateJpaAutoConfiguration.class })

public class SbHibernateShoppingCartApplication {

    public static void main(String[] args) {
        SpringApplication.run(SbHibernateShoppingCartApplication.class, args);
    }

    ........

}
Sau đó cấu hình các Spring BEAN cần thiết cho Hibernate.
SbHibernateShoppingCartApplication.java
package org.o7planning.sbshoppingcart;

import java.util.Properties;

import javax.sql.DataSource;

import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.hibernate5.HibernateTransactionManager;
import org.springframework.orm.hibernate5.LocalSessionFactoryBean;

@SpringBootApplication

@EnableAutoConfiguration(exclude = { //  
        DataSourceAutoConfiguration.class, //
        DataSourceTransactionManagerAutoConfiguration.class, //
        HibernateJpaAutoConfiguration.class })

public class SbHibernateShoppingCartApplication {

    @Autowired
    private Environment env;
    
    
    public static void main(String[] args) {
        SpringApplication.run(SbHibernateShoppingCartApplication.class, args);
    }
    
    
    @Bean(name = "dataSource")
    public DataSource getDataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();

        // See: application.properties
        dataSource.setDriverClassName(env.getProperty("spring.datasource.driver-class-name"));
        dataSource.setUrl(env.getProperty("spring.datasource.url"));
        dataSource.setUsername(env.getProperty("spring.datasource.username"));
        dataSource.setPassword(env.getProperty("spring.datasource.password"));

        System.out.println("## getDataSource: " + dataSource);

        return dataSource;
    }

    @Autowired
    @Bean(name = "sessionFactory")
    public SessionFactory getSessionFactory(DataSource dataSource) throws Exception {
        Properties properties = new Properties();

        // See: application.properties  
        properties.put("hibernate.dialect", env.getProperty("spring.jpa.properties.hibernate.dialect"));
        properties.put("hibernate.show_sql", env.getProperty("spring.jpa.show-sql"));
        properties.put("current_session_context_class", //
                env.getProperty("spring.jpa.properties.hibernate.current_session_context_class"));

        LocalSessionFactoryBean factoryBean = new LocalSessionFactoryBean();

        // Package contain entity classes
        factoryBean.setPackagesToScan(new String[] { "" });
        factoryBean.setDataSource(dataSource);
        factoryBean.setHibernateProperties(properties);
        factoryBean.afterPropertiesSet();
        //
        SessionFactory sf = factoryBean.getObject();
        System.out.println("## getSessionFactory: " + sf);
        return sf;
    }

    @Autowired
    @Bean(name = "transactionManager")
    public HibernateTransactionManager getTransactionManager(SessionFactory sessionFactory) {
        HibernateTransactionManager transactionManager = new HibernateTransactionManager(sessionFactory);

        return transactionManager;
    }
}

7. Bảo mật và UserDetailsService

Trong ứng dụng này chúng ta có 2 vai trò ROLE_MANAGERROLE_EMPLOYEE.
  • ROLE_EMPLOYEE: Vai trò này dành cho nhân viên, người dùng có vai trò này có thể xem danh sách các đơn hàng, xem chi tiết đơn hàng, và xem hồ sơ của họ.
  • ROLE_MANAGER: Vai trò này dành cho người quản lý, người dùng có vai trò này có thể xem danh sách các đơn hàng, xem chi tiết đơn hàng, xem hồ sơ của họ, thêm và sửa đổi sản phẩm.
WebSecurityConfig.java
package org.o7planning.sbshoppingcart.config;

import org.o7planning.sbshoppingcart.service.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

	@Autowired
	UserDetailsServiceImpl userDetailsService;

	@Bean
	public BCryptPasswordEncoder passwordEncoder() {
		BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
		return bCryptPasswordEncoder;
	}

	@Autowired
	public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {

		// Sét đặt dịch vụ để tìm kiếm User trong Database.
		// Và sét đặt PasswordEncoder.
		auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());

	}

	@Override
	protected void configure(HttpSecurity http) throws Exception {

		http.csrf().disable();

		// Các yêu cầu phải login với vai trò ROLE_EMPLOYEE hoặc ROLE_MANAGER.
		// Nếu chưa login, nó sẽ redirect tới trang /admin/login.
		http.authorizeRequests().antMatchers("/admin/orderList", "/admin/order", "/admin/accountInfo")//
				.access("hasAnyRole('ROLE_EMPLOYEE', 'ROLE_MANAGER')");

		// Các trang chỉ dành cho MANAGER
		http.authorizeRequests().antMatchers("/admin/product").access("hasRole('ROLE_MANAGER')");

		// Khi người dùng đã login, với vai trò XX.
		// Nhưng truy cập vào trang yêu cầu vai trò YY,
		// Ngoại lệ AccessDeniedException sẽ ném ra.
		http.authorizeRequests().and().exceptionHandling().accessDeniedPage("/403");

		// Cấu hình cho Login Form.
		http.authorizeRequests().and().formLogin()//

				// 
				.loginProcessingUrl("/j_spring_security_check") // Submit URL
				.loginPage("/admin/login")//
				.defaultSuccessUrl("/admin/accountInfo")//
				.failureUrl("/admin/login?error=true")//
				.usernameParameter("userName")//
				.passwordParameter("password")

				// Cấu hình cho trang Logout.
				// (Sau khi logout, chuyển tới trang home)
				.and().logout().logoutUrl("/admin/logout").logoutSuccessUrl("/");

	}
}
Lớp UserDetailsServiceImpl thực hiện interface UserDetailsService, nó được sử dụng để tìm kiếm người dùng trong database ứng với tên đăng nhập của người dùng, đồng thời thiết lập các vai trò cho người dùng này.
UserDetailsServiceImpl.java
package org.o7planning.sbshoppingcart.service;

import java.util.ArrayList;
import java.util.List;

import org.o7planning.sbshoppingcart.dao.AccountDAO;
import org.o7planning.sbshoppingcart.entity.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private AccountDAO accountDAO;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Account account = accountDAO.findAccount(username);
        System.out.println("Account= " + account);

        if (account == null) {
            throw new UsernameNotFoundException("User " //
                    + username + " was not found in the database");
        }

        // EMPLOYEE,MANAGER,..
        String role = account.getUserRole();

        List<GrantedAuthority> grantList = new ArrayList<GrantedAuthority>();

        // ROLE_EMPLOYEE, ROLE_MANAGER
        GrantedAuthority authority = new SimpleGrantedAuthority(role);

        grantList.add(authority);

        boolean enabled = account.isActive();
        boolean accountNonExpired = true;
        boolean credentialsNonExpired = true;
        boolean accountNonLocked = true;

        UserDetails userDetails = (UserDetails) new User(account.getUserName(), //
                account.getEncrytedPassword(), enabled, accountNonExpired, //
                credentialsNonExpired, accountNonLocked, grantList);

        return userDetails;
    }

}

8. Các lớp Entity

Account.java
package org.o7planning.sbshoppingcart.entity;

import java.io.Serializable;

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

@Entity
@Table(name = "Accounts")
public class Account implements Serializable {

    private static final long serialVersionUID = -2054386655979281969L;

    public static final String ROLE_MANAGER = "MANAGER";
    public static final String ROLE_EMPLOYEE = "EMPLOYEE";

    @Id
    @Column(name = "User_Name", length = 20, nullable = false)
    private String userName;

    @Column(name = "Encryted_Password", length = 128, nullable = false)
    private String encrytedPassword;

    @Column(name = "Active", length = 1, nullable = false)
    private boolean active;

    @Column(name = "User_Role", length = 20, nullable = false)
    private String userRole;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getEncrytedPassword() {
        return encrytedPassword;
    }

    public void setEncrytedPassword(String encrytedPassword) {
        this.encrytedPassword = encrytedPassword;
    }

    public boolean isActive() {
        return active;
    }

    public void setActive(boolean active) {
        this.active = active;
    }

    public String getUserRole() {
        return userRole;
    }

    public void setUserRole(String userRole) {
        this.userRole = userRole;
    }

    @Override
    public String toString() {
        return "[" + this.userName + "," + this.encrytedPassword + "," + this.userRole + "]";
    }

}
Product.java
package org.o7planning.sbshoppingcart.entity;

import java.io.Serializable;
import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

@Entity
@Table(name = "Products")
public class Product implements Serializable {

    private static final long serialVersionUID = -1000119078147252957L;

    @Id
    @Column(name = "Code", length = 20, nullable = false)
    private String code;

    @Column(name = "Name", length = 255, nullable = false)
    private String name;

    @Column(name = "Price", nullable = false)
    private double price;

    @Lob
    @Column(name = "Image", length = Integer.MAX_VALUE, nullable = true)
    private byte[] image;
    
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "Create_Date", nullable = false)
    private Date createDate;

    public Product() {
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public Date getCreateDate() {
        return createDate;
    }

    public void setCreateDate(Date createDate) {
        this.createDate = createDate;
    }

    public byte[] getImage() {
        return image;
    }

    public void setImage(byte[] image) {
        this.image = image;
    }

}
Order.java
package org.o7planning.sbshoppingcart.entity;

import java.io.Serializable;
import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;

@Entity
@Table(name = "Orders", //
        uniqueConstraints = { @UniqueConstraint(columnNames = "Order_Num") })
public class Order implements Serializable {

    private static final long serialVersionUID = -2576670215015463100L;

    @Id
    @Column(name = "ID", length = 50)
    private String id;

    @Column(name = "Order_Date", nullable = false)
    private Date orderDate;

    @Column(name = "Order_Num", nullable = false)
    private int orderNum;

    @Column(name = "Amount", nullable = false)
    private double amount;

    @Column(name = "Customer_Name", length = 255, nullable = false)
    private String customerName;

    @Column(name = "Customer_Address", length = 255, nullable = false)
    private String customerAddress;

    @Column(name = "Customer_Email", length = 128, nullable = false)
    private String customerEmail;

    @Column(name = "Customer_Phone", length = 128, nullable = false)
    private String customerPhone;

    public String getId() {
        return id;
    }

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

    public Date getOrderDate() {
        return orderDate;
    }

    public void setOrderDate(Date orderDate) {
        this.orderDate = orderDate;
    }

    public int getOrderNum() {
        return orderNum;
    }

    public void setOrderNum(int orderNum) {
        this.orderNum = orderNum;
    }

    public double getAmount() {
        return amount;
    }

    public void setAmount(double amount) {
        this.amount = amount;
    }

    public String getCustomerName() {
        return customerName;
    }

    public void setCustomerName(String customerName) {
        this.customerName = customerName;
    }

    public String getCustomerAddress() {
        return customerAddress;
    }

    public void setCustomerAddress(String customerAddress) {
        this.customerAddress = customerAddress;
    }

    public String getCustomerEmail() {
        return customerEmail;
    }

    public void setCustomerEmail(String customerEmail) {
        this.customerEmail = customerEmail;
    }

    public String getCustomerPhone() {
        return customerPhone;
    }

    public void setCustomerPhone(String customerPhone) {
        this.customerPhone = customerPhone;
    }

}
OrderDetail.java
package org.o7planning.sbshoppingcart.entity;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.ForeignKey;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

@Entity
@Table(name = "Order_Details")
public class OrderDetail implements Serializable {

    private static final long serialVersionUID = 7550745928843183535L;

    @Id
    @Column(name = "ID", length = 50, nullable = false)
    private String id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "ORDER_ID", nullable = false, //
            foreignKey = @ForeignKey(name = "ORDER_DETAIL_ORD_FK"))
    private Order order;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "PRODUCT_ID", nullable = false, //
            foreignKey = @ForeignKey(name = "ORDER_DETAIL_PROD_FK"))
    private Product product;

    @Column(name = "Quanity", nullable = false)
    private int quanity;

    @Column(name = "Price", nullable = false)
    private double price;

    @Column(name = "Amount", nullable = false)
    private double amount;

    public String getId() {
        return id;
    }

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

    public Order getOrder() {
        return order;
    }

    public void setOrder(Order order) {
        this.order = order;
    }

    public Product getProduct() {
        return product;
    }

    public void setProduct(Product product) {
        this.product = product;
    }

    public int getQuanity() {
        return quanity;
    }

    public void setQuanity(int quanity) {
        this.quanity = quanity;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public double getAmount() {
        return amount;
    }

    public void setAmount(double amount) {
        this.amount = amount;
    }

}

9. Các lớp DAO

AccountDAO.java
package org.o7planning.sbshoppingcart.dao;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.o7planning.sbshoppingcart.entity.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

@Transactional
@Repository
public class AccountDAO {

    @Autowired
    private SessionFactory sessionFactory;

    public Account findAccount(String userName) {
        Session session = this.sessionFactory.getCurrentSession();
        return session.find(Account.class, userName);
    }

}
OrderDAO.java
package org.o7planning.sbshoppingcart.dao;

import java.util.Date;
import java.util.List;
import java.util.UUID;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.query.Query;
import org.o7planning.sbshoppingcart.entity.Order;
import org.o7planning.sbshoppingcart.entity.OrderDetail;
import org.o7planning.sbshoppingcart.entity.Product;
import org.o7planning.sbshoppingcart.model.CartInfo;
import org.o7planning.sbshoppingcart.model.CartLineInfo;
import org.o7planning.sbshoppingcart.model.CustomerInfo;
import org.o7planning.sbshoppingcart.model.OrderDetailInfo;
import org.o7planning.sbshoppingcart.model.OrderInfo;
import org.o7planning.sbshoppingcart.pagination.PaginationResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

@Transactional
@Repository
public class OrderDAO {

	@Autowired
	private SessionFactory sessionFactory;

	@Autowired
	private ProductDAO productDAO;

	private int getMaxOrderNum() {
		String sql = "Select max(o.orderNum) from " + Order.class.getName() + " o ";
		Session session = this.sessionFactory.getCurrentSession();
		Query<Integer> query = session.createQuery(sql, Integer.class);
		Integer value = (Integer) query.getSingleResult();
		if (value == null) {
			return 0;
		}
		return value;
	}

	@Transactional(rollbackFor = Exception.class)
	public void saveOrder(CartInfo cartInfo) {
		Session session = this.sessionFactory.getCurrentSession();

		int orderNum = this.getMaxOrderNum() + 1;
		Order order = new Order();

		order.setId(UUID.randomUUID().toString());
		order.setOrderNum(orderNum);
		order.setOrderDate(new Date());
		order.setAmount(cartInfo.getAmountTotal());

		CustomerInfo customerInfo = cartInfo.getCustomerInfo();
		order.setCustomerName(customerInfo.getName());
		order.setCustomerEmail(customerInfo.getEmail());
		order.setCustomerPhone(customerInfo.getPhone());
		order.setCustomerAddress(customerInfo.getAddress());

		session.persist(order);

		List<CartLineInfo> lines = cartInfo.getCartLines();

		for (CartLineInfo line : lines) {
			OrderDetail detail = new OrderDetail();
			detail.setId(UUID.randomUUID().toString());
			detail.setOrder(order);
			detail.setAmount(line.getAmount());
			detail.setPrice(line.getProductInfo().getPrice());
			detail.setQuanity(line.getQuantity());

			String code = line.getProductInfo().getCode();
			Product product = this.productDAO.findProduct(code);
			detail.setProduct(product);

			session.persist(detail);
		}

		// Order Number!
		cartInfo.setOrderNum(orderNum);
		// Flush
		session.flush();
	}

	// @page = 1, 2, ...
	public PaginationResult<OrderInfo> listOrderInfo(int page, int maxResult, int maxNavigationPage) {
		String sql = "Select new " + OrderInfo.class.getName()//
				+ "(ord.id, ord.orderDate, ord.orderNum, ord.amount, "
				+ " ord.customerName, ord.customerAddress, ord.customerEmail, ord.customerPhone) " + " from "
				+ Order.class.getName() + " ord "//
				+ " order by ord.orderNum desc";

		Session session = this.sessionFactory.getCurrentSession();
		Query<OrderInfo> query = session.createQuery(sql, OrderInfo.class);
		return new PaginationResult<OrderInfo>(query, page, maxResult, maxNavigationPage);
	}

	public Order findOrder(String orderId) {
		Session session = this.sessionFactory.getCurrentSession();
		return session.find(Order.class, orderId);
	}

	public OrderInfo getOrderInfo(String orderId) {
		Order order = this.findOrder(orderId);
		if (order == null) {
			return null;
		}
		return new OrderInfo(order.getId(), order.getOrderDate(), //
				order.getOrderNum(), order.getAmount(), order.getCustomerName(), //
				order.getCustomerAddress(), order.getCustomerEmail(), order.getCustomerPhone());
	}

	public List<OrderDetailInfo> listOrderDetailInfos(String orderId) {
		String sql = "Select new " + OrderDetailInfo.class.getName() //
				+ "(d.id, d.product.code, d.product.name , d.quanity,d.price,d.amount) "//
				+ " from " + OrderDetail.class.getName() + " d "//
				+ " where d.order.id = :orderId ";

		Session session = this.sessionFactory.getCurrentSession();
		Query<OrderDetailInfo> query = session.createQuery(sql, OrderDetailInfo.class);
		query.setParameter("orderId", orderId);

		return query.getResultList();
	}

}
ProductDAO.java
package org.o7planning.sbshoppingcart.dao;

import java.io.IOException;
import java.util.Date;

import javax.persistence.NoResultException;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.query.Query;
import org.o7planning.sbshoppingcart.entity.Product;
import org.o7planning.sbshoppingcart.form.ProductForm;
import org.o7planning.sbshoppingcart.model.ProductInfo;
import org.o7planning.sbshoppingcart.pagination.PaginationResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Transactional
@Repository
public class ProductDAO {

    @Autowired
    private SessionFactory sessionFactory;

    public Product findProduct(String code) {
        try {
            String sql = "Select e from " + Product.class.getName() + " e Where e.code =:code ";

            Session session = this.sessionFactory.getCurrentSession();
            Query<Product> query = session.createQuery(sql, Product.class);
            query.setParameter("code", code);
            return (Product) query.getSingleResult();
        } catch (NoResultException e) {
            return null;
        }
    }

    public ProductInfo findProductInfo(String code) {
        Product product = this.findProduct(code);
        if (product == null) {
            return null;
        }
        return new ProductInfo(product.getCode(), product.getName(), product.getPrice());
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public void save(ProductForm productForm) {

        Session session = this.sessionFactory.getCurrentSession();
        String code = productForm.getCode();

        Product product = null;

        boolean isNew = false;
        if (code != null) {
            product = this.findProduct(code);
        }
        if (product == null) {
            isNew = true;
            product = new Product();
            product.setCreateDate(new Date());
        }
        product.setCode(code);
        product.setName(productForm.getName());
        product.setPrice(productForm.getPrice());

        if (productForm.getFileData() != null) {
            byte[] image = null;
            try {
                image = productForm.getFileData().getBytes();
            } catch (IOException e) {
            }
            if (image != null && image.length > 0) {
                product.setImage(image);
            }
        }
        if (isNew) {
            session.persist(product);
        }
        // Nếu có lỗi tại DB, ngoại lệ sẽ ném ra ngay lập tức
        session.flush();
    }

    public PaginationResult<ProductInfo> queryProducts(int page, int maxResult, int maxNavigationPage,
            String likeName) {
        String sql = "Select new " + ProductInfo.class.getName() //
                + "(p.code, p.name, p.price) " + " from "//
                + Product.class.getName() + " p ";
        if (likeName != null && likeName.length() > 0) {
            sql += " Where lower(p.name) like :likeName ";
        }
        sql += " order by p.createDate desc ";
        // 
        Session session = this.sessionFactory.getCurrentSession();
        Query<ProductInfo> query = session.createQuery(sql, ProductInfo.class);

        if (likeName != null && likeName.length() > 0) {
            query.setParameter("likeName", "%" + likeName.toLowerCase() + "%");
        }
        return new PaginationResult<ProductInfo>(query, page, maxResult, maxNavigationPage);
    }

    public PaginationResult<ProductInfo> queryProducts(int page, int maxResult, int maxNavigationPage) {
        return queryProducts(page, maxResult, maxNavigationPage, null);
    }

}

10. Các lớp Model

CartInfo.java
package org.o7planning.sbshoppingcart.model;

import java.util.ArrayList;
import java.util.List;

public class CartInfo {

    private int orderNum;

    private CustomerInfo customerInfo;

    private final List<CartLineInfo> cartLines = new ArrayList<CartLineInfo>();

    public CartInfo() {

    }

    public int getOrderNum() {
        return orderNum;
    }

    public void setOrderNum(int orderNum) {
        this.orderNum = orderNum;
    }

    public CustomerInfo getCustomerInfo() {
        return customerInfo;
    }

    public void setCustomerInfo(CustomerInfo customerInfo) {
        this.customerInfo = customerInfo;
    }

    public List<CartLineInfo> getCartLines() {
        return this.cartLines;
    }

    private CartLineInfo findLineByCode(String code) {
        for (CartLineInfo line : this.cartLines) {
            if (line.getProductInfo().getCode().equals(code)) {
                return line;
            }
        }
        return null;
    }

    public void addProduct(ProductInfo productInfo, int quantity) {
        CartLineInfo line = this.findLineByCode(productInfo.getCode());

        if (line == null) {
            line = new CartLineInfo();
            line.setQuantity(0);
            line.setProductInfo(productInfo);
            this.cartLines.add(line);
        }
        int newQuantity = line.getQuantity() + quantity;
        if (newQuantity <= 0) {
            this.cartLines.remove(line);
        } else {
            line.setQuantity(newQuantity);
        }
    }

    public void validate() {

    }

    public void updateProduct(String code, int quantity) {
        CartLineInfo line = this.findLineByCode(code);

        if (line != null) {
            if (quantity <= 0) {
                this.cartLines.remove(line);
            } else {
                line.setQuantity(quantity);
            }
        }
    }

    public void removeProduct(ProductInfo productInfo) {
        CartLineInfo line = this.findLineByCode(productInfo.getCode());
        if (line != null) {
            this.cartLines.remove(line);
        }
    }

    public boolean isEmpty() {
        return this.cartLines.isEmpty();
    }

    public boolean isValidCustomer() {
        return this.customerInfo != null && this.customerInfo.isValid();
    }

    public int getQuantityTotal() {
        int quantity = 0;
        for (CartLineInfo line : this.cartLines) {
            quantity += line.getQuantity();
        }
        return quantity;
    }

    public double getAmountTotal() {
        double total = 0;
        for (CartLineInfo line : this.cartLines) {
            total += line.getAmount();
        }
        return total;
    }

    public void updateQuantity(CartInfo cartForm) {
        if (cartForm != null) {
            List<CartLineInfo> lines = cartForm.getCartLines();
            for (CartLineInfo line : lines) {
                this.updateProduct(line.getProductInfo().getCode(), line.getQuantity());
            }
        }

    }

}
CartLineInfo.java
package org.o7planning.sbshoppingcart.model;
 

public class CartLineInfo {
 
    private ProductInfo productInfo;
    private int quantity;
 
    public CartLineInfo() {
        this.quantity = 0;
    }
 
    public ProductInfo getProductInfo() {
        return productInfo;
    }
 
    public void setProductInfo(ProductInfo productInfo) {
        this.productInfo = productInfo;
    }
 
    public int getQuantity() {
        return quantity;
    }
 
    public void setQuantity(int quantity) {
        this.quantity = quantity;
    }
 
    public double getAmount() {
        return this.productInfo.getPrice() * this.quantity;
    }
    
}
CustomerInfo.java
package org.o7planning.sbshoppingcart.model;

import org.o7planning.sbshoppingcart.form.CustomerForm;

public class CustomerInfo {

    private String name;
    private String address;
    private String email;
    private String phone;

    private boolean valid;

    public CustomerInfo() {

    }

    public CustomerInfo(CustomerForm customerForm) {
        this.name = customerForm.getName();
        this.address = customerForm.getAddress();
        this.email = customerForm.getEmail();
        this.phone = customerForm.getPhone();
        this.valid = customerForm.isValid();
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public boolean isValid() {
        return valid;
    }

    public void setValid(boolean valid) {
        this.valid = valid;
    }

}
OrderDetailInfo.java
package org.o7planning.sbshoppingcart.model;

public class OrderDetailInfo {
    private String id;

    private String productCode;
    private String productName;

    private int quanity;
    private double price;
    private double amount;

    public OrderDetailInfo() {

    }

    // Sử dụng cho JPA/Hibernate Query.
    public OrderDetailInfo(String id, String productCode, //
            String productName, int quanity, double price, double amount) {
        this.id = id;
        this.productCode = productCode;
        this.productName = productName;
        this.quanity = quanity;
        this.price = price;
        this.amount = amount;
    }

    public String getId() {
        return id;
    }

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

    public String getProductCode() {
        return productCode;
    }

    public void setProductCode(String productCode) {
        this.productCode = productCode;
    }

    public String getProductName() {
        return productName;
    }

    public void setProductName(String productName) {
        this.productName = productName;
    }

    public int getQuanity() {
        return quanity;
    }

    public void setQuanity(int quanity) {
        this.quanity = quanity;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public double getAmount() {
        return amount;
    }

    public void setAmount(double amount) {
        this.amount = amount;
    }
}
OrderInfo.java
package org.o7planning.sbshoppingcart.model;

import java.util.Date;
import java.util.List;

public class OrderInfo {

    private String id;
    private Date orderDate;
    private int orderNum;
    private double amount;

    private String customerName;
    private String customerAddress;
    private String customerEmail;
    private String customerPhone;

    private List<OrderDetailInfo> details;

    public OrderInfo() {

    }

    // Sử dụng cho Hibernate Query.
    public OrderInfo(String id, Date orderDate, int orderNum, //
            double amount, String customerName, String customerAddress, //
            String customerEmail, String customerPhone) {
        this.id = id;
        this.orderDate = orderDate;
        this.orderNum = orderNum;
        this.amount = amount;

        this.customerName = customerName;
        this.customerAddress = customerAddress;
        this.customerEmail = customerEmail;
        this.customerPhone = customerPhone;
    }

    public String getId() {
        return id;
    }

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

    public Date getOrderDate() {
        return orderDate;
    }

    public void setOrderDate(Date orderDate) {
        this.orderDate = orderDate;
    }

    public int getOrderNum() {
        return orderNum;
    }

    public void setOrderNum(int orderNum) {
        this.orderNum = orderNum;
    }

    public double getAmount() {
        return amount;
    }

    public void setAmount(double amount) {
        this.amount = amount;
    }

    public String getCustomerName() {
        return customerName;
    }

    public void setCustomerName(String customerName) {
        this.customerName = customerName;
    }

    public String getCustomerAddress() {
        return customerAddress;
    }

    public void setCustomerAddress(String customerAddress) {
        this.customerAddress = customerAddress;
    }

    public String getCustomerEmail() {
        return customerEmail;
    }

    public void setCustomerEmail(String customerEmail) {
        this.customerEmail = customerEmail;
    }

    public String getCustomerPhone() {
        return customerPhone;
    }

    public void setCustomerPhone(String customerPhone) {
        this.customerPhone = customerPhone;
    }

    public List<OrderDetailInfo> getDetails() {
        return details;
    }

    public void setDetails(List<OrderDetailInfo> details) {
        this.details = details;
    }

}
ProductInfo.java
package org.o7planning.sbshoppingcart.model;

import org.o7planning.sbshoppingcart.entity.Product;

public class ProductInfo {
    private String code;
    private String name;
    private double price;

    public ProductInfo() {
    }

    public ProductInfo(Product product) {
        this.code = product.getCode();
        this.name = product.getName();
        this.price = product.getPrice();
    }

    // Sử dụng trong JPA/Hibernate query
    public ProductInfo(String code, String name, double price) {
        this.code = code;
        this.name = name;
        this.price = price;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

}

11. Form bean & Validator

CustomerForm.java
package org.o7planning.sbshoppingcart.form;

import org.o7planning.sbshoppingcart.model.CustomerInfo;

public class CustomerForm {

    private String name;
    private String address;
    private String email;
    private String phone;

    private boolean valid;

    public CustomerForm() {

    }

    public CustomerForm(CustomerInfo customerInfo) {
        if (customerInfo != null) {
            this.name = customerInfo.getName();
            this.address = customerInfo.getAddress();
            this.email = customerInfo.getEmail();
            this.phone = customerInfo.getPhone();
        }
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public boolean isValid() {
        return valid;
    }

    public void setValid(boolean valid) {
        this.valid = valid;
    }

}
ProductForm.java
package org.o7planning.sbshoppingcart.form;

import org.o7planning.sbshoppingcart.entity.Product;
import org.springframework.web.multipart.MultipartFile;

public class ProductForm {
    private String code;
    private String name;
    private double price;

    private boolean newProduct = false;

    // Upload file.
    private MultipartFile fileData;

    public ProductForm() {
        this.newProduct= true;
    }

    public ProductForm(Product product) {
        this.code = product.getCode();
        this.name = product.getName();
        this.price = product.getPrice();
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public MultipartFile getFileData() {
        return fileData;
    }

    public void setFileData(MultipartFile fileData) {
        this.fileData = fileData;
    }

    public boolean isNewProduct() {
        return newProduct;
    }

    public void setNewProduct(boolean newProduct) {
        this.newProduct = newProduct;
    }

}
CustomerFormValidator.java
package org.o7planning.sbshoppingcart.validator;

import org.apache.commons.validator.routines.EmailValidator;
import org.o7planning.sbshoppingcart.form.CustomerForm;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;

@Component
public class CustomerFormValidator implements Validator {

	private EmailValidator emailValidator = EmailValidator.getInstance();

	// Validator này chỉ dùng để kiểm tra đối với CustomerForm.
	@Override
	public boolean supports(Class<?> clazz) {
		return clazz == CustomerForm.class;
	}

	@Override
	public void validate(Object target, Errors errors) {
		CustomerForm custInfo = (CustomerForm) target;

		// Kiểm tra các trường (field) của CustomerForm.
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "NotEmpty.customerForm.name");
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "email", "NotEmpty.customerForm.email");
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "address", "NotEmpty.customerForm.address");
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "phone", "NotEmpty.customerForm.phone");

		if (!emailValidator.isValid(custInfo.getEmail())) {
			errors.rejectValue("email", "Pattern.customerForm.email");
		}
	}

}
ProductFormValidator.java
package org.o7planning.sbshoppingcart.validator;

import org.o7planning.sbshoppingcart.dao.ProductDAO;
import org.o7planning.sbshoppingcart.entity.Product;
import org.o7planning.sbshoppingcart.form.ProductForm;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;

@Component
public class ProductFormValidator implements Validator {

	@Autowired
	private ProductDAO productDAO;

	// Validator này chỉ dùng để kiểm tra class ProductForm.
	@Override
	public boolean supports(Class<?> clazz) {
		return clazz == ProductForm.class;
	}

	@Override
	public void validate(Object target, Errors errors) {
		ProductForm productForm = (ProductForm) target;

		// Kiểm tra các trường (field) của ProductForm.
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "code", "NotEmpty.productForm.code");
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "NotEmpty.productForm.name");
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "price", "NotEmpty.productForm.price");

		String code = productForm.getCode();
		if (code != null && code.length() > 0) {
			if (code.matches("\\s+")) {
				errors.rejectValue("code", "Pattern.productForm.code");
			} else if (productForm.isNewProduct()) {
				Product product = productDAO.findProduct(code);
				if (product != null) {
					errors.rejectValue("code", "Duplicate.productForm.code");
				}
			}
		}
	}

}
Tập tin validation.properties chứa các mã thông báo lỗi khi người dùng nhập trên Form khai báo sản phẩm, và Form nhập thông tin khách hàng.
validation.properties
NotEmpty.customerForm.name=Name is required
NotEmpty.customerForm.email=Email is required
NotEmpty.customerForm.address=Address is required
NotEmpty.customerForm.phone=Phone is required
 
Pattern.customerForm.email=Email is not valid
 
 
NotEmpty.productForm.name=Product name is required
NotEmpty.productForm.code=Product code is required
Pattern.productForm.code=Product code is not valid
Duplicate.productForm.code=Duplicate products
 
 
NotFound.loginForm.account=Account not found
Disabled.loginForm.account=Account is disabled
WebConfiguration.java
package org.o7planning.sbshoppingcart.config;

import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfiguration implements WebMvcConfigurer {

	@Bean
	public MessageSource messageSource() {
		ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
		// Tải file: validation.properties
		messageSource.setBasename("classpath:validation");
		messageSource.setDefaultEncoding("UTF-8");
		return messageSource;
	}
 
}

12. PaginationResult & Utils

PaginationResult.java
package org.o7planning.sbshoppingcart.pagination;

import java.util.ArrayList;
import java.util.List;

import org.hibernate.ScrollMode;
import org.hibernate.ScrollableResults;
import org.hibernate.query.Query;

public class PaginationResult<E> {

	private int totalRecords;
	private int currentPage;
	private List<E> list;
	private int maxResult;
	private int totalPages;

	private int maxNavigationPage;

	private List<Integer> navigationPages;

	// @page: 1, 2, ..
	public PaginationResult(Query<E> query, int page, int maxResult, int maxNavigationPage) {
		final int pageIndex = page - 1 < 0 ? 0 : page - 1;

		int fromRecordIndex = pageIndex * maxResult;
		int maxRecordIndex = fromRecordIndex + maxResult;

		ScrollableResults resultScroll = query.scroll(ScrollMode.SCROLL_INSENSITIVE);

		List<E> results = new ArrayList<>();

		boolean hasResult = resultScroll.first();

		if (hasResult) {
			// Cuộn tới vị trí:
			hasResult = resultScroll.scroll(fromRecordIndex);

			if (hasResult) {
				do {
					E record = (E) resultScroll.get(0);
					results.add(record);
				} while (resultScroll.next()//
						&& resultScroll.getRowNumber() >= fromRecordIndex
						&& resultScroll.getRowNumber() < maxRecordIndex);

			}

			// Chuyển tới bản ghi cuối
			resultScroll.last();
		}

		// Tổng số bản ghi.
		this.totalRecords = resultScroll.getRowNumber() + 1;
		this.currentPage = pageIndex + 1;
		this.list = results;
		this.maxResult = maxResult;

		if (this.totalRecords % this.maxResult == 0) {
			this.totalPages = this.totalRecords / this.maxResult;
		} else {
			this.totalPages = (this.totalRecords / this.maxResult) + 1;
		}

		this.maxNavigationPage = maxNavigationPage;

		if (maxNavigationPage < totalPages) {
			this.maxNavigationPage = maxNavigationPage;
		}

		this.calcNavigationPages();
	}

	private void calcNavigationPages() {

		this.navigationPages = new ArrayList<Integer>();

		int current = this.currentPage > this.totalPages ? this.totalPages : this.currentPage;

		int begin = current - this.maxNavigationPage / 2;
		int end = current + this.maxNavigationPage / 2;

		// Trang đầu tiên
		navigationPages.add(1);
		if (begin > 2) {

			// Dùng cho '...'
			navigationPages.add(-1);
		}

		for (int i = begin; i < end; i++) {
			if (i > 1 && i < this.totalPages) {
				navigationPages.add(i);
			}
		}

		if (end < this.totalPages - 2) {

			// Dùng cho '...'
			navigationPages.add(-1);
		}
		// Trang cuối cùng.
		navigationPages.add(this.totalPages);
	}

	public int getTotalPages() {
		return totalPages;
	}

	public int getTotalRecords() {
		return totalRecords;
	}

	public int getCurrentPage() {
		return currentPage;
	}

	public List<E> getList() {
		return list;
	}

	public int getMaxResult() {
		return maxResult;
	}

	public List<Integer> getNavigationPages() {
		return navigationPages;
	}

}
Utils.java
package org.o7planning.sbshoppingcart.utils;

import javax.servlet.http.HttpServletRequest;

import org.o7planning.sbshoppingcart.model.CartInfo;

public class Utils {

	// Thông tin các sản phẩm trong giỏ hàng, được lưu trữ trong Session.
	public static CartInfo getCartInSession(HttpServletRequest request) {
 
		CartInfo cartInfo = (CartInfo) request.getSession().getAttribute("myCart");

	 
		if (cartInfo == null) {
			cartInfo = new CartInfo(); 
			
			request.getSession().setAttribute("myCart", cartInfo);
		}

		return cartInfo;
	}

	public static void removeCartInSession(HttpServletRequest request) {
		request.getSession().removeAttribute("myCart");
	}

	public static void storeLastOrderedCartInSession(HttpServletRequest request, CartInfo cartInfo) {
		request.getSession().setAttribute("lastOrderedCart", cartInfo);
	}

	public static CartInfo getLastOrderedCartInSession(HttpServletRequest request) {
		return (CartInfo) request.getSession().getAttribute("lastOrderedCart");
	}
	 
}

13. Controllers

MainController.java
package org.o7planning.sbshoppingcart.controller;

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.o7planning.sbshoppingcart.dao.OrderDAO;
import org.o7planning.sbshoppingcart.dao.ProductDAO;
import org.o7planning.sbshoppingcart.entity.Product;
import org.o7planning.sbshoppingcart.form.CustomerForm;
import org.o7planning.sbshoppingcart.model.CartInfo;
import org.o7planning.sbshoppingcart.model.CustomerInfo;
import org.o7planning.sbshoppingcart.model.ProductInfo;
import org.o7planning.sbshoppingcart.pagination.PaginationResult;
import org.o7planning.sbshoppingcart.utils.Utils;
import org.o7planning.sbshoppingcart.validator.CustomerFormValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

@Controller
@Transactional
public class MainController {

	@Autowired
	private OrderDAO orderDAO;

	@Autowired
	private ProductDAO productDAO;

	@Autowired
	private CustomerFormValidator customerFormValidator;

	@InitBinder
	public void myInitBinder(WebDataBinder dataBinder) {
		Object target = dataBinder.getTarget();
		if (target == null) {
			return;
		}
		System.out.println("Target=" + target);

		// Trường hợp update SL trên giỏ hàng.
		// (@ModelAttribute("cartForm") @Validated CartInfo cartForm)
		if (target.getClass() == CartInfo.class) {

		}

		// Trường hợp save thông tin khách hàng.
		// (@ModelAttribute @Validated CustomerInfo customerForm)
		else if (target.getClass() == CustomerForm.class) {
			dataBinder.setValidator(customerFormValidator);
		}

	}

	@RequestMapping("/403")
	public String accessDenied() {
		return "/403";
	}

	@RequestMapping("/")
	public String home() {
		return "index";
	}

	// Danh sách sản phẩm.
	@RequestMapping({ "/productList" })
	public String listProductHandler(Model model, //
			@RequestParam(value = "name", defaultValue = "") String likeName,
			@RequestParam(value = "page", defaultValue = "1") int page) {
		final int maxResult = 5;
		final int maxNavigationPage = 10;

		PaginationResult<ProductInfo> result = productDAO.queryProducts(page, //
				maxResult, maxNavigationPage, likeName);

		model.addAttribute("paginationProducts", result);
		return "productList";
	}

	@RequestMapping({ "/buyProduct" })
	public String listProductHandler(HttpServletRequest request, Model model, //
			@RequestParam(value = "code", defaultValue = "") String code) {

		Product product = null;
		if (code != null && code.length() > 0) {
			product = productDAO.findProduct(code);
		}
		if (product != null) {

			// 
			CartInfo cartInfo = Utils.getCartInSession(request);

			ProductInfo productInfo = new ProductInfo(product);

			cartInfo.addProduct(productInfo, 1);
		}

		return "redirect:/shoppingCart";
	}

	@RequestMapping({ "/shoppingCartRemoveProduct" })
	public String removeProductHandler(HttpServletRequest request, Model model, //
			@RequestParam(value = "code", defaultValue = "") String code) {
		Product product = null;
		if (code != null && code.length() > 0) {
			product = productDAO.findProduct(code);
		}
		if (product != null) {

			CartInfo cartInfo = Utils.getCartInSession(request);

			ProductInfo productInfo = new ProductInfo(product);

			cartInfo.removeProduct(productInfo);

		}

		return "redirect:/shoppingCart";
	}

	// POST: Cập nhập số lượng cho các sản phẩm đã mua.
	@RequestMapping(value = { "/shoppingCart" }, method = RequestMethod.POST)
	public String shoppingCartUpdateQty(HttpServletRequest request, //
			Model model, //
			@ModelAttribute("cartForm") CartInfo cartForm) {

		CartInfo cartInfo = Utils.getCartInSession(request);
		cartInfo.updateQuantity(cartForm);

		return "redirect:/shoppingCart";
	}

	// GET: Hiển thị giỏ hàng.
	@RequestMapping(value = { "/shoppingCart" }, method = RequestMethod.GET)
	public String shoppingCartHandler(HttpServletRequest request, Model model) {
		CartInfo myCart = Utils.getCartInSession(request);

		model.addAttribute("cartForm", myCart);
		return "shoppingCart";
	}

	// GET: Nhập thông tin khách hàng.
	@RequestMapping(value = { "/shoppingCartCustomer" }, method = RequestMethod.GET)
	public String shoppingCartCustomerForm(HttpServletRequest request, Model model) {

		CartInfo cartInfo = Utils.getCartInSession(request);

		if (cartInfo.isEmpty()) {

			return "redirect:/shoppingCart";
		}
		CustomerInfo customerInfo = cartInfo.getCustomerInfo();

		CustomerForm customerForm = new CustomerForm(customerInfo);

		model.addAttribute("customerForm", customerForm);

		return "shoppingCartCustomer";
	}

	// POST: Save thông tin khách hàng.
	@RequestMapping(value = { "/shoppingCartCustomer" }, method = RequestMethod.POST)
	public String shoppingCartCustomerSave(HttpServletRequest request, //
			Model model, //
			@ModelAttribute("customerForm") @Validated CustomerForm customerForm, //
			BindingResult result, //
			final RedirectAttributes redirectAttributes) {

		if (result.hasErrors()) {
			customerForm.setValid(false);
			// Forward tới trang nhập lại.
			return "shoppingCartCustomer";
		}

		customerForm.setValid(true);
		CartInfo cartInfo = Utils.getCartInSession(request);
		CustomerInfo customerInfo = new CustomerInfo(customerForm);
		cartInfo.setCustomerInfo(customerInfo);

		return "redirect:/shoppingCartConfirmation";
	}

	// GET: Xem lại thông tin để xác nhận.
	@RequestMapping(value = { "/shoppingCartConfirmation" }, method = RequestMethod.GET)
	public String shoppingCartConfirmationReview(HttpServletRequest request, Model model) {
		CartInfo cartInfo = Utils.getCartInSession(request);

		if (cartInfo == null || cartInfo.isEmpty()) {

			return "redirect:/shoppingCart";
		} else if (!cartInfo.isValidCustomer()) {

			return "redirect:/shoppingCartCustomer";
		}
		model.addAttribute("myCart", cartInfo);

		return "shoppingCartConfirmation";
	}

	// POST: Gửi đơn hàng (Save).
	@RequestMapping(value = { "/shoppingCartConfirmation" }, method = RequestMethod.POST)

	public String shoppingCartConfirmationSave(HttpServletRequest request, Model model) {
		CartInfo cartInfo = Utils.getCartInSession(request);

		if (cartInfo.isEmpty()) {

			return "redirect:/shoppingCart";
		} else if (!cartInfo.isValidCustomer()) {

			return "redirect:/shoppingCartCustomer";
		}
		try {
			orderDAO.saveOrder(cartInfo);
		} catch (Exception e) {

			return "shoppingCartConfirmation";
		}

		// Xóa giỏ hàng khỏi session.
		Utils.removeCartInSession(request);

		// Lưu thông tin đơn hàng cuối đã xác nhận mua.
		Utils.storeLastOrderedCartInSession(request, cartInfo);

		return "redirect:/shoppingCartFinalize";
	}

	@RequestMapping(value = { "/shoppingCartFinalize" }, method = RequestMethod.GET)
	public String shoppingCartFinalize(HttpServletRequest request, Model model) {

		CartInfo lastOrderedCart = Utils.getLastOrderedCartInSession(request);

		if (lastOrderedCart == null) {
			return "redirect:/shoppingCart";
		}
		model.addAttribute("lastOrderedCart", lastOrderedCart);
		return "shoppingCartFinalize";
	}

	@RequestMapping(value = { "/productImage" }, method = RequestMethod.GET)
	public void productImage(HttpServletRequest request, HttpServletResponse response, Model model,
			@RequestParam("code") String code) throws IOException {
		Product product = null;
		if (code != null) {
			product = this.productDAO.findProduct(code);
		}
		if (product != null && product.getImage() != null) {
			response.setContentType("image/jpeg, image/jpg, image/png, image/gif");
			response.getOutputStream().write(product.getImage());
		}
		response.getOutputStream().close();
	}

}
AdminController.java
package org.o7planning.sbshoppingcart.controller;

import java.util.List;

import org.apache.commons.lang.exception.ExceptionUtils;
import org.o7planning.sbshoppingcart.dao.OrderDAO;
import org.o7planning.sbshoppingcart.dao.ProductDAO;
import org.o7planning.sbshoppingcart.entity.Product;
import org.o7planning.sbshoppingcart.form.ProductForm;
import org.o7planning.sbshoppingcart.model.OrderDetailInfo;
import org.o7planning.sbshoppingcart.model.OrderInfo;
import org.o7planning.sbshoppingcart.pagination.PaginationResult;
import org.o7planning.sbshoppingcart.validator.ProductFormValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam; 
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

@Controller
@Transactional 
public class AdminController {

	@Autowired
	private OrderDAO orderDAO;

	@Autowired
	private ProductDAO productDAO;

	@Autowired
	private ProductFormValidator productFormValidator;

	@InitBinder
	public void myInitBinder(WebDataBinder dataBinder) {
		Object target = dataBinder.getTarget();
		if (target == null) {
			return;
		}
		System.out.println("Target=" + target);

		if (target.getClass() == ProductForm.class) {
			dataBinder.setValidator(productFormValidator); 
		}
	}

	// GET: Hiển thị trang login
	@RequestMapping(value = { "/admin/login" }, method = RequestMethod.GET)
	public String login(Model model) {

		return "login";
	}

	@RequestMapping(value = { "/admin/accountInfo" }, method = RequestMethod.GET)
	public String accountInfo(Model model) {

		UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
		System.out.println(userDetails.getPassword());
		System.out.println(userDetails.getUsername());
		System.out.println(userDetails.isEnabled());

		model.addAttribute("userDetails", userDetails);
		return "accountInfo";
	}

	@RequestMapping(value = { "/admin/orderList" }, method = RequestMethod.GET)
	public String orderList(Model model, //
			@RequestParam(value = "page", defaultValue = "1") String pageStr) {
		int page = 1;
		try {
			page = Integer.parseInt(pageStr);
		} catch (Exception e) {
		}
		final int MAX_RESULT = 5;
		final int MAX_NAVIGATION_PAGE = 10;

		PaginationResult<OrderInfo> paginationResult //
				= orderDAO.listOrderInfo(page, MAX_RESULT, MAX_NAVIGATION_PAGE);

		model.addAttribute("paginationResult", paginationResult);
		return "orderList";
	}

	// GET: Hiển thị product
	@RequestMapping(value = { "/admin/product" }, method = RequestMethod.GET)
	public String product(Model model, @RequestParam(value = "code", defaultValue = "") String code) {
		ProductForm productForm = null;

		if (code != null && code.length() > 0) {
			Product product = productDAO.findProduct(code);
			if (product != null) {
				productForm = new ProductForm(product);
			}
		}
		if (productForm == null) {
			productForm = new ProductForm();
			productForm.setNewProduct(true);
		}
		model.addAttribute("productForm", productForm);
		return "product";
	}

	// POST: Save product
	@RequestMapping(value = { "/admin/product" }, method = RequestMethod.POST)
	public String productSave(Model model, //
			@ModelAttribute("productForm") @Validated ProductForm productForm, //
			BindingResult result, //
			final RedirectAttributes redirectAttributes) {

		if (result.hasErrors()) {
			return "product";
		}
		try {
			productDAO.save(productForm);
		} catch (Exception e) {
			Throwable rootCause = ExceptionUtils.getRootCause(e);
			String message = rootCause.getMessage();
			model.addAttribute("errorMessage", message);
			// Show product form.
			return "product";
		}

		return "redirect:/productList";
	}

	@RequestMapping(value = { "/admin/order" }, method = RequestMethod.GET)
	public String orderView(Model model, @RequestParam("orderId") String orderId) {
		OrderInfo orderInfo = null;
		if (orderId != null) {
			orderInfo = this.orderDAO.getOrderInfo(orderId);
		}
		if (orderInfo == null) {
			return "redirect:/admin/orderList";
		}
		List<OrderDetailInfo> details = this.orderDAO.listOrderDetailInfos(orderId);
		orderInfo.setDetails(details);

		model.addAttribute("orderInfo", orderInfo);

		return "order";
	}

}

14. Static & Thymeleaf Templates

styles.css
html {
    background: white;
}
h3 {
    margin: 0px;
    padding: 0px;
}
body {
    max-width: 860px;
    min-width: 360px;
    margin: 0px auto;
    background: #F8F8F8;
    padding:0px 5px;
    text-align:center;
}
 
.page-title  {
    font-size:120%;
    text-align: left;
    margin:10px 0px;
}
.header-container {
    text-align: left;
    border-bottom: 1px solid #ccc;
    position: relative;
    background: #5f5f5f;
    color: white;
}
.header-container .header-bar  {
    position: absolute;
    right: 10px;
    bottom: 20px;
}
.header-container .header-bar  a  {
    color: white;
    font-size: bold;
}
 
.footer-container {
    text-align: center;
    margin-top: 10px;
    padding: 5px 0px 0px 0px;
    border-top: 1px solid #ccc;
}
.menu-container {
    text-align: right;
    margin: 10px 5px;
}
.menu-container a {
    margin: 5px 5px 5px 10px;
    color: #004d99;
    font-weight: bold;
    text-decoration: none;
}
 
.site-name {
    font-size:200%;
    margin:10px 10px 10px 0px;
    padding: 5px;
}
 
.container  {
    margin: 5px 0px;
}
 
.demo-container, .login-container, .account-container {
    padding: 5px;
    border: 1px solid #ccc;
    text-align:left;
    margin:20px 0px;
}
 
.customer-info-container {
    text-align: left;
    border: 1px solid #ccc;
    padding: 0px 5px;
}
.product-preview-container {
    border: 1px solid #ccc;
    padding: 5px;
    width: 250px;
    margin: 10px ;
    display: inline-block;
    text-align:left;
}
 
.product-preview-container input {
    width: 50px;
}
 
 
.product-image {
    width: 120px;
    height: 80px;
}
 
ul {
    list-style-type: none;
    padding-left: 5px;
    margin:5px;
}
 
 
.navi-item {
    margin: 5px 5px 5px 20px;
}
 
.button-update-sc {
    color: red;
    margin: 5px 5px 5px 20px;
}
 
.button-send-sc {
    color: red;
    margin: 5px 5px 5px 20px;
}
 
.error-message {
    font-size: 90%;
    color: red;
    font-style: italic;
}
 
.price {
    color: blue;
    font-weight: bold;
}
 
.subtotal {
    color: red;
    font-weight: bold;
}
 
.total {
    color: red;
    font-weight: bold;
    font-size: 120%;
}
 
table td {
    padding: 5px;
}
_footer.html
<div class="footer-container" xmlns:th="http://www.thymeleaf.org">
 
   @Copy right <a href="http://o7planning.org" target="_blank">o7planning.org</a>
   <br>
   See more <a>demo</a>
    
</div>
_header.html
<div class="header-container"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">

   <div class="site-name">Online Shop</div>
  
   <div class="header-bar">
      <th:block sec:authorize="isAuthenticated()">
         Hello
         <a th:href="@{/admin/accountInfo}" th:utext="${#request.userPrincipal.name}">..</a>
         &nbsp;|&nbsp;
         <a th:href="@{/admin/logout}">Logout</a>
      </th:block>
      
      <th:block sec:authorize="!isAuthenticated()">
         <a th:href="@{/admin/login}">Login</a>
      </th:block>
   </div>
  
</div>
_menu.html
<div class="menu-container"
   xmlns:th="http://www.thymeleaf.org"
   xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4" >
   <a th:href="@{/}">Home</a>
   |
   <a th:href="@{/productList}">
   Product List
   </a>
   |
   <a th:href="@{/shoppingCart}">
   My Cart
   </a>
   |
   <th:block sec:authorize="hasAnyRole('ROLE_MANAGER','ROLE_EMPLOYEE')">
      <a th:href="@{/admin/orderList}">
      Order List
      </a>
      |
   </th:block>
   <th:block sec:authorize="hasRole('ROLE_MANAGER')">
      <a th:href="@{/admin/product}">
      Create Product
      </a>
      |
   </th:block>
</div>
403.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
   <head>
      <meta charset="UTF-8">
      <title>Access Denied</title>
      <link rel="stylesheet" type="text/css" th:href="@{/styles.css}">
   </head>
   
   <body>
   
      <th:block th:include="/_header"></th:block>
      <th:block th:include="/_menu"></th:block>
      
      <div class="page-title">Access Denied!</div>
      <h3 style="color:red;">Sorry, you can not access this page!</h3>
      
      <th:block th:include="/_footer"></th:block>
      
   </body>
</html>
accountInfo.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
   <head>
      <meta charset="UTF-8">
      <title>Account Info</title>
      <link rel="stylesheet" type="text/css" th:href="@{/styles.css}">
   </head>
   <body>
   
      <th:block th:include="/_header"></th:block>
      <th:block th:include="/_menu"></th:block>
      
      <div class="page-title">Account Info</div>
      <div class="account-container">
         <ul>
            <li>User Name: <span th:utext="${#request.userPrincipal.name}"></span></li>
            <li>
               Role:
               <ul>
                  <li th:each="auth : ${userDetails.authorities}" th:utext="${auth.authority}"></li>
               </ul>
            </li>
         </ul>
      </div>
      
      <th:block th:include="/_footer"></th:block>
      
   </body>
</html>
index.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
   <head>
      <meta charset="UTF-8">
      <title>Books Shop Online</title>
      <link rel="stylesheet" type="text/css" th:href="@{/styles.css}" />
   </head>
   <body>
      <th:block th:include="/_header"></th:block>
      <th:block th:include="/_menu"></th:block>
      
      <div class="page-title">Shopping Cart Demo</div>
      <div class="demo-container">
         <h3>Demo content</h3>
         <ul>
            <li>Buy online</li>
            <li>Admin pages</li>
            <li>Reports</li>
         </ul>
      </div>
      
      <th:block th:include="/_footer"></th:block>
      
   </body>
</html>
login.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
   <head>
      <meta charset="UTF-8">
      <title>Login</title>
      <link rel="stylesheet" type="text/css" th:href="@{/styles.css}">
   </head>
   <body>
      <th:block th:include="/_header"></th:block>
      <th:block th:include="/_menu"></th:block>
      
      <div class="page-title">Login (For Employee, Manager)</div>
      
      <div class="login-container">
         <h3>Enter username and password</h3>
         <br>
         <!-- /login?error=true -->
         <th:block th:if="${#session != null && #session.getAttribute('SPRING_SECURITY_LAST_EXCEPTION') != null}">
             <div th:if= "${#request.getParameter('error') == 'true'}"
                style="color: red; margin: 10px 0px;">
                Login Failed!!!<br /> Reason :
                <span th:utext="${#session.getAttribute('SPRING_SECURITY_LAST_EXCEPTION').message}"></span>
             </div>
         </th:block>
         
         <form method="POST"
            th:action="@{/j_spring_security_check}">
            <table>
               <tr>
                  <td>User Name *</td>
                  <td><input name="userName" /></td>
               </tr>
               <tr>
                  <td>Password *</td>
                  <td><input type="password" name="password" /></td>
               </tr>
               <tr>
                  <td>&nbsp;</td>
                  <td>
                      <input type="submit" value="Login" />
                      <input type="reset"  value="Reset" />
                  </td>
               </tr>
            </table>
         </form>
         
         <span class="error-message" th:utext="${error}"></span>
      </div>
      
      <div style="text-align:left">
          <h3>User/Password:</h3>
          <ul>        
             <li>employee1/123</li>
             <li>manager1/123</li>   
          </ul>
      </div>
      
      <th:block th:include="/_footer"></th:block>
      
   </body>
</html>
order.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
   <head>
      <meta charset="UTF-8">
      <title>Product List</title>
      <link rel="stylesheet" type="text/css" th:href="@{/styles.css}">
   </head>
   <body>
      <th:block th:include="/_header"></th:block>
      <th:block th:include="/_menu"></th:block>
      
      <div class="page-title">Order Info</div>
      
      <div class="customer-info-container">
         <h3>Customer Information:</h3>
         <ul>
            <li>Name: <span th:utext="${orderInfo.customerName}"></span></li>
            <li>Email: <span th:utext="${orderInfo.customerEmail}"></span></li>
            <li>Phone: <span th:utext="${orderInfo.customerPhone}"></span></li>
            <li>Address: <span th:utext="${orderInfo.customerAddress}"></span></li>
         </ul>
         <h3>Order Summary:</h3>
         <ul>
            <li>Total:
               <span class="total" th:utext="${#numbers.formatDecimal(orderInfo.amount,3,2,'COMMA')}">         
               </span>
            </li>
         </ul>
      </div>
      <br/>
      <table border="1" style="width:100%">
         <tr>
            <th>Product Code</th>
            <th>Product Name</th>
            <th>Quantity</th>
            <th>Price</th>
            <th>Amount</th>
         </tr>
         <tr th:each="orderDetailInfo : ${orderInfo.details}">
            <td th:utext="${orderDetailInfo.productCode}"></td>
            <td th:utext="${orderDetailInfo.productName}"></td>
            <td th:utext="${orderDetailInfo.quanity}"></td>
            <td th:utext="${#numbers.formatDecimal(orderDetailInfo.price,3,2,'COMMA')}"></td>
            <td th:utext="${#numbers.formatDecimal(orderDetailInfo.amount,3,2,'COMMA')}"></td>
         </tr>
      </table>
      
      <th:block th:include="/_footer"></th:block>
      
   </body>
</html>
orderList.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
   <head>
      <meta charset="UTF-8">
      <title>Product List</title>
      <link rel="stylesheet" type="text/css" th:href="@{/styles.css}">
   </head>
   <body>
      <th:block th:include="/_header"></th:block>
      <th:block th:include="/_menu"></th:block>

      <div class="page-title">Order List</div>
      <div>Total Order Count: <span th:utext="${paginationResult.totalRecords}"></span></div>
      <table border="1" style="width:100%">
         <tr>
            <th>Order Num</th>
            <th>Order Date</th>
            <th>Customer Name</th>
            <th>Customer Address</th>
            <th>Customer Email</th>
            <th>Amount</th>
            <th>View</th>
         </tr>
         <tr th:each="orderInfo : ${paginationResult.list}">
            <td th:utext="${orderInfo.orderNum}"></td>
            <td th:utext="${#dates.format(orderInfo.orderDate,'dd-MM-yyyy HH:mm')}"></td>
            <td th:utext="${orderInfo.customerName}"></td>
            <td th:utext="${orderInfo.customerAddress}"></td>
            <td th:utext="${orderInfo.customerEmail}"></td>
            <td style="color:red;" th:utext="${#numbers.formatDecimal(orderInfo.amount,3,2,'COMMA')}">
            </td>
            <td><a th:href="@{|/admin/order?orderId=${orderInfo.id}|}">View</a></td>
         </tr>
      </table>
      <div class="page-navigator" th:if="${paginationResult.totalPages > 1}" >
         <th:block th:each="page: ${paginationResult.navigationPages}">
            <a th:if="${page != -1}" class="nav-item"
               th:href="@{|/admin/orderList?page=${page}|}" th:utext="${page}"></a>
               
            <span th:if="${page == -1}" class="nav-item"> ... </span>
         </th:block>
      </div>

      <th:block th:include="/_footer"></th:block>
      
   </body>
</html>
product.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
   <head>
      <meta charset="UTF-8">
      <title>Product</title>
      <link rel="stylesheet" type="text/css" th:href="@{/styles.css}">
   </head>
   <body>
   
      <th:block th:include="/_header"></th:block>
      <th:block th:include="/_menu"></th:block>
      
      <div class="page-title">Product</div>
      <div th:if="${errorMessage!= null}" class="error-message" th:utext="${errorMessage}">
      </div>
      
      <form  th:object="${productForm}" th:action="@{/admin/product}"
              method="POST" enctype="multipart/form-data">
         <table style="text-align:left;">
            <tr>
               <td>Code *</td>
               <td style="color:red;">
                  <input th:field="*{newProduct}" type="hidden" />
                  <input th:if="${productForm.newProduct}" type="text"
                     th:field="*{code}" />  
                  <th:block th:if="${!productForm.newProduct}">   
                       <span th:utext="${productForm.code}"></span>   
                       <input type="hidden" th:field="*{code}" />                                       
                  </th:block>   
               </td>
               <td>
                  <span class="error-message" th:if="${#fields.hasErrors('code')}" th:errors="*{code}">..</span>
               </td>   
            </tr>
            <tr>
               <td>Name *</td>
               <td><input th:field="*{name}"  /></td>
               <td>
                  <span class="error-message" th:if="${#fields.hasErrors('name')}" th:errors="*{name}">..</span>
               </td>
            </tr>
            <tr>
               <td>Price *</td>
               <td><input th:field="*{price}"  /></td>
               <td>
                  <span class="error-message" th:if="${#fields.hasErrors('price')}" th:errors="*{price}">..</span>
               </td>
            </tr>
            <tr>
               <td>Image</td>
               <td>
                  <img th:src="@{|/productImage?code=${productForm.code}|}" width="100"/>
               </td>
               <td> </td>
            </tr>
            <tr>
               <td>Upload Image</td>
               <td><input type="file" th:field="*{fileData}" /></td>
               <td> </td>
            </tr>
            <tr>
               <td>&nbsp;</td>
               <td>
                  <input type="submit" value="Submit" />
                  <input type="reset" value="Reset" />
               </td>
            </tr>
         </table>
      </form>
      
      <th:block th:include="/_footer"></th:block>
      
   </body>
</html>
productList.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
   xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
   <head>
      <meta charset="UTF-8">
      <title>Product List</title>
      <link rel="stylesheet" type="text/css" th:href="@{/styles.css}">
   </head>
   <body>
      <th:block th:include="/_header"></th:block>
      <th:block th:include="/_menu"></th:block>
      
      <div class="page-title">Product List</div>
      
      <div class="product-preview-container" th:each="prodInfo : ${paginationProducts.list}">
         <ul>
            <li><img class="product-image"
               th:src="@{|/productImage?code=${prodInfo.code}|}" /></li>
            <li>Code: <span th:utext="${prodInfo.code}"></span></li>
            <li>Name: <span th:utext="${prodInfo.name}"></span></li>
            <li>Price: <span th:utext="${#numbers.formatDecimal(prodInfo.price,3,2,'COMMA')}"></span></li>
            <li>
               <a th:href="@{|/buyProduct?code=${prodInfo.code}|}">Buy Now</a>
            </li>
            <!-- For Manager edit Product -->
            <th:block sec:authorize="hasAuthority('ROLE_MANAGER')">
               <li>
                 <a style="color:red;"
                    th:href="@{|/admin/product?code=${prodInfo.code}|}">Edit Product</a>
               </li>
            </th:block>
         </ul>
      </div>
      
      <br/>
      <div class="page-navigator" th:if="${paginationProducts.totalPages > 1}">
         <th:block th:each="page : ${paginationProducts.navigationPages}">
        
            <a th:href="@{|/productList?page=${page}|}" th:if="${page != -1}"
               class="nav-item" th:utext="${page}"></a>
              
            <span class="nav-item" th:if="${page == -1}"> ... </span>
            
         </th:block>
      </div>
      
      <th:block th:include="/_footer"></th:block>
      
   </body>
</html>
shoppingCart.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
   <head>
      <meta charset="UTF-8">
      <title>Shopping Cart</title>
      <link rel="stylesheet" type="text/css" th:href="@{/styles.css}">
   </head>
   <body>
      <th:block th:include="/_header"></th:block>
      <th:block th:include="/_menu"></th:block>
      
      <div class="page-title">My Cart</div>
      
      <th:block th:if="${cartForm == null || cartForm.cartLines == null || cartForm.cartLines.empty}">
         <h2>There is no items in Cart</h2>
         <a th:href="@{/productList}">Show Product List</a>
      </th:block>
      
      <th:block th:if="${cartForm != null && cartForm.cartLines != null && !cartForm.cartLines.empty}">
         <form method="POST" th:object="${cartForm}" th:action="@{/shoppingCart}">
         
               <div class="product-preview-container"
                        th:each="cartLineInfo, varStatus : ${cartForm.cartLines}">
                  <ul>
                     <li><img class="product-image"
                        th:src="@{|/productImage?code=${cartLineInfo.productInfo.code}|}" />
                     </li>
                     <li>Code: <span th:utext="${cartLineInfo.productInfo.code}"></span>
                        <input type="hidden"       
                           th:name="|cartLines[${varStatus.index}].productInfo.code|"                        
                           th:value="${cartLineInfo.productInfo.code}" />
                     </li>
                     <li>Name: <span th:utext="${cartLineInfo.productInfo.name}"></span></li>
                     <li>Price:
                        <span class="price"
                           th:utext="${#numbers.formatDecimal(cartLineInfo.productInfo.price,3,2,'COMMA')}">
                        </span>
                     </li>
                     <li>Quantity:
                        <input
                            th:name="|cartLines[${varStatus.index}].quantity|"
                            th:value="${cartLineInfo.quantity}" />
                     </li>
                     <li>Subtotal:
                        <span class="subtotal"
                           th:utext="${#numbers.formatDecimal(cartLineInfo.amount,3,2,'COMMA')}">
                        </span>
                     </li>
                     <li>
                        <a th:href="@{|/shoppingCartRemoveProduct?code=${cartLineInfo.productInfo.code}|}">
                        Delete
                        </a>
                     </li>
                  </ul>
               </div>
            
            <div style="clear: both"></div>
            <input class="button-update-sc" type="submit" value="Update Quantity" />
            <a class="navi-item"
               th:href="@{/shoppingCartCustomer}">Enter Customer Info</a>
            <a class="navi-item"
               th:href="@{/productList}">Continue Buy</a>
         </form>
      </th:block>
      
      <th:block th:include="/_footer"></th:block>
      
   </body>
</html>
shoppingCartConfirmation.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
   <head>
      <meta charset="UTF-8">
      <title>Shopping Cart Confirmation</title>
      <link rel="stylesheet" type="text/css" th:href="@{/styles.css}">
   </head>
   <body>
      <th:block th:include="/_header"></th:block>
      <th:block th:include="/_menu"></th:block>
      
      <div class="page-title">Confirmation</div>
      <div class="customer-info-container">
         <h3>Customer Information:</h3>
         <ul>
            <li>Name: <span th:utext="${myCart.customerInfo.name}"></span></li>
            <li>Email: <span th:utext="${myCart.customerInfo.email}"></span></li>
            <li>Phone: <span th:utext="${myCart.customerInfo.phone}"></span></li>
            <li>Address: <span th:utext="${myCart.customerInfo.address}"></span></li>
         </ul>
         <h3>Cart Summary:</h3>
         <ul>
            <li>Quantity: <span th:utext="${myCart.quantityTotal}"></span>$</li>
            <li>Total:
               <span class="total"
                  th:utext="${#numbers.formatDecimal(myCart.amountTotal,3,2,'COMMA')}">  
               </span>
            </li>
         </ul>
      </div>
      <form method="POST" th:action="@{/shoppingCartConfirmation}">
         <!-- Edit Cart -->
         <a class="navi-item" th:href="@{/shoppingCart}">
         Edit Cart
         </a>
         <!-- Edit Customer Info -->
         <a class="navi-item" th:href="@{/shoppingCartCustomer}">
         Edit Customer Info
         </a>
         <!-- Send/Save -->
         <input type="submit" value="Send" class="button-send-sc" />
      </form>
      <div class="container">
         <div class="product-preview-container" th:each="cartLineInfo : ${myCart.cartLines}">
            <ul>
               <li>
                  <img class="product-image"
                     src="@{|/productImage?code=${cartLineInfo.productInfo.code}|}" />
               </li>
               <li>
                  Code: <span th:utext="${cartLineInfo.productInfo.code}"></span>
                  <input
                     type="hidden" name="code" th:value="${cartLineInfo.productInfo.code}" />
               </li>
               <li>Name: <span th:utext="${cartLineInfo.productInfo.name}"></span></li>
               <li>Price:
                  <span class="price"
                     th:utext="${#numbers.formatDecimal(cartLineInfo.productInfo.price,3,2,'COMMA')}">
                  </span>
               </li>
               <li>Quantity: <span th:utext="${cartLineInfo.quantity}"></span></li>
               <li>Subtotal:
                  <span class="subtotal"
                     th:utext="${#numbers.formatDecimal(cartLineInfo.amount,3,2,'COMMA')}">
                  </span>
               </li>
            </ul>
         </div>
      </div>
      
      <th:block th:include="/_footer"></th:block>
      
   </body>
</html>
shoppingCartCustomer.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
   <head>
      <meta charset="UTF-8">
      <title>Enter Customer Information</title>
      <link rel="stylesheet" type="text/css" th:href="@{/styles.css}">
   </head>
   <body>
      <th:block th:include="/_header"></th:block>
      <th:block th:include="/_menu"></th:block>
      
      <div class="page-title">Enter Customer Information</div>
      
      <form method="POST" th:object = "${customerForm}" th:action="@{/shoppingCartCustomer}">
         
         <table>
            <tr>
               <td>Name *</td>
               <td><input th:field="*{name}" /></td>
               <td>
                  <span class="error-message"
                     th:if="${#fields.hasErrors('name')}" th:errors="*{name}">..</span>
               </td>
            </tr>
            <tr>
               <td>Email *</td>
               <td><input th:field="*{email}" /></td>
               <td>
                  <span class="error-message"
                     th:if="${#fields.hasErrors('email')}" th:errors="*{email}">..</span>
               </td>
            </tr>
            <tr>
               <td>Phone *</td>
               <td><input th:field="*{phone}" /></td>
               <td>
                  <span class="error-message"
                     th:if="${#fields.hasErrors('phone')}" th:errors="*{phone}">..</span>
               </td>
            </tr>
            <tr>
               <td>Address *</td>
               <td><input th:field="*{address}" /></td>
               <td>
                  <span class="error-message"
                     th:if="${#fields.hasErrors('address')}" th:errors="*{address}">..</span>
               </td>
            </tr>
            <tr>
               <td>&nbsp;</td>
               <td><input type="submit" value="Submit" /> <input type="reset"
                  value="Reset" /></td>
            </tr>
         </table>
         
      </form>
      
      <th:block th:include="/_footer"></th:block>
      
   </body>
</html>
shoppingCartFinalize.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
   <head>
      <meta charset="UTF-8">
      <title>Shopping Cart Finalize</title>
      <link rel="stylesheet" type="text/css" th:href="@{/styles.css}">
   </head>
   <body>
   
      <th:block th:include="/_header"></th:block>
      <th:block th:include="/_menu"></th:block>
      
      <div class="page-title">Finalize</div>
      <div class="container">
         <h3>Thank you for Order</h3>
         Your order number is: <span th:utext="${lastOrderedCart.orderNum}"></span>
      </div>
      
      <th:block th:include="/_footer"></th:block>
      
   </body>
</html>

Các hướng dẫn Spring Boot

Show More