SpringBoot第04天_Spring Data JPA_IoC

  安裝啟用XAMPP 將MySQL環境架設起來後

這裡若要更改預設springboot預設專案的port可以於
src/main/resources/application.properties

增加這行
server.port = 9090

這邊我mysql phpmyadmin用的port剛好也是8080會有衝突
所以更改我們的springboot專案預設port


於mysql (phpmyadmin)創建我們的資料庫命名為crmdb

於資料庫中產生一張customer 資料表

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
CREATE TABLE CUSTOMER(
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  first_name VARCHAR(64),
  last_name VARCHAR(64),
  email_address VARCHAR(64),
  address VARCHAR(64),
  city VARCHAR(64),
  country VARCHAR(64),
  phone_number VARCHAR(24)
);
以及相應測試用假資料

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
insert into customer (id, first_name, last_name, email_address, address, city, country, phone_number) values (1, 'Barrett', 'Velez', 'bvelez0@google.pl', '4747 Butternut Crossing', 'Pestovo', 'Russia', '+7 (584) 984-8612');
insert into customer (id, first_name, last_name, email_address, address, city, country, phone_number) values (2, 'Martynne', 'Haydn', 'mhaydn1@multiply.com', '713 Maple Wood Circle', 'Carmen', 'Philippines', '+63 (477) 280-2056');
insert into customer (id, first_name, last_name, email_address, address, city, country, phone_number) values (3, 'Myrle', 'Pawelek', 'mpawelek2@state.gov', '034 Mariners Cove Point', 'Itaberaí', 'Brazil', '+55 (445) 165-2033');
insert into customer (id, first_name, last_name, email_address, address, city, country, phone_number) values (4, 'Mano', 'Borit', 'mborit3@google.it', '78 Buell Court', 'Charxin', 'Uzbekistan', '+998 (642) 742-1954');
insert into customer (id, first_name, last_name, email_address, address, city, country, phone_number) values (5, 'Paloma', 'Rahill', 'prahill4@soundcloud.com', '43 Lawn Street', 'Zárate', 'Argentina', '+54 (368) 282-2644');
insert into customer (id, first_name, last_name, email_address, address, city, country, phone_number) values (6, 'Suki', 'Ogborne', 'sogborne5@europa.eu', '6588 Mallory Center', 'Saint-Pierre-Montlimart', 'France', '+33 (111) 706-0255');
insert into customer (id, first_name, last_name, email_address, address, city, country, phone_number) values (7, 'Gram', 'Verrillo', 'gverrillo6@linkedin.com', '7171 Waywood Court', 'Oeleu', 'Indonesia', '+62 (857) 330-9040');
insert into customer (id, first_name, last_name, email_address, address, city, country, phone_number) values (8, 'Conney', 'Loynton', 'cloynton7@hhs.gov', '57 Dunning Crossing', 'Koltushi', 'Russia', '+7 (346) 224-9111');
insert into customer (id, first_name, last_name, email_address, address, city, country, phone_number) values (9, 'Bernadina', 'Kigelman', 'bkigelman8@cbsnews.com', '47754 Boyd Park', 'Lajaluhur', 'Indonesia', '+62 (196) 645-7046');
insert into customer (id, first_name, last_name, email_address, address, city, country, phone_number) values (10, 'Alexis', 'Noble', 'anoble9@nymag.com', '309 Larry Avenue', 'San Nicolas', 'Philippines', '+63 (655) 552-9158');
insert into customer (id, first_name, last_name, email_address, address, city, country, phone_number) values (11, 'Justina', 'Laherty', 'jlahertya@hud.gov', '51 Shelley Way', 'Sogamoso', 'Colombia', '+57 (573) 348-0943');
insert into customer (id, first_name, last_name, email_address, address, city, country, phone_number) values (12, 'Con', 'Spritt', 'csprittb@ted.com', '73915 Elmside Circle', 'Xin’an', 'China', '+86 (622) 226-5655');
insert into customer (id, first_name, last_name, email_address, address, city, country, phone_number) values (13, 'Latrena', 'Ruoff', 'lruoffc@nymag.com', '93272 Darwin Junction', 'Lewoduli', 'Indonesia', '+62 (327) 579-4999');
insert into customer (id, first_name, last_name, email_address, address, city, country, phone_number) values (14, 'Gertie', 'Purrington', 'gpurringtond@1688.com', '2 Garrison Crossing', 'Corbeil-Essonnes', 'France', '+33 (244) 432-0071');
insert into customer (id, first_name, last_name, email_address, address, city, country, phone_number) values (15, 'Carine', 'Barca', 'cbarcae@omniture.com', '46 Graedel Alley', 'Mafra', 'Portugal', '+351 (419) 967-4740');
insert into customer (id, first_name, last_name, email_address, address, city, country, phone_number) values (16, 'Nessie', 'Evens', 'nevensf@usgs.gov', '31285 Katie Place', 'Ambatofinandrahana', 'Madagascar', '+261 (499) 450-0639');
insert into customer (id, first_name, last_name, email_address, address, city, country, phone_number) values (17, 'Bab', 'Dorton', 'bdortong@wp.com', '743 Bluestem Pass', 'Kallinge', 'Sweden', '+46 (535) 556-8266');
insert into customer (id, first_name, last_name, email_address, address, city, country, phone_number) values (18, 'Lusa', 'Hopewell', 'lhopewellh@sphinn.com', '6396 Magdeline Pass', 'Santo Tomé', 'Argentina', '+54 (360) 275-1761');
insert into customer (id, first_name, last_name, email_address, address, city, country, phone_number) values (19, 'Shanon', 'Glitherow', 'sglitherowi@t-online.de', '55136 Helena Place', 'Carabamba', 'Peru', '+51 (648) 793-7888');
insert into customer (id, first_name, last_name, email_address, address, city, country, phone_number) values (20, 'Gabrila', 'Yakunikov', 'gyakunikovj@unesco.org', '1 American Ash Plaza', 'Tanjungpati', 'Indonesia', '+62 (799) 367-1979');
insert into customer (id, first_name, last_name, email_address, address, city, country, phone_number) values (21, 'Katerina', 'Annion', 'kannionk@godaddy.com', '4253 Chinook Center', 'Cirumput', 'Indonesia', '+62 (215) 106-1538');
insert into customer (id, first_name, last_name, email_address, address, city, country, phone_number) values (22, 'Flint', 'Menghi', 'fmenghil@surveymonkey.com', '27039 Dovetail Terrace', 'Uzda', 'Belarus', '+375 (332) 414-7310');
insert into customer (id, first_name, last_name, email_address, address, city, country, phone_number) values (23, 'Daniella', 'Batch', 'dbatchm@bbc.co.uk', '24125 Fair Oaks Park', 'Oakland', 'United States', '+1 (510) 424-1379');
insert into customer (id, first_name, last_name, email_address, address, city, country, phone_number) values (24, 'Loella', 'Fayerman', 'lfayermann@ebay.co.uk', '1433 Delladonna Lane', 'Petropavlovka', 'Russia', '+7 (696) 497-9759');
insert into customer (id, first_name, last_name, email_address, address, city, country, phone_number) values (25, 'Adrianna', 'Andryushin', 'aandryushino@amazon.co.jp', '75 Brown Crossing', 'Mariestad', 'Sweden', '+46 (764) 551-4393');
insert into customer (id, first_name, last_name, email_address, address, city, country, phone_number) values (26, 'Guthrey', 'Bywater', 'gbywaterp@constantcontact.com', '08974 Messerschmidt Point', 'Staraya Mayna', 'Russia', '+7 (251) 431-2848');
insert into customer (id, first_name, last_name, email_address, address, city, country, phone_number) values (27, 'Nicolina', 'Joist', 'njoistq@wordpress.com', '0 Southridge Circle', 'Chernogorsk', 'Russia', '+7 (675) 623-5429');
insert into customer (id, first_name, last_name, email_address, address, city, country, phone_number) values (28, 'Elka', 'Licciardo', 'elicciardor@vimeo.com', '3 Steensland Avenue', 'Cuiabá', 'Brazil', '+55 (403) 487-3462');
insert into customer (id, first_name, last_name, email_address, address, city, country, phone_number) values (29, 'Almira', 'Brilleman', 'abrillemans@barnesandnoble.com', '098 Knutson Road', 'Rio Meão', 'Portugal', '+351 (938) 194-1676');
insert into customer (id, first_name, last_name, email_address, address, city, country, phone_number) values (30, 'Carlina', 'Arias', 'cariast@bigcartel.com', '0517 Burning Wood Plaza', 'Bonneuil-sur-Marne', 'France', '+33 (178) 824-7401');
insert into customer (id, first_name, last_name, email_address, address, city, country, phone_number) values (31, 'Rod', 'Valentelli', 'rvalentelliu@shutterfly.com', '352 Kropf Junction', 'Tulsīpur', 'Nepal', '+977 (458) 625-0649');
insert into customer (id, first_name, last_name, email_address, address, city, country, phone_number) values (32, 'Shelby', 'Mutton', 'smuttonv@icq.com', '21 Summerview Center', 'Venlo', 'Netherlands', '+31 (963) 497-3313');
insert into customer (id, first_name, last_name, email_address, address, city, country, phone_number) values (33, 'Sigfried', 'Baskeyfied', 'sbaskeyfiedw@cam.ac.uk', '89 Lunder Avenue', 'Storozhevaya', 'Russia', '+7 (962) 488-0857');

src/main/resources/application.properties
當中繼續配置連線參數

1
2
3
4
5

server.port = 9090
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3307/crmdb
spring.datasource.username=root
spring.datasource.password=123456


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

Pom.xml配置
加入spring-boot-starter-data-jpa依賴
Spring Data JPA是Spring基於Hibernate開發的一個JPA框架。可簡化JPA的寫法,實現對資料的訪問和操作。除了「CRUD」外,還包括如分頁、排序​​等一些常用的功能。

以及javax.validation依賴

1
2
3
4
5
6
7
8
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
  <groupId>javax.validation</groupId>
  <artifactId>validation-api</artifactId>
</dependency>


建立一個Model 類別,命名為Customer.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
package model;

import javax.persistence.*;
import javax.validation.constraints.NotBlank;

@Entity
@Table(name="customer")
public class Customer {
    @Id
    @Column
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Long id;
    @Column
    @NotBlank
    String firstName;
    @Column
    @NotBlank
    String lastName;
    @Column
    String emailAddress;
    @Column
    String address;
    @Column
    String city;
    @Column
    String country;
    @Column
    String phoneNumber;

    public Long getId() {
        return id;
    }

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

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getEmailAddress() {
        return emailAddress;
    }

    public void setEmailAddress(String emailAddress) {
        this.emailAddress = emailAddress;
    }

    public String getAddress() {
        return address;
    }

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

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getCountry() {
        return country;
    }

    public void setCountry(String country) {
        this.country = country;
    }

    public String getPhoneNumber() {
        return phoneNumber;
    }

    public void setPhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }
}

@Entity的Bean是告訴Spring這是資料模型層的宣告

@Table : Table的name對映到資料庫中的資料表名稱

@Column : 對應到Table的欄位中的欄位名稱

@Id : 是此資料表的Primary Key

@GeneratedValue : 告訴此Column的生成方式
TABLE:使用一個特定的資料庫表格來保存主鍵。
SEQUENCE:根據底層資料庫的序列來生成主鍵,條件是數據庫支持序列。
IDENTITY:主鍵由資料庫自動生成(主要是自動增長型)
AUTO:主鍵由程式控制。默認的配置。如果不指定主鍵生成策略,默認為AUTO。


數據存儲庫和服務
大部分 CRUD 操作都可以由 Spring Data JPA 處理

建立 Spring Data JPA Repositories 類別
可以簡單創建一個擴展 CrudRepository.java 的interface,而無需執行任何其他操作。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
package repositories;

import model.Customer;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface CustomerRepository extends PagingAndSortingRepository<Customer, Long>, JpaSpecificationExecutor<Customer> {
}

只要在這些 Repository 介面中撰寫特定命名規則的方法名,即可自動查詢到資料結果。

擴展了 PagingAndSortingRepository 以允許對結果進行分頁和排序,還擴展了 JpaSpecificationExecutor,以便我們可以使用自定義 Hibernate Criteria 規範執行過濾。

@Repository是Spring註解,指示裝飾的類是存儲庫。存儲庫是一種用於封裝倉庫,搜索和搜索行為的機制,需要新增相應Repository, 才能執行新增, 修改, 刪除, 查詢等功能。

JpaSpecificationExecutor 用來做負責查詢的介面。
PagingAndSortingRepository 是CrudRepository的子介面,添加分頁和排序的功能

創建一個 CustomerDatatableFilter.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package spec;

import model.Customer;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.ArrayList;

public class CustomerDatatableFilter implements org.springframework.data.jpa.domain.Specification<Customer>{

    String userQuery;

    public CustomerDatatableFilter(String queryString) {
        this.userQuery = queryString;
    }

    @Override
    public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
        ArrayList<Predicate> predicates = new ArrayList<>();

        if (userQuery != null && userQuery != "") {
            predicates.add(criteriaBuilder.like(root.get("firstName"), '%' + userQuery + '%'));
            predicates.add(criteriaBuilder.like(root.get("lastName"), '%' + userQuery + '%'));
            predicates.add(criteriaBuilder.like(root.get("city"), '%' + userQuery + '%'));
            predicates.add(criteriaBuilder.like(root.get("emailAddress"), '%' + userQuery + '%'));
            predicates.add(criteriaBuilder.like(root.get("phoneNumber"), '%' + userQuery + '%'));
            predicates.add(criteriaBuilder.like(root.get("country"), '%' + userQuery + '%'));
        }

        return (! predicates.isEmpty() ? criteriaBuilder.or(predicates.toArray(new Predicate[predicates.size()])) : null);
    }
}


允許使用者藉由一個搜索輸入文字框進行特定客戶資訊欄位模糊查詢。
接著創建一個service類別,取名為CustomerService.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package services;
import model.Customer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import repositories.CustomerRepository;
import spec.CustomerDatatableFilter;

@Service
public class CustomerService {
    private final CustomerRepository customerRepository;

    @Autowired
    public CustomerService(CustomerRepository customerRepository) {
        this.customerRepository = customerRepository;
    }

    public Page<Customer> getCustomersForDatatable(String queryString, Pageable pageable) {

        CustomerDatatableFilter customerDatatableFilter = new CustomerDatatableFilter(queryString);

        return customerRepository.findAll(customerDatatableFilter, pageable);
    }
}
Specification 是Spring Data JPA提供的一個查詢規範,要做複雜的查詢,只需圍繞這個規範來設置查詢條件即可


創建CustomerController.java

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
package com.example.demo2_themeleaf;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import model.Customer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import repositories.CustomerRepository;
import services.CustomerService;

import javax.validation.Valid;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Controller
@RequestMapping("/customer")
public class CustomerController {
    @Autowired
    private final CustomerRepository customerRepository;

    @Autowired
    private final CustomerService customerService;

    public CustomerController(CustomerRepository customerRepository, CustomerService customerService) {
        this.customerRepository = customerRepository;
        this.customerService = customerService;
    }

    @GetMapping
    public String index() {
        return "/customer/index.html";
    }

    @RequestMapping(value = "/data_for_datatable", method = RequestMethod.GET, produces = "application/json")
    @ResponseBody
    public String getDataForDatatable(@RequestParam Map<String, Object> params) {
        int draw = params.containsKey("draw") ? Integer.parseInt(params.get("draw").toString()) : 1;
        int length = params.containsKey("length") ? Integer.parseInt(params.get("length").toString()) : 30;
        int start = params.containsKey("start") ? Integer.parseInt(params.get("start").toString()) : 30;
        int currentPage = start / length;

        String sortName = "id";
        String dataTableOrderColumnIdx = params.get("order[0][column]").toString();
        String dataTableOrderColumnName = "columns[" + dataTableOrderColumnIdx + "][data]";
        if (params.containsKey(dataTableOrderColumnName))
            sortName = params.get(dataTableOrderColumnName).toString();
        String sortDir = params.containsKey("order[0][dir]") ? params.get("order[0][dir]").toString() : "asc";

        Sort.Order sortOrder = new Sort.Order((sortDir.equals("desc") ? Sort.Direction.DESC : Sort.Direction.ASC), sortName);
        Sort sort = Sort.by(sortOrder);

        Pageable pageRequest = PageRequest.of(currentPage,
                length,
                sort);

        String queryString = (String) (params.get("search[value]"));

        Page<Customer> customers = customerService.getCustomersForDatatable(queryString, pageRequest);

        long totalRecords = customers.getTotalElements();

        List<Map<String, Object>> cells = new ArrayList<>();
        customers.forEach(customer -> {
            Map<String, Object> cellData = new HashMap<>();
            cellData.put("id", customer.getId());
            cellData.put("firstName", customer.getFirstName());
            cellData.put("lastName", customer.getLastName());
            cellData.put("emailAddress", customer.getEmailAddress());
            cellData.put("city", customer.getCity());
            cellData.put("country", customer.getCountry());
            cellData.put("phoneNumber", customer.getPhoneNumber());
            cells.add(cellData);
        });

        Map<String, Object> jsonMap = new HashMap<>();

        jsonMap.put("draw", draw);
        jsonMap.put("recordsTotal", totalRecords);
        jsonMap.put("recordsFiltered", totalRecords);
        jsonMap.put("data", cells);

        String json = null;
        try {
            json = new ObjectMapper().writeValueAsString(jsonMap);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }

        return json;
    }


    @GetMapping("/edit/{id}")
    public String edit(@PathVariable String id, Model model) {
        Customer customerInstance = customerRepository.findById(Long.valueOf(id)).get();

        model.addAttribute("customerInstance", customerInstance);

        return "/customer/edit.html";
    }

    @PostMapping("/update")
    public String update(@Valid @ModelAttribute("customerInstance") Customer customerInstance,
                         BindingResult bindingResult,
                         Model model,
                         RedirectAttributes atts) {
        if (bindingResult.hasErrors()) {
            return "/customer/edit.html";
        } else {
            if (customerRepository.save(customerInstance) != null)
                atts.addFlashAttribute("message", "Customer updated successfully");
            else
                atts.addFlashAttribute("message", "Customer update failed.");

            return "redirect:/customer/";
        }
    }

    @GetMapping("/create")
    public String create(Model model)
    {
        model.addAttribute("customerInstance", new Customer());
        return "/customer/create.html";
    }

    @PostMapping("/save")
    public String save(@Valid @ModelAttribute("customerInstance") Customer customerInstance,
                       BindingResult bindingResult,
                       Model model,
                       RedirectAttributes atts) {
        if (bindingResult.hasErrors()) {
            return "/customer/create.html";
        } else {
            if (customerRepository.save(customerInstance) != null)
                atts.addFlashAttribute("message", "Customer created successfully");
            else
                atts.addFlashAttribute("message", "Customer creation failed.");

            return "redirect:/customer/";
        }
    }

    @PostMapping("/delete")
    public String delete(@RequestParam Long id, RedirectAttributes atts) {
        Customer customerInstance = customerRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("Customer Not Found:" + id));

        customerRepository.delete(customerInstance);

        atts.addFlashAttribute("message", "Customer deleted.");

        return "redirect:/customer/";
    }

}


Spring 中的核心思為Ioc 與 DI ,所謂IoC就是 Inversion of Control 控制反轉,而DI (依賴注入)則是去實現 Ioc 的一個方式。Spring自2.5版本之後提供@Autowired的實踐機制,可對class成員變量、方法及構造函數進行標註,完成自動裝配加載的工作,也就是DI。


建立畫面

於templates目錄下在多創建一個目錄命名為customer
之後分別產生對應方法回傳的畫面
/resources/templates/customer/index.html

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <title>Simple CRM - Customer Management made Simple</title>
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" type="text/css"/>
  <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
  <link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/css/bootstrap.css" rel="stylesheet"
        crossorigin="anonymous">
  <link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"
        integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous">
  <link href="https://cdn.datatables.net/1.10.20/css/dataTables.bootstrap4.min.css" rel="stylesheet"/>
  <script src="https://cdn.datatables.net/1.10.20/js/jquery.dataTables.min.js"></script>
  <script src="https://cdn.datatables.net/1.10.20/js/dataTables.bootstrap4.min.js"></script>
</head>
<body>
<nav class="navbar navbar-dark bg-dark fixed-top">
  <div class="container">
    <div class="navbar-header">
      <a class="navbar-brand" href="/">Simple CRM</a>
    </div>
  </div>
</nav>
<div class="container" style="margin-top:80px">

  <h1 class="pb-2 border-bottom row">
    <span class="col-sm-6 pb-4">Customer List</span>
    <span class="col-sm-6 text-sm-right pb-4">
            <a href="/customer/create" class="btn btn-primary btn-outline-primary d-block d-sm-inline-block">Create Customer</a>
        </span>
  </h1>


  <div class="alert alert-info" th:if="${message}">
    <h3 th:text="${message}"></h3>
  </div>

  <div class="mt-5">
    <table id="customerTable" class="table table-striped table-bordered" style="width:100%">
      <thead>
      <tr>
        <th>Id</th>
        <th>First Name</th>
        <th>Last Name</th>
        <th>Email</th>
        <th>City</th>
        <th>Country</th>
        <th>Phone</th>
      </tr>
      </thead>
    </table>
  </div>

</div>
<footer class="footer navbar-dark bg-dark fixed-bottom">
  <div class="container">
    <div class="row">
      <div class="col-md-4"></div>
      <div class="col-md-4">
        <p class="text-center text-muted">&copy;
          <span th:text="${#dates.format(#dates.createNow(), 'yyyy')}"></span>
        </p>
      </div>
    </div>
  </div>
</footer>

<script>
  var url = '/customer/data_for_datatable';

  $(document).ready(function () {

    $('#customerTable').DataTable({
      "ajax": url,
      "processing": true,
      "serverSide": true,
      "columns": [
        {
          "data": "id",
          "render": function (data, type, row, meta) {
            return '<a href="/customer/edit/' + row.id + '">' + data + '</a>';
          }
        },
        {
          "data": "firstName",
          "render": function (data, type, row, meta) {
            return '<a href="/customer/edit/' + row.id + '">' + data + '</a>';
          }
        },
        {
          "data": "lastName",
          "render": function (data, type, row, meta) {
            return '<a href="/customer/edit/' + row.id + '">' + data + '</a>';
          }
        },
        {"data": "emailAddress"},
        {"data": "city"},
        {"data": "country"},
        {"data": "phoneNumber"}
      ]
    });
  });
</script>
</body>
</html>

/resources/templates/customer/create.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <title>Simple CRM - Customer Management made Simple</title>

  <link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/css/bootstrap.css" rel="stylesheet"
        crossorigin="anonymous">

</head>
<body>
<nav class="navbar navbar-dark bg-dark fixed-top">
  <div class="container">
    <div class="navbar-header">
      <a class="navbar-brand" href="/">Simple CRM</a>
    </div>
  </div>
</nav>
<div class="container" style="margin-top:80px">

  <h1 class="pb-2 border-bottom row">
    <span class="col-sm pb-4">New Customer Details</span> </span>
  </h1>


  <div class="mt-5 card card-body bg-light">

    <form action="/customer/save" th:object="${customerInstance}" class="form" method="post">

      <div class="alert alert-danger" th:if="${! #fields.errors('all').isEmpty()}">
        <li th:each="e : ${#fields.detailedErrors()}" th:class="${e.global}? globalerr : fielderr">
          <span th:text="${e.global}? '*' : ${e.fieldName}">The field name</span>
          <span th:text="${e.message}">The error message</span>
        </li>
      </div>

      <div class="row">
        <div class="form-group col-6">
          <label>First Name</label>
          <input class="form-control" name="firstName" th:value="${customerInstance?.firstName}"/>
        </div>
        <div class="form-group col-6">
          <label>Last Name</label>
          <input class="form-control" name="lastName" th:value="${customerInstance?.lastName}"/>
        </div>
      </div>

      <div class="row">
        <div class="form-group col-6">
          <label>Email Address</label>
          <input class="form-control" name="emailAddress" th:value="${customerInstance?.emailAddress}"/>
        </div>
        <div class="form-group col-6">
          <label>Phone Number</label>
          <input class="form-control" name="phoneNumber" th:value="${customerInstance?.phoneNumber}"/>
        </div>
      </div>

      <div class="row">
        <div class="form-group col">
          <label>Address</label>
          <input class="form-control" name="address" th:value="${customerInstance?.address}"/>
        </div>
      </div>

      <div class="row">
        <div class="form-group col-6">
          <label>City</label>
          <input class="form-control" name="city" th:value="${customerInstance?.city}"/>
        </div>
        <div class="form-group col-6">
          <label>Country</label>
          <input class="form-control" name="country" th:value="${customerInstance?.country}"/>
        </div>
      </div>

      <div class="row">
        <div class="col">
          <button type="submit" class="btn btn-success btn-block">Create Customer</button>
        </div>
      </div>
    </form>
  </div>
</div>
<footer class="footer navbar-dark bg-dark fixed-bottom">
  <div class="container">
    <div class="row">
      <div class="col-md-4"></div>
      <div class="col-md-4">
        <p class="text-center text-muted">&copy;
          <span th:text="${#dates.format(#dates.createNow(), 'yyyy')}"></span>
        </p>
      </div>
    </div>
  </div>
</footer>
</body>
</html>


/resources/templates/customer/edit.html

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
  <title>Simple CRM - Customer Management made Simple</title>

  <link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.3/css/bootstrap.css" rel="stylesheet"
        crossorigin="anonymous">

</head>
<body>
<nav class="navbar navbar-dark bg-dark fixed-top">
  <div class="container">
    <div class="navbar-header">
      <a class="navbar-brand" href="/">Simple CRM</a>
    </div>
  </div>
</nav>
<div class="container" style="margin-top:80px">

  <h1 class="pb-2 border-bottom row">
    <span class="col-sm-6 pb-4">Customer: <span th:text="${customerInstance.id}"></span> </span>
    <span class="col-sm-6 text-sm-right pb-4">
            <form action="/customer/delete" method="post" onsubmit="return confirm('Are you sure?');">
                <input type="hidden" name="id" th:value="${customerInstance.id}"/>
                <button class="btn btn-danger btn-outline-danger d-block d-sm-inline-block">Delete Customer</button>
            </form>
        </span>
  </h1>


  <div class="mt-5 card card-body bg-light">

    <form action="/customer/update" th:object="${customerInstance}" class="form" method="post">

      <div class="alert alert-danger" th:if="${! #fields.errors('all').isEmpty()}">
        <li th:each="e : ${#fields.detailedErrors()}" th:class="${e.global}? globalerr : fielderr">
          <span th:text="${e.global}? '*' : ${e.fieldName}">The field name</span>
          <span th:text="${e.message}">The error message</span>
        </li>
      </div>

      <input name="id" type="hidden" th:value="${customerInstance.id}"/>

      <div class="row">
        <div class="form-group col-6">
          <label>First Name</label>
          <input class="form-control" name="firstName" th:value="${customerInstance?.firstName}"/>
        </div>
        <div class="form-group col-6">
          <label>Last Name</label>
          <input class="form-control" name="lastName" th:value="${customerInstance?.lastName}"/>
        </div>
      </div>

      <div class="row">
        <div class="form-group col-6">
          <label>Email Address</label>
          <input class="form-control" name="emailAddress" th:value="${customerInstance?.emailAddress}"/>
        </div>
        <div class="form-group col-6">
          <label>Phone Number</label>
          <input class="form-control" name="phoneNumber" th:value="${customerInstance?.phoneNumber}"/>
        </div>
      </div>

      <div class="row">
        <div class="form-group col">
          <label>Address</label>
          <input class="form-control" name="address" th:value="${customerInstance?.address}"/>
        </div>
      </div>

      <div class="row">
        <div class="form-group col-6">
          <label>City</label>
          <input class="form-control" name="city" th:value="${customerInstance?.city}"/>
        </div>
        <div class="form-group col-6">
          <label>Country</label>
          <input class="form-control" name="country" th:value="${customerInstance?.country}"/>
        </div>
      </div>

      <div class="row">
        <div class="col">
          <button type="submit" class="btn btn-success btn-block">Save Changes</button>
        </div>
      </div>
    </form>
  </div>
</div>
<footer class="footer navbar-dark bg-dark fixed-bottom">
  <div class="container">
    <div class="row">
      <div class="col-md-4"></div>
      <div class="col-md-4">
        <p class="text-center text-muted">&copy;
          <span th:text="${#dates.format(#dates.createNow(), 'yyyy')}"></span>
        </p>
      </div>
    </div>
  </div>
</footer>
</body>
</html>




Ref:
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/
https://www.baeldung.com/java-bean-validation-not-null-empty-blank
https://geek-docs.com/spring-boot/spring-boot-tutorials/repository.html
https://blog.csdn.net/canot/article/details/51455967
https://morosedog.gitlab.io/springboot-20190328-springboot14/
https://ithelp.ithome.com.tw/articles/10220008/
http://blog.appx.tw/2017/08/21/spring-%E8%A8%BB%E8%A7%A3-%E4%B9%8B-autowired/


留言

這個網誌中的熱門文章

經得起原始碼資安弱點掃描的程式設計習慣培養(五)_Missing HSTS Header

經得起原始碼資安弱點掃描的程式設計習慣培養(三)_7.Cross Site Scripting(XSS)_Stored XSS_Reflected XSS All Clients

(2021年度)駕訓學科筆試準備題庫歸納分析_法規是非題