关系映射-二次查询方案
一对一 、一对多关系映射,是在实体类中通过“二次查询”来实现。不通过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()方法时才建立映射关系)
由于 实体对象生命是短暂的,所有缓存是安全的,也就不需要清理缓存了。实体对象与二级缓存和谐共存。