关系映射-二次查询方案
一对一 、一对多关系映射,是在实体类中通过“二次查询”来实现。不通过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工具 在一个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中的实体对象的特点是:
- 多实例的(每次请求都会new新的实体对象)
- 有状态的(实体对象可能已经调用getProductCategory()方法建立了一对一、一对多的映射关系)
- 生命短暂的(一个request请求结束了,本次请求产生的所有实体对象的生命就结束了)
- 不有复用 (不能多次请求之间共享同个实体对象)
从二级缓存中获取的实体对象的特点是:
- 每次获取的实体对象都是克隆的一份 (相当于new,修改实体对象的属性是安全的)
- 实体对象未建立一对一、一对多的映射关系 (只有调用getProductCategory()方法时才建立映射关系)
由于 实体对象生命是短暂的,所有缓存是安全的,也就不需要清理缓存了。实体对象与二级缓存和谐共存。