前台页面已经准备好,接下来就是后台提供数据接口了。
1、数据库表
CREATE TABLE `tb_brand` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '品牌id',
`name` varchar(50) NOT NULL COMMENT '品牌名称',
`image` varchar(200) DEFAULT '' COMMENT '品牌图片地址',
`letter` char(1) DEFAULT '' COMMENT '品牌的首字母',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=325400 DEFAULT CHARSET=utf8 COMMENT='品牌表,一个品牌下有多个商品(spu),一对多关系';
简单的四个字段,不多解释。
这里需要注意的是,品牌和商品分类之间是多对多关系。因此我们有一张中间表,来维护两者间关系:
CREATE TABLE `tb_category_brand` (
`category_id` bigint(20) NOT NULL COMMENT '商品类目id',
`brand_id` bigint(20) NOT NULL COMMENT '品牌id',
PRIMARY KEY (`category_id`,`brand_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='商品分类和品牌的中间表,两者是多对多关系';
但是,你可能会发现,这张表中并没有设置外键约束,似乎与数据库的设计范式不符。为什么这么做?
- 外键会严重影响数据库读写的效率
- 数据删除时会比较麻烦
在电商行业,性能是非常重要的。我们宁可在代码中通过逻辑来维护表关系,也不设置外键。
2、实体类
package com.leyou.item.pojo;
import lombok.Data;
import tk.mybatis.mapper.annotation.KeySql;
import javax.persistence.Id;
import javax.persistence.Table;
@Table(name = "tb_brand")
@Data
public class Brand {
@Id
@KeySql(useGeneratedKeys = true)
private Long id;
private String name;// 品牌名称
private String image;// 品牌图片
private Character letter;
}
3、mapper
通用 mapper 来简化开发:
package com.leyou.item.mapper;
import com.leyou.item.pojo.Brand;
import tk.mybatis.mapper.common.Mapper;
public interface BrandMapper extends Mapper<Brand> {
}
4、controller
编写 controller 先思考四个问题,这次没有前端代码,需要我们自己来设定
-
请求方式:查询,肯定是
Get
-
请求路径:分页查询,
/brand/page
-
请求参数:根据我们刚才编写的页面,有分页功能,有排序功能,有搜索过滤功能,因此至少要有5个参数:
page
:当前页,introws
:每页大小,intsortBy
:排序字段,Stringdesc
:是否为降序,booleankey
:搜索关键词,String
-
响应结果:分页结果一般至少需要两个数据
total
:总条数items
:当前页数据totalPage
:有些还需要总页数
这里我们封装一个类,来表示分页结果:
package com.leyou.common.vo;
import lombok.Data;
import java.util.List;
@Data
public class PageResult<T> {
private Long total;// 总条数
private Integer totalPage;// 总页数
private List<T> items;// 当前页数据
public PageResult(){}
public PageResult(Long total, List<T> items){
this.total = total;
this.items = items;
}
public PageResult(Long total,Integer totalPage, List<T> items){
this.total = total;
this.totalPage = totalPage;
this.items = items;
}
}
另外,这个PageResult
以后可能在其它项目中也有需求,因此我们将其抽取到ly-common
中,提高复用性:
接下来,我们编写 Controller
package com.leyou.item.controller;
import com.leyou.common.vo.PageResult;
import com.leyou.item.pojo.Brand;
import com.leyou.item.service.BrandService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("brand")
public class BrandController {
@Autowired
private BrandService brandService;
/**
* 分页查询品牌
* @param page
* @param rows
* @param sortBy
* @param desc
* @param key
* @return
*/
@GetMapping("page")
public ResponseEntity<PageResult<Brand>> queryBrandByPage(
@RequestParam(value = "page", defaultValue = "1") Integer page,
@RequestParam(value = "rows", defaultValue = "5") Integer rows,
@RequestParam(value = "sortBy", required = false) String sortBy,
@RequestParam(value = "desc", defaultValue = "false") Boolean desc,
@RequestParam(value = "key", required = false) String key) {
PageResult<Brand> result = this.brandService.queryBrandByPage(page,rows,sortBy,desc, key);
return ResponseEntity.ok(result);
}
}
5、Service
package com.leyou.item.service;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.common.vo.PageResult;
import com.leyou.item.mapper.BrandMapper;
import com.leyou.item.pojo.Brand;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import tk.mybatis.mapper.entity.Example;
import java.util.List;
@Service
public class BrandService {
@Autowired
private BrandMapper brandMapper;
public PageResult<Brand> queryBrandByPage(
Integer page, Integer rows, String sortBy, Boolean desc, String key) {
// 开始分页
PageHelper.startPage(page, rows);
// 过滤
Example example = new Example(Brand.class);
if (StringUtils.isNotBlank(key)) {
example.createCriteria().orLike("name", "%" + key + "%")
.orEqualTo("letter", key.toUpperCase());
}
if (StringUtils.isNotBlank(sortBy)) {
// 排序
String orderByClause = sortBy + (desc ? " DESC" : " ASC");
example.setOrderByClause(orderByClause);
}
// 查询
List<Brand> list = brandMapper.selectByExample(example);
if(CollectionUtils.isEmpty(list)){
throw new LyException(ExceptionEnum.BRAND_NOT_FOUND);
}
//解析分页结果
PageInfo<Brand> pageInfo = new PageInfo<>(list);
// 返回结果
return new PageResult<>(pageInfo.getTotal(), list);
}
}
完整结构:
6、测试
通过浏览器访问试试:http://api.leyou.com/api/item/brand/page
可以配置打印 mybatis 的日志信息
ly-item-service
里面添加配置:
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
会在控制台打印 sql 语句
2019-03-10 17:19:00.201 INFO 236 --- [nio-8081-exec-2] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring FrameworkServlet 'dispatcherServlet'
2019-03-10 17:19:00.201 INFO 236 --- [nio-8081-exec-2] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started
2019-03-10 17:19:00.234 INFO 236 --- [nio-8081-exec-2] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 33 ms
Creating a new SqlSession
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@651968aa] was not registered for synchronization because synchronization is not active
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2de986b5] was not registered for synchronization because synchronization is not active
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3f167967] was not registered for synchronization because synchronization is not active
2019-03-10 17:19:00.326 INFO 236 --- [nio-8081-exec-2] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2019-03-10 17:19:01.471 INFO 236 --- [nio-8081-exec-2] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
JDBC Connection [HikariProxyConnection@1314791003 wrapping com.mysql.jdbc.JDBC4Connection@50363de] will not be managed by Spring
==> Preparing: SELECT id,name,parent_id,is_parent,sort FROM tb_category WHERE parent_id = ?
==> Parameters: 0(Long)
<== Columns: id, name, parent_id, is_parent, sort
<== Row: 1, 图书、音像、电子书刊, 0, 1, 1
<== Row: 74, 手机, 0, 1, 2
<== Row: 103, 家用电器, 0, 1, 3
<== Row: 202, 数码, 0, 1, 4
<== Row: 264, 家居家装, 0, 1, 5
<== Row: 322, 电脑办公, 0, 1, 6
<== Row: 417, 厨具, 0, 1, 7
<== Row: 471, 个护化妆, 0, 1, 8
<== Row: 548, 服饰内衣, 0, 1, 9
<== Row: 666, 钟表, 0, 1, 10
<== Row: 680, 鞋靴, 0, 1, 11
<== Row: 718, 母婴, 0, 1, 12
<== Row: 808, 礼品箱包, 0, 1, 13
<== Row: 871, 食品饮料、保健食品, 0, 1, 14
<== Row: 931, 珠宝, 0, 1, 15
<== Row: 1020, 汽车用品, 0, 1, 16
<== Row: 1117, 运动健康, 0, 1, 17
<== Row: 1230, 玩具乐器, 0, 1, 18
<== Row: 1293, 彩票、旅行、充值、票务, 0, 1, 19
<== Row: 1329, 生鲜, 0, 1, 20
<== Row: 1401, 整车, 0, 1, 21
<== Total: 21
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3f167967]
JDBC Connection [HikariProxyConnection@2071312311 wrapping com.mysql.jdbc.JDBC4Connection@50363de] will not be managed by Spring
==> Preparing: SELECT count(0) FROM tb_brand
==> Parameters:
<== Columns: count(0)
<== Row: 165
<== Total: 1
==> Preparing: SELECT id,name,image,letter FROM tb_brand order by id ASC LIMIT ?
==> Parameters: 5(Integer)
<== Columns: id, name, image, letter
<== Row: 1115, HTC, , H
<== Row: 1528, LG, , L
<== Row: 1912, NEC, , N
<== Row: 2032, OPPO, http://img10.360buyimg.com/popshop/jfs/t2119/133/2264148064/4303/b8ab3755/56b2f385N8e4eb051.jpg, O
<== Row: 2505, TCL, , T
<== Total: 5
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@651968aa]
JDBC Connection [HikariProxyConnection@1503963007 wrapping com.mysql.jdbc.JDBC4Connection@50363de] will not be managed by Spring
==> Preparing: SELECT count(0) FROM tb_brand
==> Parameters:
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@16c31a01] was not registered for synchronization because synchronization is not active
<== Columns: count(0)
<== Row: 165
<== Total: 1
==> Preparing: SELECT id,name,image,letter FROM tb_brand order by id ASC LIMIT ?
==> Parameters: 5(Integer)
<== Columns: id, name, image, letter
<== Row: 1115, HTC, , H
<== Row: 1528, LG, , L
<== Row: 1912, NEC, , N
<== Row: 2032, OPPO, http://img10.360buyimg.com/popshop/jfs/t2119/133/2264148064/4303/b8ab3755/56b2f385N8e4eb051.jpg, O
<== Row: 2505, TCL, , T
<== Total: 5
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2de986b5]
JDBC Connection [HikariProxyConnection@473657713 wrapping com.mysql.jdbc.JDBC4Connection@50363de] will not be managed by Spring
==> Preparing: SELECT count(0) FROM tb_brand
==> Parameters:
<== Columns: count(0)
<== Row: 165
<== Total: 1
==> Preparing: SELECT id,name,image,letter FROM tb_brand order by id ASC LIMIT ?
==> Parameters: 5(Integer)
<== Columns: id, name, image, letter
<== Row: 1115, HTC, , H
<== Row: 1528, LG, , L
<== Row: 1912, NEC, , N
<== Row: 2032, OPPO, http://img10.360buyimg.com/popshop/jfs/t2119/133/2264148064/4303/b8ab3755/56b2f385N8e4eb051.jpg, O
<== Row: 2505, TCL, , T
<== Total: 5
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@16c31a01]