前一章簡述了已經實現了對數據庫的增刪改查以及複雜查詢的功能,這一步將對相應的功能方法封裝成WEB接口,對外提供WEB接口服務。前端
控制層的角色是負責對訪問路由處處理過程的關聯映射和封裝,在這裏咱們隊Book
新建一個控制類便可,在文件夾Controller
上右鍵,New->Java Class
新建BookController
類。做爲控制類,該類須要使用@Controller
註解使之可以被識別爲控制類對象,在這裏咱們使用@RestController
,該註解包含了@Controller
,至關於@Controller+@ResponseBody兩個註解的結合,適合返回Json格式的控制器使用。java
@RestController @RequestMapping(path = "/library") public class BookController { @Autowired private BookJpaRepository bookJpaRepository; @Autowired private BookService bookService; }
考慮到數據庫的操做須要用到BookJpaRepository
和BookService
,這裏首先聲明這兩個屬性,並使用@Autowired
註解自動裝配。git
在類上使用@RequestMapping(path = "/library")
註解後,定義了該類的路徑都是/library
開始,能夠統一接口路徑,避免重複書寫。github
/** * 新增書籍 * @param name * @param author * @param image * @return */ @PostMapping("/save") public Map<String, Object> save(@RequestParam String name, @RequestParam String author, @RequestParam String image){ Book book = new Book(); Map<String, Object> rsp = new HashMap<>(); book.setName(name); book.setAuthor(author); book.setImage(image); bookJpaRepository.save(book); rsp.put("data", book); rsp.put("code", "0"); rsp.put("info", "成功"); return rsp; }
使用@PostMapping
表示接口只接受POST請求,WEB接口路徑爲/library/save
,該接口返回的是一個Map類型對象,可是因爲類使用@RestController
註解後,使得返回結果會自動轉換成Json字符串格式。web
接口參數@RequestParam
的註解用於將指定的請求參數賦值給方法中的形參,默認根據參數名匹配,也可使用value
指定參數名,支持的參數以下:spring
value
功能同樣,默認與形參名相同自動關聯name
功能相同在該接口中,經過形參自動綁定取的入參,而後經過BookJpaRepository
直接save
保存新增數據,save
新增後,該記錄自動生成的id
值已經被設置到book
變量。sql
爲了接口通用,返回值增長了字段code
和info
分別用來返回錯誤碼和錯誤信息,返回數據放在字段data
。數據庫
單元測試代碼json
@Autowired private WebApplicationContext wac; private MockMvc mockMvc; @Before public void setUp (){ mockMvc = MockMvcBuilders.webAppContextSetup(wac).build(); }
引入Web的MVC單元測試對象,而後編寫Web新增接口測試用例:bootstrap
@Test public void webApi(){ try { String urlRoot = "/library"; String urlApi = urlRoot + "/view/1"; MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get(urlApi) .contentType(MediaType.APPLICATION_JSON_UTF8)) .andExpect(MockMvcResultMatchers.status().isOk()) .andReturn(); System.out.println("WEB測試返回[" + urlApi + "]:" + mvcResult.getResponse().getContentAsString()); } catch (Exception e) { e.printStackTrace(); } }
執行結果:
根據數據ID刪除書籍,且數據id做爲請求路徑的一部分,不經過@RequestParam
獲取,而是經過@PathVariable("id")
,代碼以下:
/** * 刪除書籍 * @param id * @return */ @GetMapping("/remove/{id}") public Map<String, Object> removeById(@PathVariable("id") Integer id){ Map<String, Object> rsp = new HashMap<>(); Optional<Book> book = bookJpaRepository.findById(id); if(!book.isPresent()) { rsp.put("code", 1001); rsp.put("info", "書籍ID[" + id + "]不存在"); }else { bookJpaRepository.deleteById(id); rsp.put("code", 0); rsp.put("info", "書籍ID[" + id + "]刪除成功"); rsp.put("data", book); } return rsp; }
@PathVariable
只支持一個屬性value,類型是爲String,表明綁定的屬性名稱,默認綁定爲同名的形參。
在接口中,咱們使用@GetMapping
接收處理GET請求,若是成功返回書籍信息,不然返回錯誤信息。
使用瀏覽器測試結果以下:
刪除不存在的書籍時
正常刪除數據
根據書籍ID更新書籍信息,參數信息使用HttpServletRequest
和路徑參數相配合
/** * 更新書籍 * @param id * @param request * @return */ @PostMapping("/edit/{id}") public Map<String, Object> updateById(@PathVariable("id") Integer id, HttpServletRequest request){ Map<String, Object> rsp = new HashMap<>(); Optional<Book> book = bookJpaRepository.findById(id); if(!book.isPresent()) { rsp.put("code", 1001); rsp.put("info", "書籍ID[" + id + "]不存在"); }else { Book bookUpd = book.get(); if(request.getParameter("name") != null){ bookUpd.setName(request.getParameter("name")); } if(request.getParameter("author") != null){ bookUpd.setAuthor(request.getParameter("author")); } if(request.getParameter("image") != null){ bookUpd.setImage(request.getParameter("image")); } rsp.put("code", 0); rsp.put("info", "書籍ID[" + id + "]更新成功"); rsp.put("data", bookUpd); } return rsp; }
HttpServletRequest
對象表明客戶端的請求,當客戶端經過HTTP協議訪問服務器時,HTTP請求頭中的全部信息都封裝在這個對象中,經過這個對象提供的方法,能夠得到客戶端請求的全部信息。
單元測試用例:
@Test public void webBookEdit() throws Exception { String url = "/library/edit/2"; // 只修更名字 MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post(url) .param("name", "webBookEdit1") .contentType(MediaType.APPLICATION_JSON_UTF8)) .andExpect(MockMvcResultMatchers.status().isOk()) .andReturn(); System.out.println("1-WEB測試返回[" + url + "]:" + mvcResult.getResponse().getContentAsString()); // 修更名字和做者 mvcResult = mockMvc.perform(MockMvcRequestBuilders.post(url) .param("name", "webBookEdit2") .param("author", "webBookEdit2") .contentType(MediaType.APPLICATION_JSON_UTF8)) .andExpect(MockMvcResultMatchers.status().isOk()) .andReturn(); System.out.println("2-WEB測試返回[" + url + "]:" + mvcResult.getResponse().getContentAsString()); }
執行結果(JSON格式化處理過)
Hibernate: select book0_.id as id1_0_0_, book0_.author as author2_0_0_, book0_.image as image3_0_0_, book0_.name as name4_0_0_ from library_book book0_ where book0_.id=? 1-WEB測試返回[/library/edit / 2]: { "code": 0, "data": { "id": 2, "name": "webBookEdit1", "author": "arbboter", "image": "https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2656353677,2997395625&fm=26&gp=0.jpg" }, "info": "書籍ID[2]更新成功" } Hibernate: select book0_.id as id1_0_0_, book0_.author as author2_0_0_, book0_.image as image3_0_0_, book0_.name as name4_0_0_ from library_book book0_ where book0_.id=? 2-WEB測試返回[/library/edit/2]: { "code": 0, "data": { "id": 2, "name": "webBookEdit2", "author": "webBookEdit2", "image": "https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2656353677,2997395625&fm=26&gp=0.jpg" }, "info": "書籍ID[2]更新成功" }
從上述執行結果咱們能夠看到,在使用JpaRepository
的save
更新數據時,只會更新非null
字段,且返回結果包括完整的更新後的數據內容,即默認支持按設定的字段更新,而不是每次須要全字段更新。
使用路徑參數根據書籍ID獲取書籍內容信息,代碼以下:
/** * 查看書籍 * @param id * @return */ @GetMapping("/view/{id}") public Map<String, Object> findById(@PathVariable("id") Integer id){ Map<String, Object> rsp = new HashMap<>(); Optional<Book> book = bookJpaRepository.findById(id); if(!book.isPresent()) { rsp.put("code", 1001); rsp.put("info", "書籍ID[" + id + "]不存在"); }else { rsp.put("code", 0); rsp.put("info", "成功"); rsp.put("data", book); } return rsp; }
測試執行結果以下:
因爲咱們以前的搜索接口的入參類型爲Map
,可是Web接口的入參信息都是從HttpServletRequest
獲取,所以首先須要將須要的入參信息從HttpServletRequest
轉換到Map
類型,然再使用。考慮到該轉換功能爲通用型,所以能夠將該函數封裝到系統的工具包下面,新建Util
包,而後右鍵新建Util
文件,完成數據的轉換函數,代碼以下:
public class Util { /** * 把 @HttpServletRequest 轉換成普通的字典 * @param request * @return */ public static Map getParameterMap(HttpServletRequest request) { // 參數Map Map properties = request.getParameterMap(); // 返回值Map Map returnMap = new HashMap(); Iterator entries = properties.entrySet().iterator(); Map.Entry entry; String name = ""; String value = ""; while (entries.hasNext()) { entry = (Map.Entry) entries.next(); name = (String) entry.getKey(); Object valueObj = entry.getValue(); if(null == valueObj){ value = ""; }else if(valueObj instanceof String[]){ String[] values = (String[])valueObj; for(int i=0;i<values.length;i++){ value = values[i] + ","; } value = value.substring(0, value.length()-1); }else{ value = valueObj.toString(); } returnMap.put(name, value); } return returnMap; } }
而後使用咱們BookService
實現的封裝的複雜查詢接口便可,代碼以下:
/** * 搜索查詢接口 * @param request * @return */ @PostMapping("/search") public Map<String, Object> search(HttpServletRequest request){ Map<String, String> map = new HashMap<>(); map = Util.getParameterMap(request); Page<Book> books = bookService.search(map); Map<String, Object> rsp = new HashMap<>(); rsp.put("code", 0); rsp.put("info", "成功"); rsp.put("rows", books.getContent()); rsp.put("total", books.getTotalElements()); return rsp; }
此處返回rows
和total
是爲了後續Web頁面的bootstrap-table
須要,該控件根據這兩個數據以表格化的形式展現查詢結果數據。
因爲此處使用POST請求類型,測試時依舊使用MockMvc
和WebApplicationContext
,測試代碼以下:
@Test public void webSearch() throws Exception{ String url = "/library/search"; // 1-無條件 MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post(url) .contentType(MediaType.APPLICATION_JSON_UTF8)) .andExpect(MockMvcResultMatchers.status().isOk()) .andReturn(); System.out.println("1-無條件-WEB測試返回[" + url + "]:" + mvcResult.getResponse().getContentAsString()); // 2-根據做者名查詢 mvcResult = mockMvc.perform(MockMvcRequestBuilders.post(url) .param("author", "做者_3") .contentType(MediaType.APPLICATION_JSON_UTF8)) .andExpect(MockMvcResultMatchers.status().isOk()) .andReturn(); System.out.println("2-根據做者名(做者_3)查詢-WEB測試返回[" + url + "]:" + mvcResult.getResponse().getContentAsString()); }
測試執行結果以下:
Hibernate: select TOP(?) book0_.id as id1_0_, book0_.author as author2_0_, book0_.image as image3_0_, book0_.name as name4_0_ from library_book book0_ where 1=1 order by book0_.id desc Hibernate: select count(book0_.id) as col_0_0_ from library_book book0_ where 1=1 1-無條件-WEB測試返回[/library/search]: { "total": 22, "code": 0, "rows": [{ "id": 26, "name": "書名_19", "author": "做者_4", "image": "img19" }, { "id": 25, "name": "書名_18", "author": "做者_3", "image": "img18" }, { "id": 24, "name": "書名_17", "author": "做者_2", "image": "img17" }, { "id": 23, "name": "書名_16", "author": "做者_1", "image": "img16" }, { "id": 22, "name": "書名_15", "author": "做者_0", "image": "img15" }, { "id": 21, "name": "書名_14", "author": "做者_4", "image": "img14" }, { "id": 20, "name": "書名_13", "author": "做者_3", "image": "img13" }, { "id": 19, "name": "書名_12", "author": "做者_2", "image": "img12" }, { "id": 18, "name": "書名_11", "author": "做者_1", "image": "img11" }, { "id": 17, "name": "書名_10", "author": "做者_0", "image": "img10" } ], "info": "成功" } Hibernate: select TOP(?) book0_.id as id1_0_, book0_.author as author2_0_, book0_.image as image3_0_, book0_.name as name4_0_ from library_book book0_ where book0_.author=? order by book0_.id desc 2-根據做者名(做者_3)查詢-WEB測試返回[/library/search]: { "total": 4, "code": 0, "rows": [{ "id": 25, "name": "書名_18", "author": "做者_3", "image": "img18" }, { "id": 20, "name": "書名_13", "author": "做者_3", "image": "img13" }, { "id": 15, "name": "書名_8", "author": "做者_3", "image": "img8" }, { "id": 10, "name": "書名_3", "author": "做者_3", "image": "img3" } ], "info": "成功" }
到這裏,整個項目的全部服務器後端部分已經完成,已經能夠提供給前端使用各類經常使用的Web接口,下一篇咱們將從前端一塊兒整合整個項目,實現數據的展現和管理,敬請期待。
[下一篇]()