SprintBoot系列(十一):JPA实战视图对象Vo层

Vo 视图对象,用于展示层,它的作用是把某个指定页面(或组件)的所有数据封装起来。

先来做一个简单的Vo例子

model:SpuEntity实体类的代码

package com.fcors.fcors.model;

import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;
import java.sql.Timestamp;
import java.util.Objects;

@Getter
@Setter
@Entity
@Table(name = "spu")
public class SpuEntity extends BaseEntity{
    @Id
    @Column(name = "id")
    private Long id;
    private String title;
    private String subtitle;
    private Long categoryId;
    private Integer rootCategoryId;
    private byte online;
    private String price;
    private Integer sketchSpecId;
    private Integer defaultSkuId;
    private String img;
    private String discountPrice;
    private String description;
    private String tags;
    private Byte isTest;
    private String spuThemeImg;
    private String forThemeImg;

}

如果我们需要在展示的时候,只展示id/title/subtitle/online如何通过vo处理呢?

1.1创建一个SpuSimplifyVO

在项目中创建一个package,并命名为vo

在vo下面创建一个SpuSimplifyVO的类,把需要显示的列上去。切记添加上getter和setter,不然无法序列化

package com.fcors.fcors.vo;

import lombok.Getter;
import lombok.Setter;
import java.util.Date;

@Getter
@Setter
public class SpuSimplifyVO {
    private Long id;
    private String title;
    private String subtitle;
    private byte online;
    /*将在baseEntity不显示的属性显示出来*/
    private Date createTime;
    private Date updateTime;
    private Date deleteTime;
}

1.2在SpuController中追加一个入口测试

package com.fcors.fcors.api.sample.v1;


import com.fcors.fcors.exception.NotFoundException;
import com.fcors.fcors.model.SpuEntity;
import com.fcors.fcors.service.SpuService;
import com.fcors.fcors.vo.SpuSimplifyVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
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.RestController;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Positive;
import java.util.List;

@RestController
@RequestMapping("/spu/")
@Validated
public class SpuController {
    @Autowired
    private SpuService spuService;

    @GetMapping("/id/{id}/detailTest")
    public SpuSimplifyVO findOneByIdFiltter(@PathVariable @Positive Long id){
        SpuSimplifyVO spu = spuService.findOneByIdFiltter(id);
        if(spu ==null){
            throw new NotFoundException(30003);
        }
        return spu;
    }

}
JAVA、基础技术、技术与框架SprintBoot系列(十一):JPA实战视图对象Vo层插图

从结果我们可以得到只想要的部分,并且之前因为添加@ JsonIgnore 注解不显示的成员变量也显示出来了。但如上例子有一个问题,他无法进行List的序列化。

demo2:vo的一对多输出单一对象

根据逻辑Spu与Sku、SpuImgList、SpuDetailImg都是存在一对多的关系,我们修改model。

2.1model下的Spu代码。为了不影响性能,多方都是通过懒加载方法。

package com.fcors.fcors.model;

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;
import java.sql.Timestamp;
import java.util.List;
import java.util.Objects;

@Getter
@Setter
@Entity
@Table(name = "spu")
public class SpuEntity extends BaseEntity{
    @Id
    @Column(name = "id")
    private Long id;
    private String title;
    private String subtitle;
    private Long categoryId;
    private Integer rootCategoryId;
    private byte online;
    private String price;
    private Integer sketchSpecId;
    private Integer defaultSkuId;
    private String img;
    private String discountPrice;
    private String description;
    private String tags;
    private Byte isTest;
    private String spuThemeImg;
    private String forThemeImg;

    @OneToMany(fetch=FetchType.LAZY)
    @JoinColumn(name="spuId")
    private List<SkuEntity> skuEntityList;

    @OneToMany(fetch=FetchType.LAZY)
    @JoinColumn(name="spuId")
    @JsonIgnore
    private List<SpuImgEntity> spuImgEntities;

    @OneToMany(fetch=FetchType.LAZY)
    @JoinColumn(name="spuId")
    @JsonIgnore
    private List<SpuDetailImgEntity> spuDetailImgEntityList;

}

如果使用懒加载的话,需要注意:

2.2在service层添加@Transactional注解

2.3pom.xml引入组件

<dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-hibernate5</artifactId>
            <version>2.9.8</version>
 </dependency>

2.3.2 yml 配置

spring:
    jpa:
        properties:
            hibernate:
                  enable_lazy_load_no_trans: true

2.4vo下面的SpuSimplifyVO代码

package com.fcors.fcors.vo;

import com.fcors.fcors.model.SkuEntity;
import com.fcors.fcors.model.SpuDetailImgEntity;
import com.fcors.fcors.model.SpuImgEntity;
import lombok.Getter;
import lombok.Setter;
import java.util.Date;
import java.util.List;

@Getter
@Setter
public class SpuSimplifyVO {
    private Long id;
    private String title;
    private String subtitle;
    private byte online;
    /*将在baseEntity不显示的属性显示出来*/
//    private Date createTime;
//    private Date updateTime;
//    private Date deleteTime;

    private List<SkuEntity> skuEntityList;

    private List<SpuImgEntity> spuImgEntities;
//
//
//    private List<SpuDetailImgEntity> spuDetailImgEntities;
}

如果接口出现以下错误

Resolved [org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class gani.vankek3api.vo.VankeSaloutStockSimplifyVO]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No s

就是vo的类没有添加上@Getter和@Setter

此时执行接口,我们发现会提醒报错

 WARN 2956 --- [nio-8082-exec-1] .m.m.a.ExceptionHandlerExceptionResolver : Resolved 
[org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: failed to lazily initialize a collection of role: com.fcors.fcors.model.SpuEntity.spuImgEntities, could not initialize proxy - no Session; nested exception is com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role: com.fcors.fcors.model.SpuEntity.spuImgEntities, could not initialize proxy - no Session (through reference chain: com.fcors.fcors.vo.SpuSimplifyVO["SpuImgEntities"])]

该如何处理呢?

2.5引入dozer-core库

修改pom.xml文件

<dependency>
	<groupId>com.github.dozermapper</groupId>
	<artifactId>dozer-core</artifactId>
	<version>6.5.0</version>
</dependency>

2.6service的代码:findOneByIdFiltter()

package com.fcors.fcors.service;

import com.fcors.fcors.model.SkuEntity;
import com.fcors.fcors.model.SpuEntity;
import com.fcors.fcors.model.SpuImgEntity;
import com.fcors.fcors.repository.SpuRepository;
import com.fcors.fcors.vo.SpuSimplifyVO;
import com.github.dozermapper.core.DozerBeanMapperBuilder;
import com.github.dozermapper.core.Mapper;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;
@Transactional
@Service
public class SpuServiceImpl implements SpuService{

    @Autowired
    SpuRepository spuRepository;

    @Override
    public SpuEntity findOneById(Long id) {
        SpuEntity spuEntity = this.spuRepository.findOneById(id);

        return spuEntity;
    }
    /** 返回一个SpuSimplifyVO **/
    @Override
    public SpuSimplifyVO findOneByIdFiltter(Long id) {
        SpuEntity spuEntity = this.spuRepository.findOneById(id);
        SpuSimplifyVO vo = new SpuSimplifyVO();
        BeanUtils.copyProperties(spuEntity,vo);
        return vo;
    }
    @Override
    public List<SpuEntity> getLastPagingSpu() {
        return this.spuRepository.findAll();
    }
}

2.7修改SpuController:findOneByIdFiltter()

package com.fcors.fcors.api.sample.v1;


import com.fcors.fcors.exception.NotFoundException;
import com.fcors.fcors.model.SpuEntity;
import com.fcors.fcors.service.SpuService;
import com.fcors.fcors.vo.SpuSimplifyVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
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.RestController;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Positive;
import java.util.List;

@RestController
@RequestMapping("/spu/")
@Validated
public class SpuController {
    @Autowired
    private SpuService spuService;
    @GetMapping("/id/{id}/detail")
    public SpuEntity getOneByName(@PathVariable @Positive Long id){
        SpuEntity spu = spuService.findOneById(id);
        if(spu ==null){
            throw new NotFoundException(30003);
        }
        return spu;
    }

    /** 返回一个SpuSimplifyVO **/
    @GetMapping("/id/{id}/detailTest")
    public SpuSimplifyVO findOneByIdFiltter(@PathVariable @Positive Long id){
        SpuSimplifyVO spu = spuService.findOneByIdFiltter(id);
        if(spu ==null){
            throw new NotFoundException(30003);
        }
        return spu;
    }

    @GetMapping("/latest")
    public List<SpuEntity> getLastPagingSpu(){
        List<SpuEntity> SpuList = this.spuService.getLastPagingSpu();
        return SpuList;
    }
}

被 @JsonIgnore 注释的两个List不序列化。

JAVA、基础技术、技术与框架SprintBoot系列(十一):JPA实战视图对象Vo层插图1

测试Test接口,我们不难发现通过VO层可以把@JsonIgnore spuImgEntities也能成功输出。

JAVA、基础技术、技术与框架SprintBoot系列(十一):JPA实战视图对象Vo层插图2

demo3:vo输出分页List

之前的案例,我们都是findone,返回一条数据,如果返回时多条数据的时候,vo该如何处理?

3.1、修改SpuservicesImpl:getLastPagingSpu()

package com.fcors.fcors.service;

import com.fcors.fcors.model.SkuEntity;
import com.fcors.fcors.model.SpuEntity;
import com.fcors.fcors.model.SpuImgEntity;
import com.fcors.fcors.repository.SpuRepository;
import com.fcors.fcors.vo.SpuSimplifyVO;
import com.github.dozermapper.core.DozerBeanMapperBuilder;
import com.github.dozermapper.core.Mapper;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.List;
@Transactional
@Service
public class SpuServiceImpl implements SpuService{

    @Autowired
    SpuRepository spuRepository;

    @Override
    public SpuEntity findOneById(Long id) {
        SpuEntity spuEntity = this.spuRepository.findOneById(id);

        return spuEntity;
    }
    @Override
    public SpuSimplifyVO findOneByIdFiltter(Long id) {
        SpuEntity spuEntity = this.spuRepository.findOneById(id);
        SpuSimplifyVO vo = new SpuSimplifyVO();
        BeanUtils.copyProperties(spuEntity,vo);
        return vo;
    }
    @Override
    public List<SpuSimplifyVO> getLastPagingSpu() {
        Mapper mapper = DozerBeanMapperBuilder.buildDefault();
        List<SpuEntity> spuEntities = this.spuRepository.findAll();
        List<SpuSimplifyVO> vos = spuEntities.stream()
                .map(s->{
                    return mapper.map(s,SpuSimplifyVO.class);
                })
                .collect(Collectors.toList());

//        List<SpuSimplifyVO> vos = new ArrayList<>();
//        spuEntities.forEach(s->{
//            SpuSimplifyVO vo = mapper.map(s,SpuSimplifyVO.class);
//            vos.add(vo);
//        });
        return vos;
    }
}

3.2、修改controller:getLastPagingSpu()

package com.fcors.fcors.api.sample.v1;


import com.fcors.fcors.exception.NotFoundException;
import com.fcors.fcors.model.SpuEntity;
import com.fcors.fcors.service.SpuService;
import com.fcors.fcors.vo.SpuSimplifyVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
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.RestController;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Positive;
import java.util.List;

@RestController
@RequestMapping("/spu/")
@Validated
public class SpuController {
    @Autowired
    private SpuService spuService;
    @GetMapping("/id/{id}/detail")
    public SpuEntity getOneByName(@PathVariable @Positive Long id){
        SpuEntity spu = spuService.findOneById(id);
        if(spu ==null){
            throw new NotFoundException(30003);
        }
        return spu;
    }


    @GetMapping("/id/{id}/detailTest")
    public SpuSimplifyVO findOneByIdFiltter(@PathVariable @Positive Long id){
        SpuSimplifyVO spu = spuService.findOneByIdFiltter(id);
        if(spu ==null){
            throw new NotFoundException(30003);
        }
        return spu;
    }

    @GetMapping("/latest")
    public List<SpuSimplifyVO> getLastPagingSpu(){
        List<SpuSimplifyVO> SpuList = this.spuService.getLastPagingSpu();
        return SpuList;
    }
}

上述代码存在一个问题,list的分页和排序问题。

4、Vo层更高级的用法:处理数据

Vo层还有一个更高级的用法,用于处理我们想要的数据。下面以category为例子。

以下是 category的数据表结构。is_root=1代表是一级分类;

JAVA、基础技术、技术与框架SprintBoot系列(十一):JPA实战视图对象Vo层插图3

根据上面的数据结构,如果我们现在需要一个json,格式{“main”:[manidList],”children”:[{chlidrenList}]}

4.1、创建一个CategoryController

package com.fcors.fcors.api.sample.v1;

import com.fcors.fcors.service.CategoryService;
import com.fcors.fcors.vo.CategoriesAllVO;
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.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("category")
@RestController
@ResponseBody
public class CategoryController {

    @Autowired
    private CategoryService categoryService;


    @GetMapping("/all")
    public CategoriesAllVO getAll(){
        CategoriesAllVO categories = categoryService.getAll();
        return categories;
    }
}

4.2、创建一个Category的model

package com.fcors.fcors.model;

import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;
import java.sql.Timestamp;
import java.util.Date;
import java.util.Objects;
@Getter
@Setter
@Entity
@Table(name = "category")
public class CategoryEntity extends BaseEntity{
    @Id
    @Column(name = "id")
    private Long id;
    private String name;
    private String description;
    private Date createTime;
    private Date updateTime;
    private Date deleteTime;
    private Boolean isRoot;
    private Long parentId;
    private String img;
    private Long index;
    private Long online;
    private Long level;

}

4.2、在VO下面创建一个CategoryPureVO

我们之前创建的Vo过滤是通过在service中处理的,其实也可以在Vo中直接处理。

JAVA、基础技术、技术与框架SprintBoot系列(十一):JPA实战视图对象Vo层插图4
package com.fcors.fcors.vo;

import com.fcors.fcors.model.CategoryEntity;
import lombok.Getter;
import lombok.Setter;
import org.springframework.beans.BeanUtils;

@Getter
@Setter
public class CategoryPureVO {
    private Long id;
    private String name;
    private Boolean isRoot;
    private String img;
    private Long parentId;
    private Long index;

    public CategoryPureVO(CategoryEntity category) {
        BeanUtils.copyProperties(category, this);
    }
}
JAVA、基础技术、技术与框架SprintBoot系列(十一):JPA实战视图对象Vo层插图5

4.3、在repository创建categoryRepository

package com.fcors.fcors.repository;

import com.fcors.fcors.model.CategoryEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;
@Repository
public interface CategoryRepository extends JpaRepository<CategoryEntity,Long> {

    List<CategoryEntity>  findAllByIsRootOrderByIndexAsc(boolean is_root);

}

4.3、在vo下面创建一个CategoriesAllVO

通过VO的构造函数,进行数据的处理。

package com.fcors.fcors.vo;

import com.fcors.fcors.model.CategoryEntity;
import lombok.Getter;
import lombok.Setter;

import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;

@Getter
@Setter
public class CategoriesAllVO {
    private List<CategoryPureVO> roots;
    private List<CategoryPureVO> subs;

    public CategoriesAllVO(Map<Integer, List<CategoryEntity>> map) {
        /*
        this.roots = map.get(1).stream().map(r-> {
            return new CategoryPureVO(r);
        }).collect(Collectors.toList());
        */
        this.roots = map.get(1).stream()
                .map(CategoryPureVO::new)
                .collect(Collectors.toList());
        this.subs = map.get(2).stream()
                .map(CategoryPureVO::new)
                .collect(Collectors.toList());
    }
}

如果不熟悉stream可以快速学习

https://www.fcors.com/%e6%8a%80%e6%9c%af%e4%b8%8e%e6%a1%86%e6%9e%b6/java%E4%B9%8B%E6%B7%B1%E5%BA%A6%E6%8E%8C%E6%8F%A1java-stream-%E6%B5%81%E6%93%8D%E4%BD%9C/

4.5、 创建service

接口:CategoryService

package com.fcors.fcors.service;

import com.fcors.fcors.vo.CategoriesAllVO;

public interface CategoryService {
    CategoriesAllVO getAll();
}

方法:CategoryServiceImpl

package com.fcors.fcors.service;

import com.fcors.fcors.model.CategoryEntity;
import com.fcors.fcors.repository.CategoryRepository;
import com.fcors.fcors.vo.CategoriesAllVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class CategoryServiceImpl implements CategoryService {
    @Autowired
    private CategoryRepository categoryRepository;
    public CategoriesAllVO getAll() {
        List<CategoryEntity> roots = categoryRepository.findAllByIsRootOrderByIndexAsc(true);
        List<CategoryEntity> subs = categoryRepository.findAllByIsRootOrderByIndexAsc(false);
        Map<Integer, List<CategoryEntity>> categories = new HashMap<>();
        categories.put(1, roots);
        categories.put(2, subs);
        CategoriesAllVO categoriesAllVO = new CategoriesAllVO(categories);
        return categoriesAllVO;
    }
}

测试结果

JAVA、基础技术、技术与框架SprintBoot系列(十一):JPA实战视图对象Vo层插图6

成功返回!

下面开始讲解关于JPA的分页和排序

http://www.fcors.com/%E6%8A%80%E6%9C%AF%E4%B8%8E%E6%A1%86%E6%9E%B6/sprintboot%E7%B3%BB%E5%88%97%EF%BC%88%E5%8D%81%E4%BA%8C%EF%BC%89%EF%BC%9Aorm%E7%9A%84%E5%88%86%E9%A1%B5%E5%92%8C%E6%8E%92%E5%BA%8F%E5%AE%9E%E6%88%98