这一节的主要内容是完成规格参数的列表查询功能。
一,新增product/attr/base/list接口
这个接口用来查询规格参数列表。
@RequestMapping("/base/list/{catelogId}")
public R list(@RequestParam Map<String, Object> params,@PathVariable Long catelogId){
PageUtils page = attrService.queryBaseAttrPage(params, catelogId);
return R.ok().put("page", page);
}
考虑到前端展示属性信息包含其所属分类和分组信息,我们用一个VO来封装响应数据。
后端实现如下。
public PageUtils queryBaseAttrPage(Map<String, Object> params, Long catelogId) {
QueryWrapper<AttrEntity> queryWrapper = new QueryWrapper<>();
if(catelogId != 0){
queryWrapper.eq("catelog_id",catelogId);
}
String key = (String) params.get("key");
if(StrUtil.isNotEmpty(key)){
//attr_id attr_name
queryWrapper.and((wrapper)->{
wrapper.eq("attr_id",key).or().like("attr_name",key);
});
}
IPage<AttrEntity> page = this.page(
new Query<AttrEntity>().getPage(params),
queryWrapper
);
PageUtils pageUtils = new PageUtils(page);
List<AttrEntity> records = page.getRecords();
List<AttrRespVo> respVos = records.stream().map((attrEntity) -> {
AttrRespVo attrRespVo = new AttrRespVo();
BeanUtils.copyProperties(attrEntity, attrRespVo);
//1、设置分类和分组的名字
AttrAttrgroupRelationEntity attrId = relationDao.selectOne(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attrEntity.getAttrId()));
if (attrId != null && attrId.getAttrGroupId()!=null) {
AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrId.getAttrGroupId());
attrRespVo.setGroupName(attrGroupEntity.getAttrGroupName());
}
CategoryEntity categoryEntity = categoryDao.selectById(attrEntity.getCatelogId());
if (categoryEntity != null) {
attrRespVo.setCatelogName(categoryEntity.getName());
}
return attrRespVo;
}).collect(Collectors.toList());
pageUtils.setList(respVos);
return pageUtils;
在这段代码中,有一个开发中需要遵循的原则:避免使用JOIN查询以提高性能。
在数据库查询中,JOIN操作可能导致性能问题,尤其是涉及多表关联时。JOIN操作会增加查询复杂度,可能导致大量的磁盘I/O操作和CPU计算开销,尤其是在处理大数据量时。此外,JOIN查询还可能因索引失效而进一步降低查询效率,尤其是在没有适当索引支持的情况下。因此,减少JOIN操作是提高查询性能的有效手段之一。
具体来说,代码通过在关联表中冗余存储分类名称和分组名称,从而避免了在查询时进行表的JOIN操作。
代码首先通过QueryWrapper构建查询条件,并执行分页查询获取AttrEntity对象列表。随后,遍历这些对象,通过额外的查询操作来获取分类名称和分组名称,而不是直接通过JOIN操作获取这些信息。
这种做法的必要性在于,JOIN操作可能会导致性能瓶颈,尤其是在涉及多表关联查询的情况下。
二,踩坑记录
启动项目失败,原因是出现了循环依赖。
The dependencies of some of the beans in the application context form a cycle:
attrController (field private com.atguigu.gulimall.product.service.AttrService com.atguigu.gulimall.product.controller.AttrController.attrService)
↓
attrService (field com.atguigu.gulimall.product.service.CategoryService com.atguigu.gulimall.product.service.impl.AttrServiceImpl.categoryService)
↓
categoryService (field com.atguigu.gulimall.product.service.CategoryBrandRelationService com.atguigu.gulimall.product.service.impl.CategoryServiceImpl.categoryBrandRelationService)
┌─────┐
| categoryBrandRelationService (field com.atguigu.gulimall.product.service.BrandService com.atguigu.gulimall.product.service.impl.CategoryBrandRelationServiceImpl.brandService)
↑ ↓
| brandService (field com.atguigu.gulimall.product.service.CategoryBrandRelationService com.atguigu.gulimall.product.service.impl.BrandServiceImpl.categoryBrandRelationService)
└─────┘
当然可以。下面是针对每一种解决Spring循环依赖方法的代码示例。
1. 使用 @Lazy
注解
解决思路:通过使用 @Lazy
注解,可以让Spring容器延迟初始化bean,直到真正使用时才创建实例。
示例代码:
public class BrandServiceImpl extends ServiceImpl<BrandDao, BrandEntity> implements BrandService {
@Autowired
@Lazy
private CategoryBrandRelationService categoryBrandRelationService;
// ....
}
2. 使用 @PostConstruct
注解
解决思路:
- 这种方式不是@Lazy简单。
- ServiceA不要自动注入ServiceB,而是提供一个public的setServiceB方法。
- ServiceB自动注入ServiceA,且提供一个PostConstruct方法,在方法中调用ServiceA的set方法,把自身对象传递给ServiceB
示例代码:
@Service
public class ServiceA {
private ServiceB serviceB;
public void setServiceB(ServiceB serviceB) {
this.serviceB = serviceB;
}
}
@Service
public class ServiceB {
@Autowired
private ServiceA serviceA;
@PostConstruct
public void setServiceB() {
this.serviceA.setServiceB(this);
}
}
这段代码展示了如何通过使用setter注入和@PostConstruct
注解来解决循环依赖的问题。下面是对这个方案的解释:
代码分析
-
ServiceA 类:
@Service public class ServiceA { private ServiceB serviceB; public void setServiceB(ServiceB serviceB) { this.serviceB = serviceB; } }
- ServiceA 类包含一个
ServiceB
类型的私有成员变量serviceB
。 - 提供了一个
setServiceB
方法来注入ServiceB
的实例。
- ServiceA 类包含一个
-
ServiceB 类:
@Service public class ServiceB { @Autowired private ServiceA serviceA; @PostConstruct public void setServiceB() { this.serviceA.setServiceB(this); } }
- ServiceB 类包含一个
ServiceA
类型的私有成员变量serviceA
,并通过@Autowired
注解自动注入。 - 定义了一个带有
@PostConstruct
注解的方法setServiceB
,该方法在bean初始化完成后执行。 - 在
setServiceB
方法中,通过调用serviceA
的setServiceB
方法来设置ServiceB
的实例。
- ServiceB 类包含一个
解决方案分析
-
setter注入:
- ServiceA 的
serviceB
成员变量通过setter方法注入。 - ServiceB 的
serviceA
成员变量通过Spring自动注入。
- ServiceA 的
-
@PostConstruct 注解:
- ServiceB 类中的
setServiceB
方法使用了@PostConstruct
注解,这意味着该方法将在bean初始化完成之后调用。 - 在
setServiceB
方法中,通过调用serviceA
的setServiceB
方法来设置ServiceB
的实例。
- ServiceB 类中的