实体类 ProductCategory
过程同上,这里不赘述了 ,代码如下
domain实体类
package com.artisan.product.domain; import lombok.Data; import javax.persistence.*; import java.util.Date; @Data @Table(name = "product_category") @Entity public class ProductCategory { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private String categoryId; private String categoryName; private Integer categoryType; private Date createTime; private Date updateTime; }
Dao接口
package com.artisan.product.repository; import com.artisan.product.domain.ProductCategory; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; public interface ProductCategoryRepository extends JpaRepository<ProductCategory, String> { List<ProductCategory> findByCategoryTypeIn(List<Integer> categoryTypeList); }
单元测试
package com.artisan.product.repository; import com.artisan.product.domain.ProductCategory; import com.netflix.discovery.converters.Auto; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.Arrays; import java.util.List; import static org.junit.Assert.*; @RunWith(SpringRunner.class) @SpringBootTest public class ProductCategoryRepositoryTest { @Autowired private ProductCategoryRepository productCategoryRepository; @Test public void findByCategoryTypeIn() { List<ProductCategory> list = productCategoryRepository.findByCategoryTypeIn(Arrays.asList(99, 98, 97)); Assert.assertEquals(3,list.size()); } }
Service层
ProductService 接口
package com.artisan.product.service; import com.artisan.product.domain.Product; import java.util.List; public interface ProductService { // 查询上架商品 List<Product> getAllUpProduct(); }
ProductService 接口实现类
package com.artisan.product.service.impl; import com.artisan.product.domain.Product; import com.artisan.product.enums.ProductStatusEnum; import com.artisan.product.repository.ProductRepository; import com.artisan.product.service.ProductService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class ProductServiceImpl implements ProductService { @Autowired private ProductRepository productRepository; @Override public List<Product> getAllUpProduct() { return productRepository.findByProductStatus(ProductStatusEnum.UP.getCode()); } }
ProductStatusEnum
为了方便,将状态封装到了Enum中
package com.artisan.product.enums; import lombok.Getter; @Getter public enum ProductStatusEnum { UP(0,"上架"), DOWN(1,"下架"); private int code ; private String msg; ProductStatusEnum(int code, String msg){ this.code = code; this.msg = msg; } }
对接口进行单元测试
package com.artisan.product.service; import com.artisan.product.ArtisanProductApplicationTests; import com.artisan.product.domain.Product; import com.artisan.product.enums.ProductStatusEnum; import com.artisan.product.repository.ProductRepository; import org.junit.Assert; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.List; import static org.junit.Assert.*; @Component public class ProductServiceTest extends ArtisanProductApplicationTests { @Autowired private ProductRepository productRepository; @Test public void getAllUpProduct() { List<Product> list = productRepository.findByProductStatus(ProductStatusEnum.UP.getCode()); Assert.assertEquals(3,list.size()); } }
ProductCategoryService 接口
package com.artisan.product.service; import com.artisan.product.domain.ProductCategory; import java.util.List; public interface ProductCategoryService { List<ProductCategory> findByCategoryTypeIn(List<Integer> categoryTypeList); }
ProductCategoryService 接口实现类
package com.artisan.product.service.impl; import com.artisan.product.domain.ProductCategory; import com.artisan.product.repository.ProductCategoryRepository; import com.artisan.product.service.ProductCategoryService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class ProductCategoryServiceImpl implements ProductCategoryService { @Autowired private ProductCategoryRepository productCategoryRepository; @Override public List<ProductCategory> findByCategoryTypeIn(List<Integer> categoryTypeList) { return productCategoryRepository.findByCategoryTypeIn(categoryTypeList); } }
单元测试
package com.artisan.product.service.impl; import com.artisan.product.ArtisanProductApplicationTests; import com.artisan.product.domain.ProductCategory; import com.artisan.product.repository.ProductCategoryRepository; import org.junit.Assert; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.Arrays; import java.util.List; import static org.junit.Assert.*; @Component public class ProductCategoryServiceImplTest extends ArtisanProductApplicationTests { @Autowired private ProductCategoryRepository productCategoryRepository; @Test public void findByCategoryTypeIn() { List<ProductCategory> list = productCategoryRepository.findByCategoryTypeIn(Arrays.asList(99,98,97)); Assert.assertEquals(3,list.size()); } }
Controller层
先来观察下,返回给前端的数据
code , msg , 泛型的data 是最外层的数据,那封装下吧 。 可以理解为也是一个VO(View Object)对象,包含3个节点(code msg 泛型的data)
同时data节点 [] ,自然是个数组了,可包含多个{}对象。
按照上图的划分,也把这些信息封装成VO吧。
为了避免引起误解,我们把
改为products .
VO封装
ResultVO 前后台交互的统一格式模板
package com.artisan.product.vo; import lombok.Getter; @Getter public class Result<T> { private Integer code ; private String msg ; private T data; /** * 成功时候的调用 * */ public static <T> Result<T> success(T data){ return new Result<T>(data); } private Result(T data) { this.code = 0; this.msg = "success"; this.data = data; } /** * 失败时候的调用 * */ public static <T> Result<T> error(ErrorCodeMsg cm){ return new Result<T>(cm); } private Result(ErrorCodeMsg cm) { if(cm == null) { return; } this.code = cm.getCode(); this.msg = cm.getMsg(); } }
用到了 ErrorCodeMsg
package com.artisan.product.vo; import lombok.Getter; @Getter public class ErrorCodeMsg { private int code; private String msg; // 异常 public static ErrorCodeMsg SERVER_ERROR = new ErrorCodeMsg(-1, "服务端异常"); private ErrorCodeMsg(int code, String msg) { this.code = code; this.msg = msg; } }
ProductVO :返回给前台的商品信息格式,包含目录信息
package com.artisan.product.vo; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import java.util.List; @Data public class ProductVO { // @JsonProperty注解用于属性上,作用是把该属性的名称序列化为另外一个名称, // 如把categoryName属性序列化为name // 【这里约定给前台返回的节点名为name, 但是为了方便理解这个name到底是什么的name,在vo中定义了方便理解的属性名】 @JsonProperty("name") private String categoryName; @JsonProperty("type") private Integer categoryType; // 因为这个节点下可能返回多个ProductInfoVO,因此定义一个List集合 @JsonProperty("products") private List<ProductInfoVO> productInfoVOList ; }
ProductInfoVO 具体产品的数据VO
package com.artisan.product.vo; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import java.math.BigDecimal; @Data public class ProductInfoVO { @JsonProperty("id") private String productId; @JsonProperty("name") private String productName; @JsonProperty("price") private BigDecimal productPrice; @JsonProperty("description") private String productDescription; @JsonProperty("icon") private String productIcon; }
Controller层逻辑
分析约定的前后台交互的JSON格式:
- 每个ProductVO中我们需要获取产品目录名称及产品目录的category_type ,
调用ProductCategoryService#categoryService方法即可。categoryService - 的入参为categoryTypeList,因此需要调用ProductService#getAllUpProduct获取所有上架商品对应的categoryType.
- 获取到了后台的数据后,按照约定的格式拼装返回JSON串即可
package com.artisan.product.controller; import com.artisan.product.domain.Product; import com.artisan.product.domain.ProductCategory; import com.artisan.product.service.ProductCategoryService; import com.artisan.product.service.ProductService; import com.artisan.product.vo.ProductInfoVO; import com.artisan.product.vo.ProductVO; import com.artisan.product.vo.Result; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @RestController @RequestMapping("/product") public class ProductController { @Autowired private ProductService productService; @Autowired private ProductCategoryService categoryService; @GetMapping("/list") private Result list() { //1. 查询所有在架的商品 List<Product> productInfoList = productService.getAllUpProduct(); //2. 获取类目type列表 List<Integer> categoryTypeList = productInfoList.stream() .map(Product::getCategoryType) .collect(Collectors.toList()); //3. 从数据库查询类目 List<ProductCategory> categoryList = categoryService.findByCategoryTypeIn(categoryTypeList); //4. 构造数据 List<ProductVO> productVOList = new ArrayList<>(); for (ProductCategory productCategory : categoryList) { ProductVO productVO = new ProductVO(); // 设置属性 productVO.setCategoryName(productCategory.getCategoryName()); productVO.setCategoryType(productCategory.getCategoryType()); // ProductInfoVO 集合 List<ProductInfoVO> productInfoVOList = new ArrayList<>(); for (Product product : productInfoList) { // 挂到对应的的categoryType下 if (product.getCategoryType().equals(productCategory.getCategoryType())) { ProductInfoVO productInfoVO = new ProductInfoVO(); // 将属性copy到productInfoVO,避免逐个属性set,更简洁 BeanUtils.copyProperties(product, productInfoVO); productInfoVOList.add(productInfoVO); } } productVO.setProductInfoVOList(productInfoVOList); productVOList.add(productVO); } return Result.success(productVOList); } }
启动测试
访问 http://localhost:8080/product/list
{ "code": 0, "msg": "success", "data": [ { "name": "热饮", "type": 99, "products": [ { "id": "1", "name": "拿铁咖啡", "price": 20.99, "description": "咖啡,提神醒脑", "icon": null }, { "id": "3", "name": "卡布奇诺", "price": 15, "description": "卡布奇诺的香味", "icon": null } ] }, { "name": "酒水", "type": 98, "products": [ { "id": "2", "name": "青岛纯生", "price": 7.5, "description": "啤酒", "icon": null } ] } ] }
格式化看下更加直观:
知识点总结
Java8的Stream
//2. 获取类目type列表 List<Integer> categoryTypeList = productInfoList.stream() .map(Product::getCategoryType) .collect(Collectors.toList());
使用Java8中的Stream可以方便的对集合对象进行各种便利、高效的聚合操作,或者大批量数据操作。
map生成的是个一对一映射,相当于for循环
注意:流只能使用一次,使用结束之后,这个流就无法使用了。
Beanutils.copyProperties( )
// 将属性copy到productInfoVO,避免逐个属性set,更简洁 BeanUtils.copyProperties(product, productInfoVO);
org.springframework.beans.BeanUtils# copyProperties作用是将一个Bean对象中的数据封装到另一个属性结构相似的Bean对象中。
同时org.apache.commons.beanutils.BeanUtils也有个copyProperties
需要注意的是这俩的copyProperties方法参数位置不同
org.springframework.beans.BeanUtils#copyProperties(sourceDemo, targetDemo) org.apache.commons.beanutils.BeanUtils#copyProperties(targetDemo, sourceDemo)
Github地址
https://github.com/yangshangwei/springcloud-o2o/tree/master/artisan-product








