1. #{}和${}的区别是什么?
2. 通常一个 Xml 映射文件,都会写一个 Dao 接口与之对应, 请问,这个 Dao 接口的工作原理是什么?Dao 接口里的方法, 参数不同时,方法能重载吗?
3. Mybatis 是如何进行分页的?分页插件的原理是什么?
4. Mybatis 是如何将 sql 执行结果封装为目标对象并返回的?
都有哪些映射形式?
5. Xml 映射文件中,除了常见的 select|insert|update|delete 标 签之外,还有哪些标签?
6. 简述 Mybatis 的插件运行原理,以及如何编写一个插件
7. 一级、二级缓存
8. Mybatis 是否支持延迟加载?如果支持,它的实现原理是什么?
概念: 延迟加载的原理是在查询时只加载部分数据,当需要访问未加载的数据时再进行加载。这样可以减少查询所需的时间和资源,提高系统性能。
原理: MyBatis实现延迟加载的方式是使用代理对象,在访问未加载的数据时触发代理对象的方法,从而进行数据的加载。延迟加载可以通过配置文件或注解来实现。
9. Mybatis 映射文件中,如果 A 标签通过 include 引用了 B 签的内容,请问,B 标签能否定义在 A 标签的后面,还是说 必须定义在 A 标签的前面?
10. 简述 Mybatis 的 Xml 映射文件和 Mybatis 内部数据结构之间的映射关系?
3. MyBatis 使用过程?生命周期?
MyBatis 基本使用的过程大概可以分为这么几步:
- 1)创建 SqlSessionFactory
可以从配置或者直接编码来创建 SqlSessionFactory
- 2)通过 SqlSessionFactory 创建 SqlSession
SqlSession(会话)可以理解为程序和数据库之间的桥梁
SqlSession session = sqlSessionFactory.openSession();
- 3)通过 sqlsession 执行数据库操作
可以通过 SqlSession 实例来直接执行已映射的 SQL 语句:
Blog blog = (Blog)session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
更常用的方式是先获取 Mapper(映射),然后再执行 SQL 语句:
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);
- 4)调用 session.commit()提交事务
如果是更新、删除语句,我们还需要提交一下事务。
- 5)调用 session.close()关闭会话
最后一定要记得关闭会话
4. 在 mapper 中如何传递多个参数?
方法 1:顺序传参法
public User selectUser(String name, int deptId);
<select id="selectUser" resultMap="UserResultMap">
select * from user
where user_name = #{0} and dept_id = #{1}
</select>
\#{}
里面的数字代表传入参数的顺序。- 这种方法不建议使用,sql 层表达不直观,且一旦顺序调整容易出错。
方法 2:@Param 注解传参法
public User selectUser(@Param("userName") String name, int @Param("deptId") deptId);
<select id="selectUser" resultMap="UserResultMap">
select * from user
where user_name = #{userName} and dept_id = #{deptId}
</select>
\#{}
里面的名称对应的是注解@Param 括号里面修饰的名称。- 这种方法在参数不多的情况还是比较直观的,(推荐使用)。
方法 3:Map 传参法
public User selectUser(Map<String, Object> params);
<select id="selectUser" parameterType="java.util.Map" resultMap="UserResultMap">
select * from user
where user_name = #{userName} and dept_id = #{deptId}
</select>
\#{}
里面的名称对应的是 Map 里面的 key 名称。- 这种方法适合传递多个参数,且参数易变能灵活传递的情况。
方法 4:Java Bean 传参法
public User selectUser(User user);
<select id="selectUser" parameterType="com.jourwon.pojo.User" resultMap="UserResultMap">
select * from user
where user_name = #{userName} and dept_id = #{deptId}
</select>
\#{}
里面的名称对应的是 User 类里面的成员属性。- 这种方法直观,需要建一个实体类,扩展不容易,需要加属性,但代码可读性强,业务逻辑处理方便,推荐使用。(推荐使用)。
6. Mybatis 是否可以映射 Enum 枚举类?
- Mybatis 当然可以映射枚举类,不单可以映射枚举类,Mybatis 可以映射任何对象到表的一列上。映射方式为自定义一个 TypeHandler,实现 TypeHandler 的 setParameter()和 getResult()接口方法。
- TypeHandler 有两个作用,一是完成从 javaType 至 jdbcType 的转换,二是完成 jdbcType 至 javaType 的转换,体现为 setParameter()和 getResult()两个方法,分别代表设置 sql 问号占位符参数和获取列查询结果
8. 模糊查询 like 语句该怎么写?
- 1 ’
%${question}%
’ 可能引起 SQL 注入,不推荐 - 2
"%"#{question}"%"
注意:因为#{…}
解析成 sql 语句时候,会在变量外侧自动加单引号’ ',所以这里 % 需要使用双引号" ",不能使用单引号 ’ ',不然会查不到任何结果。 - 3
CONCAT('%',#{question},'%')
使用 CONCAT()函数,(推荐 ✨) - 4 使用 bind 标签(不推荐)
11. 如何获取生成的主键?
- 新增标签中添加:keyProperty=" ID " 即可
<insert id="insert" useGeneratedKeys="true" keyProperty="userId" >
insert into user(
user_name, user_password, create_time)
values(#{userName}, #{userPassword} , #{createTime, jdbcType= TIMESTAMP})
</insert>
12. MyBatis 支持动态 SQL 吗?
MyBatis 中有一些支持动态 SQL 的标签,它们的原理是使用 OGNL 从 SQL 参数对象中计算表达式的值,根据表达式的值动态拼接 SQL,以此来完成动态 SQL 的功能。
- if
根据条件来组成 where 子句
<select id="findActiveBlogWithTitleLike"
resultType="Blog">
SELECT * FROM BLOG
WHERE state = ‘ACTIVE’
<if test="title != null">
AND title like #{title}
</if>
</select>
- choose (when, otherwise)
这个和 Java 中的 switch 语句有点像
-
<where>可以用在所有的查询条件都是动态的情况
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG
<where>
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</where>
</select>
- <set> 可以用在动态更新的时候
<update id="updateAuthorIfNecessary">
update Author
<set>
<if test="username != null">username=#{username},</if>
<if test="password != null">password=#{password},</if>
<if test="email != null">email=#{email},</if>
<if test="bio != null">bio=#{bio}</if>
</set>
where id=#{id}
</update>
-
foreach
看到名字就知道了,这个是用来循环的,可以对集合进行遍历
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
<where>
<foreach item="item" index="index" collection="list"
open="ID in (" separator="," close=")" nullable="true">
#{item}
</foreach>
</where>
</select>
13. MyBatis 如何执行批量操作?
第一种方法:使用 foreach 标签
foreach 的主要用在构建 in 条件中,它可以在 SQL 语句中进行迭代一个集合。foreach 标签的属性主要有 item,index,collection,open,separator,close。
- item 表示集合中每一个元素进行迭代时的别名,随便起的变量名;
- index 指定一个名字,用于表示在迭代过程中,每次迭代到的位置,不常用;
- open 表示该语句以什么开始,常用“(”;
- separator 表示在每次进行迭代之间以什么符号作为分隔符,常用“,”;
- close 表示以什么结束,常用“)”。
在使用 foreach 的时候最关键的也是最容易出错的就是 collection 属性,该属性是必须指定的,但是在不同情况下,该属性的值是不一样的,主要有以下 3 种情况:
- 如果传入的是单参数且参数类型是一个 List 的时候,collection 属性值为 list
- 如果传入的是单参数且参数类型是一个 array 数组的时候,collection 的属性值为 array
- 如果传入的参数是多个的时候,我们就需要把它们封装成一个 Map 了
15. 能说说 MyBatis 的工作原理吗?
我们最后把整个的工作流程串联起来,简单总结一下:
- 读取 MyBatis 配置文件——mybatis-config.xml 、加载映射文件——映射文件即 SQL 映射文件,文件中配置了操作数据库的 SQL 语句。最后生成一个配置对象。
- 构造会话工厂:通过 MyBatis 的环境等配置信息构建会话工厂 SqlSessionFactory。
- 创建会话对象:由会话工厂创建 SqlSession 对象,该对象中包含了执行 SQL 语句的所有方法。
- Executor 执行器:MyBatis 底层定义了一个 Executor 接口来操作数据库,它将根据 SqlSession 传递的参数动态地生成需要执行的 SQL 语句,同时负责查询缓存的维护。
- StatementHandler:数据库会话器,串联起参数映射的处理和运行结果映射的处理。
- 参数处理:对输入参数的类型进行处理,并预编译。
- 结果处理:对返回结果的类型进行处理,根据对象映射规则,返回相应的对象。
16. MyBatis 的功能架构是什么样的?
我们一般把 Mybatis 的功能架构分为三层:
- API 接口层:提供给外部使用的接口 API,开发人员通过这些本地 API 来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。
- 数据处理层:负责具体的 SQL 查找、SQL 解析、SQL 执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。
- 基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。
7. 为什么 Mapper 接口不需要实现类?
我们来看一下获取 Mapper 的过程:
定义的 Mapper 接口是没有实现类的,Mapper 映射其实是通过动态代理实现的
获取 Mapper 的过程,需要先获取 MapperProxyFactory——Mapper 代理工厂。
MapperProxyFactory 代理工厂的作用是生成 MapperProxy(Mapper 代理对象)。
MapperProxy 里,通常会生成一个 MapperMethod 对象
MapperMethod 里的 excute 方法,会真正去执行 sql。这里用到了命令模式
18.Mybatis 都有哪些 Executor 执行器?
Mybatis 有三种基本的 Executor 执行器,SimpleExecutor、ReuseExecutor、BatchExecutor。
- SimpleExecutor:每执行一次 update 或 select,就开启一个 Statement 对象,用完立刻关闭 Statement 对象。
- ReuseExecutor:执行 update 或 select,以 sql 作为 key 查找 Statement 对象,存在就使用,不存在就创建,用完后,不关闭 Statement 对象,而是放置于 Map<String, Statement>内,供下一次使用。简言之,就是重复使用 Statement 对象。
- BatchExecutor:执行 update(没有 select,JDBC 批处理不支持 select),将所有 sql 都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个 Statement 对象,每个 Statement 对象都是 addBatch()完毕后,等待逐一执行 executeBatch()批处理。与 JDBC 批处理相同
如何编写一个插件?
- 实现 Mybatis 的 Interceptor 接口并重写 intercept()方法
- 然后再给插件编写注解,确定要拦截的对象,要拦截的方法
- 最后,再 MyBatis 配置文件里面配置插件
<plugins>
<plugin interceptor="xxx.MyPlugin">
<property name="dbType",value="mysql"/>
</plugin>
</plugins>
19. 说说 Mybatis 的插件运行原理,如何编写一个插件?
Mybatis 会话的运行需要 ParameterHandler、ResultSetHandler、StatementHandler、Executor 这四大对象的配合,插件的原理就是在这四大对象调度的时候,插入一些我我们自己的代码。
具体使用 JDK 的动态代理,为目标对象生成代理对象。它提供了一个工具类Plugin
,实现了InvocationHandler
接口。代理对象在调用方法的时候,就会进入 invoke 方法,在 invoke 方法中,如果存在签名的拦截方法,拦截方法就会在这里被我们调用,然后就返回结果。如果不存在签名的拦截方法,那么将直接反射调用我们要执行的方法。
21.说说 JDBC 的执行步骤?
通过Class.forName()
方法加载对应的数据库驱动
使用DriverManager.getConnection()
方法建立到数据库的连接。这一步需要提供数据库 URL、用户名和密码作为参数。
第三步,创建Statement
对象
通过建立的数据库连接对象Connection
创建Statement
、PreparedStatement
或CallableStatement
对象,用于执行 SQL 语句。
使用Statement
或PreparedStatement
对象执行 SQL 语句
执行查询(SELECT)语句时,使用executeQuery()
方法,它返回ResultSet
对象;
执行更新(INSERT、UPDATE、DELETE)语句时,使用executeUpdate()
方法,它返回一个整数表示受影响的行数。
第五步,处理结果集
如果执行的是查询操作,需要处理ResultSet
对象来获取数据。
第六步,关闭资源
最后,需要依次关闭ResultSet
、Statement
和Connection
等资源,释放数据库连接等资源。
22.创建连接拿到的是什么对象?
在 JDBC 的执行步骤中,创建连接后拿到的对象是java.sql.Connection
对象。
Connection
对象代表了应用程序和数据库的一个连接会话。
通过调用DriverManager.getConnection()
方法并传入数据库的 URL、用户名和密码等信息来获得这个对象。
一旦获得Connection
对象,就可以使用它来创建执行 SQL 语句的Statement
、PreparedStatement
和CallableStatement
对象,以及管理事务等。
23.Statement 与 PreparedStatement 的区别
Statement
和PreparedStatement
都是用于执行 SQL 语句的接口,
①、每次执行Statement
对象的executeQuery
或executeUpdate
方法时,SQL 语句在数据库端都需要重新编译和执行。这适用于一次性执行的 SQL 语句。
②、PreparedStatement 代表预编译的 SQL 语句的对象。这意味着 SQL 语句在PreparedStatement
对象创建时就被发送到数据库进行预编译。
之后,可以通过设置参数值来多次高效地执行这个 SQL 语句。这不仅减少了数据库编译 SQL 语句的开销,也提高了性能,尤其是对于重复执行的 SQL 操作。
Statement 不支持参数化查询。如果需要在 SQL 语句中插入变量,通常需要通过字符串拼接的方式来实现,这会增加 SQL 注入攻击的风险。
PreparedStatement 支持参数化查询,即可以在 SQL 语句中使用问号(?
)作为参数占位符。通过setXxx
方法(如setString
、setInt
)设置参数,可以有效防止 SQL 注入。
24. 什么是 SQL 注入?如何防止 SQL 注入?
SQL 注入是一种代码注入技术,通过在输入字段中插入专用的 SQL 语句,从而欺骗数据库执行恶意 SQL,从而获取敏感数据、修改数据,或者删除数据等。
实际的 SQL 语句类似于:
SELECT * FROM students WHERE studentId = 117
这是我们期望用户输入的正确方式。但是,如果用户输入了117 OR 1=1
,那么 SQL 语句就变成了:
SELECT * FROM students WHERE studentId = 117 OR 1=1
由于1=1
为真,所以这个查询将返回所有学生的信息,而不仅仅是 ID 为 117 的学生。
①、使用参数化查询
使用参数化查询,即使用PreparedStatement
对象,通过setXxx
方法设置参数值,而不是通过字符串拼接 SQL 语句。这样可以有效防止 SQL 注入。
②、限制用户输入
对用户输入进行验证和过滤,只允许输入预期的数据,不允许输入特殊字符或 SQL 关键字