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">© <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">© <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">© <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/
留言
張貼留言