当前位置: 首页 > news >正文

MyBatis 常见面试题

目录

  • 1.MyBatis——概述
    • 1.1.什么是 ORM 框架?
    • 1.2.✨谈谈对 MyBatis 的理解。
    • 1.3.使用 MyBatis 相对于直接使用 SQL 有哪些优点?
    • 1.4.MyBatis 有什么优缺点?
    • 1.5.✨MyBatis 的分层结构是什么样的?
    • 1.6.✨MyBatis 的执行流程是什么样的?
    • 1.7.MyBatis 中的 SqlSessionFactory 接口有什么作用?
    • 1.8.MyBatis中的 SqlSession 接口有什么作用?
    • 1.9.✨MyBatis 中的 Executor 执行器是什么?有哪些 Executor?
    • 1.10.MyBatis 中如何指定使用哪一种 Executor 执行器?
  • 2.MyBatis——配置文件与注解
    • 2.1.✨MyBatis 中 DAO 接口的工作原理是什么?DAO 接口里的方法,参数不同时,方法能重载吗?
    • 2.2.Mybatis 如何获取插入数据的主键 id?
    • 2.3.MyBatis 中如何设置延迟加载?如果支持,它的实现原理是什么?
  • 3.MyBatis——映射文件
    • 3.1.✨MyBatis 中的 #{} 和 ${} 的区别是什么?
    • 3.2.XML 映射文件中,除了常见的 select、insert、update、delete 标签之外,还有哪些标签?
    • 3.3.MyBatis 中的 resultMap 和 resultType 有什么区别?
    • 3.4.MyBatis 中的 SQL ID 和命名空间是指什么?它们是否可以重复?
    • 3.5.MyBatis 是否可以映射 Enum 枚举类?
    • 3.6.MyBatis 中 String 类型的属性如何映射到数据库表中的 varchar 字段?
    • 3.7.在 MyBatis 中,如果实体类中某一属性是 String 类型,数据库中对应的字段是 Integer 会怎么样?
    • 3.8.MyBatis 的 XML 映射文件和 MyBatis 内部数据结构之间的映射关系是什么样的?
  • 4.MyBatis——其它功能
    • 4.1.MyBatis 是如何进行分页的?分页插件的原理是什么?
    • 4.2.✨MyBatis 的插件运行原理是什么?如何自定义插件?
    • 4.3.✨介绍一下 MyBatis 的缓存机制。

参考文章:
MyBatis3——入门介绍
MyBatis 常见面试题总结

1.MyBatis——概述

1.1.什么是 ORM 框架?

(1)ORM 框架是 Object-Relational Mapping(对象关系映射)框架的简称,用于将对象模型和关系数据库之间进行映射,即将 Java 对象映射到数据库表中的记录,或将数据库表中的记录映射为 Java 对象,从而使得开发者可以使用面向对象的方式来操作数据库。

(2)ORM 框架通常提供了一系列的映射规则映射配置,开发者只需要编写简单的代码和配置就可以完成对象和关系数据库之间的映射。ORM 框架还提供了丰富的查询和操作 API,使得开发者可以方便地进行 CRUD 操作和复杂的查询。

(3)ORM 框架的优点包括:

  • 高效的开发:ORM 框架可以自动生成大量的数据访问代码,开发者只需要编写少量的业务逻辑代码就可以完成数据库操作,从而提高开发效率。
  • 可移植性:ORM 框架可以屏蔽不同数据库之间的差异,使得应用程序具有较高的可移植性,可以轻松地在不同的数据库中进行部署。
  • 高性能:ORM 框架通常具有较好的性能和高效的查询能力,可以有效地减少数据库操作的次数,提高数据访问的效率。
  • 易于维护:ORM 框架可以使代码结构更加清晰和易于维护,同时也可以减少代码量,提高代码的可读性和可维护性。

(4)常见的 ORM 框架有 Hibernate、MyBatis、Spring Data JPA 等,它们在应用开发中发挥着重要的作用。

1.2.✨谈谈对 MyBatis 的理解。

(1)MyBatis 是一款基于 Java 的持久层框架,也是一个半自动 ORM 框架,它提供了优雅的 SQL 映射方式灵活的配置方式,可以帮助开发者快速、高效地访问数据库。在 MyBatis 中,开发者可以使用 XML 配置文件或者注解来编写 SQL 查询语句,同时可以通过一系列的对象映射配置将数据库表中的数据映射到 Java 对象中,使得开发者可以在应用程序中方便地操作这些数据。

(2)MyBatis 的主要特点包括:

  • 灵活的 SQL 映射方式:MyBatis 可以将 SQL 语句映射到 Java 方法中,同时支持动态 SQL 语句和嵌套查询等高级功能。
  • 简单的配置方式:MyBatis 的配置文件是基于 XML 的,支持自定义类型处理器、对象工厂、插件等,具有高度的灵活性和可扩展性。
  • 强大的缓存机制:MyBatis 支持一级缓存和二级缓存,可以有效地提高数据库查询性能。
  • 与 Spring 等框架的集成:MyBatis 可以与 Spring、Spring Boot 等主流的 Java 框架无缝集成,方便开发者进行应用开发和维护。

(3)使用 MyBatis 可以有效地提高 Java 应用的开发效率和数据库查询性能,特别是在需要对 SQL 语句进行严格控制的场景下。同时,MyBatis 还支持多种数据库的访问,包括 MySQL、Oracle、SQL Server 等,非常适合在企业级应用中使用。

① 之所以称 Mybatis 是一个半 ORM 框架,是因为在查询关联对象或关联集合对象时,需要手动编写 SQL 来完成
持久层是软件开发中的一个概念,指的是负责将数据持久化到存储介质(通常是数据库)中,并提供对数据的读取、更新和删除等操作的一部分。持久层的主要目的是为了解决数据的长期存储和访问的需求

1.3.使用 MyBatis 相对于直接使用 SQL 有哪些优点?

使用 MyBatis 相对于直接使用SQL有以下几个优势:

  • 简化 SQL 编写:MyBatis 提供了一种声明式的 SQL 编写方式,通过使用 XML 配置文件或注解,可以将 SQL 语句与 Java 代码进行解耦,使得代码更加清晰和易于维护。同时,MyBatis 支持动态 SQL,可以根据不同的条件灵活地构建 SQL 语句,减少了手动拼接 SQL 的繁琐和风险。
  • 数据和对象的映射:MyBatis 提供了强大的结果映射功能,可以将数据库查询结果自动映射到 Java 对象中,避免了手动处理结果集的繁琐和容易出错。通过配置查询结果与 Java 对象之间的映射关系,可以方便地进行对象的读写操作,提高了开发效率。
  • 缓存机制:MyBatis 内置了缓存机制,可以在查询时缓存查询结果,减少数据库的访问次数,提高查询性能。通过缓存的使用,可以显著减少对数据库的访问,适用于那些相对稳定的数据,提高了系统的吞吐量。
  • 数据库的兼容性:MyBatis 支持多种主流数据库,如 MySQL、Oracle、SQL Server 等,通过配置文件即可切换数据库,方便应对不同的开发环境和需求。同时,MyBatis 提供了对数据库方言的支持,可以针对不同的数据库进行优化和适配。
  • 插件机制:MyBatis 提供了强大的插件机制,可以自定义和扩展 MyBatis 的功能。开发者可以通过编写自定义插件,修改 MyBatis 的行为,如添加额外的功能、性能监控、日志记录等,满足特定的业务需求和开发场景。

总的来说,使用 MyBatis 可以简化 SQL 编写、实现数据和对象的映射、提供缓存功能、提供数据库兼容性,并通过插件机制灵活扩展功能。通过这些优势,可以提高开发效率、降低维护成本,并提供更好的性能和可扩展性。然而,对于简单的数据库操作,直接使用 SQL 可能更加直观和方便。因此,对于具体的开发需求,可以根据实际情况选择适合的方式。

1.4.MyBatis 有什么优缺点?

(1)MyBatis 作为一款流行的 Java 持久化框架,有以下优缺点:

  • 优点:

    • 灵活性高:MyBatis 允许开发人员通过 XML注解的方式来定义 SQL 语句,并且可以实现自定义的 SQL 语句和结果集的映射规则。这种灵活性使得 MyBatis 可以适应各种不同的应用场景。
    • 易于学习和使用:相对于其他持久化框架,MyBatis 的学习和使用成本较低。它提供了简单易用的 API,可以帮助开发人员编写复杂的 SQL 语句,并处理结果集的映射。
    • 性能高:MyBatis 的性能相对较高,它采用了缓存机制预编译语句等技术来提高 SQL 查询的性能。
    • 易于扩展:MyBatis 提供了插件机制,开发人员可以通过插件来扩展 MyBatis 的功能,实现自定义的逻辑。
  • 缺点:

    • 开发效率相对较低:相对于 ORM 框架,MyBatis 需要开发人员手动编写 SQL 语句,并处理结果集的映射,这需要花费更多的时间和精力。
    • 配置复杂:MyBatis 的配置相对较为复杂,需要开发人员理解 XML 和 Java 注解,以及映射器的配置方式。
    • 不适合大规模数据处理:MyBatis 在处理大规模数据时性能可能不如其他持久化框架,因为它的缓存机制是针对小规模数据的。

(2)总之,MyBatis 是一款性能较高且灵活的持久化框架,但需要开发人员花费更多的时间和精力来编写 SQL 语句和处理结果集的映射。在应用场景选择时需要综合考虑其优缺点。

1.5.✨MyBatis 的分层结构是什么样的?

参考文章:Mybatis 层次结构与执行流程

在这里插入图片描述

1.6.✨MyBatis 的执行流程是什么样的?

(1)MyBatis 的执行流程如下:

  • 读取配置文件和映射文件:MyBatis 首先会读取配置文件和映射文件,配置文件包含了 MyBatis 的全局配置信息,映射文件则包含了 SQL 语句和 Java 对象之间的映射关系。
  • 创建 SqlSessionFactory:通过配置文件构建 SqlSessionFactory,SqlSessionFactory 是 MyBatis 的核心对象,负责创建 SqlSession对象。
  • 创建 SqlSession:通过 SqlSessionFactory 创建 SqlSession 对象,SqlSession 是执行持久化操作的核心对象,它提供了操作数据库的 API
  • 执行 SQL 语句:通过 SqlSession 执行 SQL 语句,MyBatis 支持动态 SQL 语句,可以根据参数的不同生成不同的 SQL 语句。
  • 返回结果:执行 SQL 语句后,MyBatis 将结果映射成Java对象并返回,返回的对象可以是一个普通Java对象、Map、List等。
  • 关闭 SqlSession:执行完数据库操作后,需要手动关闭 SqlSession,释放资源。

(2)总的来说,MyBatis 的执行流程可以分为两个阶段:

  • 在配置阶段,MyBatis 会读取配置文件和映射文件,并创建 SqlSessionFactory;
  • 在运行阶段,MyBatis 会根据 SQL 语句和 Java 对象之间的映射关系执行 SQL 语句,并将结果映射成 Java 对象返回。

示例代码如下所示:

public class MyBatisTest {//根据全局配置文件 mybatis-config.xml,利用 SqlSessionFactoryBuilder 创建 SqlSessionFactorypublic SqlSessionFactory getSqlSessionFactory() throws IOException {String resource = "mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);return new SqlSessionFactoryBuilder().build(inputStream);}/** 1、根据 xml 配置文件(全局配置文件)创建一个 SqlSessionFactory 对象,其包含数据源等一些运行环境信息* 2、sql 映射文件: 配置了每一个sql,以及sql的封装规则等* 3、将 sql 映射文件注册在全局配置文件中* 4、写代码:* 		1)根据全局配置文件得到 SqlSessionFactory;* 		2)使用 sqlSession 工厂,获取到 sqlSession 对象使用他来执行增删改查* 			一个 sqlSession就是代表和数据库的一次会话,用完关闭* 		3)使用 sql 的唯一标志来告诉 MyBatis 执行哪个 sql,sql都是保存在 sql 映射文件中的*///新版本常用的方式:接口式编程/** 1、接口式编程* 	原生:		Dao		====>  DaoImpl* 	mybatis:	Mapper	====>  xxMapper.xml** 2、SqlSession代表和数据库的一次会话;用完必须关闭;* 3、SqlSession和connection一样她都是非线程安全。每次使用都应该去获取新的对象。* 4、mapper接口没有实现类,但是mybatis会为这个接口生成一个代理对象。* 		(将接口和xml进行绑定)* 		EmployeeMapper empMapper =	sqlSession.getMapper(EmployeeMapper.class);* 5、两个重要的配置文件:* 		mybatis的全局配置文件:包含数据库连接池信息,事务管理器信息等...系统运行环境信息* 		sql映射文件:保存了每一个sql语句的映射信息:将sql抽取出来。**/@Testpublic void test02() throws IOException {// 1.获取 sqlSessionFactory 对象SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();// 2.获取 sqlSession 对象SqlSession openSession = sqlSessionFactory.openSession();try {// 3.获取接口的实现类对象,会为接口自动的创建一个代理对象,代理对象去执行增删改查方法EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);Employee employee = mapper.getEmpById(1);System.out.println(mapper.getClass());System.out.println(employee);}finally{openSession.close();}}
}

1.7.MyBatis 中的 SqlSessionFactory 接口有什么作用?

(1)在 MyBatis 中,SqlSessionFactory 接口是核心接口之一,它的主要作用是用于创建 SqlSession 实例。具体来说,SqlSessionFactory 接口有以下作用:

  • 创建 SqlSession:通过 SqlSessionFactory 接口的 openSession 方法可以创建一个 SqlSession 实例。SqlSession 是与数据库交互的主要对象,它提供了执行 SQL 语句、管理事务等方法。
  • 保持数据库连接:SqlSessionFactory 会负责管理数据库连接的创建和关闭。它根据配置文件中的设置来创建数据库连接池,确保应用程序与数据库之间的连接能够高效地复用。
  • 管理对象映射:SqlSessionFactory 会加载配置文件中的对象映射信息,将数据库表中的数据映射为 Java 对象,并提供对象与数据库之间的映射关系管理。
  • 管理事务:SqlSessionFactory 也负责管理事务的创建和提交。在需要进行事务控制的情况下,可以通过 SqlSessionFactory 创建的 SqlSession 来启动、提交或回滚事务。

(2)总之,SqlSessionFactory 接口是 MyBatis 框架中用于管理 SqlSession 实例的工厂,它封装了数据库连接、对象映射和事务等细节,提供了方便的 API 供开发者使用。通过 SqlSessionFactory,开发者可以获取到可用的 SqlSession 实例,从而进行与数据库的交互操作。

1.8.MyBatis中的 SqlSession 接口有什么作用?

(1)在 MyBatis 中,SqlSession 接口是执行 SQL 语句、与数据库进行交互的主要对象,它有以下几个主要的作用:

  • 执行 SQL 语句:SqlSession 提供了一系列的方法来执行各种类型的 SQL 语句,包括查询语句(select)、插入语句(insert)、更新语句(update)和删除语句(delete)。通过 SqlSession 可以执行预编译的 SQL 语句,并获取执行结果。
  • 提供事务管理:SqlSession 可以管理事务的启动、提交和回滚操作。通过 SqlSession,可以手动开始一个新的事务,提交事务或者回滚事务。同时,也可以设置事务的隔离级别(Isolation Level)和自动提交设置等。
  • 提供对象映射功能:SqlSession 可以将查询结果映射为 Java 对象。通过配置对应的 Mapper 接口和映射文件,可以将数据库表中的数据映射为 Java 对象,并进行操作和处理。
  • 提供缓存管理:SqlSession 会包含一个一级缓存 (Local Cache) 用于缓存查询结果。通过一级缓存,可以在当前 SqlSession 内部重用查询结果,提高查询性能。在需要的时候,可以手动清空缓存。
  • 关闭和资源释放:一旦 SqlSession 完成了数据库操作,应该及时关闭并释放相关的资源。通过调用 SqlSession 的 close 方法,可以关闭 SqlSession,释放数据库连接和其他资源。

(2)总之,SqlSession 是 MyBatis 中与数据库交互的核心接口,它提供了执行 SQL 语句、事务管理、对象映射和缓存管理等功能。通过 SqlSession,开发者可以有效地执行、管理和控制与数据库的交互操作。

1.9.✨MyBatis 中的 Executor 执行器是什么?有哪些 Executor?

(1)在 MyBatis 中,Executor 执行器是核心组件之一,负责处理 SQL 语句的执行,包括查询、插入、更新和删除等操作。Executor 在执行 SQL 语句之前会先对 SQL 语句进行解析,并将解析得到的 SQL 语句传递给底层的 JDBC 驱动程序执行。

(2)MyBatis 提供了三种类型的 Executor 执行器:

  • SimpleExecutor:每次执行都会创建一个新的 Statement 对象,并直接执行 SQL 语句,执行完毕后关闭 Statement 对象。
  • ReuseExecutor:在相同的 SQL 语句被多次执行时,会复用先前创建的 Statement 对象,而不是每次都创建新的 Statement 对象。在执行完毕后,会将 Statement 对象缓存起来,以供下次使用。
  • BatchExecutor:批量执行 SQL 语句,将多个 SQL 语句打包成一个批量请求,一次性发送给数据库执行。这种方式可以减少网络传输的开销,提高性能。

(3)在默认情况下,MyBatis 会使用 ReuseExecutor 执行器,这种执行器可以有效地缓存 Statement 对象,避免重复创建,提高性能。但是,如果 SQL 语句执行的不是很频繁,或者需要执行的 SQL 语句的参数比较复杂,那么可能会造成缓存带来的性能损失。此时,可以考虑使用 SimpleExecutor 执行器或者关闭缓存功能。

在这里插入图片描述

1.10.MyBatis 中如何指定使用哪一种 Executor 执行器?

(1)在 MyBatis 中,可以通过配置文件或者代码来指定使用哪一种 Executor 执行器。以下是两种常用的方式:

  • 配置文件方式:在 MyBatis 的配置文件(一般是 mybatis-config.xml)中,可以通过 <settings> 标签来配置使用的执行器类型。典型的配置项是 <setting name="defaultExecutorType" value="SIMPLE"/>,其中 defaultExecutorType 用于指定默认的执行器类型,SIMPLE 表示简单执行器。你可以将 value 改为其他值,如 REUSE 表示可重用执行器,BATCH 表示批处理执行器等。下面是一个示例:
<configuration><settings><setting name="defaultExecutorType" value="REUSE"/></settings><!-- 其他配置 -->
</configuration>
  • 编程方式:除了通过配置文件,你还可以通过代码方式来指定执行器类型。在创建 SqlSessionFactory 对象时,可以通过调用 Configuration 类中的 setDefaultExecutorType 方法来设置。在下面的示例中,首先创建一个 Configuration 对象,并通过 setDefaultExecutorType 方法设置执行器类型为 REUSE。然后使用 SqlSessionFactoryBuilder 构建 SqlSessionFactory 对象时,将创建的 Configuration 对象传递进去。
Configuration configuration = new Configuration();
configuration.setDefaultExecutorType(ExecutorType.REUSE);SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);

(2)通过上述方式,你可以指定使用哪一种 Executor 执行器来执行 SQL 语句。根据实际需求,选择合适的执行器类型可以提高查询性能或满足特定的事务需求。

2.MyBatis——配置文件与注解

2.1.✨MyBatis 中 DAO 接口的工作原理是什么?DAO 接口里的方法,参数不同时,方法能重载吗?

(1)在 MyBatis 中,DAO (Data Access Object) 接口是用于定义数据访问方法的接口,它的工作原理是将接口中的方法与映射文件中的 SQL 语句进行绑定,从而实现数据访问。

(2)具体来说,MyBatis 将 DAO 接口中的方法与映射文件中的 SQL 语句的 id 相对应,通过反射机制动态生成 DAO 接口的实现类,并将映射文件中的 SQL 语句和方法绑定在一起。这样,在执行 DAO 接口中的方法时,MyBatis 就会根据方法名和参数类型找到相应的 SQL 语句,并执行该 SQL 语句,最终返回查询结果或者影响的行数。例如,下面是一个 UserDao 接口的例子:

public interface UserDao {User findUserById(int id);List<User> findUserByUsername(String username);void insertUser(User user);void updateUser(User user);void deleteUser(int id);
}

在这个例子中,UserDao 接口定义了五个方法,分别用于查询用户、插入用户、更新用户和删除用户等操作。这些方法都对应着映射文件中的 SQL 语句,例如:

<!-- 根据用户id查询用户 -->
<select id="findUserById" resultType="User">select * from user where id = #{id}
</select><!-- 根据用户名查询用户列表 -->
<select id="findUserByUsername" resultType="User">select * from user where username = #{username}
</select><!-- 插入用户 -->
<insert id="insertUser">insert into user (id, username, password) values (#{id}, #{username}, #{password})
</insert><!-- 更新用户 -->
<update id="updateUser">update user set username = #{username}, password = #{password} where id = #{id}
</update><!-- 删除用户 -->
<delete id="deleteUser">delete from user where id = #{id}
</delete>

在这些 SQL 语句中,#{id}、#{username}、#{password} 等都是占位符,用于接收方法中的参数。当执行 UserDao 接口中的方法时,MyBatis 将会将占位符替换成实际的参数值,并执行相应的 SQL 语句,最终返回查询结果或者影响的行数。

需要注意的是:MyBatis 中的 DAO 接口并不需要实现类,而是通过动态代理来创建 DAO 接口的实例。因此,在使用 DAO 接口时,需要通过 MyBatis 的配置文件来指定映射文件的位置,并获取 DAO 接口的实例。

(3)Mybatis 的 Dao 接口可以有多个重载方法,但是多个接口对应的映射必须只有一个,即 XML 中的 id 不允许重复,否则启动会报错

2.2.Mybatis 如何获取插入数据的主键 id?

(1)在 MyBatis 中,获取插入数据之后的主键 id 的方式通常有以下两种:

  • 使用数据库的自增主键:如果插入数据时使用的是数据库的自增主键,可以通过在插入数据语句中添加 useGeneratedKeys="true" keyProperty="id" 来自动获取主键 id,其中 id 是对应的实体类中主键字段的属性名。这样,在插入数据成功之后,MyBatis 会将生成的主键值自动赋值到对应的实体类中,并返回插入的行数。
<!--获取自增主键的值:1.MySQL支持自增主键,在 MyBatis 中,自增主键值的获取是利用statement.getGenreatedKeys()得到的;2.useGeneratedKeys="true":使用自增主键获取主键值策略;3.keyProperty="id":指定对应的主键属性,也就是 MyBatis 获取到主键以后,将这个值封装给 Java Bean 的哪个属性,此处将得到的自增主键的值封装到返回的 Employee 对象的 id 属性中
-->
<insert id="addEmp" parameterType="com.atguigu.mybatis.bean.Employee" useGeneratedKeys="true" keyProperty="id">insert into tbl_employee(last_name,email,gender)values (#{last_name},#{email},#{gender})
</insert>
  • 使用数据库的序列:如果插入数据时使用的是数据库的序列,可以在插入数据时通过 selectKey 标签来获取序列的值,并将其设置到对应的实体类中。例如,可以在插入数据的 SQL 语句后添加一个 selectKey 标签,然后使用 keyProperty 属性指定实体类中的主键字段,使用 order 属性指定 SQL 语句的执行顺序,使用 resultType 属性指定返回值的类型,最后返回插入的行数。

(2)需要注意的是,在使用自增主键或序列时,需要保证数据库中对应的表中已经存在主键或序列。否则,在插入数据时可能会出现错误。同时,MyBatis 也支持其他的主键生成方式,如使用 UUID、Snowflake 算法等,具体可以根据业务需求进行选择。

2.3.MyBatis 中如何设置延迟加载?如果支持,它的实现原理是什么?

(1)延迟加载指在需要使用某个关联对象时才真正去查询数据库获取该对象。延迟加载可以减少不必要的数据库查询,提高查询性能。MyBatis 中主要通过两种方式实现:

  • 延迟加载查询:在需要的时候才执行额外的查询,比如使用 select 语句进行关联查询时,可以设置 lazyLoadingEnabled 为 true,然后使用 select 标签来定义关联查询,即可实现延迟加载。
  • 延迟加载属性:将对象的某些属性的加载延迟到访问该属性时才进行查询,可以使用 resultMap 标签的 association 或 collection 标签来定义延迟加载的属性,然后设置 lazyLoadingEnabled 为 true 即可。

(2)MyBatis 延迟加载的实现原理是使用动态代理,对关联对象生成一个代理对象,在需要使用该对象时再去执行真正的查询。具体实现分为两种情况:

  • 基于 CGLIB 的延迟加载:如果延迟加载的对象是一个类(而不是接口),MyBatis 会使用 CGLIB 生成一个该类的子类作为代理对象。在代理对象的方法中,会先判断关联对象是否已经加载,如果没有加载则执行查询操作,并把查询结果赋给关联对象,最后再执行原本的方法。
  • 基于 JDK 动态代理的延迟加载:如果延迟加载的对象是一个接口,MyBatis 会使用 JDK 的动态代理生成一个该接口的代理对象。在代理对象的方法中,会先判断关联对象是否已经加载,如果没有加载则执行查询操作,并把查询结果赋给关联对象,最后再执行原本的方法。

3.MyBatis——映射文件

3.1.✨MyBatis 中的 #{} 和 ${} 的区别是什么?

(1)首先,#{} 和 ${} 是 Mybatis 中提供的两种占位符语法,都是用来实现动态 SQL 的方式,通过这两种方式可以把参数传递到 XML 文件或注解中,在执行操作之前,Mybatis 会对这 2 个占位符动态地进行解析,但它们之间也存在一些区别:

  • 参数替换方式:
    • #{}#{} 使用预编译参数的方式进行替换,相当于向 PreparedStatement 的里面的预处理语句设置参数,并且会将参数值进行自动类型转换,并使用安全的方式绑定到 SQL 语句中。这种方式可以预防 SQL 注入攻击,并且能够处理特殊字符的转义和防止数据类型错误。
    • ${}${} 使用字符串替换的方式,仅仅将 ${} 中的内容直接替换成对应的值,没有预编译过程。这种方式适用于简单的字符串替换和简单的表达式计算,但会增加安全风险。
  • 安全性:
    • #{} 更安全:#{} 能够防止 SQL 注入攻击,因为参数值会被预编译和转义,不会将参数值作为用户输入的一部分直接拼接到 SQL 语句中。
    • ${} 存在安全风险:${} 直接进行字符串替换,存在 SQL 注入风险。如果 ${} 中的值来自用户输入,需要额外谨慎处理和验证。
  • 数据类型转换:
    • #{} 自动类型转换:#{} 可以根据参数类型进行自动类型转换,将参数值按照合适的方式绑定到 SQL 语句中。
    • ${} 没有类型转换:${} 只是简单的字符串替换,不会对参数值进行类型转换。

(2)所以在实际应用中尽可能地使用 #{},不过在一些特殊场景下需要用到 ${},例如:

  • 动态指定表名或列名:当需要动态指定表名或列名时,尤其是在 SQL 的 FROM 和 SELECT 子句中,只能使用 ${} 进行替换。这是因为表名和列名无法使用预编译参数 #{} 来动态绑定。例如:
SELECT * FROM ${tableName}
  • 在动态 SQL 片段中引用变量或表达式:当在 MyBatis 的动态 SQL 片段中需要引用变量或进行表达式计算时,也只能使用 ${}。因为 #{} 只支持参数绑定,无法直接进行表达式计算。例如:
<if test="${age} > 18">SELECT * FROM users
</if>

有关 SQL 注入攻击的具体知识可以查看 SQL 注入攻击介绍这篇文章。

3.2.XML 映射文件中,除了常见的 select、insert、update、delete 标签之外,还有哪些标签?

(1)除了常见的 select、insert、update、delete 标签之外,MyBatis 映射文件中还有以下常用标签:

  • resultMap:用于将查询结果映射到 Java 对象中,可以自定义映射规则,包括字段名、类型、关系等。
  • parameterMap:用于定义查询语句中的参数类型和名称,已经逐渐被 parameterType 标签替代。
  • include:用于将重复的 SQL 片段抽象为单独的模板文件,并在映射文件中引用,以提高代码的复用性。
  • sql:用于定义可重用的 SQL 片段,可以在其它语句中引用,以减少重复代码。
  • if:用于在 SQL 语句中添加条件判断,根据不同的条件生成不同的 SQL 语句。
  • choosewhenotherwise:用于在 SQL 语句中实现类似于 Java 中的 switch-case 结构,根据不同的条件生成不同的 SQL 语句。
  • set:用于在 UPDATE 语句中设置要更新的字段和值。
  • where:用于在 SQL 语句中添加 WHERE 子句,根据不同的条件生成不同的 WHERE 子句。
  • foreach:用于在 SQL 语句中实现 foreach 循环,可以遍历一个集合,并将集合元素作为参数插入到 SQL 语句中。
  • bind:用于定义变量,可以将变量用于 SQL 语句中。

(2)除了上述标签之外,MyBatis 还有一些其他标签,如 cache 用于定义二级缓存,selectKey 用于在插入语句执行后获取生成的主键值等。不同的标签可以组合使用,以实现更加复杂的数据访问逻辑。

3.3.MyBatis 中的 resultMap 和 resultType 有什么区别?

(1)在 MyBatis 中,resultMapresultType用于处理查询结果映射的两种方式

  • resultMapresultMap 是一种高级映射方式,通过自定义配置映射规则将查询结果集中的列映射到目标对象的属性上。通过使用 resultMap 可以实现复杂的对象关系映射,支持关联查询和嵌套查询,可以做到灵活的结果集处理和对象封装。resultMap 的配置信息包括列名、属性名、Java 类型、映射关系等。例如:
<resultMap id="userMap" type="User"><id property="id" column="user_id" /><result property="name" column="user_name" /><result property="age" column="user_age" />
</resultMap>
  • resultTyperesultType 是一种简单的映射方式,直接将查询结果集中的列映射到 Java 对象的属性上,不支持复杂的关联查询和嵌套查询。resultType 的配置信息是一个具体的 Java 类型,将查询结果自动映射到该类型的对象中。例如:
<resultType type="User" />

(2)它们区别总结如下:

  • resultMap 适合处理复杂映射关系,支持关联查询和嵌套查询,可以灵活地配置映射规则,适用于结果集与对象之间的复杂映射。
  • resultType 简单方便,适用于结果集与对象之间的简单映射,不支持复杂的关联查询和嵌套查询。

在实际应用中,根据查询的复杂度和对象关系,选择合适的映射方式来处理查询结果,可以更好地满足业务需求。

3.4.MyBatis 中的 SQL ID 和命名空间是指什么?它们是否可以重复?

(1)在 MyBatis 中,SQL ID 和命名空间是用来标识一个 SQL 语句的两个重要属性

  • SQL ID 是指一个 SQL 语句在其所在的 Mapper 接口或 XML 映射文件中的标识符,用于唯一标识一个映射文件中的 SQL 语句。在 Mapper 接口中,SQL ID 就是接口中的方法名,在 XML 映射文件中,SQL id 通过 select、insert、update、delete 等标签的 id 属性指定。
  • 命名空间是指 Mapper 接口或 XML 映射文件的命名空间,用于唯一标识一个 Mapper 接口或 XML 映射文件。命名空间在 Mapper 接口中可以使用 @Mapper 或 @Namespace 注解指定,在 XML 映射文件中需要在根节点 中使用 namespace 属性指定。
  • 两者的关系:SQL ID 和命名空间的组合可以唯一标识一个 SQL 语句,例如 “com.example.mapper.UserMapper.selectById” 表示 UserMapper 接口中的 selectById 方法。

(2)在 MyBatis 中,SQL ID 和命名空间的重复是不允许的,重复的 SQL ID 或命名空间会导致配置错误。每个 SQL ID 在同一命名空间下应该是唯一的,而不同命名空间之间可以有相同的 SQL ID。这种设计使得 MyBatis 可以更加规范和清晰地组织 SQL 语句,并且可以避免 SQL 语句冲突和混乱。

3.5.MyBatis 是否可以映射 Enum 枚举类?

(1)MyBatis 可以映射 Enum 枚举类。在 MyBatis 的映射文件中,可以使用 Enum 的名称或者是 Enum 的全限定类名来指定 Enum 类型的参数。例如,使用 ${EnumName} 或者是 ${EnumClass.ENUM_NAME} 来引用 Enum 类型的参数。在映射文件中,可以使用 typeHandler 属性来指定 Enum 类型的处理器,MyBatis 提供了默认的 EnumTypeHandler 处理器,用于将 Enum 类型映射为数据库中的相应类型。

(2)例如,在映射文件中定义一个枚举类型的字段:

<resultMap id="personMap" type="Person"><result column="gender" property="gender" javaType="GenderEnum" />
</resultMap>

然后定义对应的 GenderEnum:

public enum GenderEnum {MALE,FEMALE
}

在查询时,可以通过参数指定相应的 GenderEnum:

<select id="selectPersonByGender" resultMap="personMap">select * from person where gender = #{gender, jdbcType=VARCHAR, typeHandler=org.apache.ibatis.type.EnumTypeHandler}
</select>

这样,就可以将查询结果映射为相应的 GenderEnum 类型。

3.6.MyBatis 中 String 类型的属性如何映射到数据库表中的 varchar 字段?

(1)在 MyBatis 中,可以通过 @Column 注解或 XML 配置来映射 String 类型的属性到数据库中的 varchar 字段:

  • 使用注解方式的示例:
public class User {@Column(name = "username")private String username;// getter and setter
}
  • 使用 XML 配置方式的示例:
<resultMap id="userResultMap" type="User"><id property="id" column="id" /><result property="username" column="username" jdbcType="VARCHAR" />
</resultMap>

(2)在上述示例中,我们将 User 类中的 username 属性映射到了数据库表中的 varchar 字段。使用注解方式时,可以使用 @Column 注解指定数据库字段名;使用 XML 配置方式时,可以在 <result> 元素内指定 column 属性,并指定 jdbcTypeVARCHAR。值得注意的是,MyBatis 默认情况下会根据 Java 对象的属性名和数据库表的列名进行自动映射,所以如果属性名与数据库列名保持一致,可以省略字段名的指定

(3)另外,如果数据库表中的字段类型与 Java 对象的属性类型不完全一致,MyBatis 会进行自动类型转换。在上述示例中,String 类型的属性会被映射到 varchar 字段,如果数据库字段类型为其他字符串类型(例如 text),MyBatis 也会进行自动转换。但如果数据库字段类型为数字类型(如 int)时,可能需要进行一些特殊的配置或类型转换。

3.7.在 MyBatis 中,如果实体类中某一属性是 String 类型,数据库中对应的字段是 Integer 会怎么样?

(1)如果在 MyBatis 的实体类中将一个 String 类型的属性映射到数据库中的 Integer 字段,会导致类型不匹配的错误。MyBatis 默认情况下会使用 Java 的类型和数据库的类型进行自动类型转换,但是 String 到 Integer 之间的转换无法自动进行

(2)此时需要进行类型转换的配置。有两种方式可以解决这个问题:

  • 使用 TypeHandler 进行类型转换:可以自定义一个实现了 TypeHandler 接口的类型处理器,通过重写 setParameter()getResult() 等方法,自定义 String 到 Integer 的转换逻辑,并在实体类的属性映射中使用 @TypeHandler 注解或在 XML 中配置 <typeHandler> 来指定使用该类型处理器,示例如下:
public class StringToIntegerTypeHandler extends BaseTypeHandler<String> {@Overridepublic void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {ps.setInt(i, Integer.parseInt(parameter));}@Overridepublic String getNullableResult(ResultSet rs, String columnName) throws SQLException {// 根据实际情况进行逻辑处理return String.valueOf(rs.getInt(columnName));}// 其他重写的 getNullableResult 方法
}
public class User {//在实体类的属性映射中使用注解@Column(name = "age")@TypeHandler(StringToIntegerTypeHandler.class)private String age;//...
}
  • 在 SQL 映射文件中使用特殊的转换函数:在 SQL 语句中使用类型转换的数据库函数(例如 CAST()CONVERT())将数据库中的 Integer 字段转换为 String 类型。示例:
<!-- 使用 CAST 函数进行转换 -->
<select id="getUserAge" resultType="String">SELECT CAST(age AS CHAR) as age FROM user WHERE id = #{id}
</select><!-- 使用 CONVERT 函数进行转换 -->
<select id="getUserAge" resultType="String">SELECT CONVERT(age, CHAR) as age FROM user WHERE id = #{id}
</select>

3.8.MyBatis 的 XML 映射文件和 MyBatis 内部数据结构之间的映射关系是什么样的?

(1)MyBatis 将所有 XML 配置信息都封装到 All-In-One 重量级对象 Configuration 内部。在 XML 映射文件中,有以下映射关系:

  • SQL 语句映射:XML 映射文件中的 <select><insert><update><delete> 标签中定义的 SQL 语句,会映射为 MyBatis 内部的 MappedStatement 对象,其中包含了 SQL 语句的信息,如 ID、参数映射、结果映射等。
  • 参数映射:XML 映射文件中的参数定义,如 <parameter> 标签中的属性,会映射为 MyBatis 内部的 ParameterMapping 对象。ParameterMapping 对象包含了参数的名称、类型、模式(输入、输出等)等信息。
  • 结果映射:XML 映射文件中的查询结果映射定义,如 <resultMap> 标签和 <result> 标签,会映射为 MyBatis 内部的 ResultMap 对象和 ResultMapping 对象。ResultMap 对象包含了结果映射的信息,如 ID、结果类型、属性映射等,而 ResultMapping 对象包含了具体的结果映射信息,如列名、属性名称、类型处理器等。
  • 缓存配置:XML 映射文件中的 <cache> 标签中的缓存配置信息,会映射为 MyBatis 内部的 Cache 对象。Cache 对象用于管理查询结果的缓存,提高查询性能。

(2)通过这种方式,MyBatis 可以将 XML 映射文件中的 SQL 语句、参数映射和结果映射等信息转化为 MyBatis 内部对应的数据结构,从而实现 SQL 的执行和结果的映射。这种映射关系使得开发人员可以使用 XML 配置文件来定义和管理 SQL 语句,同时利用 MyBatis 的内部机制来完成 SQL 的执行与结果处理。

4.MyBatis——其它功能

4.1.MyBatis 是如何进行分页的?分页插件的原理是什么?

(1)MyBatis 支持两种分页方式:物理分页逻辑分页

  • 物理分页是指通过 SQL 语句的 limit 和 offset 子句来实现分页;
  • 逻辑分页是指在查询时获取所有的数据,然后通过代码来进行分页。

(2)对于基于物理分页,MyBatis 提供了一种名为分页插件 (PageHelper) 的机制来实现分页。分页插件的原理是通过动态修改 SQL 语句,在原始 SQL 语句中添加 limit 和 offset 子句,从而实现分页。具体来说,分页插件会在执行查询语句前,将原始 SQL 语句中的 limit 和 offset 替换成实际的分页参数,例如:

<select id="findUserByUsername" resultType="User">select * from user where username = #{username} limit #{offset}, #{limit}
</select>

在上述 SQL 语句中,#{offset} 和 #{limit} 分别代表分页查询的起始位置和每页数据的数量。当执行该 SQL 语句时,分页插件会根据传入的参数来动态生成 limit 和 offset 子句,从而实现分页查询。

(3)在使用分页插件 PageHelper 时,需要将分页插件添加到 MyBatis 的配置文件中,并在查询方法中传入分页参数,例如:

<!--添加分页插件到 MyBatis 配置文件中-->
<plugins><plugin interceptor="com.github.pagehelper.PageInterceptor"><property name="dialect" value="mysql" /></plugin>
</plugins>
// 查询用户列表,并进行分页
public List<User> findUserByUsername(String username, int pageNum, int pageSize) {PageHelper.startPage(pageNum, pageSize);return userDao.findUserByUsername(username);
}

在上述代码中,通过 PageHelper.startPage() 方法设置分页参数,并调用 userDao.findUserByUsername() 方法执行分页查询。PageHelper 会在执行查询方法时,根据传入的分页参数自动添加 limit 和 offset 子句,从而实现分页查询。

需要注意的是:分页插件是一种实现分页的机制,它并不是 MyBatis 的核心功能。因此,在使用分页插件时,需要选择稳定的版本,并根据实际需求来配置分页参数,以保证分页查询的效率和准确性。

4.2.✨MyBatis 的插件运行原理是什么?如何自定义插件?

(1)MyBatis 的插件 (Interceptor) 是 MyBatis 中提供的一种扩展机制,可以在 SQL 执行前后、结果集处理前后等不同的阶段对 MyBatis 的核心功能进行增强修改。插件可以用于实现自定义的功能,如日志记录、性能监控、权限控制等。

(2)插件的运行原理是基于 Java 的动态代理机制。当 MyBatis 执行 SQL 语句时,MyBatis 会将 SQL 语句交给 Executor 对象进行处理。在执行 SQL 语句前,MyBatis 会依次将 Executor 对象和所有的 Interceptor 对象进行包装。Interceptor 对象会按照配置的顺序依次执行,可以在 SQL 执行前后、结果集处理前后等不同的阶段进行自定义处理。最后,Executor 对象执行 SQL 语句,并返回结果。

(3)在执行过程中,Interceptor 对象通过动态代理机制,将 Executor 对象进行包装,并在 Executor 对象的方法执行前后进行自定义处理。例如,在执行 SQL 语句前可以记录 SQL 执行日志,在执行 SQL 语句后可以进行性能统计,对返回结果进行加工等。Interceptor 对象对 Executor 对象的包装,类似于装饰器模式,可以实现对 Executor 对象的无侵入式扩展。

(4)自定义插件的一般步骤:

  • 实现 MyBatis 提供的 Interceptor 接口,并实现其中的方法;
  • 使用 @Intercepts 注解完成插件签名,即告诉 MyBatis 当前插件用来拦截哪个对象的哪个方法;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import java.util.Properties;//完成插件签名:告诉 MyBatis 当前插件用来拦截哪个对象的哪个方法
@Intercepts(
{@Signature(type=StatementHandler.class, method="parameterize",args=java.sql.Statement.class)
})
public class MyFirstPlugin implements Interceptor {//intercept:拦截目标对象的目标方法的执行@Overridepublic Object intercept(Invocation invocation) throws Throwable {//执行目标方法Object proceed = invocation.proceed();//返回执行后的返回值return null;}// plugin: 包装目标对象的,包装:为目标对象创建一个代理对象@Overridepublic Object plugin(Object target) {//我们可以借助 Plugin 的 wrap 方法来使用当前 Interceptor 包装我们目标对象System.out.println("MyFirstPlugin...plugin:mybatis将要包装的对象"+target);Object wrap = Plugin.wrap(target, this);//返回为当前target创建的动态代理return wrap;}//setProperties:将插件注册时的 property 属性设置进来@Overridepublic void setProperties(Properties properties) {System.out.println("插件配置的信息:"+properties);}
}
  • 将写好的插件注册到全局配置文件中;
<!--plugins:注册插件  -->
<plugins><plugin interceptor="com.atguigu.mybatis.dao.MyFirstPlugin"><property name="username" value="root"/><property name="password" value="123456"/></plugin>
</plugins>

4.3.✨介绍一下 MyBatis 的缓存机制。

(1)MyBatis 的缓存机制可以分为一级缓存和二级缓存两个层级:

  • 一级缓存:
    • 作用范围:一级缓存是在同一个 SQL Session 内部有效。
    • 默认开启:一级缓存在默认情况下是开启的,且无法关闭。
    • 执行流程:当执行一个查询语句时,查询结果会被缓存到该 SQL Session 的一级缓存中。之后如果再次执行相同的查询,MyBatis 会直接从一级缓存中获取缓存的结果,而不去查询数据库。
    • 更新操作:如果执行了插入、更新或删除操作,MyBatis 会自动清空一级缓存,避免脏数据的使用。
    • 检查机制:一级缓存的数据是在 SQL Session 之内共享的,因此需要进行缓存的有效性检查,以确保数据的一致性。
  • 二级缓存:
    • 作用范围:二级缓存是在不同的 SQL Session 之间有效,它的作用域是基于 Mapper 接口的。
    • 开启方式:要启用二级缓存,需要在 MyBatis 的配置文件中配置 <cache> 标签,并在 Mapper 接口上添加 @CacheNamespace 注解或者在映射文件中配置 <cache-ref> 标签。
    • 工作原理:当执行一个查询语句时,结果会被缓存到对应 Mapper 接口的二级缓存中。之后,如果其他 SQL Session 执行相同的查询,MyBatis 会先检查二级缓存,若存在缓存数据,则直接从缓存返回结果,而不再查询数据库。
    • 更新操作:执行插入、更新或删除操作时,会自动清空该 Mapper 接口对应的二级缓存,避免脏数据。
    • 配置方式:可以在映射文件中使用 <cache> 标签进行二级缓存的配置,可以指定缓存的类型(如 FIFO、LRU、LRU-Soft、Soft)和大小等参数来进行详细的配置。

需要注意的是,缓存是有一定的开销的,使用缓存的同时要考虑数据一致性的问题。在某些情况下,可能需要手动清空缓存或者关闭缓存功能,以保证数据的正确性。

(2)二级缓存是在不同的 SQL Session 之间共享的缓存,它的工作机制可以概括为以下几个步骤:

  • 查询时的缓存查找:当执行一个查询语句时,MyBatis 会先检查对应的 Mapper 接口的二级缓存,看是否存在缓存的结果。如果存在,则直接从缓存中返回结果,而不去查询数据库。
  • 缓存未命中:如果二级缓存中没有找到对应的缓存结果,MyBatis 会执行查询操作,将查询结果存入一级缓存(当前的 SQL Session 的缓存)和二级缓存(对应 Mapper 接口的缓存)中。这样,下次相同的查询请求就可以直接从缓存中获取结果。
  • 更新操作的缓存失效:当执行了插入、更新或删除操作时,MyBatis 会自动清空对应的 Mapper 接口的二级缓存,以避免使用脏数据。这是因为更新操作可能会导致缓存数据不再与数据库中的数据保持一致。
  • 缓存的生命周期:二级缓存的生命周期是与应用程序的 SQL Session 相关的。当 SQL Session 关闭时,一级缓存会被清空,而二级缓存则会继续存在。
  • 缓存的配置和管理:MyBatis 提供了灵活的配置选项来管理二级缓存,可以通过在映射文件中使用 <cache> 标签进行配置,指定缓存的类型(如 FIFO、LRU、LRU-Soft、Soft)和大小等参数。可以根据具体需求来调整缓存的策略和行为。

需要注意的是,二级缓存是基于 Mapper 接口的作用域的,不同的 Mapper 接口之间的缓存是隔离的。而且,在多个应用程序实例中使用二级缓存时,需要考虑缓存的共享和数据一致性的问题。

相关文章:

  • 如何搭建废品上门回收小程序
  • 字节开源的netPoll底层LinkBuffer设计与实现
  • golang之net/http模块学习
  • 【Vulnhub 靶场】【Hackable: III】【简单 - 中等】【20210602】
  • C盘瘦身,C盘清理
  • 关于照片时间轴修改的方法根据文件名修改拍摄日期、创建日期等信息根据时间戳文件名修改照片信息
  • linux 13-2day 日志轮转 日志目录 轮转参数
  • 计算机病毒判定专家系统原理与设计《文字提取人工修正》
  • Docker安装与使用
  • vue-loader是如何工作的?
  • 财务机器人(RPA)会影响会计人员从业吗?
  • 《opencv实用探索·十四》VideoCapture播放视频和视像头调用
  • 720度vr虚拟家居展厅提升客户的参观兴致
  • 轻量封装WebGPU渲染系统示例<43>- PBR材质与阴影实(源码)
  • WT588F02B-8S语音芯片助力破壁机:智能声音播放提示IC引领健康生活新潮流
  • #Java异常处理
  • (十五)java多线程之并发集合ArrayBlockingQueue
  • 【React系列】如何构建React应用程序
  • 002-读书笔记-JavaScript高级程序设计 在HTML中使用JavaScript
  • Angular6错误 Service: No provider for Renderer2
  • javascript数组去重/查找/插入/删除
  • JAVA之继承和多态
  • Python实现BT种子转化为磁力链接【实战】
  • 第三十一到第三十三天:我是精明的小卖家(一)
  • 对象管理器(defineProperty)学习笔记
  • 对象引论
  • 聊一聊前端的监控
  • 七牛云假注销小指南
  • 前端技术周刊 2019-01-14:客户端存储
  • 浅析微信支付:申请退款、退款回调接口、查询退款
  • 如何学习JavaEE,项目又该如何做?
  • 使用 @font-face
  • 腾讯优测优分享 | Android碎片化问题小结——关于闪光灯的那些事儿
  • 想使用 MongoDB ,你应该了解这8个方面!
  • ​低代码平台的核心价值与优势
  • #【QT 5 调试软件后,发布相关:软件生成exe文件 + 文件打包】
  • #Js篇:单线程模式同步任务异步任务任务队列事件循环setTimeout() setInterval()
  • (arch)linux 转换文件编码格式
  • (C++17) optional的使用
  • (LeetCode C++)盛最多水的容器
  • (MIT博士)林达华老师-概率模型与计算机视觉”
  • (附源码)基于SSM多源异构数据关联技术构建智能校园-计算机毕设 64366
  • (强烈推荐)移动端音视频从零到上手(上)
  • (三维重建学习)已有位姿放入colmap和3D Gaussian Splatting训练
  • (一)基于IDEA的JAVA基础12
  • (转)甲方乙方——赵民谈找工作
  • **PyTorch月学习计划 - 第一周;第6-7天: 自动梯度(Autograd)**
  • .Net FrameWork总结
  • .net 中viewstate的原理和使用
  • .net6解除文件上传限制。Multipart body length limit 16384 exceeded
  • .netcore 获取appsettings
  • .w文件怎么转成html文件,使用pandoc进行Word与Markdown文件转化
  • @EnableWebMvc介绍和使用详细demo
  • @hook扩展分析
  • @modelattribute注解用postman测试怎么传参_接口测试之问题挖掘