MyBatis复习笔记

embedded/2025/1/12 6:30:54/

文章目录

  • MyBatis 简介和基本使用
    • MyBatis 简介
    • 持久层框架对比
    • MyBatis 快速入门
      • 目录结构
      • 使用思路
      • 第一步:导入依赖,准备实体类
      • 第二步:准备 mapper 接口
      • 第三步:在 mapper.xml 中写 SQL
      • 第四步:编写 mybaits 配置文件
      • 第五步:运行测试 mybaits 代码
  • MyBatis 使用细节
    • 配置文件解释
      • SQL 日志
      • 指定 Mapper 映射文件
      • 引入 properties 配置文件
      • 设置类别名
      • 开启驼峰映射
      • 整合
    • 向 SQL 语句传参
      • #{} 形式
      • ${} 形式
      • 传入单个简单类型参数
      • 传入实体类型参数
      • 传入多个简单类型参数
      • 传入 Map 类型参数
    • SQL 语句返回参数
      • 返回参数概述
      • 返回单个简单类型
      • 返回实体类对象
      • 返回 Map 类型
      • 返回 List 类型
      • 返回自增主键
      • 插入非自增长型主键
      • 实体类属性和数据库字段不对应解决方法总结
  • MyBatis 多表映射
    • 实体类设计方案
    • 自定义映射
      • 自定义映射 `<resultMap>`
      • 对象属性赋值 `<association>` [ 对一 ]
      • 集合属性赋值 `<collection>` [ 对多 ]
      • 对一映射示例
      • 对多映射示例
      • 多表映射优化 `<autoMappingBehavior>`
  • MyBatis 动态语句
    • if 和 where 标签 【多条件动态判断】
    • set 标签【更新信息】
    • choose / when / otherwise 标签【多条件选一】
    • foreach 标签【批量操作】
    • SQL 片段抽取
  • MyBatis 分页和逆向
    • SQL 分页回顾
    • 分页插件 PageHelper
    • 逆向工程
    • 使用 MyBatisX 插件
      • 第一步:安装插件
      • 第二步:IDEA 连接数据库
      • 第三步:使用逆向插件
      • 第四步:查看生成结果

MyBatis 简介和基本使用

MyBatis 简介


MyBatis最初是Apache的一个开源项目iBatis, 2010年6月这个项目由Apache Software Foundation迁移到了Google Code。随着开发团队转投Google Code旗下, iBatis3.x正式更名为MyBatis。代码于2013年11月迁移到Github。
.
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

持久层框架对比


  • JDBC
    • SQL 夹杂在Java代码中耦合度高,导致硬编码内伤
    • 维护不易且实际开发需求中 SQL 有变化,频繁修改的情况多见
    • 代码冗长,开发效率低
  • Hibernate 和 JPA
    • 操作简便,开发效率高
    • 程序中的长难复杂 SQL 需要绕过框架
    • 内部自动生成的 SQL,不容易做特殊优化
    • 基于全映射的全自动框架,大量字段的 POJO 进行部分映射时比较困难。
    • 反射操作太多,导致数据库性能下降
  • **MyBatis **
    • 轻量级,性能出色
    • SQL 和 Java 编码分开,功能边界清晰。Java代码专注业务、SQL语句专注数据
    • 开发效率稍逊于 Hibernate,但是完全能够接收
      .

开发效率:Hibernate > Mybatis > JDBC
运行效率:JDBC > Mybatis > Hibernate

MyBatis 快速入门

目录结构


在这里插入图片描述

使用思路


在 MyBatis 框架里,SQL 语句的编写位置有了变化,不再像以前那样写在 Java 类中,而是改到了 XML 文件或者通过注解 来定义。
.
比较推荐的做法是在 XML 文件中编写 SQL 语句,这样做的好处是能让使用者把精力都集中在 SQL 代码本身上,无需再去操心像 JDBC 这类相关代码了。

Mybatis 中的 Mapper 接口相当于以前的 Dao。但是区别在于,Mapper 仅仅只是建接口即可,我们不需要提供实现类,具体的 SQL 写到对应的 Mapper 文件,该用法的思路如下图所示:

在这里插入图片描述

Controller层 调用 Service层Service层调用 Mapper接口
然后我们调用接口底层会返回一个接口代理对象重写 Mapper.xml 文件的方法

第一步:导入依赖,准备实体类


  • pom.xml
<dependencies><!-- mybatis依赖 --><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.11</version></dependency><!-- MySQL驱动 mybatis底层依赖jdbc驱动实现,本次不需要导入连接池,mybatis自带! --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.25</version></dependency><!--junit5测试--><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter-api</artifactId><version>5.3.1</version></dependency>
</dependencies>
  • com.mangfu.pojo.Employee
java">public class Employee {private Integer empId;private String empName;private Double empSalary;//get|set...

第二步:准备 mapper 接口


  • com.mangfu.mapper.EmployeeMapper
java">public interface EmployeeMapper {/*** 根据员工 id 查询员工信息* @param empId 员工id* @return 员工实体类对象*/Employee selectEmployee(Integer empId);
}

第三步:在 mapper.xml 中写 SQL


  • resources/mappers.EmployeeMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!-- namespace = 接口的全限定名-->
<mapper namespace="com.mangfu.mapper.EmployeeMapper"><select id="selectEmployee" resultType="com.mangfu.pojo.Employee"><!-- #{empId}代表动态传入的参数,并且进行赋值!后面详细讲解 -->select emp_id empId,emp_name empName, emp_salary empSalary fromt_emp where emp_id = #{empId}</select>
</mapper>

第四步:编写 mybaits 配置文件


mybatis 框架配置文件: 数据库连接信息,性能配置,mapper.xml 配置等!
.
习惯上命名为 mybatis-config.xml,将来整合 Spring 之后,这个配置文件可以省略,所以大家操作时可以直接复制、粘贴。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><!-- environments表示配置Mybatis的开发环境,可以配置多个环境,在众多具体环境中,使用default属性指定实际运行时使用的环境。default属性的取值是environment标签的id属性的值。 --><environments default="development"><!-- environment表示配置Mybatis的一个具体的环境 --><environment id="development"><!-- Mybatis的内置的事务管理器 --><transactionManager type="JDBC"/><!-- 配置数据源 --><dataSource type="POOLED"><!-- 建立数据库连接的具体信息 --><property name="driver" value="com.mysql.cj.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/mybatis-example"/><property name="username" value="root"/><property name="password" value="Ting123321"/></dataSource></environment></environments><mappers><!-- Mapper注册:指定Mybatis映射文件的具体位置 --><!-- mapper标签:配置一个具体的Mapper映射文件 --><!-- resource属性:指定Mapper映射文件的实际存储位置,这里需要使用一个以类路径根目录为基准的相对路径 --><!--    对Maven工程的目录结构来说,resources目录下的内容会直接放入类路径,所以这里我们可以以resources目录为基准 --><mapper resource="mappers/EmployeeMapper.xml"/></mappers></configuration>

第五步:运行测试 mybaits 代码


  • 用输入流读取 mybatis-config.xml 核心配置文件
  • 创建 sqlSessionFactoryBuilder 类用来生产 SqlSessionFactory
  • 并用 sqlSessionFactoryBuilderbuild 方法来读取核心配置文件,返回一个 SqlSessionFactory可以用来获取 SqlSession
  • SqlSessionFactoryopenSession 方法返回 sql 会话对象
    • openSession() 会自动开启事务, 但是不会自动提交 | 需要调用sqlSession.commit() 提交事务
    • openSession(true) true 会自动开启事务,自动提交事务
  • 然后使用 SqlSessiongetMapper 方法,参数是数据库对应的实体类的 class。返回一个接口类型的参数
  • 调用这个接口里面的方法会自动执行 映射文件里面的 方法。
  • 最后记得提交事务,并关闭 SqlSession 会话
  • test/com.mangfu.test.MyBatisTest
java">public class MyBatisTest {@Testpublic void testSelectEmployee() throws IOException {// 1.创建SqlSessionFactory对象// ①声明Mybatis全局配置文件的路径String mybatisConfigFilePath = "mybatis-config.xml";// ②以输入流的形式加载Mybatis配置文件InputStream inputStream = Resources.getResourceAsStream(mybatisConfigFilePath);// ③基于读取Mybatis配置文件的输入流创建SqlSessionFactory对象SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);// 2.使用SqlSessionFactory对象开启一个会话SqlSession session = sessionFactory.openSession();// 3.根据EmployeeMapper接口的Class对象获取Mapper接口类型的对象(动态代理技术)EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);// 4. 调用代理类方法既可以触发对应的SQL语句Employee employee = employeeMapper.selectEmployee(1);System.out.println("employee = " + employee);// 4.关闭SqlSessionsession.commit(); //提交事务 [DQL不需要,其他需要]session.close(); //关闭会话}
}

MyBatis 使用细节

配置文件解释


SQL 日志

logimpl:指定 MyBaits 所用日志的具体实现。没指定时 MyBatis 会按照一定的顺序自动查找并尝试使用可用的日志框架。
.
参数:SLF4J | LOG4J(3.5.9 起废弃) | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING

   <settings><!-- 开启了 mybatis 的日志输出, 选择使用 system 进行控制台输出--><setting name="logImpl" value="STDOUT_LOGGING"/></settings>

指定 Mapper 映射文件

  • 第一种方式

<mapper resource=.../>这种方式需要一个个指定 xml 映射文件比较麻烦 推荐使用第二种

  <mappers><!-- mapper标签:配置一个具体的Mapper xml映射文件 --><!-- resource属性:指定Mapper映射文件的实际存储位置,这里需要使用一个以类路径根目录为基准的相对路径 --><!--    对Maven工程的目录结构来说,resources目录下的内容会直接放入类路径,所以这里我们可以以resources目录为基准 --><mapper resource="mappers/EmployeeMapper.xml"/><mapper resource="mappers/StudentMapper.xml"/>	</mappers>
  • 第二种方式

<package name=.../>这种方式可以一件指定包下的 xml 映射文件。推荐使用这种

<mappers><!-- 使用条件 1. mapper 接口和映射文件所在的包必须一致 用 / 不要用 . 创建2. 2. mapper 接口的名字和映射文件的名字必须一样--><!--此时这个包下的所有 Mapper 映射文件将被自动加载、注册,比较方便。--><package name="com.mangfu.mapper"/></mappers>

引入 properties 配置文件

<properties resource = .../> :可以引入 properties 文件。用 ${key} 方式访问

<!--引入 properties 文件, 此后就可以在当前文件中使用${key}的方式访问 value--><properties resource="jdbc.properties"/><!-- environments表示配置Mybatis的开发环境,可以配置多个 environment 环境,在众多具体环境中,使用default属性指定实际运行时使用的环境。default属性的取值是environment标签的id属性的值。 --><environments default="development"><!-- environment表示配置Mybatis的一个具体的环境 --><environment id="development"><!-- Mybatis的内置的事务管理器MANAGED 不会自动开启事务! | JDBC 自动开启事务, 需要自己提交事务--><transactionManager type="JDBC"/><!-- 配置数据源type=POOLED mybatis 帮助我们维护一个连接池 | UNPOOLED 每次都新建或者释放连接--><dataSource type="POOLED"><property name="driver" value="${jdbc.driver}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></dataSource></environment></environments>

设置类别名

namespace 用不了别名

  • 第一种方式
  • typeAlias type = .../>:设置类别名,要一个个设置比较麻烦
	 <typeAliases><!--type: 设置需要起别名的类型alias: 设置某个类型的别名不设置 alias 就用默认的别名就是它的类名--><typeAlias type="com.mangfu.Employee"/></typeAliases>
<!-- namespace = 接口的全限定名-->
<mapper namespace="com.mangfu.mapper.EmployeeMapper"><!--这里 resultType 就只用简写了--><select id="selectEmployee" resultType="Employee"><!-- #{empId}代表动态传入的参数,并且进行赋值!后面详细讲解 -->select emp_id empId,emp_name empName, emp_salary empSalary fromt_emp where emp_id = #{empId}</select>
</mapper>
  • 第二种方式

package name =.../>:这种方式可以把包下类一键设置默认别名【类名】推荐使用这种方式

<!--通过包设置类型别名, 指定包下所有的类型都将全部拥有默认的别名-->
<typeAliases><package name="com.mangfu"/>
</typeAliases>

注意在批量声明的情况下,要更改单个别名用 @Alias("别名")

java">@Alias("author"):public class Author{
...
}

开启驼峰映射

返回单个实体类的类型,要求列名和属性名一致。才能进行实体类映射

<!-- 在全局范围内对Mybatis进行配置 -->
<settings><!-- 具体配置 --><!-- 从org.apache.ibatis.session.Configuration类中可以查看能使用的配置项 --><!-- 将mapUnderscoreToCamelCase属性配置为true,表示开启自动映射驼峰式命名规则 --><!-- 规则要求数据库表字段命名方式:单词_单词 --><!-- 规则要求Java实体类属性名命名方式:首字母小写的驼峰式命名 --><setting name="mapUnderscoreToCamelCase" value="true"/></settings>
  • 举例
数据库字段 t_user
pojo 实体类属性 tUser [驼峰式命名]
他们会自动映射 

整合

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><!--MyBatis 核心配置文件中的标签必须要按照指定的顺序配置The content of element type "configuration" must match"(properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?)".--><settings><!-- 开启了 mybatis 的日志输出, 选择使用 system 进行控制台输出--><setting name="logImpl" value="STDOUT_LOGGING"/><!--开启驼峰映射--><setting name="mapUnderscoreToCamelCase" value="true"/></settings><!--引入 properties 文件, 此后就可以在当前文件中使用${key}的方式访问 value--><properties resource="jdbc.properties"/><typeAliases><!--type: 设置需要起别名的类型alias: 设置某个类型的别名不设置 alias 就用默认的别名就是它的类名-->
<!--        <typeAlias type="com.atguigu.mybatis.pojo.User" alias="abc"></typeAlias>-->
<!--        <typeAlias type="com.atguigu.mybatis.pojo.User"></typeAlias>--><!--通过包设置类型别名, 指定包下所有的类型都将全部拥有默认的别名--><package name="com.mangfu.pojo"/></typeAliases><!-- environments表示配置Mybatis的开发环境,可以配置多个 environment 环境,在众多具体环境中,使用default属性指定实际运行时使用的环境。default属性的取值是environment标签的id属性的值。 --><environments default="development"><!-- environment表示配置Mybatis的一个具体的环境 --><environment id="development"><!-- Mybatis的内置的事务管理器MANAGED 不会自动开启事务! | JDBC 自动开启事务, 需要自己提交事务--><transactionManager type="JDBC"/><!-- 配置数据源type=POOLED mybatis 帮助我们维护一个连接池 | UNPOOLED 每次都新建或者释放连接--><dataSource type="POOLED"><property name="driver" value="${jdbc.driver}"/><property name="url" value="${jdbc.url}"/><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.password}"/></dataSource></environment></environments><mappers><!-- mapper标签:配置一个具体的Mapper xml映射文件 --><!-- resource属性:指定Mapper映射文件的实际存储位置,这里需要使用一个以类路径根目录为基准的相对路径 --><!--    对Maven工程的目录结构来说,resources目录下的内容会直接放入类路径,所以这里我们可以以resources目录为基准 --><mapper resource="mappers/EmployeeMapper.xml"/><mapper resource="mappers/StudentMapper.xml"/>以上方式比较麻烦需要一个个写 mapper xml 映射文件路径所以采用批量映射比较方便<!-- 使用条件 1. mapper 接口和映射文件所在的包必须一致 用 / 不要用 . 创建2. 2. mapper 接口的名字和映射文件的名字必须一样--><!--此时这个包下的所有 Mapper 映射文件将被自动加载、注册,比较方便。--><package name="com.mangfu.mapper"/></mappers></configuration>

向 SQL 语句传参


#{} 形式

#{}:如果传过来的值是字符串类型。那两边会自动加上 单引号。当传递给 #{} 的参数值是非字符串类型(如整数、浮点数、布尔值等),MyBatis 不会为这些值添加引号。

java">#{ key }原理: 占位符 + 赋值 先 emp_id = ?,  然后 ? = 赋值

${} 形式

${}:它会直接将传过来变量的值替换到 SQL 语句中。

java">${ key }: 字符串拼接 " emp_id  = " + id

通常不会采用 ${} 的方式传值。一个特定的适用场景是:通过Java程序动态生成数据库表,表名部分需要Java程序通过参数传入;而JDBC对于表名部分是不能使用问号占位符的,此时只能使用 ${}
结论:动态的是值就是用 #{},动态变化的部分是诸如容器名、标签、列名、SQL 关键字等非普通值的情况时,则需要使用 ${} 进行拼接,

传入单个简单类型参数

单个简单类型参数,在#{}中可以随意命名,但是没有必要。通常还是使用和接口方法参数同名。

  • Mapper 接口中抽象方法的声明
java">Employee selectEmployee(Integer empId);
  • SQL语句
<select id="selectEmployee" resultType="com.atguigu.mybatis.entity.Employee">select emp_id empId,emp_name empName,emp_salary empSalary from t_emp where emp_id=#{empId}
</select>

传入实体类型参数

mapper 接口中的方法参数为实体类对象时,此时可以使用 ${}#{},通过访问实体类对象中的 属性名 获取属性值,注意${}需要手动加单引号
.
原理:Mybatis会根据#{}中传入的数据,加工成 getXxx()方法【pojo 类中必须有 get 方法】,通过反射在实体类对象中调用这个 get 方法,从而获取到对应的数据。填充到#{}解析后的问号占位符这个位置。

  • pojo
java">public class Employee {private Integer empId;private String empName;private Double empSalary;... get|set
}
  • Mapper 接口中抽象方法的声明
java">int insertEmployee(Employee employee);
  • SQL语句
<insert id="insertEmployee">insert into t_emp(emp_name,emp_salary) values(#{empName},#{empSalary})
</insert>

传入多个简单类型参数

方案1:注解指定 @Param 注解 指定多个简单参数的 key。 key = @Param("value值") 可以理解成别名 [推荐]
.
方案2:mybatis 默认机制

  • arg0 arg1… 形参从左到右依次对应 (name, salary) 【name-> key = arg0】 【salary -> key = arg1
  • param1 param2… (name, salary) 【name-> key = param1】 【 salary -> key = param2
  • Mapper 接口中抽象方法的声明
java">int updateEmployee(@Param("empId") Integer empId,@Param("empSalary") Double empSalary);
  • SQL 语句
<update id="updateEmployee">update t_emp set emp_salary=#{empSalary} where emp_id=#{empId}
</update>

传入 Map 类型参数

有很多零散的参数需要传递,但是没有对应的实体类类型可以使用。使用@Param 注解一个一个传入又太麻烦了。所以都封装到Map中。
.
使用 map:手动创建map集合,将这些数据放在map中,只需要通过${}#{}访问map集合的键就可以获取相对应的值,

  • Mapper 接口中抽象方法的声明
java">int updateEmployeeByMap(Map<String, Object> paramMap);
  • SQL语句
<update id="updateEmployeeByMap">update t_emp set emp_salary=#{empSalaryKey} where emp_id=#{empIdKey}</update>
  • 测试类
java">private SqlSession session;
//junit5会在每一个@Test方法前执行@BeforeEach方法
@BeforeEach
public void init() throws IOException {session = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml")).openSession();
}@Test
public void testUpdateEmpNameByMap() {EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);Map<String, Object> paramMap = new HashMap<>();//设置要传入的 map 的 key 和 valueparamMap.put("empSalaryKey", 999.99);paramMap.put("empIdKey", 5);//把 map 传递进去int result = mapper.updateEmployeeByMap(paramMap);System.out.println("result = " + result);
}//junit5会在每一个@Test方法后执行@@AfterEach方法
@AfterEach
public void clear() {session.commit();session.close();
}

SQL 语句返回参数


返回参数概述

数据输出总体上有两种形式:

  • 增删改操作返回的受影响行数:直接使用 int 或 long 类型接收即可
  • 查询操作的查询结果
    .

我们需要做的是,指定查询的输出数据类型即可!
并且插入场景下,实现主键数据回显示!

返回单个简单类型

retultType = 类的全限定名 | 别名:声明返回的类型

  • Mapper 接口中的抽象方法
java">int selectEmpCount();
  • SQL 语句
java">//这里 resultType 本来返回的是 java.lang.Integer 主类可以简写为 int
<select id="selectEmpCount" resultType="int">select count(*) from t_emp
</select>

别名问题

  • 基本数据类型 int double... -> _int _double...
  • 包装数据类型 Integer Double... -> int(或integer) double...
  • 集合容器类型 Map LIst HashMap.. -> 小写即可 map list...
  • 自定义类:使用<typeAliases> 标签

返回实体类对象

查询:返回单个实体类型,要求列名和属性名一致。才能进行实体类的属性映射

  • 解决方法1:给字段属性起别名
  • 解决方法2:在 mybatis-config 中设置 <setting name="mapUnderscoreToCamelCase" value="true"/> 自动进行驼峰映射 emp_id -> empId
  • Mapper 接口的抽象方法
java">Employee selectEmployee(Integer empId);
  • SQL 语句
<!-- 编写具体的SQL语句,使用id属性唯一的标记一条SQL语句 -->
<!-- resultType属性:指定封装查询结果的Java实体类的全类名 除非起别名 -->
<select id="selectEmployee" resultType="com.atguigu.mybatis.entity.Employee"><!-- Mybatis负责把SQL语句中的#{}部分替换成“?”占位符 --><!-- 给每一个字段设置一个别名,让别名和Java实体类中属性名一致 -->select emp_id empId,emp_name empName,emp_salary empSalary from t_emp where emp_id=#{maomi}</select>

返回 Map 类型

适用于SQL查询返回的各个字段综合起来并不和任何一个现有的实体类对应,没法封装到实体类对象中。能够封装成实体类类型的,就不使用Map类型。
列名就是 key , 查询出的列的结果就是 value

  • Mapper 接口的抽象方法
java">Map<String,Object> selectEmpNameAndMaxSalary();
  • SQL语句
<!-- Map<String,Object> selectEmpNameAndMaxSalary(); -->
<!-- 返回工资最高的员工的姓名和他的工资 -->
<select id="selectEmpNameAndMaxSalary" resultType="map">SELECTemp_name 员工姓名,emp_salary 员工工资,(SELECT AVG(emp_salary) FROM t_emp) 部门平均工资FROM t_emp WHERE emp_salary=(SELECT MAX(emp_salary) FROM t_emp)
</select>
  • 测试类
java">@Test
public void testQueryEmpNameAndSalary() {EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);Map<String, Object> resultMap = employeeMapper.selectEmpNameAndMaxSalary();Set<Map.Entry<String, Object>> entrySet = resultMap.entrySet();for (Map.Entry<String, Object> entry : entrySet) {//key:就是列名员工姓名, 员工工资, 部门平均工资String key = entry.getKey();//value: 就是查询出来的值 jerry, 999.9 659Object value = entry.getValue();/*部门平均工资=659.363333333员工姓名=jerry员工工资=999.99*/log.info(key + "=" + value);}
}

返回 List 类型

查询结果返回多个实体类对象,希望把多个实体类对象放在List 集合中返回。此时不需要任何特殊处理,resultType 不需要指定集合类型, 只需要指定泛型即可也就是 实体类的类型。每一条记录封装成一个对象。然后全部放进 List 集合

  • Mapper 接口中抽象方法
java">List<Employee> selectAll();
  • SQL 语句
<!-- List<Employee> selectAll(); -->
<select id="selectAll" resultType="com.atguigu.mybatis.entity.Employee">select emp_id empId,emp_name empName,emp_salary empSalaryfrom t_emp
</select>
  • 测试
java">@Test
public void testSelectAll() {EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);List<Employee> employeeList = employeeMapper.selectAll();for (Employee employee : employeeList) {System.out.println("employee = " + employee);}
}

返回自增主键

Mybatis是将自增主键的值设置到实体类对象中,而不是以 Mapper 接口方法返回值的形式返回。

  • useGeneratedKeys = true:告诉MyBatis在执行插入操作后,要尝试获取由数据库自动生成的键值(通常是主键)
  • keyProperty = "...":指定主键在实体类对象中对应的属性名,Mybatis会将拿到的主键值存入这个属性
  • Mapper 接口中抽象方法
java">int insertEmployee(Employee employee);
  • SQL 语句
<!-- int insertEmployee(Employee employee); -->
<!-- useGeneratedKeys属性告诉MyBatis在执行插入操作后,要尝试获取由数据库自动生成的键值(通常是主键)-->
<!-- keyProperty属性可以指定主键在实体类对象中对应的属性名,Mybatis会将拿到的主键值存入这个属性 -->
<insert id="insertEmployee" useGeneratedKeys="true" keyProperty="empId">insert into t_emp(emp_name,emp_salary)values(#{empName},#{empSalary})
</insert>
  • 测试类
java">@Test
public void testSaveEmp() {EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);Employee employee = new Employee();employee.setEmpName("john");employee.setEmpSalary(666.66);employeeMapper.insertEmployee(employee);//自增主键返回给了 empIdSystem.out.println("employee.getEmpId() = " + employee.getEmpId());
}

插入非自增长型主键

对于不支持自增主键的数据库(比如 Oracle),或者当你需要使用字符串类型的主键(如UUID)时,你可以使用 MyBatisselectKey 元素。
.
在插入记录之前,selectKey 元素会先运行。它会生成一个主键值,并将其设置到你的对象属性中。然后,MyBatis 才会执行插入操作。

.
这种做法非常适合使用 UUID 作为主键的情况。当然,生成主键的方法不止一种,你也可以在 Java 代码中生成 UUID 并手动设置到对象的属性里。不过,根据你的具体需求和场景,选择最合适的生成方式是非常重要的。
.
简单来说,selectKey 就是先帮你生成一个主键,然后MyBatis再用这个主键去插入新记录。

  • <selectKey>:帮你生成一个主键,然后MyBatis再用这个主键去插入新记录。
  • order = "before" | "after">: sql语句是在插入语句之前还是之后执行
  • resultType = ...:返回值类型
  • keyProperty = ...:查询结果给哪个属性赋值
  • SQL 语句
   <!--期望, 非自增长大的主键, 交给 mybatis 帮助我们维护--><insert id="insertTeacher"><!--插入之前, 先指定一段sql语句, 生成一个主键值order="before | after" sql语句是在插入语句之前还是之后执行resultType = 返回值类型keyProperty = 查询结果给哪个属性赋值--><selectKey order="BEFORE" resultType="string" keyProperty="tId"><!-- 把 UUID 中 - 换成空字符-->select REPLACE(UUID(),'-','');</selectKey>INSERT INTO teacher (t_id, t_name)VALUES(#{tId}, #{tName});</insert>

实体类属性和数据库字段不对应解决方法总结

返回单个实体类型,要求列名和属性名一致。才能进行实体类的属性映射

  • 规则要求数据库表字段命名方式:单词_单词
  • 规则要求Java实体类属性名命名方式:首字母小写的驼峰式命名

实体类 和 数据库字段

java">tId 对应 t_id
tName 对应 t_Name

解决方法一

方案1:起别名 select t_id tId, t_name tName from teacher where t_id = #{tId}

<select id="queryById" resultType="teacher">select t_id tId, t_name tName from teacher where t_id = #{tId}
</select>

解决方法二

mapper.config开启驼峰式映射 <setting name="mapUnderscoreToCamelCase" value = "true"/>

<settings><!--开启驼峰映射--><setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<select id="queryById" resultType="teacher">select * from teacher where t_id = #{tId}
</select>

解决方法三

使用自定义映射

<!--resultMap: 设置自定义的映射关系id: 唯一标识type: 处理映射关系的实体类的类型常用的标签:id: 处理主键和实体类中实现的映射关系result: 处理普通字段和实体类中属性的映射关系column: 设置映射关系中的字段名, 必须是SQL查询出的某个字段property: 设置映射关系中的属性的属性名, 必须是处理的实体类类型中的属性名
-->
<resultMap id="tMap" type="teacher"><id column="t_id" property="tId"/><result column="t_name" property="tName"/>
</resultMap><select id="queryById" resultType="tMap">select * from teacher where t_id = #{tId}
</select>

MyBatis 多表映射

实体类设计方案


  • 对一:例如一个订单对应一个顾客

实体类设计:对一关系下,类中只要包含单个对方对象类型属性即可!

java">//顾客表
public class Customer {private Integer customerId;private String customerName;}//订单表
public class Order {private Integer orderId;private String orderName;private Customer customer;// 体现的是对一的关系: 包含单个对方类型对象}  
  • 对多:例如一个顾客对应多个订单

实体类设计:对多关系下,类中只要包含对方类型集合属性即可!

java">public class Customer {private Integer customerId;private String customerName;private List<Order> orderList;// 体现的是对多的关系,包含对方集合类型
}public class Order {private Integer orderId;private String orderName;}
  • 总结
  • 对一,属性中包含对方对象
  • 对多,属性中包含对方对象集合

只有真实发生多表查询时,才需要设计和修改实体类,否则不提前设计和修改实体类!

无论多少张表联查,实体类设计都是两两考虑!【就是一次考虑两张表之间的关系就行】

  • 比如 第一张表和第二张表。第二张表和第三张表这样
    .

在查询映射的时候,只需要关注本次查询相关的属性!例如:查询订单和客户的关系。不要关注客户对订单的关系。

自定义映射


自定义映射 <resultMap>

<!--resultMap: 设置自定义的映射关系id: 唯一标识type: 处理映射关系的实体类的类型常用的标签:id: 处理主键和实体类中实现的映射关系result: 处理普通字段和实体类中属性的映射关系column: 设置映射关系中的字段名, 必须是SQL查询出的某个字段property: 设置映射关系中的属性的属性名, 必须是处理的实体类类型中的属性名
-->
<resultMap id="tMap" type="teacher"><id column="t_id" property="tId"/><result column="t_name" property="tName"/>
</resultMap><select id="queryById" resultType="tMap">select * from teacher where t_id = #{tId}
</select>

对象属性赋值 <association> [ 对一 ]

    <!--自定义映射关系, 定义嵌套对象的映射关系--><resultMap id="orderMap" type="order"><!--第一层属性 order 对象--><!-- order 的主键 id 标签--><id column="order_id" property="orderId" /><!--普通列--><result column="order_name" property="orderName" /><result column="order_Id" property="orderId" />就是实体类里面还有个对象属性<!--对象属性赋值property 表里对象属性名javaType 表里对象类型--><association property="customer" javaType="Customer"><id column="customer_id" property="customerId" /><result column="customer_name" property="customerName" /></association></resultMap>

在这里插入图片描述

集合属性赋值 <collection> [ 对多 ]

<resultMap id="customerMap" type="customer"><id column="customer_id" property="customerId"/><result column="customer_name"  property="customerName"/><!--<association property="" 对一的对象进行赋值--><!--给集合属性赋值property 集合属性名ofType 集合的泛型类型--><collection property="orderList" ofType="order"><id column="order_id" property="orderId" /><result column="order_name" property="orderName" /><result column="customer_id" property="customerId" />//注意这里不要考虑对一设置的对象属性 Customer</collection></resultMap>

在这里插入图片描述

对一映射示例

需求

根据ID查询订单,以及订单关联的用户的信息!

  • 实体类
java">//Customer
@Data
public class Customer {private Integer customerId;private String customerName;}//Order
@Data
public class Order {private Integer orderId;private String orderName;private Integer customerId;//一个订单 对应一个客户 对一private Customer customer;}
  • Mapper 接口
java">public interface OrderMapper {//根据id查询订单信息和订单对应的客户Order queryById(Integer id);}
  • mapper.xml
<!-- namespace = 接口的全限定名-->
<mapper namespace="com.atguigu.mapper.OrderMapper"><!--自定义映射关系, 定义嵌套对象的映射关系--><resultMap id="orderMap" type="order"><!--第一层属性 order 对象--><!-- order 的主键 id 标签--><id column="order_id" property="orderId" /><!--普通列--><result column="order_name" property="orderName" /><result column="order_Id" property="orderId" /><!--对象属性赋值property 表里对象属性名javaType 表里对象类型--><association property="customer" javaType="Customer"><id column="customer_id" property="customerId" /><result column="customer_name" property="customerName" /></association></resultMap><!--Order queryOrderById(Integer id);--><select id="queryById" resultMap="orderMap">SELECT * FROM t_order tor JOIN t_customer turON tor.customer_id = tur.customer_idWHERE tor.order_id = #{id};</select>
</mapper>
  • 测试
java">public class MybatisTest {private SqlSession session;@BeforeEachpublic void init() throws IOException {session = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config.xml")).openSession();}@Testpublic void testToOne() {//查询订单和对应的客户OrderMapper mapper = session.getMapper(OrderMapper.class);Order order = mapper.queryById(1);System.out.println(order);System.out.println(order.getCustomer());}@AfterEachpublic void clean() {session.close();}}

对多映射示例

  • pojo
java">//Customer
@Data
public class Customer {private Integer customerId;private String customerName;//一个客户对应多个订单//对多: 装对方类型的集合即可private List<Order> orderList;}//Order
@Data
public class Order {private Integer orderId;private String orderName;private Integer customerId;//一个订单 对应一个客户 对一private Customer customer;}
  • Mapper 接口
java">public interface CustomerMapper {//查询所有客户信息以及客户对应的订单信息List<Customer> queryList();}
  • mapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><!-- namespace = 接口的全限定名-->
<mapper namespace="com.atguigu.mapper.CustomerMapper"><resultMap id="customerMap" type="customer"><id column="customer_id" property="customerId"/><result column="customer_name"  property="customerName"/><!--<association property="" 对一的对象进行赋值--><!--给集合属性赋值property 集合属性名ofType 集合的泛型类型--><collection property="orderList" ofType="order"><id column="order_id" property="orderId" /><result column="order_name" property="orderName" /><result column="customer_id" property="customerId" /><!-- customer 需不需要赋值, 不需要--></collection></resultMap><select id="queryList" resultMap="customerMap">SELECT * FROM t_order torJOIN t_customer turON tor.customer_id = tur.customer_id</select>
</mapper>

在这里插入图片描述

多表映射优化 <autoMappingBehavior>


MyBatis 提供了三种自动映射列到字段或属性的模式:

  • NONE:关闭自动映射功能。
  • PARTIAL 【默认】:仅自动映射未定义嵌套结果映射的字段。
  • FULL:可自动映射包括嵌套结果集在内的任何复杂结果。
    .

MyBatis 中,将 autoMappingBehavior 设为full后,进行多表关联查询并使用 resultMap 映射时,若 列名属性名 符合命名映射规则(二者相等,或开启驼峰映射 规则也适用),对应的 result 标签 便 可省 略,系统能自动完成映射。

  • 修改 mybaits-config.xml
<!--开启resultMap自动映射 -->
<setting name="autoMappingBehavior" value="FULL"/>
  • 修改 teacherMapper.xml
<resultMap id="teacherMap" type="teacher"><id property="tId" column="t_id" /><!-- 开启自动映射,并且开启驼峰式支持!可以省略 result!-->
<!--        <result property="tName" column="t_name" />--><collection property="students" ofType="student" ><id property="sId" column="s_id" />
<!--            <result property="sName" column="s_name" />--></collection>
</resultMap>

MyBatis 动态语句

if 和 where 标签 【多条件动态判断】


<if>

  • 通过 test 属性 对传入的参数做比较运算。
  • 当运算结果为 true 时,会将标签内的 sql 语句进行拼接;若结果为 false,则不拼接标签内部语句。
  • 对于大于和小于的比较符号,不推荐直接写,建议使用实体符号(如 &gt; 表示大于,&lt; 表示小于)。

.
<where>

  • 自动添加 / 去除 where 关键字:只要其内部有任何一个 if 条件满足,就会自动添加 where 关键字;若所有 if 条件都不满足,会自动去掉 where 关键字。
  • 自动去除多余逻辑关键字:能够自动去掉多余的 andor 关键字,以此避免因条件不满足等情况出现语法错误,确保生成的 sql 语句符合语法规范。
<!-- namespace = 接口的全限定名-->
<mapper namespace="com.atguigu.mapper.EmployeeMapper"><!--List<Employee> query(@Param("name") String name,@Param("salary") Double salary);假如两个都满足 where emp_name = #{name} and emp_salary = #{salary}假如第一个满足 where emp_name = #{name}假如第一个满足第二个不满足 where and emp_salary = #{salary} 错误假如都不满足 where 错误所以需要多弄一个 where 标签。自动处理这些情况--><select id="query" resultType="employee">select * from t_emp<where><if test="name != null">emp_name = #{name}</if><if test="salary != null and salary &gt; 100">and emp_salary = #{salary}</if></where></select></mapper>

set 标签【更新信息】


<set>

  • 自动去掉多余的,
  • 自动添加 set 关键字
  <!--根据员工 id 更新员工的数据, 我们要求 传入的 name 和 salary 不为 null 的才更新int update(Employee employee);全部满足: set emp_name = #{empNamee}, emp_salary = #{empSalary} 没问题第一个满足: set emp_name = #{empNamee}, 多了个,第二个满足: set emp_salary = #{empSalary} 没问题都不满足: set 语法错误所以需要 set 标签自动处理这些情况set:1.自动去掉多余的,2.自动添加set关键字--><update id="update">update t_emp<set><if test="empName != null">emp_name = #{empName}</if><if test="empSalary">emp_salary = #{empSalary}</if></set>where emp_id = #{empId}</update>

choose / when / otherwise 标签【多条件选一】


在多个分支条件中,仅执行一个。

  • 从上到下依次执行条件判断
  • 遇到的第一个满足条件的分支会被采纳
  • 被采纳分支后面的分支都将不被考虑
  • 如果所有的 when test 分支都不满足,那么就执行 otherwise 分支
<!-- List<Employee> selectEmployeeByConditionByChoose(Employee employee) -->
<select id="selectEmployeeByConditionByChoose" resultType="com.atguigu.mybatis.entity.Employee">select emp_id,emp_name,emp_salary from t_empwhere<choose><when test="empName != null">emp_name=#{empName}</when><when test="empSalary &lt; 3000">emp_salary &lt; 3000</when><otherwise>1=1</otherwise></choose><!--第一种情况:第一个when满足条件 where emp_name=?第二种情况:第二个when满足条件 where emp_salary < 3000第三种情况:两个when都不满足 where 1=1 执行了otherwise-->
</select>

foreach 标签【批量操作】


  • collection属性:要遍历的集合
  • item属性:遍历集合的过程中能得到每一个具体对象,在item属性中设置一个名字,将来通过这个名字引用遍历出来的对象
  • separator属性:指定当foreach标签的标签体重复拼接字符串时,各个标签体字符串之间的分隔符
  • open属性:指定整个循环把字符串拼好后,字符串整体的前面要添加的字符串
  • close属性:指定整个循环把字符串拼好后,字符串整体的后面要添加的字符串

foreach 标签中的 collection 属性注意事项

在接口里,若对于 List 类型 的参数没有使用 @Param 注解去指定一个具体名字,此时在 foreach 标签的 collection 属性中,默认能够用 “collection” 或者 “list” 来引用这个 List 集合,我们可以从异常信息中发现这一情况,比如出现如下异常信息:

java">Parameter 'empList' not found. Available parameters are [arg0, collection, list]

不过在实际开发过程中,为防止因这种比较隐晦的表达方式而产生误会,更建议大家使用 @Param 注解清晰明确地声明变量的名称,之后在 foreach 标签的 collection 属性 里,按照 @Param 注解所指定的名称去引用传入的参数。*

如果需要多次遍历 SQL 语句注意事项

需要设置 allowMultiQueries=true

 <dataSource type="POOLED"><!-- 建立数据库连接的具体信息 --><property name="driver" value="com.mysql.cj.jdbc.Driver"/>//这里设置 在 url 后面添加 allowMultiQueries=true<property name="url" value="jdbc:mysql://localhost:3306/mybatis-example?allowMultiQueries=true"/><property name="username" value="root"/><property name="password" value="Ting123321"/></dataSource>
  • Mapper 接口
java">	//根据 id 的批量查询List<Employee> queryBatch(@Param("ids") List<Integer> id);//根据 id 批量删除List<Employee> deleteBatch(@Param("ids") List<Integer> ids);//批量添加int insertBatch(@Param("list") List<Employee> employeeList);//批量修改int updateBatch(@Param("list") List<Employee> employeeList);

Mapper.xml

   	<!--根据 id 批量查询--><select id="queryBatch" resultType="Employee">select * from t_empwhere emp_id in<!-- id 就是 从 ids 集合中拿值, ids 表示 list 集合--><!--(1,2,3)--><foreach collection="ids" open="(" separator="," close=")" item="id">#{id}</foreach></select><!--根据id的批量删除--><delete id="deleteBatch">delete from t_emp where id in<!--(1,2,3)--><foreach collection="ids" open="(" separator="," close=")" item="id">#{id}</foreach></delete><!--根据id的批量插入--><insert id="insetBatch">insert into t_emp (emp_name, emp_salary)values<!--('xx', 200), ('xx', 200), ('xx', 200)--><foreach collection="list" separator="," item="employee">(#{employee.empName}, #{employee.empSalary})</foreach></insert><!--根据id的批量修改--><!--如果一个标签涉及多个语句! 需要设置允许指定多语句【把sql语句遍历多遍】 需要设置allowMultQueries = true --><update id="updateBatch"><foreach collection="list" item="emp"><!--update t_emp set emp_name = #{emp.empName}, emp_salary = #{emp.empSalary}where emp_id = #{emp.empId};update t_emp set emp_name = #{emp.empName}, emp_salary = #{emp.empSalary}where emp_id = #{emp.empId};...-->update t_emp set emp_name = #{emp.empName}, emp_salary = #{emp.empSalary}where emp_id = #{emp.empId};</foreach></update>

SQL 片段抽取


<include reifd = "指定SQL片段">

   <sql id="selectSql">select * from t_emp</sql><select id="query" resultType="employee">//这里就是抽取的SQL 等价于 select * from t_emp<include refid="selectSql" /><where><if test="name != null">emp_name = #{name}</if><if test="salary != null and salary &gt; 100">and emp_salary = #{salary}</if></where></select>

MyBatis 分页和逆向

SQL 分页回顾


查询数据

前面是索引,后面是查询几条数据

java">//从 0 所以开始查询 5 条数据
SELECT * FROM table LIMIT 0, 5

返回某页数据

  • 公式:limit ( (startPage-1) * pageSize, pageSize )
  • startPage要查询的页码pageSize此页面总记录数
比如
0     4     81     5     92     6     103     7      11这里返回 第二页数据 就是 
SELECT * FROM table LIMIT ( (2-1) * 4,  4)
也就是
SELECT * FROM table LIMIT (4,  4) 
刚好从索引 4 开始返回 4 条数据

分页插件 PageHelper


MyBatis 对插件进行了标准化的设计,并提供了一套可扩展的插件机制。插件可以在用于语句执行过程中进行拦截,并允许通过自定义处理程序来拦截和修改 SQL 语句、映射语句的结果等。

  • 导入依赖
<dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper</artifactId><version>5.1.11</version>
</dependency>
  • mybatis-config.xml 配置分页插件

其中,com.github.pagehelper.PageInterceptorPageHelper 插件的名称,dialect 属性用于指定数据库类型(支持多种数据库)MyBatis 其他插件也要这么安装

java"><plugins><plugin interceptor="com.github.pagehelper.PageInterceptor"><property name="helperDialect" value="mysql"/></plugin>
</plugins>
java">@Service
public class EmpServiceImpl implements EmpService {@Autowirdprivate EmpMapper empMapper;/*** @param page 页码* @param pageSize 每页记录数*/@Overridepublic PageResult<Emp> page(Integer page, Integer pageSize) {//1. 设置分页参数 分页查询的结果封装到 Page 对象中 /**Page 是 ArrayList 的子类所以 Page 本质上是 List 类型 Page 封装了分页查询所有的信息包括总页数....**/PageHelper.startPage(page, pageSize);//2. 执行查询List<Emp> empList = empMapper.list(); //3. 解析查询结果, 并封装 Page<Emp> p = (Page<Emp>)empList;//Total 是获取总记录数 Reuslt 是获取结果列表 return new PageREsult<Emp>(p.getTotal(), p.getResult);}
}

逆向工程


ORM,即对象 - 关系映射技术,能使数据库操作更贴合面向对象思维。它分半自动、全自动两类:

  • 半自动 ORM:程序员要手动写 SQL 语句或配置文件,以此关联实体类与数据表,还得自行把查询结果转为实体对象。映射关系常用 XML 文件、注解来指定,像 MyBatis 就属此类,使用它需掌握扎实的 SQL 与数据库知识。
  • 全自动 ORM:实体类与数据表自动映射,调用 API 操作数据库时,框架自动生成、执行 SQL 语句,还能把查询结果无缝转换成实体对象,且自动优化性能。像 Hibernate、Spring Data JPA、MyBatis - Plus 等都是,上手相对容易,无需过多钻研 SQL 语法。

逆向工程

MyBatis 的逆向工程堪称开发 “利器”,它能自动产出持久层代码与映射文件。依据数据库表结构及预设参数,迅速生成实体类、Mapper.xml 文件、Mapper 接口等,助力开发者快速搭建 DAO 层,无缝切入业务开发
.
实现逆向工程主要有两条路:借助 MyBatis Generator 插件,或是利用 Maven 插件。操作时,得给定数据库连接 URL、用户名、密码、目标表名、生成文件路径等配置参数。
.
注意:逆向工程只能生成单表crud的操作,多表查询依然需要我们自己编写!

使用 MyBatisX 插件


MyBatisX 是一个 MyBatis 的代码生成插件,可以通过简单的配置和操作快速生成 MyBatis Mapper、pojo 类和 Mapper.xml 文件。下面是使用 MyBatisX 插件实现逆向工程的步骤:

第一步:安装插件

IntelliJ IDEA 中打开插件市场,搜索 MyBatisX 并安装。

第二步:IDEA 连接数据库

在这里插入图片描述
在这里插入图片描述

第三步:使用逆向插件

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

第四步:查看生成结果

在这里插入图片描述


http://www.ppmy.cn/embedded/153227.html

相关文章

Java中的反射机制及其应用场景

目录 什么是Java反射机制&#xff1f; 工作原理 主要应用场景 注意事项 总结 什么是Java反射机制&#xff1f; Java反射机制是一种强大的工具&#xff0c;它允许程序在运行时访问、检查和修改其本身的类和对象的信息。通过反射&#xff0c;开发者可以在不知道类的具体实现…

【游戏设计原理】53 - 解决问题的障碍

1. 分析并总结原理 核心观点 游戏本质是一系列问题解决的过程&#xff0c;通过设计巧妙的问题和决策场景&#xff0c;游戏能激发玩家的兴趣和投入感。然而&#xff0c;当问题解决的过程被阻碍时&#xff0c;会降低玩家的体验甚至让他们放弃游戏。文中提到的四种障碍反映了玩家…

Scratch教学作品 | 双人足球对决——快节奏对战,足球场上的精彩较量! ⚽

今天为大家推荐一款轻松有趣的Scratch双人对战游戏——《双人足球对决》&#xff01;由pandakun制作&#xff0c;这款作品简单易上手&#xff0c;却充满竞技乐趣。你可以选择挑战AI&#xff0c;也可以和朋友来一场紧张刺激的足球对决&#xff0c;看看谁才是真正的绿茵之王&…

使用Supervisor在Ubuntu中实现后台自启动服务

在Ubuntu系统中&#xff0c;Supervisor是一个非常实用的进程管理工具&#xff0c;它可以让你的应用程序在后台运行&#xff0c;并且在系统启动时自动启动这些应用程序。下面&#xff0c;我将详细介绍如何在Ubuntu中使用Supervisor来实现后台自启动服务&#xff0c;并以一个具体…

【TI毫米波雷达】DCA1000不使用mmWave Studio的数据采集方法,以及自动化实时数据采集

【TI毫米波雷达】DCA1000不使用mmWave Studio的数据采集方法&#xff0c;以及自动化实时数据采集 mmWave Studio提供的功能完全够用了 不用去纠结用DCA1000低延迟、无GUI传数据 速度最快又保证算力无非就是就是Linux板自己写驱动做串口和UDP 做雷达产品应用也不会采用DCA1000的…

C#读取本地网络配置信息全攻略

一、引言 在当今数字化时代&#xff0c;网络已深度融入我们生活与工作的方方面面。对于软件开发而言&#xff0c;掌握本地计算机的网络配置信息显得尤为关键。想象一下&#xff0c;你正在开发一款网络诊断工具&#xff0c;需要精准定位网络连接问题&#xff0c;此时 IP 地址、…

Vue3的reactive、ref、toRefs、toRef、toRaw 和 markRaw处理响应式数据区别

reactive reactive 用于创建一个响应式对象。它接受一个普通对象&#xff0c;并返回一个响应式对象&#xff08;Proxy实例&#xff09;。 import { reactive } from vue;// 创建一个reactive类型的响应式对象 const person reactive({name: panda,age: 18 });console.log(pe…

Qt 智能指针

Qt 智能指针 文章目录 Qt 智能指针QScopedPointer1. 自动删除对象2. 转移所有权3. 管理私有数据 QSharedPointer关键特性注意事项 QWeakPointer注意事项 QPointer QScopedPointer QScopedPointer 是 Qt 提供的一个智能指针&#xff0c;主要用于简化资源管理&#xff0c;防止内…