关系映射-二次查询方案

一对一 、一对多关系映射,是在实体类中通过“二次查询”来实现。不通过join on连表的方式来实现,要得到的好处是:可实现延迟加载、可使用mybatis的二级缓存。

一对一:用selectById方法进行二次查询。逻辑在实体类上

一对多:用selectByIdIn,或条件查询出来个List结果集来实现。逻辑在实体类上

多对多:拆分成两个一对多,多对多有“中间表”,用selectByIdIn()进行二次查询来实现。

延迟加载

一对一延迟加载:po实体对象A中包含另一个po实体对象B,这就是一对一。现在已得到一个A的实例,调用A的getB()方法时,再去库中按ID查询出B实体,这就是延迟加载。
一对多延迟加载:po实体对象A中包含另一个po实体对象B的List,这就是一对多。现在已得到一个A的实例,调用A的getBList()方法时,再去库中按ID查询出B实体的List,这就是延迟加载。 使用 id in(…)这种SQL只查一次。

延迟加载的重要意义

CMS程序开发了一套标签库,可使前端页面制作人员与后端开发人员工作分离,前端页面制作人员,直接做静态面,再用标签库从后端取数据填充到模板上,实现动态网站。前提:po实体对象A中包含另一个po实体对象B,并未使用延迟加载,从库中查询A同时也查询出了B。动作:前端页面制作人员现在正在做的XX页面,页面只需要显示A对象的值,而不需要显示B对象。结果:B对象是多余的,白白从数据中查出来了,白白花了查询时间。若对象A中包含多个其它实体对象,问题就更严重了。A是主对象B是从对象,前端页面制作人员会使用哪些从对象是不确定的,并且程序已执行了页面渲染这一步。所以需要延迟加载,问题就都解决了。

1+N问题

解决1+N问题:po实体对象A中包含另一个po实体对象B,现在已得到10个A的实例放在一个List,每次调用A的getB()方法时会产生一条SQL,产生了1+10条SQL,与数据库通信次数太多,性能不高,需要优化。 先取出10个A的ID,使用id in(…)去查出10个B,再做A与B的组装工作。这样产生了1+1条SQL,与数据为通信次数为2 ,满意了。

映射关系写有实体的子类上

ProductSpu商品 Entity 子类,请把你的业务代码写在这里,ProductSpuBase是父类。

public class ProductSpu extends ProductSpuBase<ProductSpu>

与“商品业务”有关的映射关系代码,都写在ProductSpu实体类里的getXxx()方法中。有益于做缓存、有益于延迟加载、有益于代码复用、与底层的全单表操作相兼容。

一对一映射
 private ProductCategory productCategory; //一个商品--商品分类
 public ProductCategory getProductCategory() {
 if(productCategory==null){
   ProductCategoryService service=SpringContextHolder.getBean(ProductCategoryService.class);
   productCategory=service.selectById(this.getCategoryId());
  }
 return productCategory;
 }

一对多映射
 //一对多映射
 private List<ProductSku> skuList;//一个商品--多个SKU
 public List<ProductSku> getProductSkuList() {
  if(skuList==null){
   ProductSkuService service=SpringContextHolder.getBean(ProductSkuService.class);
   skuList= service.selectByWhere(new Wrapper().and("p_id=",this.getPId()).orderBy("sort acs"));//排序
  }
 return skuList;
 }
 //一对多映射
 private List<ProductParamMapping> productParamList;//一个商品--多个参数
 public List<ProductParamMapping> getProductParamList() {
 if(productParamList==null){
   ProductParamMappingService service=SpringContextHolder.getBean(ProductParamMappingService.class);
   productParamList= service.selectByWhere(new Wrapper().and("p_id=",this.getPId()).orderBy("sort acs"));//排序
  }
 return productParamList;
 }

多对多映射
 //多对多(一对多+idIn来实现)
 private List<StoreAlbumPicture> storeAlbumPictureList;//一个商品--多个图片,一个图片--多个商品
 public List<?> getStoreAlbumPictureList(){
 if(storeAlbumPictureList==null){
   ProductPictureMappingService service=SpringContextHolder.getBean(ProductPictureMappingService.class);
   List<ProductPictureMapping> mappinglist=service.selectByWhere(new Wrapper().and("p_id=",this.getPId()).orderBy("sort acs"));//排序
   List<Object> ids=batchField(mappinglist,"imgId");//批量调用对象的getXxx()方法
   StoreAlbumPictureService  service2=SpringContextHolder.getBean(StoreAlbumPictureService.class);
 storeAlbumPictureList=service2.selectByIdIn(ids);
  }
 return storeAlbumPictureList;
 }

ListIdIn工具,循环填充
 //ListIdIn工具  在一个list中做 一对一,10个商品对10个分类
 //填充 xxx,把1+N改成1+1
 public static void fillProductCategory(List<ProductSpu> list){
  List<Object> ids=batchField(list,"categoryId");//批量调用对象的getXxx()方法
  ProductCategoryService service=SpringContextHolder.getBean(ProductCategoryService.class);
  List<ProductCategory> productCategorylist=service.selectByIdIn(ids);
  fill(productCategorylist,"categoryId",list,"categoryId","productCategory");//循环填充
 }

关系映射与二级缓存

实体对象与二级缓存和谐共存

问:如何解决productCategory缓存的清理时机问题呢?

答:通过实体对象短暂生命的来解决缓存的清理问题。

一对一 、一对多关系映射,是在实体类中通过“二次查询”来实现(下例的getProductCategory()方法)。不通过join in连表的方式来实现,要得到的好处是:可实现延迟加载、可使用mybatis的二级缓存。

一个简单的示例请看一下的代码:

一对一映射
 private ProductCategory productCategory; //一个商品--商品分类
 public ProductCategory getProductCategory() {
 if(productCategory==null){
   ProductCategoryService service=SpringContextHolder.getBean(ProductCategoryService.class);
   productCategory=service.selectById(this.getCategoryId());
  }
 return productCategory;
 }

请注意第一行 private ProductCategory productCategory;

属性productCategory 起到了“缓存”的作用,这个“缓存”什么时间被清理呢?

第一次调用getProductCategory()方法时,productCategory缓存为空,会执行selectById(id),查询数据库,也会与二级缓存打交道。

第二次调用getProductCategory()方法时,由于 productCategory缓存中有值,而直接返回了。

因为还有配套的setProductCategory(xxx)方法存在,所以productCategory属性不能删除。

FDP中的实体对象的特点是:

  1. 多实例的(每次请求都会new新的实体对象)
  2. 有状态的(实体对象可能已经调用getProductCategory()方法建立了一对一、一对多的映射关系)
  3. 生命短暂的(一个request请求结束了,本次请求产生的所有实体对象的生命就结束了)
  4. 不有复用 (不能多次请求之间共享同个实体对象)

从二级缓存中获取的实体对象的特点是:

  1. 每次获取的实体对象都是克隆的一份 (相当于new,修改实体对象的属性是安全的)
  2. 实体对象未建立一对一、一对多的映射关系  (只有调用getProductCategory()方法时才建立映射关系)

由于 实体对象生命是短暂的,所有缓存是安全的,也就不需要清理缓存了。实体对象与二级缓存和谐共存。