MyBatis-Plus学习笔记总结

news2024/12/24 7:11:18

一、查询

构造器分为QueryWrapper和LambdaQueryWrapper

创建实体类User

package com.system.mybatisplus.model;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.io.Serializable;

@TableName("user") // 指定表名,如果表名和类名一致,可以省略
@Data // 使用Lombok简化开发
public class User implements Serializable { // 实现序列化接口
    @TableId(type = IdType.AUTO)
    private Long id;
    @TableField("name") // 指定表字段名,如果字段名和属性名一致,可以省略
    private String name;
    // @TableField(select = false) // select = false 表示查询时不查询该字段
    private Integer age;
    private String email;
    @TableField(exist = false) // exist = false 表示该字段不是数据库字段,但是可以使用
    private Boolean isOnline;
}

编写mapper接口UserMapper

package com.system.mybatisplus.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.system.mybatisplus.model.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

@Mapper // 此注解用于标记这是一个mybatis的mapper类,否则会报错,因为没有加@Mapper
public interface UserMapper extends BaseMapper<User> {// 继承BaseMapper
    // 根据名称查询
    User selectByName(@Param("name") String name);
}

编写service及其实现类

package com.system.mybatisplus.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.system.mybatisplus.model.User;
// 需要继承IService
public interface UserService extends IService<User> {

}
package com.system.mybatisplus.service.impl;

import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.system.mybatisplus.mapper.UserMapper;
import com.system.mybatisplus.model.User;
import com.system.mybatisplus.service.UserService;
import org.springframework.stereotype.Service;

@Service // 此注解用于标记这是一个service类,否则会报错,因为没有加@Service
// 此处继承ServiceImpl(ServiceImpl实现了IService接口),同时实现UserService接口
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}

1、等值查询

查询条件必须完全匹配才行,可以拼接多个eq

	@Test
    void testEq() {
        // 1、创建条件构造器
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        // 2、设置条件,指定字段名和字段值
        wrapper.eq("name", "jone");
        // 3、执行查询,使用selectOne方法,查询结果只能有一条,否则报错,如果查询结果有多条,使用selectList方法
        System.out.println(userMapper.selectList(wrapper));
    }
	@Test
    void testEq() {
        // 简写
        System.out.println(userMapper.selectList(new QueryWrapper<User>().eq("name", "jone")));
    }

相当于执行了如下SQL

SELECT id,name,age,email FROM user WHERE (name = ?)

可以使用LambdaQueryWrapper

LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getName, "jone"); // 等价于wrapper.eq("name", "jone"); 但是这样写更安全,因为不会出现字段名写错的情况
System.out.println(userMapper.selectList(wrapper));

同时多个查询条件,必须同时满足才行

    @Test
    void testEq() {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getName, "jone").eq(User::getAge, 19); // 相当于并列条件
        System.out.println(userMapper.selectList(wrapper));
    }
SELECT id,name,age,email FROM user WHERE (name = ? AND age = ?)

空值null的判断与处理

当某个查询条件值为空时,不参与拼接

一般用于多条件查询中

 @Test
    void testNull() {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        String name = "jone";
        // 查询条件是否为null的判断
        wrapper.eq(name != null, User::getName, name); // 如果name不为空,就加入条件,否则不加入条件
        System.out.println(userMapper.selectOne(wrapper));
    }

多条件查询也可以使用allEq

使用HashMap存储查询条件,就没法使用LambdaQueryWrapper

 @Test
    void testAllEq() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        HashMap<String, Object> map = new HashMap<>();
        map.put("name", "jone");
        map.put("age", 19);
        wrapper.allEq(map, false); // 等价于wrapper.eq("name", "jone").eq("age", 19); 如果第二个参数为false,表示如果map中有null值,就不加入条件
        System.out.println(userMapper.selectList(wrapper));
    }

除了eq,还有ne,即不等值查询

@Test
    void testNe() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.ne("name", "jone"); // 不等于
        System.out.println(userMapper.selectList(wrapper));
    }
SELECT id,name,age,email FROM user WHERE (name <> ?)  <> 不等于

2、范围查询

  1. gt 大于
@Test
    void testGt() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.gt("age", 20); // 大于,适用于数据类型为数字的字段和日期类型的字段
        System.out.println(userMapper.selectList(wrapper));
    }
  1. ge 大于等于
    @Test
    void testGe() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.ge("age", 20); // 大于等于,适用于数据类型为数字的字段和日期类型的字段
        System.out.println(userMapper.selectList(wrapper));
    }
  1. lt 小于
 @Test
    void testLt() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.lt("age", 20); // 小于,适用于数据类型为数字的字段和日期类型的字段
        System.out.println(userMapper.selectList(wrapper));
    }
  1. le 小于等于
 @Test
    void testLe() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.le("age", 20); // 小于等于,适用于数据类型为数字的字段和日期类型的字段
        System.out.println(userMapper.selectList(wrapper));
    }
  1. between
    @Test
    void testBetween() {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.between(User::getAge, 18, 20); // 适用于数据类型为数字的字段和日期类型的字段, 包含18和20
        System.out.println(userMapper.selectList(wrapper));
    }
  1. notBetween
    @Test
    void testNotBetween() {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.notBetween(User::getAge, 18, 20); // 适用于数据类型为数字的字段和日期类型的字段, 不包含18和20
        System.out.println(userMapper.selectList(wrapper));
    }

3、模糊查询

  1. like
@Test
    void testLike() {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.like(User::getName, "j"); // 模糊查询
        System.out.println(userMapper.selectList(wrapper));
    }
SELECT id,name,age,email FROM user WHERE (name LIKE %j%)
  1. notLike

查询名称中不含 j的,不区分大小写

@Test
    void testLike() {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.notLike(User::getName, "j"); // 模糊查询
        System.out.println(userMapper.selectList(wrapper));
    }
SELECT id,name,age,email FROM user WHERE (name NOT LIKE %j%)
  1. likeLeft

区分大小写

    @Test
    void testLikeLeft() {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.likeLeft(User::getName, "j"); // 左模糊查询, 等价于wrapper.like(User::getName, "%j")
        System.out.println(userMapper.selectList(wrapper));
    }
  1. likeRight
@Test
    void testLikeRight() {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        // 查找名字以j开头的用户,区分大小写
        wrapper.likeRight(User::getName, "j"); // 右模糊查询, 等价于wrapper.like(User::getName, "j%")
        System.out.println(userMapper.selectList(wrapper));
    }

4、判空查询

  1. isNull
    @Test
    void testIsNull() {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.isNull(User::getEmail); // 查找email为null的用户
        System.out.println(userMapper.selectList(wrapper));
    }
SELECT id,name,age,email FROM user WHERE (email IS NULL)
  1. isNotNull
    @Test
    void testIsNull() {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.isNotNull(User::getEmail); // 查找email不为null的用户
        System.out.println(userMapper.selectList(wrapper));
    }

5、包含查询

  1. in
    @Test
    void testIn() {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.in(User::getAge, Arrays.asList(18,19,20)); // 查找年龄为18,19,20的用户
        System.out.println(userMapper.selectList(wrapper));
    }
SELECT id,name,age,email FROM user WHERE (age IN (?,?,?))
  1. notIn
    @Test
    void testNotIn() {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.notIn(User::getAge, Arrays.asList(18,19,20)); // 查找年龄不为18,19,20的用户
        System.out.println(userMapper.selectList(wrapper));
    }
  1. inSql

可以编写嵌套查询

    @Test
    void testInSql() {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.inSql(User::getId, "select id from user where id < 3"); // 查找id小于3的用户
        System.out.println(userMapper.selectList(wrapper));
    }
SELECT id,name,age,email FROM user WHERE (id IN (select id from user where id < 3))
  1. notInSql
    @Test
    void testNotInSql() {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.notInSql(User::getId, "select id from user where id < 3"); // 查找id不小于3的用户
        System.out.println(userMapper.selectList(wrapper));
    }
SELECT id,name,age,email FROM user WHERE (id NOT IN (select id from user where id < 3))

6、分组查询

使用groupBy分组

    @Test
    void testGroupBy() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.groupBy("age"); // 按照年龄分组
        // 这里使用字符串的方式,所以只能使用QueryWrapper,不能使用LambdaQueryWrapper
        wrapper.select("age, count(*) as count"); // 查询年龄和年龄的数量
        System.out.println(userMapper.selectMaps(wrapper)); // 返回类型为List<Map<String, Object>>
    }
SELECT age, count(*) as count FROM user GROUP BY age

7、聚合查询

在6的基础上,过滤出count大于等于2的数据,就需要使用having过滤

先分组,再查询,最后过滤

    @Test
    void testHaving() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.groupBy("age"); // 按照年龄分组
        wrapper.select("age, count(*) as count"); // 查询年龄和年龄的数量
        wrapper.having("count >= 2"); // 查询数量大于等于2的年龄
        System.out.println(userMapper.selectMaps(wrapper)); // 返回类型为List<Map<String, Object>>
    }
SELECT age, count(*) as count FROM user GROUP BY age HAVING count >= 2

[{count=2, age=18}, {count=2, age=20}]

8、排序查询

  1. orderByASC 升序
    @Test
    void testOrderByAsc() {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.select(User::getId, User::getName, User::getAge); // 查询id, name, age字段
        wrapper.orderByAsc(User::getAge); // 按照年龄升序排序
        System.out.println(userMapper.selectList(wrapper));
    }
SELECT id,name,age FROM user ORDER BY age ASC
  1. orderByDesc 降序
    @Test
    void testOrderByDesc() {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.select(User::getId, User::getName, User::getAge); // 查询id, name, age字段
        wrapper.orderByDesc(User::getAge); // 按照年龄降序排序
        System.out.println(userMapper.selectList(wrapper));
    }
  1. orderBy

相比较前面两个,灵活性更高

    @Test
    void testOrderBy() {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.select(User::getId, User::getName, User::getAge); // 查询id, name, age字段
        // 第一个参数表示当该字段值为null时,是否还要作为排序条件
        // 第二个参数表示是否升序排序
        // 第三个参数表示排序的字段
        wrapper.orderBy(true, true, User::getAge); // 按照年龄升序排序
        // 当年龄相同时,按照id降序排序
        wrapper.orderBy(true, false, User::getId); // 按照id降序排序
        System.out.println(userMapper.selectList(wrapper));
    }
SELECT id,name,age FROM user ORDER BY age ASC,id DESC

9、逻辑查询

(1)内嵌逻辑查询func

当某个需求的条件有多个时,可以使用func

    @Test
    void testFunc() {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        // func函数需要传入一个Consumer接口,该接口的accept方法接收一个LambdaQueryWrapper对象
        wrapper.func(new Consumer<LambdaQueryWrapper<User>>() {
            @Override
            public void accept(LambdaQueryWrapper<User> userLambdaQueryWrapper) {
                // 这里需要使用实际开发中的业务逻辑来替换
                if(true) {
                    userLambdaQueryWrapper.eq(User::getId, 1);
                }else {
                    userLambdaQueryWrapper.ne(User::getId, 1);
                }
            }
        });
        System.out.println(userMapper.selectList(wrapper));
    }
    @Test
    void testFunc() {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        // func函数需要传入一个Consumer接口,该接口的accept方法接收一个LambdaQueryWrapper对象
        // 使用Lambda表达式来简化代码
        wrapper.func(userLambdaQueryWrapper -> {
            // 这里需要使用实际开发中的业务逻辑来替换
            if(true) {
                userLambdaQueryWrapper.eq(User::getId, 1);
            }else {
                userLambdaQueryWrapper.ne(User::getId, 1);
            }
        });
        System.out.println(userMapper.selectList(wrapper));
    }
(2)and

正常的拼接默认就是and,表示条件需要同时成立

    @Test
    void testAnd() {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.gt(User::getAge, 18).lt(User::getAge, 25); // 查询年龄大于18并且小于20的用户
        System.out.println(userMapper.selectList(wrapper));
    }

使用and嵌套

通常需要嵌套or

    @Test
    void testAnd() {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getId, 1)
                .and(userLambdaQueryWrapper -> userLambdaQueryWrapper.eq(User::getAge, 18)
                        .or().eq(User::getAge, 20)); // 查询id为1并且年龄为18或20的用户
        System.out.println(userMapper.selectList(wrapper));
    }
SELECT id,name,age,email FROM user WHERE (id = ? AND (age = ? OR age = ?))
(3)or

表示多个条件只需要成立其中之一即可

    @Test
    void testOr() {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.gt(User::getAge, 25)
                .or()
                .lt(User::getAge, 20); // 查询年龄大于25或小于20的用户
        System.out.println(userMapper.selectList(wrapper));
    }

or也可以嵌套

(4)nested

表示嵌套查询

    @Test
    void testNested() {
        // nested表示嵌套查询, 用于构建复杂的查询条件
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getId, 1)
                .nested(userLambdaQueryWrapper -> userLambdaQueryWrapper.eq(User::getAge, 18)
                        .or().eq(User::getAge, 20)); // 查询id为1并且年龄为18或20的用户
        System.out.println(userMapper.selectList(wrapper));
    }

10、自定义查询

使用apply函数

可以定制更复杂的查询条件

    @Test
    void testApply() {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        // apply方法用于拼接SQL语句,这里需要使用实际开发中的业务逻辑来替换
        wrapper.apply("id = 1"); // 查询id为1的用户
        System.out.println(userMapper.selectList(wrapper));
    }

11、last

last主要用于分页查询中,需要传入字符串参数

    @Test
    void testLast() {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.last("limit 1"); // 查询第一个用户
        System.out.println(userMapper.selectList(wrapper));
    }
SELECT id,name,age,email FROM user limit 1
    @Test
    void testLast() {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.last("limit 0, 2"); // 查询前两条数据
        System.out.println(userMapper.selectList(wrapper));
    }
SELECT id,name,age,email FROM user limit 0, 2

12、exists

    @Test
    void testExists() {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        // 查询age为18的用户是否存在,如果存在则返回true,否则返回false
        // 返回true后,会继续执行后面的查询操作
        wrapper.exists("select id from user where age = 18");
        System.out.println(userMapper.selectList(wrapper));
    }
SELECT id,name,age,email FROM user WHERE (EXISTS (select id from user where age = 18))
    @Test
    void testNotExists() {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        // 查询age为18的用户是否不存在,如果不存在则返回true,否则返回false
        // 返回true后,会继续执行后面的查询操作
        wrapper.notExists("select id from user where age = 18");
        System.out.println(userMapper.selectList(wrapper));
    }

二、主键策略

1、AUTO

需要在表的主键上设置自增,然后在实体类上的对应的属性上加上注解

@TableId(type = IdType.AUTO)
private Long id;

这样设置后,每次增加一条数据时,会自动生成对应的id

2、INPUT

不需要在表的主键上设置自增,每次新增数据时需要自己设置id

@TableId(type = IdType.AUTO)
private Long id;

3、ASSIGN_ID

使用雪花算法可以实现有序、唯一、且不直接暴露排序的数字

@TableId(type = IdType.ASSIGN_ID)
private Long id;

4、NONE

使用该策略表示不指定主键生成策略,而是跟随全局策略,可以在配置文件中使用id-type指定全局主键策略

@TableId(type = IdType.NONE)
private Long id;

5、ASSIGN_UUID

UUID是全局唯一标识符,定义为一个字符串主键,采用32位字符组成,保证始终唯一,需要设置id的类型为字符串

@TableId(type = IdType.ASSIGN_UUID)
private Long id;

三、分页查询

首先需要编写配置类

以下适用于mybatis-plus 3.5以上版本

@Configuration
// @MapperScan("com.system.mybatisplus.mapper") // 如果在启动类上已经配置了,这里就不需要再配置了
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // DbType.MYSQL 表示数据库类型是mysql
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

测试

    @Test
    void testPage() {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        // 创建一个分页对象,传入两个参数:当前页和每页显示的记录数
        IPage<User> pageParam = new Page<>(1, 2);
        // 调用分页查询的方法,将分页对象和查询条件对象传入
        userMapper.selectPage(pageParam, wrapper);
        // 从分页对象中获取分页数据
        System.out.println("总页数:" + pageParam.getPages());
        System.out.println("总记录数:" + pageParam.getTotal());
        System.out.println("当前页码:" + pageParam.getCurrent());
        System.out.println("每页显示的记录数:" + pageParam.getSize());
    }

一般会自定义查询语句,所以通用的写法如下

controller中

@RestController
public class UserController {
    @Autowired
    private UserService userService;

    @GetMapping("/users/{page}/{limit}")
    public IPage<User> listPage(@PathVariable("page") Long page, @PathVariable("limit") Long limit) {
        // 创建一个分页对象,传入两个参数:当前页和每页显示的记录数
        IPage<User> pageParam = new Page<>(page, limit);
        // 调用Service中的方法,一般还会传入一个条件查询对象
        return userService.listPage(pageParam);
    }
}

Service中

public interface UserService extends IService<User> {

    IPage<User> listPage(IPage<User> pageParam);
}
@Service // 此注解用于标记这是一个service类,否则会报错,因为没有加@Service
// 此处继承ServiceImpl(ServiceImpl实现了IService接口),同时实现UserService接口
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Autowired
    private UserMapper userMapper;
    @Override
    public IPage<User> listPage(IPage<User> pageParam) {
        return userMapper.selectUserByPage(pageParam);
    }
}

mapper中

@Mapper // 此注解用于标记这是一个mybatis的mapper类,否则会报错,因为没有加@Mapper
public interface UserMapper extends BaseMapper<User> {
	// 返回值是一个IPage对象
    IPage<User> selectUserByPage(IPage<User> pageParam); // 传入分页参数
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.system.mybatisplus.mapper.UserMapper">
    <select id="selectUserByPage" resultType="com.system.mybatisplus.model.User">
        select * from user
    </select>
</mapper>

四、SimpleQuery

SimpleQuery可以对selectList查询后的结果使用Stream流进行了一些封装,使其可以返回一些指定结果,简化了Api的调用。

1、list

例如,查询出所有User的姓名,返回一个List

使用Stream流

    @Test
    void testList() {
        List<User> users = userMapper.selectList(null);
        // 自己调用Stream的Api
        List<String> names = users.stream().map(User::getName).toList();
        names.forEach(System.out::println);
    }

使用SimpleQuery的list

// 必须传入一个wrapper,而不能写null       
List<String> names = SimpleQuery.list(new LambdaQueryWrapper<User>(), User::getName);
        names.forEach(System.out::println);

获取年龄为20的用户姓名列表

        List<String> names = SimpleQuery.list(new LambdaQueryWrapper<User>()
                .eq(User::getAge, 20), User::getName);
        System.out.println(names);
[Jack, Billie]

2、map

可以指定键值对

以id为键,user对象为值

        Map<Long, User> map = SimpleQuery.keyMap(new LambdaQueryWrapper<>(), User::getId);
        System.out.println(map);

只查询age为20的User

        Map<Long, User> map = SimpleQuery.keyMap(new LambdaQueryWrapper<User>().eq(User::getAge, 20), User::getId);
        System.out.println(map);

以id为键,name为值

这里需要使用map方法,第二、三个参数分别表示要获得的键、值

        Map<Long, String> longUserMap = SimpleQuery.map(new LambdaQueryWrapper<User>(), User::getId, User::getName);
        System.out.println(longUserMap);

{1=Jone, 2=Jack, 3=Tom, 4=Sandy, 5=Billie, 6=test}

3、group

可以按照字段进行分组

比如,按照年龄进行分组

使用group方法,第二个参数是待分组字段

        Map<Integer, List<User>> group = SimpleQuery.group(new LambdaQueryWrapper<User>(), User::getAge);
        System.out.println(group);

五、逻辑删除

逻辑删除的目的是方便做统计,状态可恢复(用户的可用与禁用)

首先数据库中的表字段添加status

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

并使用默认值1,表示未删除

然后Java实体类添加对应的属性

    @TableLogic(value = "1", delval = "0") // value是默认值,即未删除,delVal是删除时的值
    private Integer status;

测试

    @Test
    void testLogicDelete() {
        userMapper.deleteById(4L);
        System.out.println(userMapper.selectList(null));
    }

首先执行

UPDATE user SET status=0 WHERE id=? AND status=1

将id为4的user的status更新为0,表示删除

然后再次查询

SELECT id,name,age,email,status FROM user WHERE status=1

查询时都会看status是否为1(未删除)

可以全局配置逻辑删除字段及其值

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

六、通用枚举

例如,对性别字段使用枚举

0–女性, 1–男性

数据库表中添加字段

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Java中编写枚举

public enum GenderEnum {
    MAN(1, "男"), WOMAN(0, "女");
    @EnumValue // 表示插入到数据库中的是gender,即0或1
    private Integer gender;
    private String genderName;

    GenderEnum(Integer gender, String genderName){
        this.gender = gender;
        this.genderName = genderName;
    }
}

User属性

private GenderEnum gender; // 必须是gender,对应0或1

测试插入

    @Test
    void testEnum() {
        User user = new User();
        user.setName("zzs");
        user.setGender(GenderEnum.MAN); // 使用枚举值
        userMapper.insert(user);
    }

测试查询

System.out.println(userMapper.selectList(null));

[User(id=1, name=Jone, age=18, email=test1@baomidou.com, isOnline=null, status=1, gender=MAN), User(id=2,

输出的gender是MAN

七、字段处理器

实体类中使用Map集合作为属性接受前端传递的数据,但是这些数据在数据库中存储时,是使用JSON格式的数据进行存储,JSON本质上是一个字符串,即数据库中的varchar类型。要将实体类的Map类型和数据库中的varchar类型的数据相互转换,可以使用字段处理器来完成。

首先在实体类中添加一个Map集合

private Map<String, String> contact; // 联系方式,可以有两种,即手机号和座机号

在数据库中添加对应的表字段contact,设置为varchar类型。

再次修改实体类,需要添加两个地方

@TableName(value = "user", autoResultMap = true) 


@TableField(typeHandler = FastjsonTypeHandler.class)
private Map<String, String> contact; // 联系方式,可以有两种,即手机号和座机号

整体代码如下:

// 使用字段处理器,需要 autoResultMap = true
@TableName(value = "user", autoResultMap = true) // 指定表名,如果表名和类名一致,可以省略
@Data
public class User implements Serializable { // 实现序列化接口
    @TableId(type = IdType.AUTO)
    private Long id;
    @TableField("name") // 指定表字段名,如果字段名和属性名一致,可以省略
    private String name;
    // @TableField(select = false) // select = false 表示查询时不查询该字段
    private Integer age;
    private String email;
    @TableField(exist = false) // exist = false 表示该字段不是数据库字段,但是可以使用
    private Boolean isOnline;

    @TableLogic(value = "1", delval = "0") // value是默认值,即未删除,delVal是删除时的值
    private Integer status;


    private GenderEnum gender;

    // 使用FastjsonTypeHandler
    @TableField(typeHandler = FastjsonTypeHandler.class)
    private Map<String, String> contact; // 联系方式,可以有两种,即手机号和座机号
}

添加fastjson依赖

<!-- fastjson-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.31_noneautotype</version>
        </dependency>

测试插入,即Map转JSON

    @Test
    void testMapToJson(){
        User user = new User();
        user.setName("xxx");
        user.setAge(23);
        user.setGender(GenderEnum.MAN);
        // 添加Map集合
        Map<String, String> map = new HashMap<>();
        map.put("phone", "19122345566");
        map.put("tel", "001-123456");
        user.setContact(map);
        userMapper.insert(user);
    }

在数据库中contact字段存储如下

{"phone":"19122345566","tel":"001-123456"}

测试查询,即JSON转Map

    @Test
    void testJsonToMap(){
        System.out.println(userMapper.selectList(null));
    }
contact={phone=19122345566, tel=001-123456}

八、自动填充

有一些属性,比如时间,我们可以设置为自动填充

在实体类User中添加两个属性

    private Date createTime;

    private Date updateTime;

数据库对应字段 ,都是datetime类型

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

需要开启下划线和驼峰映射 map-underscore-to-camel-case: true

# mybatis-plus配置
mybatis-plus:
  global-config:
    banner: false
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印sql语句
    map-underscore-to-camel-case: true # 下划线和小驼峰映射

设置自动填充时间

写一个工具类MyMetaHandler,定义插入和更新数据时怎么自动填充

@Component
public class MyMetaHandler implements MetaObjectHandler {
    @Override
    public void insertFill(MetaObject metaObject) {
        // 插入数据时,两个时间都填充
        setFieldValByName("createTime", new Date(), metaObject);
        setFieldValByName("updateTime", new Date(), metaObject);
    }

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

在属性上写上注解

    @TableField(fill = FieldFill.INSERT) // 插入时填充
    private Date createTime;

    @TableField(fill = FieldFill.INSERT_UPDATE) // 插入或更新时填充
    private Date updateTime;

设置时区对应

yml文件中连接数据库修改serverTimezone=Asia/Shanghai

    url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai

数据库中新增查询,设置全局时间

SET GLOBAL time_zone = '+8:00';

SELECT NOW(); // 测试,看是否与本地时间相同

测试,是否能够实现时间自动填充

插入新数据时,createTime和updateTime都自动填充

@Test
void testFillTime(){
    User user = new User();
    user.setName("nihao");
    user.setAge(23);
    user.setGender(GenderEnum.MAN);
    userMapper.insert(user);
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

修改数据时,updateTime自动修改

@Test
void updateTime(){
    User user = new User();
    user.setId(9L);
    userMapper.updateById(user);
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

九、防止全表更新与删除插件

在实际开发中,全表更新和删除是非常危险的操作,Mybatis-plus提供了一个插件可以防止全表更新。

在mybatis-plus配置类中添加上

interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());

整体代码

@Configuration
// @MapperScan("com.system.mybatisplus.mapper") // 如果在启动类上已经配置了,这里就不需要再配置了
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // DbType.MYSQL 表示数据库类型是mysql
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        // 防止全表更新插件
        interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
        return interceptor;
    }
}

测试

@Test
void updateBlock(){
    User user = new User();
    user.setGender(GenderEnum.MAN);
    userMapper.update(user, null);
}

以上代码更新时未设置条件,就会导致所有数据的性别改为MAN

但是设置了防止全表更新的插件后,mybatis-plus会自动报错,抛出异常

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

十、逆向工程-MybatisX插件

在idea插件市场中找到插件并且安装

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

连接数据库,点击红线处

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

自动生成SQL

在mapper里面写上方法名,然后选中,再Alt+enter

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

十一、并发问题分析

  • 乐观锁

乐观锁是通过表字段完成设计的,其核心思想是,在读取的时候不加锁,其他请求依然可以读取到这个数据,在修改的时候判断这个数据是否有被修改过,如果有被修改过,那么本次请求的修改操作失败。

具体的SQL是这样实现的

update 表 set 字段 = 新值, version = version + 1 where version = 1

这样做不会对数据读取产生影响,并发的效率较高,但是目前看到的数据可能不是真实的数据,是被修改之前的,这在多数情况下是不会产生很大的影响。例如,有时候我们看到某种商品是有库存的,或者都加入到购物车了,但是点进去发现库存不足了。

  • 悲观锁

悲观锁是在查询的时候就锁定数据,在这次请求未完成之前,不会释放锁。等到这次请求完毕后,再释放锁,释放了锁之后,其他请求才可以对这条数据完成读写。

这样做能够保证读取到的信息就是当前的信息,保证了信息的准确性,但是并发效率很低。

所以,实际开发中,使用悲观锁的场景很少,因为需要保持效率。

乐观锁实现

首先在数据库中添加字段version,默认值为1

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

然后在实体类User中添加对应的字段

    @Version
    private Integer version;

在mybatis-plus配置类中添加乐观锁插件

        // 乐观锁插件
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());

测试并发修改

    @Test
    void testLock(){
        // 模拟操作1的查询
        User user1 = userMapper.selectById(9L);
        System.out.println("user1查询结果" + user1);
        // 模拟操作2的查询
        User user2 = userMapper.selectById(9L);
        System.out.println("user2查询结果" + user2);
        // 模拟操作1的修改
        user1.setName("zhangsan");
        userMapper.updateById(user1);
        // 模拟操作2的修改
        user2.setName("lisi");
        userMapper.updateById(user2);
    }

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

结果只有操作1修改成功,说明乐观锁生效

都能查询到,但是只有操作1修改成功,因为操作1修改时,version未发生变化,但是操作2修改时,version被操作1修改为2了。

user1查询结果User(id=9, name=nihao, age=23, email=null, isOnline=null, status=1, gender=MAN, contact=null, createTime=Thu Sep 07 08:48:53 CST 2023, updateTime=Thu Sep 07 08:52:48 CST 2023, version=1)

user2查询结果User(id=9, name=nihao, age=23, email=null, isOnline=null, status=1, gender=MAN, contact=null, createTime=Thu Sep 07 08:48:53 CST 2023, updateTime=Thu Sep 07 08:52:48 CST 2023, version=1)

十二、代码生成器

打开mybatis-plus的官网,找到代码生成器章节

适用版本:mybatis-plus-generator 3.5.1 及其以上版本,对历史版本不兼容!3.5.1 以下的请参考 代码生成器旧

首先引入依赖

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>最新版本</version>
</dependency>

本人使用的mybatis-plus版本

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.3.1</version>
        </dependency>

所以mybatis-plus-generator我也使用3.5.3.1

写个main方法执行

FastAutoGenerator.create("url", "username", "password")
    .globalConfig(builder -> {
        builder.author("baomidou") // 设置作者
            .enableSwagger() // 开启 swagger 模式
            .fileOverride() // 覆盖已生成文件
            .outputDir("D://"); // 指定输出目录
    })
    .dataSourceConfig(builder -> builder.typeConvertHandler((globalConfig, typeRegistry, metaInfo) -> {
        int typeCode = metaInfo.getJdbcType().TYPE_CODE;
        if (typeCode == Types.SMALLINT) {
            // 自定义类型转换
            return DbColumnType.INTEGER;
        }
        return typeRegistry.getColumnType(metaInfo);

    }))
    .packageConfig(builder -> {
        builder.parent("com.baomidou.mybatisplus.samples.generator") // 设置父包名
            .moduleName("system") // 设置父包模块名
            .pathInfo(Collections.singletonMap(OutputFile.xml, "D://")); // 设置mapperXml生成路径
    })
    .strategyConfig(builder -> {
        builder.addInclude("t_simple") // 设置需要生成的表名
            .addTablePrefix("t_", "c_"); // 设置过滤表前缀
    })
    .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
    .execute();

十三、SQL分析打印

需要查看执行的SQL语句时,以及了解它的执行时间,方便分析是否出现了慢SQL问题,可以使用mybatis-plus提供的SQL分析打印的功能,来获取SQL语句执行的时间。

首先引入依赖

        <!-- SQL分析打印依赖-->
        <dependency>
            <groupId>p6spy</groupId>
            <artifactId>p6spy</artifactId>
            <version>3.9.1</version>
        </dependency>

修改yml文件中连接数据库的驱动和URL

jdbc:p6spy:mysql

driver-class-name: com.p6spy.engine.spy.P6SpyDriver

# 配置数据库
spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:p6spy:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
    driver-class-name: com.p6spy.engine.spy.P6SpyDriver #com.mysql.cj.jdbc.Driver

创建

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

module.log=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
# 自定义日志打印
logMessageFormat=com.p6spy.engine.spy.appender.SingleLineFormat
#logMessageFormat=com.p6spy.engine.spy.appender.CustomLineFormat
#customLogMessageFormat=%(currentTime) | SQL耗时: %(executionTime) ms | 连接信息: %(category)-%(connectionId) | 执行语句: %(sql)
# 使用控制台记录sql
appender=com.p6spy.engine.spy.appender.StdoutLogger
## 配置记录Log例外
excludecategories=info,debug,result,batc,resultset
# 设置使用p6spy driver来做代理
deregisterdrivers=true
# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss
# 实际驱动
driverlist=com.mysql.cj.jdbc.Driver
# 是否开启慢SQL记录
outagedetection=true
# 慢SQL记录标准 秒
outagedetectioninterval=2

十四、多数据源

首先添加依赖

        <!--多数据源依赖 -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
            <version>3.1.0</version>
        </dependency>
spring:
  datasource:
    dynamic:
      primary: master #设置默认的数据源或者数据源组,默认值即为master
      strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
      datasource:
        master:
          url: jdbc:mysql://xx.xx.xx.xx:3306/dynamic
          username: root
          password: 123456
          driver-class-name: com.mysql.jdbc.Driver # 3.2.0开始支持SPI可省略此配置
        slave_1:
          url: jdbc:mysql://xx.xx.xx.xx:3307/dynamic
          username: root
          password: 123456
          driver-class-name: com.mysql.jdbc.Driver
        slave_2:
          url: ENC(xxxxx) # 内置加密,使用请查看详细文档
          username: ENC(xxxxx)
          password: ENC(xxxxx)
          driver-class-name: com.mysql.jdbc.Driver
       #......省略
       #以上会配置一个默认库master,一个组slave下有两个子库slave_1,slave_2
# 多主多从                      纯粹多库(记得设置primary)                   混合配置
spring:                               spring:                               spring:
  datasource:                           datasource:                           datasource:
    dynamic:                              dynamic:                              dynamic:
      datasource:                           datasource:                           datasource:
        master_1:                             mysql:                                master:
        master_2:                             oracle:                               slave_1:
        slave_1:                              sqlserver:                            slave_2:
        slave_2:                              postgresql:                           oracle_1:
        slave_3:                              h2:                                   oracle_2:

使用 @DS 切换数据源。

@DS 可以注解在方法上或类上,同时存在就近原则 方法上注解 优先于 类上注解

注解结果
没有@DS默认数据源
@DS(“dsName”)dsName可以为组名也可以为具体某个库的名称

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/985334.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

华为云云服务器评测|在云耀云服务器L实例上部署battle-city坦克大战小游戏

华为云云服务器评测&#xff5c;在云耀云服务器L实例上部署battle-city坦克大战小游戏 一、前言1.1 云耀云服务器L实例简介1.2 battle-city坦克大战小游戏简介 二、本次实践介绍2.1 本次实践简介2.2 本次环境规划 三、购买云耀云服务器L实例3.1 登录华为云3.2 购买云耀云服务器…

八种十倍提升API性能的方式

提起API&#xff0c;作为程序员来说并不陌生&#xff0c;很多程序员的大部分工作都是围绕着它&#xff0c; 然而&#xff0c;有些内容被大家忽略&#xff0c;API的性能会直接影响产品的用户体验&#xff0c;比如&#xff0c;一个视频软件&#xff0c;播放1s后需要加载5s&#x…

Android 状态栏显示运营商名称

Android 原生设计中在锁屏界面会显示运营商名称&#xff0c;用户界面中&#xff0c;大概是基于 icon 数量长度显示考虑&#xff0c;对运营商名称不作显示。但是国内基本都加上运营商名称。对图标显示长度优化基本都是&#xff1a;缩小运营商字体、限制字数长度、信号图标压缩上…

SAM论文翻译

文章目录 Abstract1、Introduction2、Related Work3、Methodology3.1、Semantic Graph3.2、Semantic Aware Module3.3、Decoder3.4、Loss Function 4、Experiments4.1、Datasets4.2、Implementation Details4.3、Evaluation Protocol4.4、Comparison with State-of-the-Art 论文…

SpringBoot粗浅分析

应用分析 1、依赖管理机制 在springBoot项目中&#xff0c;导入starter-web所有想换依赖都会被导入&#xff0c;甚至不用去规定它们的版本号。它是根据Maven的依赖传递原则来设置&#xff0c;只需要导入场景启动器&#xff0c;场景启动器自动把这个场景的所有核心依赖全部导入…

对极几何与三角化求3D空间坐标

一&#xff0c;使用对极几何约束求R,T 第一步&#xff1a;特征匹配。提取出有效的匹配点 void find_feature_matches(const Mat &img_1, const Mat &img_2,std::vector<KeyPoint> &keypoints_1,std::vector<KeyPoint> &keypoints_2,std::vector&l…

管理类联考——数学——汇总篇——知识点突破——数据分析——计数原理——减法原理除法原理

减法原理 正面难则反着做(“ − - −”号) 【思路】当出现“至少、至多”、“否定用语"等正面较难分类的题目&#xff0c;可以采用反面进行求解&#xff0c;注意部分反面的技巧以及“且、或"的反面用法。 除法原理 看到相同&#xff0c;定序用除法消序( “ &quo…

JavaScript中点号运算符与方括号运算符

这篇文章将介绍如何在对象中获取数据、修改数据。在JavaScript中&#xff0c;点号运算符和方括号运算符都可以用于访问对象的属性。 我们还是使用上节课的代码来演示 const ITshareArray { firstname: “张三”, secondname: “二愣子”, age: 2033-1997, job: “程序员”, fr…

自动化运维——ansible (五十二) (01)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 一、概述 1.1 为什么要用自动化运维软件 1.2 自动化运维 1.3 自动化运维要注意的方面 1.4 自动化运维主要关注的方面 1.5 常见的开源自动化运维软件 1.6 自动化运维软件…

Debian11安装MySQL8.0,链接Navicat

图文小白教程 1 下载安装MySQL1.1 从MySQL官网下载安装文件1.2 安装MySQL1.3 登录MySQL 2 配置Navicat远程访问2.1 修改配置2.2 Navicat 连接 end: 卸载 MySQL 记录于2023年9月&#xff0c;Debian11 、 MySQL 8.0.34 1 下载安装MySQL 1.1 从MySQL官网下载安装文件 打开 MySQ…

Unity 之 利用数组来管理资源

文章目录 在Unity中&#xff0c;资源数组&#xff08;Resource Arrays&#xff09;不是Unity的标准概念。然而&#xff0c;您可能在特定上下文中使用数组来管理资源或游戏对象。我将解释如何在Unity中使用数组来管理资源。 资源管理&#xff1a; 在Unity中&#xff0c;资源通常…

9.7 C高级day2 作业

#!/bin/bash mkdir ~/dir mkdir ~/dir/dir1 mkdir ~/dir/dir2 cp ./* ~/dir/dir1 -r cp ./*.sh ~/dir/dir2 cd ~/dir tar -cJf dir2.tar.xz dir2 mv dir2.tar.xz dir1 cd tar -xJf dir/dir1/dir2.tar.xz -C dir/dir1 tree ~/dir

阿里云2核2G云服务器租用价格表_一年费用_1个月和1小时收费

阿里云2核2G服务器多少钱一年&#xff1f;108元一年&#xff0c;折合9元一个月&#xff0c;配置为2核CPU、2G内存、3M带宽、50GB高效云盘的轻量应用服务器&#xff0c;如果是云服务器ECS&#xff0c;2核2G配置可以选择ECS通用算力型u1实例、突发性能实例t6和t5实例、密集计算型…

接口响应成功未有预期结果排查

最近开发中遇到一个问题&#xff0c;有一个新增接口&#xff0c;请求该接口时响应200但查询相关数据未有预期的数据&#xff0c;且日志中没有任何报错或警告&#xff1b;一般来说响应200认为是成功&#xff0c;但是结果却不符合事实&#xff1b;此时无外乎几种情况&#xff1a;…

DQN算法概述及基于Pytorch的DQN迷宫实战代码

一. DQN算法概述 1.1 算法定义 Q-Learing是在一个表格中存储动作对应的奖励值&#xff0c;即状态-价值函数Q(s,a)&#xff0c;这种算法存在很大的局限性。在现实中很多情况下&#xff0c;强化学习任务所面临的状态空间是连续的&#xff0c;存在无穷多个状态&#xff0c;这种情…

D361周赛复盘:模拟分割整数⭐+变为整除的最小次数⭐

文章目录 2843.统计对称整数的数目&#xff08;模拟&#xff0c;分割整数为两部分&#xff09;思路1.整数换成字符串版本2.直接用整数的版本 2844.生成特殊数字的最小操作(模拟&#xff0c;x能被Num整除的条件)思路完整版 2843.统计对称整数的数目&#xff08;模拟&#xff0c;…

4.矩阵的几何意义、变基与迹

文章目录 变基操作与矩阵矩阵的迹几何意义矩阵迹的几条性质 欢迎访问个人网络日志&#x1f339;&#x1f339;知行空间&#x1f339;&#x1f339; 变基操作与矩阵 我们知道空间中一点的坐标可以表示以原点为起点以该点为终点的向量。 以二维平面为例&#xff0c;如下图 选取…

D1. Too Many Segments (easy version)

题目&#xff1a;样例1&#xff1a; 输入 7 2 11 11 9 11 7 8 8 9 7 8 9 11 7 9输出 3 1 4 7 样例2&#xff1a; 输入 5 1 29 30 30 30 29 29 28 30 30 30输出 3 1 2 4 样例3&#xff1a; 输入 6 1 2 3 3 3 2 3 2 2 2 3 2 3输出 4 1 3 5 6 思路&#xff1a; 这里数据范围是…

React 全栈体系(四)

第二章 React面向组件编程 六、组件的生命周期 1. 效果 需求:定义组件实现以下功能&#xff1a; 让指定的文本做显示 / 隐藏的渐变动画从完全可见&#xff0c;到彻底消失&#xff0c;耗时2S点击“不活了”按钮从界面中卸载组件 <!DOCTYPE html> <html lang"e…

AlexNet 06

一、发展 1989年&#xff0c;Yann LeCun提出了一种用反向传导进行更新的卷积神经网络&#xff0c;称为LeNet。 1998年&#xff0c;Yann LeCun提出了一种用反向传导进行更新的卷积神经网络&#xff0c;称为LeNet-5 AlexNet&#xff0c;VGG&#xff0c;GoogleNet&#xff0c;R…