节省时间,提高效率:深入解析MyBatis Plus

1. MyBatis Plus 概述

  • Mybatis + 通用Mapper + PageHelper 升级成 MyBatis Plus

1.1 简介

官网:https://baomidou.com/

参考教程:https://baomidou.com/pages/24112f/

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

1.2 特点

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑

  • 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作

  • 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求

  • 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错

  • 支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer2005、SQLServer 等多种数据库

  • 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题

  • 支持 XML 热加载:Mapper 对应的 XML 支持热加载,对于简单的 CRUD 操作,甚至可以无 XML 启动

  • 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作

  • 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )

  • 支持关键词自动转义:支持数据库关键词(order、key…)自动转义,还可自定义关键词

  • 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用

  • 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询

  • 内置性能分析插件:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询

  • 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作

  • 内置 Sql 注入剥离器:支持 Sql 注入剥离,有效预防 Sql 注入攻击

    select * from user where username = 'jack' and password = '1234';
    -- 将 jack 替换成 jack' #
    -- sql注入,用户输入的内容,称为sql语句语法的一部分。
    select * from user where username = 'jack' #' and password = '1234';
    

2. 入门案例

2.1 搭建环境

  • 步骤

    • 步骤一:创建项目:test-mybatis-plus
    • 步骤二:修改pom.xml,添加依赖
    • 步骤三:创建yml文件,配置数据库相关
  • 步骤一:创建项目:test-mybatis-plus

  • 步骤二:修改pom.xml,添加依赖

        <!--确定spring boot的版本-->
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.3.5.RELEASE</version>
        </parent>   
    
    <dependencies>
            <!-- web 开发 -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <!--MySQL数据库驱动-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
            </dependency>
            <!--支持lombok-->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </dependency>
            <!--测试-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
            </dependency>
            <!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>3.4.0</version>
            </dependency>
        </dependencies>
    
  • 步骤三:创建yml文件,配置数据库相关

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

    spring:
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/cloud_db1?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
        username: root
        password: 1234
    mybatis-plus:
      configuration:
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  #输出日志
    

2.2 数据库和表

CREATE DATABASE cloud_db1;
USE cloud_db1;
CREATE TABLE `tmp_customer` (
  `cid` INT(11) NOT NULL AUTO_INCREMENT,
  `cname` VARCHAR(50) DEFAULT NULL,
  `password` VARCHAR(32) DEFAULT NULL,
  `telephone` VARCHAR(11) DEFAULT NULL,
  `money` DOUBLE DEFAULT NULL,
  `version` INT(11) DEFAULT NULL,
  `create_time` DATE DEFAULT NULL,
  `update_time` DATE DEFAULT NULL,
  PRIMARY KEY (`cid`)
);

INSERT  INTO `tmp_customer`(`cid`,`cname`,`password`,`telephone`,`money`,`version`,`create_time`,`update_time`) 
VALUES (1,'jack','1234','110',1000,NULL,NULL,NULL),(2,'rose','1234','112',1000,NULL,NULL,NULL),(3,'tom','1234','119',1000,NULL,NULL,NULL);

2.3 入门:查询所有

  • 步骤

    • 步骤1:配置JavaBean,添加MyBatisPlus对应的注解(表、主键、字段等)
    • 步骤2:编写dao接口,并继承BaseMapper接口
    • 步骤3:编写启动类
    • 步骤4:编写测试类
  • 步骤1:配置JavaBean

    • @TableName 表名注解,value属性设置表名
package com.czxy.domain;

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.util.List;

/**
 * @author 桐叔
 * @email liangtong@itcast.cn
 */
@Data
@TableName("tmp_customer")
public class Customer {
    @TableId(type = IdType.AUTO)
    private Integer cid;
    private String cname;
    private String password;
    private String telephone;
    private String money;

    private Integer version;

    @TableField(exist = false)
    private List<Integer> ids;
}

  • 步骤2:编写dao
package com.czxy.mp.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.czxy.mp.domain.Customer;
import org.apache.ibatis.annotations.Mapper;

/**
 * Created by liangtong.
 */
@Mapper
public interface CustomerMapper extends BaseMapper<Customer> {
}
  • 步骤3:编写启动类
package com.czxy.mp;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * Created by liangtong.
 */
@SpringBootApplication
public class MybatisPlusApplication {
    public static void main(String[] args) {
        SpringApplication.run(MybatisPlusApplication.class, args);
    }
}

  • 步骤4:编写测试类
package com.czxy;

import com.czxy.mp.MybatisPlusApplication;
import com.czxy.mp.domain.Customer;
import com.czxy.mp.mapper.CustomerMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import javax.annotation.Resource;
import java.util.List;

/**
 * Created by liangtong.
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = MybatisPlusApplication.class)
public class TestDemo01 {
    @Resource
    private CustomerMapper customerMapper;

    @Test
    public void testFindAll() {
        List<Customer> list = customerMapper.selectList(null);
        list.forEach(System.out::println);
    }
}

3. 基本操作

3.1 常见API

BaseMapper 封装CRUD操作,泛型 T 为任意实体对象

  • 增删改
方法名描述
int insert(T entity)插入一条记录,entity 为 实体对象
int delete(Wrapper wrapper)根据 entity 条件,删除记录,wrapper 可以为 null
int deleteBatchIds(Collection idList)根据ID 批量删除
int deleteById(Serializable id)根据 ID 删除
int deleteByMap(Map<String, Object> map)根据 columnMap 条件,删除记录
int update(T entity, Wrapper updateWrapper)根据 whereEntity 条件,更新记录
int updateById(T entity);根据 ID 修改
  • 查询
方法名描述
T selectById(Serializable id)根据 ID 查询
T selectOne(Wrapper queryWrapper)根据 entity 条件,查询一条记录
List selectBatchIds(Collection idList)根据ID 批量查询
List selectList(Wrapper queryWrapper)根据 entity 条件,查询全部记录
List selectByMap(Map<String, Object> columnMap)根据 columnMap 条件
List<Map<String, Object>> selectMaps(Wrapper queryWrapper)根据 Wrapper 条件,查询全部记录
List selectObjs( Wrapper queryWrapper)根据 Wrapper 条件,查询全部记录。注意: 只返回第一个字段的值
IPage selectPage(IPage page, Wrapper queryWrapper)根据 entity 条件,查询全部记录(并翻页)
IPage<Map<String, Object>> selectMapsPage(IPage page, Wrapper queryWrapper)根据 Wrapper 条件,查询全部记录(并翻页)
Integer selectCount(@Param(Constants.WRAPPER) Wrapper queryWrapper)根据 Wrapper 条件,查询总记录数

3.2 添加

    @Test
    public void testInsert() {
        Customer customer = new Customer();
        customer.setCname("测试");

        customerMapper.insert(customer);
    }
  • 获得自动增长列信息

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

    @Test
    public void testInsert() {
        Customer customer = new Customer();
        customer.setCname("测试");
        customerMapper.insert(customer);
        System.out.println(customer);
    }

3.3 更新

  • 通过id更新
    @Test
    public void testUpdate() {
        Customer customer = new Customer();
        customer.setCid(15);
        customer.setCname("测试777");
        customer.setPassword("777");

        // 需要给Customer设置@TableId
        customerMapper.updateById(customer);
    }
  • 更新所有
	@Test
    public void testUpdate2() {
        Customer customer = new Customer();
        customer.setCname("测试777");
        customer.setPassword("777");
        // 更新所有
        customerMapper.update(customer,null);
    }

3.4 删除

  • 根据id进行删除
@Test
    public void testDelete() {
        // 需要给Customer设置@TableId
        int i = customerMapper.deleteById(11);
        System.out.println(i);
    }
  • 批量删除
    @Test
    public void testBatchDelete() {
        // 需要给Customer设置@TableId
        int i = customerMapper.deleteBatchIds(Arrays.asList(9,10));
        System.out.println(i);
    }

4 查询

4.1 Map条件

    @Test
    public void testMap(){
        Map map = new HashMap();
        map.put("cname","测试");
        map.put("password","123456");

        List list = customerMapper.selectByMap(map);
        list.forEach(System.out::println);
    }

4.2 Wrapper条件

4.2.1 wrapper介绍

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

  • Wrapper : 条件构造抽象类,最顶端父类

    • AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件
      • QueryWrapper : Entity 对象封装操作类,不是用lambda语法
      • UpdateWrapper : Update 条件封装,用于Entity对象更新操作
      • AbstractLambdaWrapper : Lambda 语法使用 Wrapper统一处理解析 lambda 获取 column。
        • LambdaQueryWrapper :看名称也能明白就是用于Lambda语法使用的查询Wrapper
        • LambdaUpdateWrapper : Lambda 更新封装Wrapper
  • 如果想进行复杂条件查询,那么需要使用条件构造器 Wapper,涉及到如下方法

方法名描述
selectOne根据条件查询一个,结果:0或1,如果查询多个异常
selectCount查询总条数
selectList查询所有
selectMaps将一条记录封装到Map中,最后返回List<Map<Sting,Object>>
selectObjs将一条记录封装到Object中,最后返回List<Object>
update更新指定条件
delete删除指定条件
  • 拼凑条件相关关键字
查询方式说明
setSqlSelect设置 SELECT 查询字段
whereWHERE 语句,拼接 + WHERE 条件
andAND 语句,拼接 + AND 字段=值
andNewAND 语句,拼接 + AND (字段=值)
orOR 语句,拼接 + OR 字段=值
orNewOR 语句,拼接 + OR (字段=值)
eq等于=
allEq基于 map 内容等于=
ne不等于<>
gt大于>
ge大于等于>=
lt小于<
le小于等于<=
like模糊查询 LIKE
notLike模糊查询 NOT LIKE
inIN 查询
notInNOT IN 查询
isNullNULL 值查询
isNotNullIS NOT NULL
groupBy分组 GROUP BY
havingHAVING 关键词
orderBy排序 ORDER BY
orderAscASC 排序 ORDER BY
orderDescDESC 排序 ORDER BY
existsEXISTS 条件语句
notExistsNOT EXISTS 条件语句
betweenBETWEEN 条件语句
notBetweenNOT BETWEEN 条件语句
addFilter自由拼接 SQL
last拼接在最后,例如:last(“LIMIT 1”)

4.2.2 条件查询

  • 基本多条件查询
	@Test
    public void testWrapper(){
        // 拼凑条件
        QueryWrapper<Customer> queryWrapper = new QueryWrapper();
        // 1)模糊查询
        queryWrapper.like("cname","测试");
        // 2)等值查询
        queryWrapper.eq("password","777");
        // 3)批量查询
        queryWrapper.in("cid",1,2,3,4);
        // 4) 范围
        queryWrapper.ge("money", 800);
        queryWrapper.le("money" , 1500);

        // 查询
        List<Customer> list = customerMapper.selectList(queryWrapper);
        list.forEach(System.out::println);
    }
  • 条件判断
    @Test
    public void findCondition2() {
        Customer customer = new Customer();
        customer.setPassword("777");
        customer.setCname("888");
        customer.setIdList(Arrays.asList(2,3,4));
        customer.setCid(3);
        //条件查询
        QueryWrapper<Customer> queryWrapper = new QueryWrapper<>();
        // 1) 等值查询
        queryWrapper.eq( customer.getPassword()!=null ,"password", customer.getPassword());
        // 2) 模糊查询
        queryWrapper.like(customer.getCname() != null , "cname",customer.getCname());
        // 3) in语句
        queryWrapper.in(customer.getIdList() != null , "cid",customer.getIdList());
        // 4) 大于等于
        queryWrapper.ge(customer.getCid() != null , "cid" , customer.getCid());


        //查询
        List<Customer> list = customerMapper.selectList(queryWrapper);
        //list.forEach(customer-> System.out.println(customer));
        list.forEach(System.out::println);

    }

4.3.3 条件更新

  • 基本更新
 	@Test
    public void testWrapperUpdate(){
        //1 更新数据
        Customer customer = new Customer();
        customer.setVersion(1);

        //2 更新条件
        UpdateWrapper<Customer> updateWrapper = new UpdateWrapper<>();
        updateWrapper.in("cid", 1,2,3);

        //3 更新
        int update = customerMapper.update(customer, updateWrapper);
        System.out.println(update);
    }

4.3 分页

4.3.1 内置插件

  • 主体插件: MybatisPlusInterceptor,该插件内部插件集:
    • 分页插件: PaginationInnerInterceptor
    • 多租户插件: TenantLineInnerInterceptor
    • 动态表名插件: DynamicTableNameInnerInterceptor
    • 乐观锁插件: OptimisticLockerInnerInterceptor
    • sql性能规范插件: IllegalSQLInnerInterceptor
    • 防止全表更新与删除插件: BlockAttackInnerInterceptor

4.3.2 配置类

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

package com.czxy.mp.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author 桐叔
 * @email liangtong@itcast.cn
 */
@Configuration
public class MybatisPlusConfig {

    /**
     * 配置插件
     * @return
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){

        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        // 分页插件
        mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));

        return mybatisPlusInterceptor;
    }

    /**
     * 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题(该属性会在旧插件移除后一同移除)
     * @return
     */
    @Bean
    public ConfigurationCustomizer configurationCustomizer() {
        return configuration -> configuration.setUseDeprecatedExecutor(false);
    }
}

4.3.3 分页

@Test
    public void testPage(){
        // 分页数据
        int pageNum = 1;
        int pageSize = 3;
        Page<Customer> page = new Page<>(pageNum , pageSize);
        page.setSearchCount(true);

        // 查询
        customerMapper.selectPage(page, null);

        // 分页数据
        System.err.println("当前页码:" + page.getCurrent());
        System.err.println("每页显示记录数:" + page.getSize());
        System.err.println("总页数:" + page.getPages());
        System.err.println("总记录数:" + page.getTotal());
        System.err.println("是否有下一页:" + page.hasNext());
        System.err.println("是否有上一页:" + page.hasPrevious());
        // 分页数据列表
        page.getRecords().forEach(System.err::println);
    }

5. 常见注解

5.1 表名注解:@TableName

属性描述
value表名
keepGlobalPrefix是否保持使用全局的 tablePrefix 的值(如果设置了全局 tablePrefix 且自行设置了 value 的值)

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

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

5.2 主键注解:@TableId

属性描述
value主键字段名
type主键类型
IdType.ASSIGN_UUID ,分配UUID,MyBatisPlus维护String数据
IdType.ASSIGN_ID ,分配ID(默认使用雪花算法)MyBatisPlus维护Long数据
IdType.AUTO ,自动增长(数据库维护)
  • 测试表:User 和 Book

    CREATE TABLE tmp_user(
        uid VARCHAR(100),
        uname VARCHAR(50)
    );
    
    CREATE TABLE tmp_book(
        bid long,
        title VARCHAR(50)
    );
    
  • JavaBean

    • User

      package com.czxy.mp.domain;
      
      import com.baomidou.mybatisplus.annotation.IdType;
      import com.baomidou.mybatisplus.annotation.TableId;
      import lombok.AllArgsConstructor;
      import lombok.Data;
      import lombok.NoArgsConstructor;
      
      /**
       * @author 桐叔
       * @email liangtong@itcast.cn
       */
      @Data
      @NoArgsConstructor
      @AllArgsConstructor
      public class User {
          //@TableId(type = IdType.ASSIGN_UUID)		//随机一个字符串
          @TableId(type = IdType.ASSIGN_ID)			//随机一个数字(Long)
          private String uid;
          private String uname;
      }
      
      
    • Book

      package com.czxy.domain;
      
      import com.baomidou.mybatisplus.annotation.IdType;
      import com.baomidou.mybatisplus.annotation.TableId;
      import com.baomidou.mybatisplus.annotation.TableName;
      import lombok.AllArgsConstructor;
      import lombok.Data;
      import lombok.NoArgsConstructor;
      
      /**
       * @author 桐叔
       * @email liangtong@itcast.cn
       */
      @Data
      @NoArgsConstructor
      @AllArgsConstructor
      @TableName(value="book",keepGlobalPrefix = true)  //表名
      public class Book {
          @TableId(value = "bid", type = IdType.ASSIGN_ID)
          private Long bid;
          private String title;
      }
      
      
      
    • 测试:

      • User的UUID

        package com.czxy;
        
        import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
        import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
        import com.czxy.domain.Customer;
        import com.czxy.domain.User;
        import com.czxy.mapper.CustomerMapper;
        import com.czxy.mapper.UserMapper;
        import org.junit.Test;
        import org.junit.runner.RunWith;
        import org.springframework.boot.test.context.SpringBootTest;
        import org.springframework.test.context.junit4.SpringRunner;
        
        import javax.annotation.Resource;
        
        /**
         * @author 桐叔
         * @email liangtong@itcast.cn
         */
        @RunWith(SpringRunner.class)                                //spring 整合 junit测试
        @SpringBootTest(classes = TestMybatisPlusApplication.class) //spring boot整合junit测试
        public class TestCustomerMapper_04_user {
            @Resource
            private UserMapper userMapper;
        
            @Test
            public void testUserInsert() {
                User user = new User();
                user.setUname(null);
                user.setUname("jack");
        
                int insert = userMapper.insert(user);
                System.out.println(insert);
            }
        
        
        
        
        }
        
        
      • Book的Long ID

        package com.czxy;
        
        import com.czxy.domain.Book;
        import com.czxy.domain.User;
        import com.czxy.mapper.BookMapper;
        import org.junit.Test;
        import org.junit.runner.RunWith;
        import org.springframework.boot.test.context.SpringBootTest;
        import org.springframework.test.context.junit4.SpringRunner;
        
        import javax.annotation.Resource;
        
        /**
         * @author 桐叔
         * @email liangtong@itcast.cn
         */
        @RunWith(SpringRunner.class)                                //spring 整合 junit测试
        @SpringBootTest(classes = TestMybatisPlusApplication.class) //spring boot整合junit测试
        public class TestCustomerMapper_05_book {
            @Resource
            private BookMapper bookMapper;
        
            @Test
            public void testUserInsert() {
                Book book = new Book();
                book.setTitle("测试");
        
                int insert = bookMapper.insert(book);
                System.out.println(insert);
            }
        
        
        
        
        }
        
        

5.2 字段注解(非主键) : @TableField

属性描述
value数据库列名
fill字段自动填充策略
FieldFill.INSERT 插入时填充字段
FieldFill.UPDATE 更新时填充字段
FieldFill.INSERT_UPDATE 插入和更新时填充字段
exist是否存储到数据库(是否是临时数据)

6. 常见配置

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  #输出日志
    map-underscore-to-camel-case: true  #驼峰命名
  global-config:
    db-config:
      id-type: auto  #全局配置,id自动增强
      table-prefix: tmp_ #表名前缀
  type-aliases-package: com.czxy.mp.domain #别名包扫描路径
  mapper-locations: classpath*:/mapper/**/*.xml #映射文件位置
  • 整合xml

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

    <?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.czxy.mp.mapper.CustomerMapper">
    
        <select id="findAll" resultType="customer">
            select * from tmp_customer
        </select>
    
    </mapper>
    
  • CustomerMapper对应的方法

    package com.czxy.mp.mapper;
    
    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import com.czxy.mp.domain.Customer;
    import org.apache.ibatis.annotations.Mapper;
    
    import java.util.List;
    
    /**
     * @author 桐叔
     * @email liangtong@itcast.cn
     */
    @Mapper
    public interface CustomerMapper extends BaseMapper<Customer> {
    
        public List<Customer> findAll();
    }
    
    

7. 高级(插件机制)

7.1 自动填充

项目中经常会遇到一些数据,每次都使用相同的方式填充,例如记录的创建时间,更新时间等。

我们可以使用MyBatis Plus的自动填充功能,完成这些字段的赋值工作:

7.1.1 原理

  • 实现元对象处理器接口:com.baomidou.mybatisplus.core.handlers.MetaObjectHandler,确定填充具体操作
  • 注解填充字段:@TableField(fill = ...) 确定字段填充的时机
    • FieldFill.INSERT:插入填充字段
    • FieldFill.UPDATE:更新填充字段
    • FieldFill.INSERT_UPDATE:插入和更新填充字段

7.1.2 基本操作

  • 步骤一:修改表添加字段
alter table tmp_customer add column create_time date;
alter table tmp_customer add column update_time date;
  • 步骤二:修改JavaBean
package com.czxy.mp.domain;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;

import java.util.Date;

/**
 * Created by liangtong.
 */
@Data
@TableName("tmp_customer")
public class Customer {
    @TableId(type = IdType.AUTO)
    private Integer cid;
    private String cname;
    private String password;
    private String telephone;
    private String money;

    @TableField(value="create_time",fill = FieldFill.INSERT)
    private Date createTime;

    @TableField(value="update_time",fill = FieldFill.UPDATE)
    private Date updateTime;

}

  • 步骤三:编写处理类

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

package com.czxy.mp.handler;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * @author 桐叔
 * @email liangtong@itcast.cn
 */
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    /**
     * 插入填充
     * @param metaObject
     */
    @Override
    public void insertFill(MetaObject metaObject) {
        this.setFieldValByName("createTime", new Date(), metaObject);
    }

    /**
     * 更新填充
     * @param metaObject
     */
    @Override
    public void updateFill(MetaObject metaObject) {
        this.setFieldValByName("updateTime", new Date(), metaObject);
    }
}

  • 步骤四:测试
    @Test
    public void testInsert() {
        Customer customer = new Customer();
        customer.setCname("测试888");

        customerMapper.insert(customer);
    }

    @Test
    public void testUpdate() {
        Customer customer = new Customer();
        customer.setCid(11);
        customer.setTelephone("999");

        customerMapper.updateById(customer);
    }

7.2 乐观锁

  • 基于数据库的锁,处理的并发问题(两个操作对应一个数据)

    • 乐观锁:数据不一致,不会发生。读锁。

    • 悲观锁:数据不一致,肯定发生。写锁,又称为排它锁。

7.2.1 什么是乐观锁

  • 目的:数据必须同步。当要更新一条记录的时候,希望这条记录没有被别人更新

  • 乐观锁实现方式:(万一发生了数据不一致的解决方案)

  • 取出记录时,获取当前version
  • 更新时,带上这个version
  • 执行更新时, set version = newVersion where version = oldVersion
  • 如果version不对,就更新失败

7.2.2. 实现

  • 步骤:

    • 步骤1:环境准备(表version字段、JavaBean versoin属性、必须提供默认值)
    • 步骤2:使用乐观锁版本控制 @Version
    • 步骤3:开启乐观锁插件配置
  • 步骤一:修改表结构,添加version字段

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

  • 步骤二:修改JavaBean,添加version属性

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

    package com.czxy.mp.domain;
    
    import com.baomidou.mybatisplus.annotation.*;
    import lombok.Data;
    
    /**
     * Created by liangtong.
     */
    @Data
    @TableName("tmp_customer")
    public class Customer {
        @TableId(type = IdType.AUTO)
        private Integer cid;
        private String cname;
        private String password;
        private String telephone;
        private String money;
        @Version
        @TableField(fill = FieldFill.INSERT)
        private Integer version;
    }
    
    
  • 步骤三:元对象处理器接口添加version的insert默认值 (保证version有数据)

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

package com.czxy.mp.config;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * Created by liangtong.
 */
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
    /**
     * 插入填充
     * @param metaObject
     */
    @Override
    public void insertFill(MetaObject metaObject) {
        this.setFieldValByName("createTime", new Date(), metaObject);
        this.setFieldValByName("version", 1, metaObject);
    }

    /**
     * 更新填充
     * @param metaObject
     */
    @Override
    public void updateFill(MetaObject metaObject) {
        this.setFieldValByName("updateTime", new Date(), metaObject);
    }
}

  • 步骤四:修改 MybatisPlusConfig 开启乐观锁
package com.czxy.mp.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Created by liangtong.
 */
@Configuration
public class MybatisPlusConfig {
     */
    /**
     * 配置插件
     * @return
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor(){

        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        // 分页插件
        mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        // 乐观锁
        mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());

        return mybatisPlusInterceptor;
    }
}

  • 步骤五:测试
    • 先添加一条,保证version有数据
    • 在更新该条
    @Test
    public void testUpdate() {
        Customer customer = new Customer();
        customer.setCid(14);
        customer.setCname("测试999");
        // 与数据库中数据一致,将更新成功,否则返回失败。
        customer.setVersion(1);

        int i = customerMapper.updateById(customer);
        System.out.println(i);
    }

7.2.3 注意事项

  • 支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
  • 整数类型下 newVersion = oldVersion + 1
  • newVersion 会回写到 entity
  • 仅支持 updateById(id)update(entity, wrapper) 方法
  • update(entity, wrapper) 方法下, wrapper 不能复用!!!
  • 数据库表的version字段,必须有默认值(SQL语句默认值、或MyBatisPlus自动填充)
  • 在进行更新操作时,必须设置version值,否则无效。

7.3 逻辑删除

7.3.1 什么是逻辑删除

  • 逻辑删除,也称为假删除。就是在表中提供一个字段用于记录是否删除,实际该数据没有被删除。

7.3.2 实现

  • 步骤:

    • 步骤一:环境(表提供字段deleted、JavaBean属性 deleted、填充默认值0)
    • 步骤二:修改JavaBean,添加注解 @TableLogic
  • 步骤一:修改表结构添加deleted字段

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

  • 步骤二:修改JavaBean,给deleted字段添加@TableLogic

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

    package com.czxy.mp.domain;
    
    import com.baomidou.mybatisplus.annotation.*;
    import lombok.Data;
    
    /**
     * Created by liangtong.
     */
    @Data
    @TableName("tmp_customer")
    public class Customer {
        @TableId(type = IdType.AUTO)
        private Integer cid;
        private String cname;
        private String password;
        private String telephone;
        private String money;
        @Version
        @TableField(fill = FieldFill.INSERT)
        private Integer version;
    
        @TableLogic
        @TableField(fill = FieldFill.INSERT)
        private Integer deleted;
    }
    
    
  • 步骤三:添加数据时,设置默认“逻辑未删除值”

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

    package com.czxy.mp.config;
    
    import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
    import org.apache.ibatis.reflection.MetaObject;
    import org.springframework.stereotype.Component;
    
    import java.util.Date;
    
    /**
     * Created by liangtong.
     */
    @Component
    public class MyMetaObjectHandler implements MetaObjectHandler {
        /**
         * 插入填充
         * @param metaObject
         */
        @Override
        public void insertFill(MetaObject metaObject) {
            this.setFieldValByName("createTime", new Date(), metaObject);
            this.setFieldValByName("version", 1, metaObject);
            this.setFieldValByName("deleted", 0, metaObject);
        }
    
        /**
         * 更新填充
         * @param metaObject
         */
        @Override
        public void updateFill(MetaObject metaObject) {
            this.setFieldValByName("updateTime", new Date(), metaObject);
        }
    }
    
    
  • 步骤四:测试

        @Test
        public void testDelete() {
            // 删除时,必须保证deleted数据为“逻辑未删除值”
            int i = customerMapper.deleteById(12);
            System.out.println(i);
        }
    

7.3.3 注意

  • 如果使用逻辑删除,将delete语句,修改成了update,条件 where deleted=0
  • 同时,查询语句自动追加一个查询条件 WHERE deleted=0。如果查询没有数据,检查deleted字段的值。

7.3.4 全局配置

  • 如果使用了全局配置,可以不使用注解@TableLogic
mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: deleted  # 局逻辑删除的实体字段名
      logic-delete-value: 1  # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

7.3.5 恢复

  • 问题:进行逻辑删除后的数据,如何恢复(recovery)?

    • 需要自己编写SQL语句
  • 方案1:使用sql具有,更新deleted=0即可

    UPDATE `tmp_customer` SET `deleted`='0' WHERE `cid`='13';
    
  • 方案2:直接使用update语句,不能解决问题。

        @Test
        public void testUpdate() {
            Customer customer = new Customer();
            customer.setCid(13);
            customer.setDeleted(0);
            //更新
            customerMapper.updateById(customer);
        }
    
  • 方案3:修改Mapper,添加 recoveryById 方法,进行数据恢复。

    @Mapper
    public interface CustomerMapper extends BaseMapper<Customer> {
    
        @Update("update tmp_customer set deleted = 0 where cid = #{cid}")
        public void recoveryById(@Param("cid") Integer cid);
    }
    
    

8. 通用Service

8.1 分析

  • 通用service封装了service层常见的CURD方法

  • 通用Service分析

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

8.2 基本使用

  • 标准service:接口 + 实现

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

    • service接口

        package com.czxy.service;
        
        import com.baomidou.mybatisplus.extension.service.IService;
        import com.czxy.domain.Customer;
        
        /**
         * @author 桐叔
         * @email liangtong@itcast.cn
         */
        public interface CustomerService extends IService<Customer> {
        }
        
      
    • service实现类

      package com.czxy.service.impl;
      
      import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
      import com.czxy.domain.Customer;
      import com.czxy.mapper.CustomerMapper;
      import com.czxy.service.CustomerService;
      import org.springframework.stereotype.Service;
      import org.springframework.transaction.annotation.Transactional;
      
      /**
       * @author 桐叔
       * @email liangtong@itcast.cn
       */
      @Service
      @Transactional
      public class CustomerServiceImpl extends ServiceImpl<CustomerMapper,Customer> implements CustomerService {
      
      }
      
      

8.3 常见方法

  • 查询所有
  • 添加
  • 修改
  • 删除
package com.czxy.test;

import com.czxy.mp.Day62MybatisPlusApplication;
import com.czxy.mp.domain.Customer;
import com.czxy.mp.service.CustomerService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import javax.annotation.Resource;
import java.util.List;

/**
 * @author 桐叔
 * @email liangtong@itcast.cn
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Day62MybatisPlusApplication.class)
public class TestDay62CustomerService {

    @Resource
    private CustomerService customerService;

    @Test
    public void testSelectList() {
        List<Customer> list = customerService.list();
        list.forEach(System.out::println);
    }

    @Test
    public void testInsert() {
        Customer customer = new Customer();
        customer.setCname("张三");
        customer.setPassword("9999");
        // 添加
        customerService.save(customer);
    }

    @Test
    public void testUpdate() {
        Customer customer = new Customer();
        customer.setCid(14);
        customer.setCname("777");
        customer.setPassword("777");

        customerService.updateById(customer);
    }

    @Test
    public void testSaveOrUpdate() {
        Customer customer = new Customer();
        customer.setCid(15);
        customer.setCname("999");
        customer.setPassword("99");

        customerService.saveOrUpdate(customer);
    }

    @Test
    public void testDelete() {
        customerService.removeById(15);
    }
}

9 新功能

9.1 执行SQL分析打印

该功能依赖 p6spy 组件,完美的输出打印 SQL 及执行时长

  • p6spy 依赖引入

    <dependency>
        <groupId>p6spy</groupId>
        <artifactId>p6spy</artifactId>
        <version>3.9.1</version>
    </dependency>
    
  • 核心yml配置

    spring:
      datasource:
      	# p6spy 提供的驱动代理类,
        driver-class-name: com.p6spy.engine.spy.P6SpyDriver
        # url 固定前缀为 jdbc:p6spy,跟着冒号为对应数据库连接地址
        url: jdbc:p6spy:mysql://127.0.0.1:3306...
    

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

  • spy.properties 配置

    #3.2.1以上使用
    modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
    #3.2.1以下使用或者不配置
    #modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
    # 自定义日志打印
    logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
    #日志输出到控制台
    appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
    # 使用日志系统记录 sql
    #appender=com.p6spy.engine.spy.appender.Slf4JLogger
    # 设置 p6spy driver 代理
    deregisterdrivers=true
    # 取消JDBC URL前缀
    useprefix=true
    # 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.
    excludecategories=info,debug,result,commit,resultset
    # 日期格式
    dateformat=yyyy-MM-dd HH:mm:ss
    # 实际驱动可多个
    #driverlist=org.h2.Driver
    # 是否开启慢SQL记录
    outagedetection=true
    # 慢SQL记录标准 2 秒
    outagedetectioninterval=2
    
    

9.2 数据安全保护

为了保护数据库配置及数据安全,在一定的程度上控制开发人员流动导致敏感信息泄露

  • 步骤:

    • 步骤1:使用 AES 工具类,生成秘钥
    • 步骤2:使用 AES工具类,根据步骤1生成的秘钥对敏感信息进行加密
    • 步骤3:设置加密后的配置信息
    • 步骤4:启动服务时,使用秘钥
  • 步骤1-2:使用工具类生成秘钥以及对敏感信息进行加密

    package com.czxy;
    
    import com.baomidou.mybatisplus.core.toolkit.AES;
    import org.junit.Test;
    
    /**
     * @author 桐叔
     * @email liangtong@itcast.cn
     */
    public class TestAES {
        @Test
        public void testAes() {
            String randomKey = AES.generateRandomKey();
    
            String url =  "jdbc:p6spy:mysql://127.0.0.1:3306/zx_edu_teacher?useUnicode=true&characterEncoding=utf8";
            String username = "root";
            String password = "1234";
    
            String urlAES = AES.encrypt(url, randomKey);
            String usernameAES = AES.encrypt(username, randomKey);
            String passwordAES = AES.encrypt(password, randomKey);
    
            System.out.println("--mpw.key=" + randomKey);
            System.out.println("mpw:" + urlAES);
            System.out.println("mpw:" + usernameAES);
            System.out.println("mpw:" + passwordAES);
        }
    }
    // Jar 启动参数( idea 设置 Program arguments , 服务器可以设置为启动环境变量 )
    //--mpw.key=fddd2b7a67460e16
    //mpw:7kSEISvq3QWfnSh6vQZc2xgE+XF/sJ0WS/sgGkYpCOTQRjO1poLi3gfmGZNOwKzfqZUec0odiwAdmxcS7lfueENGIx8OmIe//d9imrGFpnkrf8jNSHdzfNPCUi3MbmUb
    //mpw:qGbCMksqA90jjiGXXRr7lA==
    //mpw:xKG9GABlywqar6CGPOSJKQ==
    
  • 步骤3:配置加密信息

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

  • 步骤4:使用秘钥启动服务

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

end

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Axure原型分享】3D多柱状图_中继器版

今天和大家分享3D多柱状图_中继器版的原型模板&#xff0c;鼠标移入时&#xff0c;对应区域的背景会高亮变色&#xff0c;并且显示对应柱状体的数据。那这个原型是用Axure原生元件制作的&#xff0c;样式交互都可以自行修改&#xff0c;图表数据在中继器表格里填写&#xff0c;…

企业举办年会,可以邀请哪些媒体进行宣传?

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 年关将至&#xff0c;筹办年会成为每个企业必做的事情&#xff0c;也是大家非常期待的年终大会&#xff0c;在我们策划年会时候&#xff0c;也要抓住最后宣传的机会。那么企业举办年会时…

自定义异常

这个类上加上controlleradvice注解

性能测试:方法、工具与最佳实践

目录 前言 1. 为什么进行性能测试&#xff1f; 2. 性能测试方法 3. 性能测试工具 Apache JMeter: LoadRunner: Gatling: Apache ab (Apache Benchmark): Locust: Tsung: BlazeMeter: K6: Neoload: WebLOAD: 4. 最佳实践 设定明确的性能测试目标&#xff1a; 模…

yarn系统架构与安装

1.1 YARN系统架构 YARN的基本思想是将资源管理和作业调度/监视功能划分为单独的守护进程。其思想是拥有一个全局ResourceManager (RM)&#xff0c;以及每个应用程序拥有一个ApplicationMaster (AM)。应用程序可以是单个作业&#xff0c;也可以是一组作业。 一个ResourceManage…

Rust 学习

Rust 官网&#xff1a;https://www.rust-lang.org/zh-CN/ 1、Rust 简介 Rust 是一门注重安全&#xff08;safety&#xff09;、速度&#xff08;speed&#xff09;和并发&#xff08;concurrency&#xff09;的现代系统编程语言。Rust 通过内存安全来实现以上目标&#xff0c;但…

YB5212A高输入耐压2A单节/双节锂电池充电芯片

YB5212A高输入耐压2A单节/双节锂电池充电芯片 概述&#xff1a; YB5212A 是一种5-18V输入&#xff0c;2A电流可支持单节和双节锂电池的同步Buck锂离子电池充电器&#xff0c;适用于便携式应用。选择引脚方便多单元充电。800khz同步降压调节器集成MOS&#xff0c;超低导通电阻&a…

聚焦中国—东盟大健康产业峰会 点靓广西“长寿福地”品牌

12月8-10日2023中国—东盟大健康产业峰会暨大健康产业博览会在南宁国际会展中心成功举办&#xff0c;本次峰会由国家中医药管理局、广西壮族自治区人民政府联合主办&#xff0c;中国老年学和老年医学学会、自治区党委宣传部、自治区民政厅、广西壮族自治区外事办公室、广西壮族…

vue 实现点击复制文本到剪贴板

vue 实现点击复制文本到剪贴板 共四种方法 1. navigator.clipboard.writeText该方法需要在安全域下才能够使用&#xff0c;比如&#xff1a;https 协议的地址、127.0.0.1、localhost <template><div><el-button type"primary" click"btn1"…

Dockerfile创建镜像介绍

1.介绍 Docker 提供了一种更便捷的方式&#xff0c;叫作 Dockerfile&#xff0c;docker build命令用于根据给定的Dockerfile构建Docker镜像。 docker build语法&#xff1a; # docker build [OPTIONS] <PATH | URL | -> 常用选项说明 --build-arg&#xff0c;设置构建时的…

命名空间 嵌套类和局部类

命名空间 命名空间可以不连续 未命名空间 命名空间的别名 using声明和using指示 嵌套类 访问嵌套类成员 嵌套类的名称查找 嵌套类中名称查找失败会在外围中查找 局部类 局部类可以访问外围函数枚举 静态对象 局部类可以定义函数但是函数必须在类内定义 局部类内不能定义静态成…

【Jeecg Boot 3 - 第二天】1.2、jar 包和 lib 依赖分离,部署包缩小100倍

一、场景 二、思路 三、实战 ▶ 2.1、项目 jar 包解压获取 lib config Stage 1&#xff1a;正常打包获取 jeecg-system-start-3.6.0.jar Stage 2&#xff1a;解压 获取如下文件 Stage 3&#xff1a;获取 lib config ▶ 2.2、获取简化版项目jar包 Stage 1&#xff1…

gitlab动态流水线

文章目录 1. 说明2. 官方样例2.1 在作业中生成配置文件&#xff0c;保存为产物2.2 将触发器作业配置为在生成配置文件的作业之后运行。 3. 实战应用3.1 背景介绍3.2 项目介绍3.3 公共项目配置3.4 测试项目配置3.5 测试 4. 总结 1. 说明 顾名思义&#xff0c;动态流水线就是一种…

Altman作了多少恶?排挤首席科学家出GPT5开发、离间董事会、PUA员工

在山姆奥特曼&#xff08;Sam Altman&#xff09;被OpenAI董事会突然解职后的几天里&#xff0c;这个消息在科技圈引发轰动&#xff0c;该公司内部员工和许多科技界人士甚至将此举比作一场政变。 奥特曼被解雇后立即传出的说法是&#xff0c;OpenAI的广大员工都很喜欢他&#x…

治疗复发或难治性多发性骨髓瘤:2期MagnetisM-3试验结果

今天给同学们分享一篇实验文章“Elranatamab in relapsed or refractory multiple myeloma: phase 2 MagnetisMM-3 trial results”&#xff0c;这篇文章发表在Nat Med期刊上&#xff0c;影响因子为82.9。 结果解读&#xff1a; 试验设计和患者 MagnetisMM-3是一项正在进行的…

以csv为源 flink 创建paimon 临时表相关 join 操作

目录 概述配置关键配置测试启动 kyuubi执行配置中的命令 bug解决bug01bug02 结束 概述 目标&#xff1a;生产中有需要外部源数据做paimon的数据源&#xff0c;生成临时表&#xff0c;以使用与现有正式表做相关统计及 join 操作。 环境&#xff1a;各组件版本如下 kyuubi 1.8…

c语言->自定义类型联合体和枚举类型

系列文章目录 文章目录 前言 ✅作者简介&#xff1a;大家好&#xff0c;我是橘橙黄又青&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;橘橙黄又青_C语言,函数,指针-CSDN博客 目的&#xff1a;学习联合体和枚举类型的…

深度学习 Day12——P1实现mnist手写数字识别

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 | 接辅导、项目定制 文章目录 前言1 我的环境2 代码实现与执行结果2.1 前期准备2.1.1 引入库2.1.2 设置GPU&#xff08;如果设备上支持GPU就使用GPU,否则使用C…

伪原创API,一文快速了解伪原创API

伪原创API&#xff0c;听起来可能对许多人来说是一个陌生的术语。然而&#xff0c;在当今数字化时代&#xff0c;尤其是在内容创作和网络营销领域&#xff0c;伪原创API正逐渐崭露头角。在本文中&#xff0c;我将向您深入介绍伪原创API是什么&#xff0c;以及它如何在实际应用中…

LED透镜粘接UV胶是一种特殊的UV固化胶

LED透镜粘接UV胶是一种特殊的UV固化胶&#xff0c;用于固定和粘合LED透镜。 它具有以下特点&#xff1a; 1. 高透明度&#xff1a;LED透镜粘接UV胶具有高透明度&#xff0c;可以确保光线的透过性&#xff0c;不影响LED的亮度和效果。 2. 快速固化&#xff1a;经过UV紫外线照射…