Integration case based on SSMP
I Implementation case scheme analysis
- Case making process
Basic CRUD function
Call the page and make all functions after confirming asynchronous submission
Add paging function and query function
II Create backend project environment
2.1 creating modules
- Create module
First, add web - "Spring Web" and SQL - "MySql Driver
Refresh maven after each module is created - Modify pom:
Delete name and description
Add pom dependencies: mybatis plus, druid
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.3</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-plus-boot-starter</artifactId> <version>1.2.6</version> </dependency>
- Set port to 80: add application YML file: server: port: 80
2.2 physical layer
- Add table:
- Add entity class: domian Book. class
- Simplify pojo development with lombok
- lombok: an entity class library that provides annotations to simplify pojo development
- lombok uses:
1. Import lombok dependent coordinates
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>
2. Add annotation on entity class
@Getter, @ Setter, @ AllArgsConstructor (parametric construction), @ NoConstructor (nonparametric construction)
@Data (provides all methods except construction methods, such as get/set, toString, hashCode, quals, etc.)
3. bug1: (you need to add lombok plug-in for idea to work)
- Book.class
- Simplify pojo development with lombok
@Data public class Book { private Integer id; private String name; private String type; private String description; }
2.3 data layer
Data layer development: (technical implementation scheme: mp, druid)
1. Configure application yml
- yaml configuration information:
bug1: when the file is garbled with comments, the project will not run and needs to be set to utf-8
bug2: put jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC wrote it wrong
# Server port number server: port: 80 # Data source information spring: datasource: druid: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/ssm_db username: root password: 123456 # Configuration information of mp mybatis-plus: global-config: db-config: # Automatically match database table TBL for BookMapper_ book. That is, xxmapper - > TBL_ xxx table-prefix: tbl_ # When the primary key id in the table increases automatically, it needs to be set id-type: auto # Load log configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
2. Data layer interface: mapper BookMapper
- 1. Inherit BaseMapper and add generic
- 2. Add the annotation @ Mapper for the modified class, which can be loaded into the spring container
- 3. Table connecting database for entity
- Method 1: the table prefix: tbl_5;specified in the yml file: Automatically match database table TBL for BookMapper_ Class name: xxx (lowercase)
- Method 2: add annotation * * @ TableName("tbl_book") on the Book entity class**
code:
package com.fairy.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.fairy.domian.Book; import org.apache.ibatis.annotations.Mapper; @Mapper public interface BookMapper extends BaseMapper<Book> { }
3. Test: add a test class BookMapperTest to test the data layer method
- Location: under the package and its sub packages of ssmpapplication
- Note: the annotation @ SpringBootTest needs to be added so that the method can be executed when the project runs
- Small bug: there is a red wavy line under the member variable bookMapper of the test class in idea, but it works normally-
Function: print log
- Add yml configuration information
Function: paging function
-
a. Encapsulating paging data with IPage
-
b. Add mybatisplus paging interceptor to realize the function
-
c. Execute sql statements with mp log lookup
code:
@SpringBootTest public class BookMapperTestCase { @Autowired private BookMapper bookMapper; @Test //Add: the id cannot be specified because it is a self incrementing primary key void testInser(){ Book book = new Book(); //book.setId(19); book.setName("sleep"); book.setType("shenghuo"); book.setDescription("I'm going to bed"); bookMapper.insert(book); } @Test //Delete: delete according to id void testDelete(){ bookMapper.deleteById(3); } @Test //Change: modify the book with id 1 void testUpdate(){ Book book= new Book(); book.setId(1); book.setName("Sadness"); book.setType("BUg ah"); book.setDescription("All night bug five five five five"); bookMapper.updateById(book); } @Test //Query: find the book with id=1 void testGetById(){ bookMapper.selectById(1); System.out.println("nihao"); } @Test //Query: selectList: query all book s // You need to fill in the parameter, null is enough void testGetAll(){ List<Book> books = bookMapper.selectList(null); } @Test void testGetPage(){ //Define page object IPage page = new Page(1,4); //Execute sql statement bookMapper.selectPage(page,null); System.out.println(page.getCurrent()); //Current page number value System.out.println(page.getSize()); //Number of displays per page System.out.println(page.getTotal()); //Total data System.out.println(page.getPages()); //PageCount System.out.println(page.getRecords()); //Detailed data } @Test //Query by criteria void testGetByCondition(){ // Using QueryWrapper method: (not recommended) // QueryWrapper<Book> qw = new QueryWrapper<>(); // qw.like("name","Spring"); // bookMapper.selectList(qw); //LambdaQueryWrapper: (recommended) String name =null; LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<Book>(); lqw.like(name != null,Book::getName,name); //condition: name cannot be empty. If it is blank, all book s will be queried bookMapper.selectList(lqw); } }
2.4 business layer
1. Business layer interface BookService
public interface BookService { Boolean save(Book book); Boolean update(Book book); Boolean delete(Integer id); Book getById(Integer id); List<Book> getAll(); IPage<Book> getPage(int currentPage,int pageSize); }
2. Business layer implementation class BookServiceImpl
- Add this class to the container: @ Service
- The reason why XXX > 0 is returned is to observe whether the operation layer is completed
@Service public class BookServiceImpl implements BookService { @Autowired private BookDao bookDao; @Override public Boolean save(Book book) { return bookDao.insert(book) > 0; } @Override public Boolean update(Book book) { return bookDao.updateById(book) > 0; } @Override public Boolean delete(Integer id) { return bookDao.deleteById(id) > 0; } @Override public Book getById(Integer id) { return bookDao.selectById(id); } @Override public List<Book> getAll() { return bookDao.selectList(null); } @Override public IPage<Book> getPage(int currentPage, int pageSize) { IPage page = new Page(currentPage,pageSize); bookDao.selectPage(page,null); return page; } }
3. Test: test the business layer method
@SpringBootTest public class BookServiceTest { @Autowired private IBookService bookService; @Test void testGetById(){ System.out.println(bookService.getById(4)); } @Test void testSave(){ Book book = new Book(); book.setType("Test data 123"); book.setName("Test data 123"); book.setDescription("Test data 123"); bookService.save(book); } @Test void testUpdate(){ Book book = new Book(); book.setId(17); book.setType("-----------------"); book.setName("Test data 123"); book.setDescription("Test data 123"); bookService.updateById(book); } @Test void testDelete(){ bookService.removeById(18); } @Test void testGetAll(){ bookService.list(); } @Test void testGetPage(){ IPage<Book> page = new Page<Book>(2,5); bookService.page(page); System.out.println(page.getCurrent()); System.out.println(page.getSize()); System.out.println(page.getTotal()); System.out.println(page.getPages()); System.out.println(page.getRecords()); } }
4. Use MP to quickly develop business layer
- 1.IBookService interface inherits IService interface
public interface IBookService extends IService<Book> { //Add non general operation API interface }
- 2.serviceImpl inherits ServiceImpl and implements IBookService
@Service public class BookServiceImpl extends ServiceImpl<BookDao, Book> implements IBookService { @Autowired private BookDao bookDao; //Add non generic operation API }
- Basic addition, deletion, modification and query have been realized. If you feel that the functions provided by MP are not enough to support your use needs, you can define a new API interface on the basis of the original interface. If you want to override, add @ override
- 3. Testing
@SpringBootTest public class BookServiceTest { @Autowired private IBookService bookService; @Test //Add: the id cannot be specified because it is a self incrementing primary key void testInser(){ Book book = new Book(); //book.setId(19); book.setName("sleep"); book.setType("shenghuo"); book.setDescription("I'm going to bed"); bookService.save(book); } @Test //Delete: delete according to id void testDelete(){ bookService.removeById(3); } @Test //Modified id: book 1 void testUpdate(){ Book book= new Book(); book.setId(1); book.setName("Sadness"); book.setType("BUg ah"); book.setDescription("All night bug five five five five"); bookService.update(book,null); } @Test //Query: find the book with id=1 void testGetById(){ bookService.getById(1); System.out.println("nihao"); } @Test void testGetAll(){ List<Book> books = bookService.list(); } @Test void testGetPage(){ IPage<Book> page = new Page<Book>(2,2); bookService.page(page); } }
2.5 presentation layer
Development of presentation layer interface based on Restful
When using Postman test
- Submission type
type | annotation | With reference | Request address |
---|---|---|---|
query | @GetMapping | @GetMapping("{id}"),@GetMapping("{currentPage}/{pageSize}") | GET http://localhost/books/2/3 |
add to | @PostMapping | @PostMapping | POST http://localhost/books |
modify | @PutMapping | @PutMapping | PUT http://localhost/books |
delete | @DeleteMapping | @DeleteMapping("{id}") | DELETE http://localhost/books |
- Implementation layer method parameters
- Transfer path variable: public Boolean delete(@PathVariable Integer id)
- Transfer json data: public Boolean save (@ requestbody book)
@RestController @RequestMapping("/books") public class BookController2 { @Autowired private IBookService bookService; @GetMapping public List<Book> getAll(){ return bookService.list(); } @PostMapping public Boolean save(@RequestBody Book book){ return bookService.save(book); } @PutMapping public Boolean update(@RequestBody Book book){ return bookService.modify(book); } @DeleteMapping("{id}") public Boolean delete(@PathVariable Integer id){ return bookService.delete(id); } @GetMapping("{id}") public Book getById(@PathVariable Integer id){ return bookService.getById(id); } @GetMapping("{currentPage}/{pageSize}") public IPage<Book> getPage(@PathVariable int currentPage,@PathVariable int pageSize){ return bookService.getPage(currentPage,pageSize, null); } }
-
New: add json object data to the body without adding id
-
Delete: Add / id directly
-
Modify: add id attribute in json
-
Paging query
Presentation layer message consistency processing
- Design the model class of the results returned by the presentation layer, which is used to unify the data format between the back-end and the front-end, also known as the front-end and back-end data protocol
- Define the model class (in the controller.utils package)
@Data public class R { private Boolean flag;//Indicates whether the test was successful private Object data;//Represents the encapsulated operation data after the test public R(List<Book> list) { } //The single parameter construction method is applicable to the service method with the return value of boolean. Such as removeById, save public R(boolean flag) { this.flag = flag; } //The two parameter construction method is applicable to service methods with return values of other types, such as list and getById. The last one is always true public R(boolean flag, Object data) { this.flag = flag; this.data = data; } //get/set method }
- Modify the return values of all methods in the controller to R-type
@RestController @RequestMapping("/books") public class BookController { @Autowired private IBookService bookService; @GetMapping public R getAll(){ return new R(true,bookService.list()); } @PostMapping public R save(@RequestBody Book book){ return new R(bookService.save(book)); } @PutMapping public R update(@RequestBody Book book){ return new R(bookService.update(book,null)); } @DeleteMapping("{id}") public R delete(@PathVariable Integer id){ return new R(bookService.removeById(id)); } @GetMapping("{id}") public R getById(@PathVariable Integer id){ return new R(true,bookService.getById(id)); } @GetMapping("{currentPage}/{pageSize}") public R getPage(@PathVariable int currentPage, @PathVariable int pageSize){ return new R(true,bookService.getPage(currentPage,pageSize)); } }
- The data format returned after the request becomes new json data
{ "flag": true, "data":{ "id": 1, "type": "Computer theory", "name": "Spring Actual combat version 5", "description": "Spring Classic tutorial for getting started" } }
III Combine the front and rear ends
3.1 preparation
-
Static resources are placed in the static directory under resources (it is recommended to clean the project's LifeCycle to avoid cache problems.)
-
Before the specific function development, test the connectivity first, send asynchronous submission (axios) through the page, and then carry out further function development after passing the debugging
3.2 realize the function of html page
1. Display main page data
- Display data on the main page: http://localhost/pages/books.html
- 1. Hook function: created() {}
- 2.axios sends asynchronous request
- axios.get("/books"): that is, access localhost/books, that is, execute the controller method corresponding to @ GetMapping, that is, getAll()
- axios.get("/books").then(): execute the inner parenthesis of then after sending the request
- res.data: the return value of the controller method, that is, R-type data. Here corresponds to the return value of getAll()
res.data.data: the return value of the service method, which corresponds to bookservice List() return value
- 3.vue's two-way data binding can load data into html pages: this dataList=res.data. data;
//1. Hook function, which is automatically executed after VUE object initialization created() { this.getAll(); }, methods: { //Display list getAll() { //axios sends asynchronous requests and then executes them in parentheses axios.get("/books").then((res)=>{ //vue's two-way data binding can load data into html pages this.dataList=res.data.data; }) },
2. Add new functions
1. Display the newly added elastic layer
//Reset Form resetForm() { this.formData = {}; }, //The add window pops up handleCreate() { //The form should be reset before each window is displayed, otherwise the last added data will be retained this.resetForm(); //Display window this.dialogFormVisible = true; },
2. Add the submit function in the new bomb layer
//Submit the form of the new window handleAdd () { //Send asynchronous request axios.post("/books",this.formData).then((res)=>{ //Display page if(res.data.flag){ //If the addition is successful, 1 Close bomb layer 2 Display successful this.dialogFormVisible = false; this.$message.success("Added successfully"); }else { //Add failed this.$message.error("Add failed"); } }).finally(()=>{ //Data must be reloaded regardless of success or failure this.getAll(); }) }, //cancel cancel(){ this.dialogFormVisible = false; this.$message.info("Cancel operation"); },
3. Add delete function
- In the request mode, Delete is used to call the corresponding operation in the background
- The deletion operation needs to pass the ID value corresponding to the current row data to the background, that is, row id
- After the deletion operation, the page loading data is refreshed dynamically
- Display corresponding prompt information according to different operation results
- Pop up a prompt box before deleting to avoid misoperation
// delete handleDelete(row) { //1. Pop up prompt box this.$confirm("This operation will permanently delete the current data. Do you want to continue?","Tips",{ type:'info' }).then(()=>{ //2. Delete business axios.delete("/books/"+row.id).then((res)=>{ if(res.data.flag){ this.$message.success("Deleted successfully"); }else{ this.$message.error("Delete failed"); } }).finally(()=>{ //Overload list this.getAll(); }); }).catch(()=>{ //3. Cancel deletion this.$message.info("Cancel delete operation"); }); },
Note: when there is only one piece of data on the last page, a BUG will appear in the deletion operation. The last page has no data but is displayed independently. The background function maintenance of the paging query function is carried out. If the current page number value is greater than the maximum page number value, the query will be executed again. In fact, there are many solutions to this problem. Here is a relatively simple solution
@GetMapping("{currentPage}/{pageSize}") public R getPage(@PathVariable int currentPage,@PathVariable int pageSize){ IPage<Book> page = bookService.getPage(currentPage, pageSize); //If the current page number value is greater than the total page number value, re execute the query operation and use the maximum page number value as the current page number value if( currentPage > page.getPages()){ page = bookService.getPage((int)page.getPages(), pageSize); } return new R(true, page); }
4. Modify function
1. The edit page pops up
//The edit window pops up handleUpdate(row) { axios.get("/books/"+row.id).then((res)=>{ if(res.data.flag){ //Display the bomb layer and load data this.formData = res.data.data; this.dialogFormVisible4Edit = true; }else{ this.$message.error("Data synchronization failed, automatic refresh"); } }); },
2. Modification
formData is bound in the model property
//modify handleEdit() { axios.put("/books",this.formData).then((res)=>{ //If the operation is successful, close the elastic layer and refresh the page if(res.data.flag){ this.dialogFormVisible4Edit = false; this.$message.success("Modified successfully"); }else { this.$message.error("Modification failed, please try again"); } }).finally(()=>{ this.getAll(); }); },
5. Exception handling function
First, add a message field to the current data result to be compatible with the operation messages appearing in the background
@Data public class R{ private Boolean flag; private Object data; private String msg; //Used to encapsulate messages //The controller method with the return value of boolean is used public R(boolean flag) { this.flag = flag; } //The return value is used by the contoller method of entity object or other data public R(boolean flag, Object data) { this.flag = flag; this.data = data; } public R(boolean flag,Obiect data,String msg) { this.flag = flag; this.data = data; this.msg = msg; } }
The background code should also be processed according to the situation. At present, it is a simulated error
@PostMapping public R save(@RequestBody Book book) throws IOException { Boolean flag = bookService.insert(book); return new R(flag , flag ? "Added successfully^_^" : "Add failed-_-!"); }
Then do unified exception handling in the presentation layer, and use the exception handler provided by spring MVC to do unified exception handling. In the controller Utils package
@RestControllerAdvice public class ProjectExceptionAdvice { @ExceptionHandler(Exception.class) public R doOtherException(Exception ex){ //Log //Send message to O & M //Send e-mail to developers and ex objects to developers ex.printStackTrace(); return new R(false,null,"System error, please try again later!"); } }
After getting the data on the page, first determine whether there is a message delivered from the background. The flag is whether the current operation is successful. If the operation result is false, read the message delivered from the background
//add to handleAdd () { //Send ajax request axios.post("/books",this.formData).then((res)=>{ //If the operation is successful, close the bomb layer and display the data if(res.data.flag){ this.dialogFormVisible = false; this.$message.success(res.data.msg); }else { //msg messages are delivered from the background, not fixed content this.$message.error(res.data.msg); } }).finally(()=>{ this.getAll(); }); },
6. Paging function
1. Display paging function
- 1.1 encapsulate request data: encapsulate the data model corresponding to paging in order to cooperate with paging components
<!--Paging component--> <div class="pagination-container"> <el-pagination class="pagiantion" @current-change="handleCurrentChange" :current-page="pagination.currentPage" :page-size="pagination.pageSize" layout="total, prev, pager, next, jumper" :total="pagination.total"> </el-pagination> </div>
data:{ pagination: { //Paging related model data. initial condition currentPage: 1, //Current page number pageSize:10, //Number of records displayed per page total:0, //Total records } },
- 1.2 send request: modify all functions of query to page query, and transfer page information parameters through path variables
getAll() { axios.get("/books/"+this.pagination.currentPage+"/"+this.pagination.pageSize).then((res) => { }); },
- 1.3 execution: the corresponding paging function is provided in the background
@GetMapping("/{currentPage}/{pageSize}") public R getAll(@PathVariable Integer currentPage,@PathVariable Integer pageSize){ IPage<Book> pageBook = bookService.getPage(currentPage, pageSize); return new R(null != pageBook ,pageBook); }
- 4.1 data binding: read the data according to the model and return to the corresponding page
getAll() { axios.get("/books/"+this.pagination.currentPage+"/"+this.pagination.pageSize).then((res) => { this.pagination.total = res.data.data.total; this.pagination.currentPage = res.data.data.current; this.pagination.pagesize = res.data.data.size; this.dataList = res.data.data.records; }); },
2. Call the current paging operation for the operation button setting of switching page number
//Switch page number handleCurrentChange(currentPage) { this.pagination.currentPage = currentPage; this.getAll(); },
7. Condition query
Conditional query can be understood as: when paging query, in addition to carrying paging data, query with several more data. These multi band data are query criteria.
-
Background query function: the query is changed from no conditions to conditions. Anyway, when there are no conditions, the query condition object uses null. Now replace it with specific conditions, which makes little difference
-
Query results: with or without conditions, there are only differences in the quantity of data, and others are different. This can be ignored
-
When the page sends the request, the two paging data still use the path variable, and other conditions are passed in the form of dynamic assembly url parameters
1. Page encapsulation query criteria field
pagination: { //Paging related model data currentPage: 1, //Current page number pageSize:10, //Number of records displayed per page total:0, //Total records name: "", type: "", description: "" },
Add the data model binding name corresponding to the query criteria field on the page
<div class="filter-container"> <el-input placeholder="Book category" v-model="pagination.type" class="filter-item"/> <el-input placeholder="Book name" v-model="pagination.name" class="filter-item"/> <el-input placeholder="Book description" v-model="pagination.description" class="filter-item"/> <el-button @click="getAll()" class="dalfBut">query</el-button> <el-button type="primary" class="butT" @click="handleCreate()">newly build</el-button> </div>
2. Send request: organize the query conditions into url parameters and add them to the request url address: splice the conditions into param.
getAll() { //1. Obtain query conditions and splice query conditions param = "?name="+this.pagination.name; param += "&type="+this.pagination.type; param += "&description="+this.pagination.description; console.log("-----------------"+ param); axios.get("/books/"+this.pagination.currentPage+"/"+this.pagination.pageSize+param).then((res) => { this.dataList = res.data.data.records; }); },
3. Execution: define entity class encapsulation query conditions in the background code: the param in the url address is automatically encapsulated into a book instance object and passed into the formal parameter
@GetMapping("{currentPage}/{pageSize}") public R getAll(@PathVariable int currentPage,@PathVariable int pageSize,Book book) { System.out.println("parameter=====>"+book); IPage<Book> pageBook = bookService.getPage(currentPage,pageSize); return new R(null != pageBook ,pageBook); }
Modify the corresponding business layer interface and implementation class
public interface IBookService extends IService<Book> { IPage<Book> getPage(Integer currentPage,Integer pageSize,Book queryBook); }
@Service public class BookServiceImpl2 extends ServiceImpl<BookDao,Book> implements IBookService { public IPage<Book> getPage(Integer currentPage,Integer pageSize,Book queryBook){ IPage page = new Page(currentPage,pageSize); LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<Book>(); lqw.like(Strings.isNotEmpty(queryBook.getName()),Book::getName,queryBook.getName()); lqw.like(Strings.isNotEmpty(queryBook.getType()),Book::getType,queryBook.getType()); lqw.like(Strings.isNotEmpty(queryBook.getDescription()),Book::getDescription,queryBook.getDescription()); return bookDao.selectPage(page,lqw); } }
4. Page echo data
getAll() { //1. Obtain query conditions and splice query conditions param = "?name="+this.pagination.name; param += "&type="+this.pagination.type; param += "&description="+this.pagination.description; console.log("-----------------"+ param); axios.get("/books/"+this.pagination.currentPage+"/"+this.pagination.pageSize+param).then((res) => { this.pagination.total = res.data.data.total; this.pagination.currentPage = res.data.data.current; this.pagination.pagesize = res.data.data.size; this.dataList = res.data.data.records; }); },