您的当前位置:首页正文

Spring 后台管理系统项目练习02----MybatisPlus,商品分类实现

2024-12-02 来源:个人技术集锦

1. MybatisPlus(MP)

1.1 MP介绍

MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

1.2 特点

无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
内置性能分析插件:可输出 SQL 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作

1.3 ORM思想

1.3.1 ORM说明

对象关系映射(英语:Object Relational Mapping,简称ORM,或O/RM,或O/R mapping),是一种程序设计技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换。从效果上说,它其实是创建了一个可在编程语言里使用的“虚拟对象数据库”。如今已有很多免费和付费的ORM产品,而有些程序员更倾向于创建自己的ORM工具。

总结:以面向对象的方式操作数据库

1.3.2 ORM特点

对象与数据库中的表什么关系? 一一映射
对象中的属性与数据库中的字段什么关系? 一一映射
以面向对象的方式操作数据库。由框架动态生成Sql语句。实现了跨数据库的操作。

1.4 MP入门案例

1.4.1 导入jar包

			<!--spring整合mybatis-plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.2.0</version>
        </dependency>

将原来的mybatis包删除或注释

1.4.2 编辑POJO对象

1.4.3 编辑Mapper接口

说明: MP将常用的CURD操作进行封装.以后用户需要使用,只需要继承公共的业务接口即可.

1.4.4 yml文件

2 查询操作

2.1 根据id进行查询

		/*根据id进行查询*/
    @Test
    public void test01(){
        int id=1;
        User user=userMapper.selectById(id);
        System.out.println(user);
    }

2.2 根据其他属性查询

2.2.1 方式1:利用对象封装数据 只能利用 set = 号

		/* username="admin110"
    * 组件:条件构造器  用来封装where条件
    * sql:select * from user where username="admin110"
    * 方式1:利用对象封装数据  只能利用 = 号
    * */
    @Test
    public void test02(){
        User temp=new User();
        temp.setUsername("admin110");//set "="
        //条件构造器,会根据对象中不为null的元素动态拼接sql
        QueryWrapper<User> queryWrapper=new QueryWrapper<>(temp);
        //如果可以保证查询结果只有一个
        User user=userMapper.selectOne(queryWrapper);

    }

2.2.2 方式2:利用逻辑运算符 实现sql操作

/*
    * 查询id>5的用户
    * sql:select * from user where id>5
    * 转义字符:> gt ,< lt , = eq
    *         >= ge ,<= le  ,!= ne
    * 方式2:利用逻辑运算符 实现sql操作
    * */
    @Test
    public void test03(){
        QueryWrapper<User> queryWrapper=new QueryWrapper<>();
        queryWrapper.ge("id", 5);
        List<User> userList=userMapper.selectList(queryWrapper);
        for (User user : userList) {
            System.out.println(user);
        }
    }

2.3 模糊查询

		/*
    * 需求:查询username 包含admin的数据,并且按照id降序排列
    * sql:select * from user where username like "%admin%" order by id desc
    * 知识点:
    * 1.like       "%admin%"    左右都有%号
    * 2.likeLeft   "%admin"     左边有%
    * 3.likeRight  "admin%"     右边有%
    * */
    @Test
    public void test04(){
        QueryWrapper<User> queryWrapper=new QueryWrapper<>();
        queryWrapper.like("username", "admin")
                .orderByDesc("id");	//降序
        List<User> userList=userMapper.selectList(queryWrapper);
        for (User user : userList) {
            System.out.println(user);
        }
    }

2.4 查询id=1,2,8,9 的数据


/*需求:查询id=1,2,8,9 的数据
    *注意:使用in关键字时,最好使用包装类型
    * */
    @Test
    public void test05(){
        Integer[] ids={1,2,8,9};
        //方式1:根据条件构造器查询
        QueryWrapper<User> queryWrapper=new QueryWrapper<>();
        queryWrapper.in("id", ids);
        List<User> userList=userMapper.selectList(queryWrapper);
        for (User user : userList) {
            System.out.println(user);
        }
        //方式2:利用API
        List list= Arrays.asList(ids);
        List<User> userList2=userMapper.selectBatchIds(list);
        System.out.println(userList2);
    }


2.5 动态查询数据 (利用 电话,邮箱查询数据)

2.5.1 eq

疑问: MP特点 根据对象(POJO)中不为null的属性充当条件

		/**
     * 需求: 动态查询数据,利用电话/邮箱查询数据
     * 疑问: MP特点 根据对象(POJO)中不为null的属性充当条件
     * API:
     *      eq(boolean true 拼接/false 不拼接, R column, Object val)
     *      StringUtils.hasLength(phone)
     */
		@Test
    public void test06() {
        String phone = "13113113667";
        String email = "123345@qq.com";
        QueryWrapper<User> queryWrapper = new QueryWrapper();
        queryWrapper.eq("phone", phone)
                    .eq("email", email);
        List<User> list = userMapper.selectList(queryWrapper);
        System.out.println(list);
    }

2.5.2 传入的数据为null或空串


2.5.3 StringUtils.hasLength

import org.springframework.util.StringUtils;

		@Test
    public void test06_1() {
        String phone = "";
        String email = "123345@qq.com";
        QueryWrapper<User> queryWrapper = new QueryWrapper();
        queryWrapper.eq(StringUtils.hasLength(phone),"phone", phone)
                    .eq(StringUtils.hasLength(email),"email", email);
        List<User> list = userMapper.selectList(queryWrapper);
        System.out.println(list);
    }

2.5.3.1 为空串 或null 时 查询所有

2.5.3.2 为null时

2.6 只查询主键信息

/*
    * 只查询主键信息
    * API:selectObjs:获取主键信息
    * 实际用途: 关联查询
    * */
    @Test
    public void test07() {
        List ids=userMapper.selectObjs(null);
        System.out.println(ids);
    }


3 修改操作

3.1 根据id修改数据

	/*
    *需求:用户的修改操作
    * 1.根据id修改数据
    * userMapper.updateById(user);
    *   原理:Id当做唯一where条件,其他不为null的属性当做set条件
    * */
    @Test
    public void test08() {
        User user = new User();
        user.setId(13).setUsername("张三").setPhone("110110");
        userMapper.updateById(user);
        System.out.println("修改成功");
    }

3.2 要求将password=123456的phone改为10086,email 10086@qq.com

/*
     *需求:用户的修改操作
     *      要求将password=123456的phone改为10086,email 10086@qq.com
     * 参数说明:
     *      1.entity: 主要目的是封装Set条件
     *      2.wrapper:封装修改条件的条件构造器
     * */
    @Test
    public void test09() {
        User user = new User();
        user.setPhone("10086").setEmail("10086@qq.com");
        //封装修改的条件构造器
        UpdateWrapper<User> updateWrapper=new UpdateWrapper<>();
        updateWrapper.eq("password", "123456");
//        userMapper.update(xxx, 条件构造器);
        userMapper.update(user,updateWrapper);
    }

4 实现商品分类业务模块 ----- 分析

4.1 路由跳转页面

需求: 当用户点击商品分类时,应该跳转到商品分类页面中.

编辑路由文件

4.2 商品分类层级代码搭建

4.2.1 编辑POJO

@Data
@Accessors(chain = true)
@TableName("item_cat")
public class ItemCat extends BasePojo{
    @TableId(type = IdType.AUTO)//主键自增
    private Integer id;         //定义主键
    private Integer parentId;   //定义父级菜单 开启驼峰规则映射
    private String name;        //分类名称
    private Boolean status;     //分类状态 0 停用 1 正常
    private Integer level;      //商品分类等级  1 2 3

    @TableField(exist = false)   //当前属性不参与MP操作
    private List<ItemCat> children; //业务数据,不是数据库字段
}

4.2.2 搭建层级代码

4.3 商品分类业务说明(难点知识!!!)

4.3.1 表设计

##查询一级商品分类信息
SELECT * FROM item_cat WHERE parent_id=0

##其中一个二级商品信息
SELECT * FROM item_cat WHERE parent_id=74

##三级
SELECT * FROM item_cat WHERE parent_id=89



4.3.2 数据结构设计

一级/二级一定有children/ 三级的children为null

5 商品分类 ----- 具体展现

4.1 业务分析

4.1.1 业务接口说明

请求路径: /itemCat/findItemCatList/{level}
请求类型: get
请求参数: level
业务说明: 查询3级分类菜单数据 要求三层结构嵌套
返回值: SysResult对象 3级商品分类信息

参数名称参数说明备注
level查询级别1查询一级分类 2查询1-2 级商品分类 3查询1-2-3级商品分类
参数名称参数说明备注
status状态信息200表示服务器请求成功 201表示服务器异常
msg服务器返回的提示信息可以为null
data服务器返回的业务数据3级商品分类信息

4.1.2 页面URL说明

4.2编辑ItemCatController

@RestController
@RequestMapping("/itemCat")
@CrossOrigin            //前后端跨域操作
public class ItemCatController {
    @Autowired
    private ItemCatService itemCatService;
    /*
    请求路径: /itemCat/findItemCatList/{level}
    请求类型: get
    请求参数: level
    业务说明: 查询3级分类菜单数据 要求三层结构嵌套
    返回值: SysResult对象 3级商品分类信息
    */
    @GetMapping("/findItemCatList/{level}")
    public SysResult findItemCatList(@PathVariable Integer level) {

        List<ItemCat> catList = itemCatService.findItemCatList(level);

        return SysResult.success(catList);
    }
}

4.1.2 升级MPjar包

<!--spring整合mybatis-plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.3</version>
        </dependency>

4.3 编辑ItemCatService

4.3.1 ItemCatServiceImpl

@Service
public class ItemCatServiceImpl implements ItemCatService{
    @Autowired
    private ItemCatMapper itemCatMapper;
    /*
    * 只查询一级菜单 parent_id=0或level=1
    * 查询二级菜单   parent_id=一级ID
    * 查询三级菜单   parent_id=二级ID
    * 优化:
    * */
    @Override
    public List<ItemCat> findItemCatList(Integer level) {
        QueryWrapper<ItemCat> queryWrapper=new QueryWrapper();
        queryWrapper.eq("parent_id", 0);
        List<ItemCat> oneList=itemCatMapper.selectList(queryWrapper);//一级集合
        //2
        for (ItemCat oneItemCat : oneList) {
            int oneId=oneItemCat.getId();//获取一级对象ID
            //清空多余条件,为了复用上面已经创建过的queryWrapper
            queryWrapper.clear();
            queryWrapper.eq("parent_id", oneId);
            List<ItemCat> twoList=itemCatMapper.selectList(queryWrapper);//二级集合
            //将二级封装到一级对象中
            oneItemCat.setChildren(twoList);

            for (ItemCat twoItemCat : twoList) {
                int twoId=twoItemCat.getId();//获取二级对象ID
                //清空多余条件
                queryWrapper.clear();
                queryWrapper.eq("parent_id", twoId);
                List<ItemCat> threeList=itemCatMapper.selectList(queryWrapper);//三级集合
                //将三级封装到二级对象中
                twoItemCat.setChildren(threeList);
            }
        }
        //
        return oneList;
    }
}

4.3.2 效果

4.3.3 优化

		/**
     * 利用Map集合封装所有的数据库记录
     * 封装数据:
     *      1.遍历所有的数据信息.
     *      2.获取每一个parentId的值.
     * 例子:
     *      1.{id=1,parentId=0,name="张三"}
     *      2.{id=2,parentId=0,name="李四"}
     *      3.{id=3,parentId=1,name="王五"}
     *      Map= {
     *          key : value
     *          0   : List[张三对象,李四对象.....],
     *          1   : List[王五对象......]
     *      }
     * @return
     */

    public Map<Integer,List<ItemCat>> getMap(){
        Map<Integer,List<ItemCat>> map = new HashMap<>();
        //1.查询所有的数据库信息
        List<ItemCat> itemCatList = itemCatMapper.selectList(null);
        //2.将数据封装到map集合中
        for (ItemCat itemCat : itemCatList){
            Integer key = itemCat.getParentId(); //获取parentId当做key
            //3.判断map集合中是否有值.
            if(map.containsKey(key)){
                //有值: 获取List集合,将自己追加到其中
                map.get(key).add(itemCat);
            }else{
                //没值: 添加数据.将自己作为第一个元素填充
                List<ItemCat> list = new ArrayList<>();
                list.add(itemCat);
                map.put(key,list);
            }
        }
        return map;
    }

    //通过map集合 获取一级二级菜单信息.
    private List<ItemCat> getTwoList(Map<Integer, List<ItemCat>> map) {
        List<ItemCat> oneList = map.get(0);
        //获取二级信息,应该先遍历一级集合
        for (ItemCat oneItemCat : oneList){
            int oneId = oneItemCat.getId();
            //根据一级Id,获取二级集合
            List<ItemCat> twoList = map.get(oneId);
            oneItemCat.setChildren(twoList);
        }
        return oneList;
    }

    //获取三级列表信息  先获取1级数据,再获取2级数据.再获取3级数据
    private List<ItemCat> getThreeList(Map<Integer, List<ItemCat>> map) {
        //1.调用2级菜单方法.
        List<ItemCat> oneList = getTwoList(map);
        //2.实现思路 遍历一级集合,获取二级数据. 封装三级菜单
        for(ItemCat oneItemCat : oneList){
            //2.1 获取二级数据
            List<ItemCat> twoList = oneItemCat.getChildren();
            if(twoList == null || twoList.size()==0){
                //判断二级集合是否为null.如果为null,表示没有二级菜单.
                //则跳过本次循环,进入下一次循环
                continue;
            }
            for (ItemCat twoItemCat : twoList){
                int twoId = twoItemCat.getId();
                List<ItemCat> threeList = map.get(twoId);
                twoItemCat.setChildren(threeList);
            }
        }
        return oneList;
    }
    
    
    @Override
    public List<ItemCat> findItemCatList(Integer level) {
        long startTime = System.currentTimeMillis();
        Map<Integer,List<ItemCat>> map = getMap();
        //根据level获取子级信息
        if(level == 1){ //只获取一级列表信息
            return map.get(0);
        }
        if(level == 2){ //获取一级和二级数据
            return getTwoList(map);
        }
        List<ItemCat> oneList = getThreeList(map);
        long endTime = System.currentTimeMillis();
        System.out.println("优化前的耗时: 500ms,优化后耗时:"+(endTime - startTime)+"ms");
        return oneList;
    }

6 新增商品分类

6.1 前端请求


6.2 ItemCatController

/*
    请求路径: /itemCat/saveItemCat
    请求类型: post
    请求参数: 表单数据
    返回值: SysResult对象
    * */
    @PostMapping("/saveItemCat")
    public SysResult saveItemCat(@RequestBody ItemCat itemCat){
        itemCatService.saveItemCat(itemCat);
        return SysResult.success();
    }

6.3 ItemCatServiceImpl

		@Override
    @Transactional
    public void saveItemCat(ItemCat itemCat) {
        itemCat.setStatus(true)
                .setCreated(new Date())
                .setUpdated(itemCat.getCreated());
        itemCatMapper.insert(itemCat);
    }

6.4 MyBatis-Plus自动填充功能

标记填充字段

6.5 自动填充配置

6.5.1 新建第三方配置包config

6.5.2 实现元对象处理器接口

com.baomidou.mybatisplus.core.handlers.MetaObjectHandler
自定义实现类 MyMetaObjectHandler

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        Date date = new Date();
        this.setFieldValByName("created", date, metaObject);
        this.setFieldValByName("updated", date, metaObject);
    }

    @Override
    public void updateFill(MetaObject metaObject) {
        this.setFieldValByName("updated", new Date(), metaObject);
    }
}

6.5.3 ItemCatServiceImpl 优化

7 删除商品分类数据

7.1 前端请求


7.2 ItemCatController

/*
    请求路径: /itemCat/deleteItemCat
    请求类型: delete
    业务描述: 当删除节点为父级时,应该删除自身和所有的子节点
    请求参数:分类id号 level
    返回值结果 SysResult对象
    */
    @DeleteMapping("/deleteItemCat")
    public SysResult deleteItemCat(ItemCat itemCat){
        itemCatService.deleteItemCat(itemCat);
        return SysResult.success();
    }

7.3 ItemCatServiceImpl

		/*
     * 删除的思路:
     *       1.判断是否为3级,如果是直接删除
     *       2.判断是否为2级,如果是2级,先删除3级,再删2级
     *       3.如果是1级,先查询2级,再删除3级/2级/1级
     * */
    @Override
    @Transactional
    public void deleteItemCat(ItemCat itemCat) {
        if (itemCat.getLevel()==3){
            itemCatMapper.deleteById(itemCat.getId());
            return;
        }
        if (itemCat.getLevel()==2){
            int twoId = itemCat.getId();
            QueryWrapper<ItemCat> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("parent_id", twoId);
            //删除的是三级数据
            itemCatMapper.delete(queryWrapper);
            //
            itemCatMapper.deleteById(twoId);
            return;
        }
        int oneId=itemCat.getId();
        QueryWrapper<ItemCat> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("parent_id", oneId);
        List twoIdList = itemCatMapper.selectObjs(queryWrapper);//有没有数据都会返回List集合
        if (twoIdList.size()==0) {
            itemCatMapper.deleteById(oneId);//如果没有二级数据,则直接删除一级信息
        }else {
            //有二级数据,可以删除3级
            //3级数据如果没有也不会报错,更新0条数据
            //sql:delete from item_cat where parent_id in(xx,xx) or id in(xx,xx)
            queryWrapper.clear();
            queryWrapper.in("parent_id",twoIdList)//3级  根据2级parent_id找三级
                        .or()
                        .in("id", twoIdList)//2  根据2级id找二级
                        .or()
                        .in("id", oneId);//1
            itemCatMapper.delete(queryWrapper);
        }
    }

7.4 效果

7.4.1 删除一级


7.4.2 删除三级目录

7.4.2 删除二级目录


7.4.3 删除一级目录(下面没有二级)




8 修改商品分类状态

8.1 ItemCatController

/*
    * 请求路径: /itemCat/status/{id}/{status}
    * 请求类型: put
    * 返回值: SysResult对象
    * */
    @PutMapping("/status/{id}/{status}")
    public SysResult updateStatus(ItemCat itemCat){
        itemCatService.updateStatus(itemCat);
        return SysResult.success();
    }

8.2 ItemCatServiceImpl

	  @Override
    public void updateStatus(ItemCat itemCat) {
        itemCatMapper.updateById(itemCat);
    }

9 商品分类修改

9.1 ItemCatController

9.2 ItemCatServiceImpl

显示全文