上節 咱們實現了根據搜索關鍵詞查詢商品列表和根據商品分類查詢,而且使用到了mybatis-pagehelper
插件,講解了如何使用插件來幫助咱們快速實現分頁數據查詢。本文咱們將繼續開發商品詳情頁面和商品留言功能的開發。前端
關於商品詳情頁,和往常同樣,咱們先來看一看jd
的示例:
從上面2張圖,咱們能夠看出來,大致上須要展現給用戶的信息。好比:商品圖片,名稱,價格,等等。在第二張圖中,咱們還能夠看到有一個商品評價頁籤
,這些都是咱們本節要實現的內容。java
咱們根據上圖(權當是需求文檔,不少需求文檔寫的比這個可能還差勁不少...)分析一下,咱們的開發大體都要關注哪些points
:git
根據咱們梳理出來的信息,接下來開始編碼就會很簡單了,你們能夠根據以前課程講解的,先自行實現一波,請開始大家的表演~github
由於咱們在實際的數據傳輸過程當中,不可能直接把咱們的數據庫entity
之間暴露到前端,並且咱們商品相關的數據是存儲在不一樣的數據表中,咱們必需要封裝一個ResponseDTO
來對數據進行傳遞。spring
ProductDetailResponseDTO
包含了商品主表信息,以及圖片列表、商品規格(不一樣SKU)以及商品具體參數(產地,生產日期等信息)@Data @ToString @Builder @AllArgsConstructor @NoArgsConstructor public class ProductDetailResponseDTO { private Products products; private List<ProductsImg> productsImgList; private List<ProductsSpec> productsSpecList; private ProductsParam productsParam; }
根據咱們以前表的設計,這裏使用生成的通用mapper就能夠知足咱們的需求。shell
從咱們封裝的要傳遞到前端的ProductDetailResponseDTO
就能夠看出,咱們能夠根據商品id
分別查詢出商品的相關信息,在controller
進行數據封裝就能夠了,來實現咱們的查詢接口。數據庫
在com.liferunner.service.IProductService
中添加接口方法:segmentfault
/** * 根據商品id查詢商品 * * @param pid 商品id * @return 商品主信息 */ Products findProductByPid(String pid);
接着,在com.liferunner.service.impl.ProductServiceImpl
中添加實現方法:api
@Override @Transactional(propagation = Propagation.SUPPORTS) public Products findProductByPid(String pid) { return this.productsMapper.selectByPrimaryKey(pid); }
直接使用通用mapper根據主鍵查詢就能夠了。session
同上,咱們依次來實現圖片、規格、以及商品參數相關的編碼工做
/** * 根據商品id查詢商品規格 * * @param pid 商品id * @return 規格list */ List<ProductsSpec> getProductSpecsByPid(String pid); ---------------------------------------------------------------- @Override public List<ProductsSpec> getProductSpecsByPid(String pid) { Example example = new Example(ProductsSpec.class); val condition = example.createCriteria(); condition.andEqualTo("productId", pid); return this.productsSpecMapper.selectByExample(example); }
/** * 根據商品id查詢商品規格 * * @param pid 商品id * @return 規格list */ List<ProductsSpec> getProductSpecsByPid(String pid); ------------------------------------------------------------------ @Override public List<ProductsSpec> getProductSpecsByPid(String pid) { Example example = new Example(ProductsSpec.class); val condition = example.createCriteria(); condition.andEqualTo("productId", pid); return this.productsSpecMapper.selectByExample(example); }
/** * 根據商品id查詢商品參數 * * @param pid 商品id * @return 參數 */ ProductsParam findProductParamByPid(String pid); ------------------------------------------------------------------ @Override public ProductsParam findProductParamByPid(String pid) { Example example = new Example(ProductsParam.class); val condition = example.createCriteria(); condition.andEqualTo("productId", pid); return this.productsParamMapper.selectOneByExample(example); }
在上面將咱們須要的信息查詢實現以後,而後咱們須要在controller對數據進行包裝,以後再返回到前端,供用戶來進行查看,在com.liferunner.api.controller.ProductController
中添加對外接口/detail/{pid}
,實現以下:
@GetMapping("/detail/{pid}") @ApiOperation(value = "根據商品id查詢詳情", notes = "根據商品id查詢詳情") public JsonResponse findProductDetailByPid( @ApiParam(name = "pid", value = "商品id", required = true) @PathVariable String pid) { if (StringUtils.isBlank(pid)) { return JsonResponse.errorMsg("商品id不能爲空!"); } val product = this.productService.findProductByPid(pid); val productImgList = this.productService.getProductImgsByPid(pid); val productSpecList = this.productService.getProductSpecsByPid(pid); val productParam = this.productService.findProductParamByPid(pid); val productDetailResponseDTO = ProductDetailResponseDTO .builder() .products(product) .productsImgList(productImgList) .productsSpecList(productSpecList) .productsParam(productParam) .build(); log.info("============查詢到商品詳情:{}==============", productDetailResponseDTO); return JsonResponse.ok(productDetailResponseDTO); }
從上述代碼中能夠看到,咱們分別查詢了商品、圖片、規格以及參數信息,使用ProductDetailResponseDTO.builder().build()
封裝成返回到前端的對象。
按照慣例,寫完代碼咱們須要進行測試。
{ "status": 200, "message": "OK", "data": { "products": { "id": "smoke-100021", "productName": "(奔跑的人生) - 中華", "catId": 37, "rootCatId": 1, "sellCounts": 1003, "onOffStatus": 1, "createdTime": "2019-09-09T06:45:34.000+0000", "updatedTime": "2019-09-09T06:45:38.000+0000", "content": "吸菸有害健康「 }, "productsImgList": [ { "id": "1", "productId": "smoke-100021", "url": "http://www.life-runner.com/product/smoke/img1.png", "sort": 0, "isMain": 1, "createdTime": "2019-07-01T06:46:55.000+0000", "updatedTime": "2019-07-01T06:47:02.000+0000" }, { "id": "2", "productId": "smoke-100021", "url": "http://www.life-runner.com/product/smoke/img2.png", "sort": 1, "isMain": 0, "createdTime": "2019-07-01T06:46:55.000+0000", "updatedTime": "2019-07-01T06:47:02.000+0000" }, { "id": "3", "productId": "smoke-100021", "url": "http://www.life-runner.com/product/smoke/img3.png", "sort": 2, "isMain": 0, "createdTime": "2019-07-01T06:46:55.000+0000", "updatedTime": "2019-07-01T06:47:02.000+0000" } ], "productsSpecList": [ { "id": "1", "productId": "smoke-100021", "name": "中華", "stock": 2276, "discounts": 1.00, "priceDiscount": 7000, "priceNormal": 7000, "createdTime": "2019-07-01T06:54:20.000+0000", "updatedTime": "2019-07-01T06:54:28.000+0000" }, ], "productsParam": { "id": "1", "productId": "smoke-100021", "producPlace": "中國", "footPeriod": "760天", "brand": "中華", "factoryName": "中華", "factoryAddress": "陝西", "packagingMethod": "盒裝", "weight": "100g", "storageMethod": "常溫", "eatMethod": "", "createdTime": "2019-05-01T09:38:30.000+0000", "updatedTime": "2019-05-01T09:38:34.000+0000" } }, "ok": true }
在文章一開始咱們就看過jd
詳情頁面,有一個詳情頁籤,咱們來看一下:
它這個實現比較複雜,咱們只實現相對重要的幾個就能夠了。
針對上圖中紅色方框圈住的內容,分別有:
咱們來實現上述分析的相對必要的一些內容。
根據咱們須要的信息,咱們須要從用戶表、商品表以及評價表中來聯合查詢數據,很明顯單表通用mapper沒法實現,所以咱們先來實現自定義查詢mapper,固然數據的傳輸對象是咱們須要先來定義的。
建立com.liferunner.dto.ProductCommentDTO
.
@Data @NoArgsConstructor @AllArgsConstructor @Builder public class ProductCommentDTO { //評價等級 private Integer commentLevel; //規格名稱 private String specName; //評價內容 private String content; //評價時間 private Date createdTime; //用戶頭像 private String userFace; //用戶暱稱 private String nickname; }
在com.liferunner.custom.ProductCustomMapper
中添加查詢接口方法:
/*** * 根據商品id 和 評價等級查詢評價信息 * <code> * Map<String, Object> paramMap = new HashMap<>(); * paramMap.put("productId", pid); * paramMap.put("commentLevel", level); *</code> * @param paramMap * @return java.util.List<com.liferunner.dto.ProductCommentDTO> * @throws */ List<ProductCommentDTO> getProductCommentList(@Param("paramMap") Map<String, Object> paramMap);
在mapper/custom/ProductCustomMapper.xml
中實現該接口方法的SQL:
<select id="getProductCommentList" resultType="com.liferunner.dto.ProductCommentDTO" parameterType="Map"> SELECT pc.comment_level as commentLevel, pc.spec_name as specName, pc.content as content, pc.created_time as createdTime, u.face as userFace, u.nickname as nickname FROM items_comments pc LEFT JOIN users u ON pc.user_id = u.id WHERE pc.item_id = #{paramMap.productId} <if test="paramMap.commentLevel != null and paramMap.commentLevel != ''"> AND pc.comment_level = #{paramMap.commentLevel} </if> </select>
若是沒有傳遞評價級別的話,默認查詢所有評價信息。
在com.liferunner.service.IProductService
中添加查詢接口方法:
/** * 查詢商品評價 * * @param pid 商品id * @param level 評價級別 * @param pageNumber 當前頁碼 * @param pageSize 每頁展現多少條數據 * @return 通用分頁結果視圖 */ CommonPagedResult getProductComments(String pid, Integer level, Integer pageNumber, Integer pageSize);
在com.liferunner.service.impl.ProductServiceImpl
實現該方法:
@Override public CommonPagedResult getProductComments(String pid, Integer level, Integer pageNumber, Integer pageSize) { Map<String, Object> paramMap = new HashMap<>(); paramMap.put("productId", pid); paramMap.put("commentLevel", level); // mybatis-pagehelper PageHelper.startPage(pageNumber, pageSize); val productCommentList = this.productCustomMapper.getProductCommentList(paramMap); for (ProductCommentDTO item : productCommentList) { item.setNickname(SecurityTools.HiddenPartString4SecurityDisplay(item.getNickname())); } // 獲取mybatis插件中獲取到信息 PageInfo<?> pageInfo = new PageInfo<>(productCommentList); // 封裝爲返回到前端分頁組件可識別的視圖 val commonPagedResult = CommonPagedResult.builder() .pageNumber(pageNumber) .rows(productCommentList) .totalPage(pageInfo.getPages()) .records(pageInfo.getTotal()) .build(); return commonPagedResult; }
由於評價過多會使用到分頁,這裏使用通用分頁返回結果,關於分頁,可查看 學習分頁傳送門。
在com.liferunner.api.controller.ProductController
中添加對外查詢接口:
@GetMapping("/comments") @ApiOperation(value = "查詢商品評價", notes = "根據商品id查詢商品評價") public JsonResponse getProductComment( @ApiParam(name = "pid", value = "商品id", required = true) @RequestParam String pid, @ApiParam(name = "level", value = "評價級別", required = false, example = "0") @RequestParam Integer level, @ApiParam(name = "pageNumber", value = "當前頁碼", required = false, example = "1") @RequestParam Integer pageNumber, @ApiParam(name = "pageSize", value = "每頁展現記錄數", required = false, example = "10") @RequestParam Integer pageSize ) { if (StringUtils.isBlank(pid)) { return JsonResponse.errorMsg("商品id不能爲空!"); } if (null == pageNumber || 0 == pageNumber) { pageNumber = DEFAULT_PAGE_NUMBER; } if (null == pageSize || 0 == pageSize) { pageSize = DEFAULT_PAGE_SIZE; } log.info("============查詢商品評價:{}==============", pid); val productComments = this.productService.getProductComments(pid, level, pageNumber, pageSize); return JsonResponse.ok(productComments); }
FBI WARNING:
@ApiParam(name = "level", value = "評價級別", required = false, example = "0")
@RequestParam Integer level
關於ApiParam參數,若是接收參數爲非字符串類型,必定要定義example爲對應類型的示例值,不然Swagger在訪問過程當中會報example轉換錯誤,由於example缺省爲""空字符串,會轉換失敗。例如咱們刪除掉level
這個字段中的example=」0「,以下爲錯誤信息(可是並不影響程序使用。)
2019-11-23 15:51:45 WARN AbstractSerializableParameter:421 - Illegal DefaultValue null for parameter type integer java.lang.NumberFormatException: For input string: "" at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) at java.lang.Long.parseLong(Long.java:601) at java.lang.Long.valueOf(Long.java:803) at io.swagger.models.parameters.AbstractSerializableParameter.getExample(AbstractSerializableParameter.java:412) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:688) at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:721) at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:166) at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serializeContents(IndexedListSerializer.java:119)
有心的小夥伴確定又注意到了,在Service中處理查詢時,我一部分使用了@Transactional(propagation = Propagation.SUPPORTS)
,一部分查詢又沒有添加事務,那麼這兩種方式有什麼不同呢?接下來,咱們來揭開神祕的面紗。
/** * Support a current transaction, execute non-transactionally if none exists. * Analogous to EJB transaction attribute of the same name. * <p>Note: For transaction managers with transaction synchronization, * {@code SUPPORTS} is slightly different from no transaction at all, * as it defines a transaction scope that synchronization will apply for. * As a consequence, the same resources (JDBC Connection, Hibernate Session, etc) * will be shared for the entire specified scope. Note that this depends on * the actual synchronization configuration of the transaction manager. * @see org.springframework.transaction.support.AbstractPlatformTransactionManager#setTransactionSynchronization */ SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
主要關注Support a current transaction, execute non-transactionally if none exists.
從字面意思來看,就是若是當前環境有事務,我就加入到當前事務;若是沒有事務,我就以非事務的方式執行。從這方面來看,貌似咱們加不加這一行其實都沒啥差異。
劃重點:NOTE,對於一個帶有事務同步的管理器來講,這裏有一丟丟的小區別啦。(因此你們在讀註釋的時候,必定要看這個Note.每每這裏面會有好東西給咱們,就至關於咱們的大喇叭!)
這個同步事務管理器定義了一個事務同步的一個範圍,若是加了這個註解,那麼就等同於我讓你來管我啦,你裏面的資源我想用就能夠用(JDBC Connection, Hibernate Session).
SUPPORTS 標註的方法能夠獲取和當前事務環境一致的 Connection 或 Session,不使用的話必定是一個新的鏈接;
再注意下面又一個 NOTE,即使上面的配置加入了,可是事務管理器的實際同步配置
會影響到真實的執行究竟是否會用你。看它的說明:@see org.springframework.transaction.support.AbstractPlatformTransactionManager#setTransactionSynchronization
.
/** * Set when this transaction manager should activate the thread-bound * transaction synchronization support. Default is "always". * <p>Note that transaction synchronization isn't supported for * multiple concurrent transactions by different transaction managers. * Only one transaction manager is allowed to activate it at any time. * @see #SYNCHRONIZATION_ALWAYS * @see #SYNCHRONIZATION_ON_ACTUAL_TRANSACTION * @see #SYNCHRONIZATION_NEVER * @see TransactionSynchronizationManager * @see TransactionSynchronization */ public final void setTransactionSynchronization(int transactionSynchronization) { this.transactionSynchronization = transactionSynchronization; }
描述信息只是說在同一個事務管理器才能起做用,並無什麼實際意義,咱們來看一下TransactionSynchronization
具體的內容:
package org.springframework.transaction.support; import java.io.Flushable; public interface TransactionSynchronization extends Flushable { /** Completion status in case of proper commit. */ int STATUS_COMMITTED = 0; /** Completion status in case of proper rollback. */ int STATUS_ROLLED_BACK = 1; /** Completion status in case of heuristic mixed completion or system errors. */ int STATUS_UNKNOWN = 2; /** * Suspend this synchronization. * Supposed to unbind resources from TransactionSynchronizationManager if managing any. * @see TransactionSynchronizationManager#unbindResource */ default void suspend() { } /** * Resume this synchronization. * Supposed to rebind resources to TransactionSynchronizationManager if managing any. * @see TransactionSynchronizationManager#bindResource */ default void resume() { } /** * Flush the underlying session to the datastore, if applicable: * for example, a Hibernate/JPA session. * @see org.springframework.transaction.TransactionStatus#flush() */ @Override default void flush() { } /** * ... */ default void beforeCommit(boolean readOnly) { } /** * ... */ default void beforeCompletion() { } /** * ... */ default void afterCommit() { } /** * ... */ default void afterCompletion(int status) { } }
事務管理器能夠經過org.springframework.transaction.support.AbstractPlatformTransactionManager#setTransactionSynchronization(int)
來對當前事務進行行爲干預,好比將它設置爲1,能夠執行事務回調,設置爲2,表示出錯了,可是若是沒有加入PROPAGATION.SUPPORTS
註解的話,即使你在當前事務中,你也不能對我進行操做和變動。
添加
PROPAGATION.SUPPORTS
以後,當前查詢中能夠對當前的事務進行設置回調動做,不添加就不行。
下一節咱們將繼續開發商品詳情展現以及商品評價業務,在過程當中使用到的任何開發組件,我都會經過專門的一節來進行介紹的,兄弟們末慌!
gogogo!