<context-param>
的内容联系起来并发送给ServletContext。<listener>
创建监听器,这个ContextLoaderListener就是用来监听ServletContext的变化,并根据<context-param>
加载applicationContext.xml文件。我们使用了<context:annotation-config/>
能够简单的帮助我们自动完成相应bean的声明。
加载完applicationContext.xml之后,会扫描service层和dao层注解。
引入外部的属性配置文件
在applicationContext.xml中配置使用阿里巴巴Druid数据库连接池,后期需要可以在其中添加多个属性配置。
在 MyBatis 中,通过 SqlSessionFactoryBuilder来生成SqlSessionFactory ,最终来生成 SqlSession。我们引入 MyBatis-Spring 后, 让SqlSessionFactoryBean代替了SqlSessionFactoryBuilder生成SqlSessionFactory。
MapperScannerConfigurer能够自动帮我们装载SqlSessionFactory 或 SqlSessionTemplate所以我们没必要在applicationContext.xml中注入sqlSession和sqlSessionFactory。
下图为对dataSource进行事务管理和配置 Annotation 驱动,扫描@Transactional注解的类定义事务。
扫描这个controller包,让SpringMVC认为该包下带注解的是controller。
扩充注解驱动。
对静态资源进行放过,比如css,js,图片等。
有了该配置,可以实现文件上传功能。不然就要引入文件上传组件。
配置视图解析。
dto,我们可以使用代码生成器生成,和下面方法一致。
我们上面介绍项目结构时说过,我们使用了generatorConfig.xml这个配置文件,其对应的mybatis-generator是一款代码生成器,可以帮助我们快速构建DAO层常见的增删查改功能,一般能够满足我们所有要求。
这个service调用了5个mapper:
@Autowired
private AdministratorMapper administratorMapper;
@Autowired
private GoodsTypeMapper goodsTypeMapper;
@Autowired
private GoodsMapper goodsMapper;
@Autowired
private GoodsImagesMapper goodsImagesMapper;
@Autowired
private GoodsImagesColorMapper goodsImagesColorMapper;
我们重点讲几个典型的,其实都大同小异。
@Override
public List<Administrator> adminLogin(String username, String password) {
AdministratorExample admin = new AdministratorExample();
admin.createCriteria().andUsernameEqualTo(username).andPasswordEqualTo(password);
List<Administrator> administrators = administratorMapper.selectByExample(admin);
return administrators;
}
在讲这个之前,我们先介绍什么是Criteria,如下图,Criteria包含一个Cretiron的集合,每一个Criteria对象内包含的Cretiron之间是由AND连接的,是逻辑与的关系。
而Criterion是最基本,它是最底层的Where条件,用于字段级的筛选,feild用于指代字段名字。
而对于oredCriteria,Example内有一个成员叫oredCriteria,是Criteria的集合,这个集合中的Criteria是由OR连接的,是逻辑或关系。
这里我们先利用CTO的Example生成一个admin对象,然后利用
admin.createCriteria().andUsernameEqualTo(username).andPasswordEqualTo(password);
List<Administrator> administrators = administratorMapper.selectByExample(admin);
可以查询参数在数据库中数据库中有没有。
再来看看selectAll:
@Override
public List<Goods> selectAll(){
GoodsExample gc = new GoodsExample();
gc.createCriteria();
return goodsMapper.selectByExample(gc);
}
这个操作是查询所有的商品,其对应的sql语句可以这样看待:
select *
from Goods
//因为createCriteria()之后没有and和or,所以没有where条件为空。
@Override
public void deleteGoodsInfo(int goodsId){
goodsMapper.deleteByPrimaryKey(goodsId);
GoodsImagesColorExample gc = new GoodsImagesColorExample();
gc.createCriteria().andGoodsIdEqualTo(goodsId);
goodsImagesColorMapper.deleteByExample(gc);
GoodsImagesExample gc1 = new GoodsImagesExample();
gc1.createCriteria().andGoodsIdEqualTo(goodsId);
goodsImagesMapper.deleteByExample(gc1);
}
因为我们图片和商品数据不在一个表中,所以我们删除商品,需要操作3个表,这里通过主键id在goods表中删除了一条,然后在GoodsImagesColor表中,删除了where条件goodsId为该函数参数的数据,同理,也删除了GoodsImages中的数据。
@Override
public List<Goods> searchGoodsWithName(String name) {
// 构建条件
GoodsExample ge = new GoodsExample();
ge.createCriteria().andNameLike("%" + name + "%");
return goodsMapper.selectByExample(ge);
}
这里我们使用了一个方法名中带有Like的方法,其作用如下: 实现mysql的like模糊查询一样的操作。这里需要自己拼接%或_。 这段代码用sql语句来描述是这样的:
select * from goods where name like ?
其实这里面和前面用到的知识都大同小异,不过出现了一个新的面孔。
@Override
public List<Goods> findGoodsWithToptype(GoodsType goodstype) {
// 根据一级类型查询所有所属二级类型
List<GoodsType> gt = goodsTypeServiceImpl.findSecondLevel(goodstype);
// 查询所有二级类型下的所有商品
List<Goods> goodsList = new ArrayList<>();
for (GoodsType goodsType : gt) {
List<Goods> goodses = this.findGoodsWithType(goodsType);
goodsList.addAll(goodses);
}
return goodsList;
}
该操作是查询某一级目录下的所有二级目录的商品。此处调用了别处的Service。
此处多了个IsNull的方法。
@Override
public List<GoodsType> findTopLevel() {
GoodsTypeExample gte = new GoodsTypeExample();
gte.createCriteria().andPidIsNull();
return goodsTypeMapper.selectByExample(gte);
}
这段Java代码可以对应的sql语句为:
select * from goodswhere pid is null
即查找pid为空的目录,因为一级目录和二级目录放在一个表中,这样就可以快速查找一级目录了。
@Override
public int insetorder(GoodsOrder goodsOrder){
return goodsOrderMapper.insertSelective(goodsOrder);
}
这里我们得了解insert和insertSelective的区别: 如果选择insert 那么所有的字段都会添加一遍,即使有的字段没有值,但是如果使用inserSelective会只给有值的字段赋值(会对传进来的值做非空判断)。
@Override
public int updataorder(GoodsOrder goodsOrder, int id){
GoodsOrderExample gc = new GoodsOrderExample();
gc.createCriteria().andIdEqualTo(id);
return goodsOrderMapper.updateByExample(goodsOrder,gc);
}
record对象则是我们需要修改的数据,example则是我们需要修改的对象 此处代表id为参数id的数据要被修改为参数goodsOrder。
@Override
@Transactional(readOnly = true)
public PageInfo queryByPage(int pageNum, int pageSize, int level, GoodsType goodsType) {
PageHelper.startPage(pageNum,pageSize);
List<Goods> goodsList = null;
if(level==1) {
goodsList = goodsShippingServiceImpl.findGoodsWithToptype(goodsType);
} else if(level==2) {
goodsList = goodsShippingServiceImpl.findGoodsWithType(goodsType);
}
PageInfo pageInfo=new PageInfo(goodsList);
return pageInfo;
}
这个util是利用PageHelper进行分页操作。
这部分应该大家都能看懂,我挑几个重点的讲述一下。
@PostMapping(value = "/admin/login",produces = {"application/json;charset=UTF-8"})
@ResponseBody
public ResponseMessage login(@RequestParam String username, @RequestParam String password, HttpSession session){
if (username==null || password==null){
return ResponseMessage.error();
}else {
MD5 md5 = MD5.create();
password = md5.digestHex(password);
List<Administrator> admin = adminService.adminLogin(username, password);
if (admin.size()==1){
session.setAttribute("admin", admin.get(0));
return ResponseMessage.success();
}else {
return ResponseMessage.error();
}
}
}
这个是后台的登录逻辑,首先是判断用户名或密码是否为空,然后讲密码使用md5算法进行转换,然后传参到adminService的adminLogin,如果有对象返回,就存一个session,返回一个ResponseMessage。否则返回处理失败的ResponseMessage。
@GetMapping(value = "/searchGoods",produces = {"application/json;charset=UTF-8"})
@ResponseBody
public ModelAndView searchGoods(String keyword,String search_field,String pageNum){
if (pageNum==null){
pageNum="1";
}
System.out.println(keyword+" "+search_field);
List<Goods> goods1=new ArrayList<>();
if (search_field.equals("商品名")){
goods1 = adminService.searchGoodsWithName(keyword);
}else {
goods1 = adminService.searchGoodsWithTypeId(Integer.parseInt(keyword));
}
int totalRecord = goods1.size();
System.out.println(totalRecord);
PageBean pb = new PageBean(Integer.parseInt(pageNum),20,totalRecord);
int startIndex = pb.getStartIndex();
List<Goods> goods = new ArrayList<>();
for (int i = startIndex; i < startIndex+20; i++) {
try {
goods.add(goods1.get(i));
}catch (Exception e){
break;
}
}
pb.setList(goods);
ModelAndView mv = new ModelAndView();
mv.addObject("goodsPageInfo",pb).addObject("keyword",keyword).addObject("search_field", search_field);
mv.setViewName("/admin/lyear_pages_doc2");
return mv;
}
这个方法是用于后台Good List查询商品的。
此处根据search_field是id还是name来使用两种方法查询
keyword是在text input标签输入的内容。
然后进行查询分页展示。
@GetMapping("/userAddress")
public ModelAndView userAddress(HttpSession session){
ModelAndView mv = new ModelAndView();
if (session.getAttribute("loginUser")==null){
mv.setViewName("/login");
}else {
int userId = ((Consumer)session.getAttribute("loginUser")).getId();
List<GoodsShippingAddress> addresses = userServiceImpl.selectAddress(userId);
mv.addObject("addresses",addresses);
mv.setViewName("userAddress");
}
return mv;
}
此处用到了ModelAndView对象,这个是关键。 他能够将View与Model捆绑在一起。 这个操作是访问收货地址,如果session中没有记录,就前往登录界面。 否则,根据用户ID,在数据库中查找地址信息,然后与视图捆绑渲染。
其他controller的其他方法都大同小异~
这里主要使用了Thymeleaf和Ajax,Thymleaf主要是在每个需要后台传数据的标签前面加th:
.
变量用${obj.id}
类似的形式。
下面是文档: Documentation - Thymeleaf https://www.thymeleaf.org/documentation.html
至于Ajax,本项目主要是类似于下面这样的形式:
$(function() {
// 直接发送请求获取数据
$.ajax({
url: "/initIndex",
method: "GET",
success: function (response) {
console.log(response);
// 获取到后台返回的一级商品类型数据
let $goodsTypes = response.objectMap.goodsTypeList;
// 遍历商品类型
for (let i = 0; i < $goodsTypes.length; i++) {
// 创建对应的标签
let $li = $("<li>").attr("id", $goodsTypes[i].id).text($goodsTypes[i].name);
let $span = $("<span>").addClass("layui-icon layui-icon-right");
// let $span = $("<span>").append($("<em>").addClass("glyphicon glyphicon-menu-right"));
$li.append($span)
// 将数据加载到网页中ul菜单中
$(".my-daohang2 ul").append($li);
}
// 加载楼层商品
let $xiaomiphonelist = response.objectMap.goodsMap["30018"];
loadGoods($xiaomiphonelist, $("#phone"));
let $tvlist = response.objectMap.goodsMap["30011"];
loadGoods($tvlist, $("#tv"));
let $tablelist = response.objectMap.goodsMap["30019"];
loadGoods($tablelist, $("#table"));
},
error: function () {
console.log("请求迷路了....")
}
});
其中,url: "/initIndex"
是将要触发传递的路径, method: "GET"
代表POST还是GET或其他方法,success:
代表如果请求成功将要进行的逻辑,error:
代表请求失败要进行的逻辑。