จาก Blog ที่แล้วไปลองใช้ Thymeleaf ไปแล้วกับการแสดงผลให้สวยงานครับ แต่อันนั้นข้อมูลที่ได้เกิดจากการ Mock จากชั้น Service ครับ สำหรับ Blog ตอนนี้มีเป้าหมาย
- CRUD ได้
- ต่อ Database ได้ โดยในที่นี้ผมใช้ MySQL ครับ
หัวข้อประมาณนี้
สิ่งที่ต้องเตรียม
ต่อ Database ต้องมีการเพิ่ม Dependency เพิ่มครับ
- JPA / Hibernate
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>
- MySQL - เรื่อง Version ยังไงต้องตรวจดูด้วยนะครับ
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.6</version> </dependency>
มาลุยกันเลยครับ
ถ้าเพิ่มข้อมูลเกี่ยวกับ Person ก็ต้องมีหน้าจอ 2 จอ
- หน้าจอ Main
- หน้าจอสำหรับ List รายการ
- หน้าจอสำหรับจัดการข้อมูล Person ครับ
- หน้าจอดู Owner ครับ (เอามาจาก Blog ตอนก่อน 55)
แก้ไขไฟล์ application.properties สำหรับการเชื่อมต่อฐานข้อมูล persondb ครับ
#Database spring.datasource.driverClassName=com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://localhost/persondb?createDatabaseIfNotExist=true spring.datasource.username=root spring.datasource.password= spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect spring.jpa.properties.hibernate.generate_statistics=true spring.jpa.hibernate.ddl-auto=update
- spring.datasource.driverClassName อันนี้ก็ใช้ของ MySQL ครับ
- spring.jpa.database-platform ก็เช่นกันครับ กำหนดให้ถูกนะครับ ถ้าลืม อาจจะเจอ Exception แบบนี้
org.hibernate.HibernateException: Access to DialectResolutionInfo cannot be null when 'hibernate.dialect' not set
- createDatabaseIfNotExist=true สำหรับ เอาไว้กำหนดว่า ถ้าไม่เจอ Database ก็ให้สร้างใหม่เลยครับ
- ตอน Run อย่าลืม เปิด Service ของ MySQL ตาม Config ที่กำหนดไว้นะครับ ไม่งั้นจะเจอ Error
Error com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure
อย่าลืมไปเอา exclude = {DataSourceAutoConfiguration.class} ออกจาก @SpringBootApplication ที่ไฟล์ [ชื่อ Project]Application.java ด้วยนะครับ เดี๋ยวจะต่อ DB ไม่ได้เอา บางที่ Spring Intializer มัน Generate มาให้ครับ
แก้ไข Entity ครับ ให้เหมาะกับ Table ครับ โดยผมเพิ่ม Column Id เข้าไปครับ และใส่ Annotation ของ Hibernate เข้าไป ซึ่งมีทั้งพวก Validate ข้อมูล และระบุ Column ครับ
@Entity public class Person { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Integer id; @NotNull @Size(min=3, max=30) private String Firstname; @NotNull @Size(min=3, max=50) private String Lastname; //มีพวก setter กับ getter อันนี้จะขอละไว้ครับ }
- เพิ่มชั้น Repositories เข้ามาครับ
โดยตัวนี้จะช่วยให้การติดต่อฐานข้อมูลง่ายขึ้นครับ
- สร้าง Folder Repositories ครับ
- สร้างไฟล์ PersonRepository.java ครับ โดยผมใช้ JPARepository ครับ เนื่องจาก Feature เยอะว่าครับ ถ้าลอง Search หลายๆเว็บจะใช้ CrudRepository ครับ JPARepository มาจาก CrudRepository, PagingAndSortingRepository, QueryByExampleExecutor และ Repository ครับ
Repository | V CrudRepository | V PagingAndSortingRepository QueryByExampleExecutor | | V V JpaRepository
- มาดูไฟล์ PersonRepository.java สมบูรณ์ครับ เนื่องจากไม่มี Cusotmize อะไรเลย ยุ่งกับ Table 1 ต่อ 1 ครับ
package com.cu.thesis.WeMuBPMN.repository; import com.cu.thesis.WeMuBPMN.entity.Person; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface PersonRepository extends JpaRepository<Person, Integer> { }
- ต่อมา Service
โดยหลักๆ จะเป็นการเตรียม Method สำหรับ List รายการ / เพิ่ม / ลบ / แก้ไข / เรียกดูข้อมูล ครับ แต่ในส่วนของ Service อันนี้ผมจะแยกเป็น Interface กับ Implement ครับ
- Interface กำหนด Spec ที่ไฟล์ PersonService.java ครับ
package com.cu.thesis.WeMuBPMN.service import com.cu.thesis.WeMuBPMN.entity.Person; public interface PersonService { Person viewOwner(); Iterable<Person> listAllPersons(); Person getPersonById(Integer id); Person savePerson(Person product); void deletePerson(Integer id); }
- Implement ใส่การทำงานจริงครับ ที่ไฟล์ PersonServiceImpl.java ครับ
package com.cu.thesis.WeMuBPMN.service; import com.cu.thesis.WeMuBPMN.entity.Person; import com.cu.thesis.WeMuBPMN.repository.PersonRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class PersonServiceImpl implements PersonService { private PersonRepository personRepository; @Autowired public void setPersonRepository(PersonRepository personRepository) { this.personRepository = personRepository; } @Override public Person viewOwner() { return new Person("Chatri", "Ngambenchawong"); } @Override public Iterable<Person> listAllPersons() { return personRepository.findAll(); } @Override public Person getPersonById(Integer id) { return personRepository.findById(id).get(); } @Override public Person savePerson(Person product) { return personRepository.save(product); } @Override public void deletePerson(Integer id) { personRepository.deleteById(id); } }
- ต่อมาเป็นตัว Controller
โดยต้องเพิ่ม 2 ไฟล์
- ไฟล์ IndexController.java เอาไว้จัดการกับทุก Request ที่เข้ามาครับ โดยใช้งานคู่กับ ไฟล์ index.html ครับ
package com.cu.thesis.WeMuBPMN.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class IndexController { @RequestMapping(value = "/") public String index() { return "index"; } }
- ไฟล์ PersonController.java เอาไว้จัดการกับทุก Request ที่เกี่ยวกับ [basepath]/persons (หน้า List) และ [basepath]/person ครับ
package com.cu.thesis.WeMuBPMN.controller; import com.cu.thesis.WeMuBPMN.entity.Person; import com.cu.thesis.WeMuBPMN.service.PersonService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller public class PersonController { @Autowired private PersonService personService; @GetMapping(value ="/person/owner") public String index(ModelMap model){ Person person = personService.viewOwner(); //person - parameter แรก ถูกเอาไปใช้ที่หน้า UI model.addAttribute("person",person); //บอกว่าไฟล์เราอยู่ที่ Folder person ไฟล์ index.html return "person/owner"; } /** * List all products. * * @param model * @return */ @RequestMapping(value = "/persons", method = RequestMethod.GET) public String listPerson(Model model) { model.addAttribute("persons", personService.listAllPersons()); System.out.println("Returning persons:"); return "person/persons"; } /** * View a specific person by its id. * * @param id * @param model * @return */ @RequestMapping(value = "person/{id}") public String showPerson(@PathVariable Integer id, Model model) { model.addAttribute("person", personService.getPersonById(id)); return "person/viewPerson"; } // Afficher le formulaire de modification du Product @RequestMapping(value = "person/edit/{id}") public String editPerson(@PathVariable Integer id, Model model) { model.addAttribute("person", personService.getPersonById(id)); return "person/editPerson"; } /** * New person. * * @param model * @return */ @RequestMapping(value = "person/new") public String newPerson(Model model) { model.addAttribute("person", new Person()); return "person/editPerson"; } /** * Save person to database. * * @param person * @return */ @RequestMapping(value = "person", method = RequestMethod.POST) public String savePerson(Person person) { personService.savePerson(person); return "redirect:/persons"; //return "redirect:/person/" + person.getId(); } /** * Delete person by its id. * * @param id * @return */ @RequestMapping(value = "person/delete/{id}") public String deletePerson(@PathVariable Integer id) { personService.deletePerson(id); return "redirect:/persons"; } }
- ส่วนของ View
หลังจากได้ Controller แล้วมาดูหน้าจอ View ครับ (อันนี้สำหรับผมแล้วเสียเวลาไปวันเต็มๆเลย)
- สำหรับ Default Path ของ Thymeleaf จะถูก Map พวกไฟล์ View และผองเพื่อน (พวก CSS - ผมใช้ Bootstrap 4, /JS/ Image) ไว้ที่ [CLASS PATH]/resources/static/ ครับ โดยมีโครงสร้างคร่าวๆ ดังนี้
resources
-> static
-> css - พวก CSS จะเก็บในนี้
-> bootstrap.xxx.css ใส่ไฟล์เกี่ยวกับ bootstrap
-> person.css ใช้คู่กับ owner.html ครับ
-> js - พวก javascript จะเก็บไว้ในนี้
-> bootstrap.xxx.js ใส่ไฟล์เกี่ยวกับ bootstrap
-> templates
-> fragments - เอาไว้เก็บไฟล์ที่สามารถ Reuse ได้
-> header.html - ในที่นี้ทำเป็นเมนูครับ
-> person - เก็บไฟล์ที่ต้องใช้คู่กับ PersonController ทั้งหมดเลย โดยตอน Return ขึ้นต้นด้วยว่า person/ชื่อไฟล์
-> persons.html สำหรับหน้าจอ List
-> editPerson.html สำหรับ Model New/Edit
-> viewPerson.html สำหรับ Model View
-> owner.html
-> index.html สำหรับหน้าจอ Main ครับ
สำหรับ View มีข้อควรระวังของ Thymeleaf
- การอ้างอิงไฟล์ CSS ใน Tag <link> ให้มีการระบุ Path ผ่าน th:href="@{/css/bootstrap.min.css}" ครับ โดยมันจะไปอ้างอิงตาม Default Path ของ Thymeleaf ครับ
<link rel="stylesheet" type="text/css" th:href="@{/css/bootstrap.min.css}" href="../../static/css/bootstrap.min.css" media="screen"/>
- การอ้างอิงไฟล์ JS ใน Tag <script> ให้มีการระบุ Path ผ่าน th:src="@{/js/bootstrap.min.js}" ครับ โดยมันจะไปอ้างอิงตาม Default Path ของ Thymeleaf ครับ
<script th:src="@{/js/bootstrap.min.js}" src="../../js/bootstrap.min.js"></script>
- สำหรับการ Include ไฟล์อื่นเข้าไป อย่างเช่น ผมต้องการ Include ไฟล์ header.html เข้าไปครับ โดยใช้ <th:block th:include= ครับ
<th:block th:include="fragments/header :: header"></th:block>
ตัวอย่าง Code ของแต่ละไฟล์ เอาแค่บางส่วนนะครับ เดี๋ยว Blog จะยาวไป
- ไฟล์ index.html ครับ
<!DOCTYPE html> <html> <head lang="en"> <title>Spring Boot Example</title> <link rel="stylesheet" type="text/css" th:href="@{/css/bootstrap.min.css}" href="../../static/css/bootstrap.min.css" media="screen"/> <script th:src="@{/js/bootstrap.min.js}" src="../../js/bootstrap.min.js"></script> <th:block th:include="fragments/header :: header"></th:block> </head> <body> <div class="container"> </div> </body> </html>
- ไฟล์ persons.html ครับ แสดงรายการของ Person ทั้งหมดครับ
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head lang="en"> <title>Spring Boot Exemple</title> <link rel="stylesheet" type="text/css" th:href="@{/css/bootstrap.min.css}" href="../../static/css/bootstrap.min.css" media="screen"/> <script th:src="@{/js/bootstrap.min.js}" src="../../js/bootstrap.min.js"></script> <th:block th:include="fragments/header :: header"></th:block> </head> <body> <div class="container"> <div th:if="${not #lists.isEmpty(persons)}"> <h2>Person List</h2> <table class="table table-striped"> <tr> <th>Id</th> <th>Firstname</th> <th>Lastname</th> <th>View</th> <th>Edit</th> <th>Delete</th> </tr> <tr th:each="person : ${persons}"> <td th:text="${person.id}"><a href="/person/${person.id}">Id</a></td> <td th:text="${person.Firstname}">Product Id</td> <td th:text="${person.Lastname}">descirption</td> <td><a th:href="${ '/person/' + person.id}">View</a></td> <td><a th:href="${'/person/edit/' + person.id}">Edit</a></td> <td><a th:href="${'/person/delete/' + person.id}">Delete</a></td> </tr> </table> </div> </div> </body> </html>
- ไฟล์ editPerson.html ครับ สำหรับ New / Edit Person ครับ
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head lang="en"> <title>Spring Boot Example</title> <link rel="stylesheet" type="text/css" th:href="@{/css/bootstrap.min.css}" href="../../static/css/bootstrap.min.css" media="screen"/> <script th:src="@{/js/bootstrap.min.js}" src="../../js/bootstrap.min.js"></script> <th:block th:include="fragments/header :: header"></th:block> </head> <body> <div class="container"> <h2>Person Details</h2> <div> <form class="form-horizontal" th:object="${person}" th:action="@{/person}" method="post"> <input type="hidden" th:field="*{id}"/> <!-- <input type="hidden" th:field="*{version}"/> --> <div class="form-group"> <label class="col-sm-2 control-label">Firstname:</label> <div class="col-sm-10"> <input type="text" class="form-control" th:field="*{Firstname}"/> </div> </div> <div class="form-group"> <label class="col-sm-2 control-label">Lastname:</label> <div class="col-sm-10"> <input type="text" class="form-control" th:field="*{Lastname}"/> </div> </div> <div class="form-group"> <button type="submit" class="btn btn-default">Submit</button> </div> </form> </div> </div> </body> </html>
ทดสอบกันครับ
- เข้าเว็บเล่นกันครับ ผ่าน http://localhost:8080 ครับ
- ลองสร้าง Person
- เข้ามาดูหน้า List Person
- View รายการ 1 รายการ
Code ที่เสร็จแล้ว
- Download ไปลองเล่นกันได้ครับ สำหรับ Repo ตรงนี้ครับ pingkunga/LearnSpringBoot: LearnSpringBoot (github.com)
สำหรับ Blog ตอนแรกคิดว่าใช้เวลาทำ 2 ชั่วโมง แต่ติดปัญหาด้าน UI จนยาวมาวันครึ่งแทนครับ ใน Blog ตอนนี้ ผมคง Focus เรื่องขา Views (UI) เพราะยังได้กลิ่นของ Code ที่ซ้ำอยู่ครับ และยังขาดในส่วนของการ Validate ข้อมูลครับ
Discover more from naiwaen@DebuggingSoft
Subscribe to get the latest posts sent to your email.