betacode

Создание веб-приложения для корзины покупок с помощью Spring Boot, Hibernate

  1. Обзор приложения
  2. Просмотр приложения
  3. Приготовить базу данных
  4. Создать проект Spring Boot
  5. Конфигурация pom.xml
  6. Конфигурация Hibernate
  7. Безопасность и  UserDetailsService
  8. Классы Entity
  9. Классы DAO
  10. Классы Model
  11. Form bean & Validator
  12. PaginationResult & Utils
  13. Controllers
  14. Static & Thymeleaf Templates

1. Обзор приложения

В данной статье я покажу вам как создать веб приложение корзины (shopping cart) с Spring Boot, Hibernate и Thymeleaf.
Приложение основано на:
  • Eclipse 4.7 (Oxygen)

  • Spring Boot 2.x

  • Hibernate 5.x

Hibernate и JPA являются 2-мя похожими технологиями. Если вы знаете Hibernate вы можете легко работать с JPA и наоборот. Но JPA не так хорошо поддерживает пагинацию (Pagination). При этом пагинация это необходиме свойство в приложении, в этом Hibernate поддерживает лучше, имеено это причина почему я использую Hibernate для данного приложения.

2. Просмотр приложения

Функция покупки для пользователя и без требования входа в систему.
Функция для сотрудгиков и менеджеров:
Функция добавления и редактирования информации продукта (Только для менеджеров).

3. Приготовить базу данных

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. Создать проект Spring Boot

На Eclipse создать проект Spring Boot.
Ввести:
  • Name: SbHibernateShoppingCart
  • Group: org.o7planning
  • Description: Shopping Cart + Spring Boot + Hibernate + Thymeleaf
  • Package: org.o7plannng.sbshoppingcart
Выбрать технологии и библиотеки, которые будут использоваться:
  • Security
  • JPA
  • Thymeleaf
  • Web
  • MySQL
  • SQL Server
  • PosgreSQL
Так как данная статья является инструкцией, я выберу библиотеки для 4 типов баз данных это Oracle, My SQL, SQL Server, PostgreSQL. С Oracle мы объявим для нее библиотеку в следующем шаге (В части конфигурации pom.xml).
Когда вы выбираете JPA, он уже включает библиотеки JPA и Hibernate.

5. Конфигурация pom.xml

Если вы используете базу данных Oracle, вам нужно объявить необходимую библиотеку для Oracle в файле 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>
Если вы подключаетесь к базе данных SQL Service, вы можете использовать одну из 2-х библиотек JTDS или 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>
Другие библиотеки:
<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>
Полное содержание 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. Конфигурация 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
Примечание Spring Boot по умолчанию автоматически конфигурирует JPA, и создает Spring BEAN связанные с JPA, эти автоматические конфигурации Spring Boot включают:
  • DataSourceAutoConfiguration
  • DataSourceTransactionManagerAutoConfiguration
  • HibernateJpaAutoConfiguration
Цель данного приложения использовать Hibernate, поэтому нам нужно отключить автоматические конфигурации у 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);
    }

    ........

}
После, конфигурировать необходимые Spring BEAN для 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. Безопасность и  UserDetailsService

В данном приложении у нас есть 2 роли ROLE_MANAGER и ROLE_EMPLOYEE.
  • ROLE_EMPLOYEE: Данная роль для сотрудников, пользователь с данной ролью может смотреть список заказов, детали заказов, и смотреть их профайлы.
  • ROLE_MANAGER: Данная роль для менеджеров, пользователь с данной ролью может смотреть список заказов, детали заказов, и смотреть их профайлы, добавлять и редактировать продукт.
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 {

      // Setting Service to find User in the database.
      // And Setting PassswordEncoder
      auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());

   }

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

      http.csrf().disable();

      // Requires login with role ROLE_EMPLOYEE or ROLE_MANAGER.
      // If not, it will redirect to /admin/login.
      http.authorizeRequests().antMatchers("/admin/orderList", "/admin/order", "/admin/accountInfo")//
            .access("hasAnyRole('ROLE_EMPLOYEE', 'ROLE_MANAGER')");

      // Pages only for MANAGER
      http.authorizeRequests().antMatchers("/admin/product").access("hasRole('ROLE_MANAGER')");

      // When user login, role XX.
      // But access to the page requires the YY role,
      // An AccessDeniedException will be thrown.
      http.authorizeRequests().and().exceptionHandling().accessDeniedPage("/403");

      // Configuration for 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")

            // Configuration for the Logout page.
            // (After logout, go to home page)
            .and().logout().logoutUrl("/admin/logout").logoutSuccessUrl("/");

   }
}
Класс UserDetailsServiceImpl выполняет интерфейс UserDetailsService, он используется для поиска пользователя в базе данных соответствуюего имени пользователя и для настройки роли данному пользователю.
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. Классы 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. Классы 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);
        }
        // If error in DB, Exceptions will be thrown out immediately
        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. Классы 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() {

    }

    // Using for 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() {

    }

    // Using for 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();
    }

    // Using in 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();

   // This validator only checks for the CustomerForm.
   @Override
   public boolean supports(Class<?> clazz) {
      return clazz == CustomerForm.class;
   }

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

      // Check the fields of 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;

   // This validator only checks for the ProductForm.
   @Override
   public boolean supports(Class<?> clazz) {
      return clazz == ProductForm.class;
   }

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

      // Check the fields of 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");
            }
         }
      }
   }

}
Файл validation.properties содержит коды оповещения ошибки, когда пользователь вводит в Form объявления продукта и Form информации покупателя.
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();
		// Load 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) {
         // Scroll to position:
         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);

         }

         // Go to Last record.
         resultScroll.last();
      }

      // Total Records
      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;

      // The first page
      navigationPages.add(1);
      if (begin > 2) {

         // Using for '...'
         navigationPages.add(-1);
      }

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

      if (end < this.totalPages - 2) {

         // Using for '...'
         navigationPages.add(-1);
      }
      // The last page.
      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 {

   // Products in the cart, stored in 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);

      // Case update quantity in cart
      // (@ModelAttribute("cartForm") @Validated CartInfo cartForm)
      if (target.getClass() == CartInfo.class) {

      }

      // Case save customer information.
      // (@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";
   }

   // Product List
   @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: Update quantity for product in cart
   @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: Show cart.
   @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: Enter customer information.
   @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 customer information.
   @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 to reenter customer info.
         return "shoppingCartCustomer";
      }

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

      return "redirect:/shoppingCartConfirmation";
   }

   // GET: Show information to confirm.
   @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: Submit Cart (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";
      }

      // Remove Cart from Session.
      Utils.removeCartInSession(request);

      // Store last cart.
      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: Show Login Page
   @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: Show 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>

Руководства Spring Boot

Show More