参考:
https://www.jianshu.com/p/06b73e8d9f56
https://juejin.cn/post/6844903585298251789
https://juejin.cn/post/6844903582639063054
一、JDBC的执行过程
(1).注册驱动
Class.forName("oracle.jdbc.OracleDriver");
(2).建立数据库连接
String url = "jdbc:oracle:thin:@localhost:1521:xe";
Connection conn = DriverManager.getConnection(url,name,password);
(3).创建操作数据库的对象Statement
statement Statement state = conn.createStatement();
(4).执行SQL语句
String sql = "select id,name from s_emp";
ResultSet rs = state.executeQuery(sql);
(5).处理结果集
while( rs.next() ){
int id = rs.getInt("id");
String name = rs.getString(2);
System.out.println(id+" "+name);
}
(6).关闭连接
rs.close();
state.close();
conn.close();
二、mybatis
1、MyBatis 编程步骤
- 创建 SqlSessionFactory 对象。
- 通过 SqlSessionFactory 获取 SqlSession 对象。
- 通过 SqlSession 获得 Mapper 代理对象。
- 通过 Mapper 代理对象,执行数据库操作。
- 执行成功,则使用 SqlSession 提交事务。
- 执行失败,则使用 SqlSession 回滚事务。
- 最终,关闭会话。
private static SqlSessionFactoryBuilder sqlSessionFactoryBuilder;
private static SqlSessionFactory sqlSessionFactory;
private static void init() throws IOException {
String resource = "mybatis-config.xml";
Reader reader = Resources.getResourceAsReader(resource);
sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
sqlSessionFactory = sqlSessionFactoryBuilder.build(reader);
}
SqlSession session= sqlSessionFactory.openSession();
UserDao userDao = session.getMapper(UserDao.class);
UserDto user = new UserDto();
user.setUsername("iMybatis");
List<UserDto> users = userDao.queryUsers(user);
public interface UserDao {
public List<UserDto> queryUsers(UserDto user) throws Exception;
}
<?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="org.iMybatis.abc.dao.UserDao">
<select id="queryUsers" parameterType="UserDto" resultType="UserDto"
useCache="false">
<![CDATA[
select * from t_user t where t.username = #{username}
]]>
</select>
</mapper>详情:https://www.jianshu.com/p/15781ec742f2
2、重要组件
核心组件主要包括以下几个:
- SqlSessionFactoryBuilder:会根据配置信息或代码来生成SqlSessionFactory;
- SqlSessionFactory:依靠工厂来生成SqlSession;
- SqlSession:是一个既可以发送SQL去执行并返回结果,也可以获取Mapper的接口;
- SQL Mapper:是MyBatis新设计的组件,由一个Java接口和XML文件构成,需要给出对应的SQL和映射规则。它负责发送SQL去执行,并返回结果。
映射器是由Java接口和XML文件(或注解)共同组成的,作用如下:
- 定义参数类型
- 描述缓存
- 描述SQL语句
- 定义查询结果和POJO的映射关系
3、映射器
映射器是MyBatis最复杂、最核心的配置,包括参数类型、动态SQL、定义SQL、延迟加载、缓存信息等功能。通过映射器,可以很容易的进行数据的增删改查操作。
3.1 延迟加载
级联的优势是能够方便地获取数据,但有时不需要获取所有数据,这样会多执行几条SQL,性能下降,为了解决这个问题,需要使用延迟加载,只要使用相关级联数据时,才会发送SQL去取回数据。
在MyBatis的配置中有2个全局的参数 lazyLoadingEnabled 和 aggressiveLazyLoading ,
第一个的含义是是否开启延迟加载功能,
第二个的含义是对任意延迟加载属性的调用,会使延迟加载的对象完整加载,否则只会按需加载。
3.2 Mybatis 是否支持延迟加载?如果支持,它的实现原理是什么?
Mybatis 仅支持 association 关联对象和 collection 关联集合对象的延迟加载。其中,association 指的就是一对一,collection 指的就是一对多查询。
在 Mybatis 配置文件中,可以配置 <setting name="lazyLoadingEnabled" value="true" /> 来启用延迟加载的功能。默认情况下,延迟加载的功能是关闭的。
它的原理是,使用 CGLIB 或 Javassist( 默认 ) 创建目标对象的代理对象。当调用代理对象的延迟加载属性的 getting 方法时,进入拦截器方法。比如调用 a.getB().getName() 方法,进入拦截器的 invoke(...) 方法,发现a.getB() 需要延迟加载时,那么就会单独发送事先保存好的查询关联 B 对象的 SQL ,把 B 查询上来,然后调用a.setB(b) 方法,于是 a 对象 b 属性就有值了,接着完成a.getB().getName() 方法的调用。这就是延迟加载的基本原理。
参考:http://svip.iocoder.cn/MyBatis/Interview/
3.3 缓存
1、一级缓存
在应用运行过程中,我们有可能在一次数据库会话中,执行多次查询条件完全相同的SQL,MyBatis提供了一级缓存的方案优化这部分场景,如果是相同的SQL语句,会优先命中一级缓存,避免直接对数据库进行查询,提高性能。具体执行过程如下图所示。
每个SqlSession中持有了Executor,每个Executor中有一个LocalCache。当用户发起查询时,MyBatis根据当前执行的语句生成MappedStatement,在Local Cache进行查询,如果缓存命中的话,直接返回结果给用户,如果缓存没有命中的话,查询数据库,结果写入Local Cache,最后返回结果给用户。
2、二级缓存
在一级缓存中,其最大的共享范围就是一个SqlSession内部,如果多个SqlSession之间需要共享缓存,则需要使用到二级缓存。开启二级缓存后,会使用CachingExecutor装饰Executor,进入一级缓存的查询流程前,先在CachingExecutor进行二级缓存的查询,具体的工作流程如下所示。
二级缓存开启后,同一个namespace下的所有操作语句,都影响着同一个Cache,即二级缓存被多个SqlSession共享,是一个全局的变量。
当开启缓存后,数据的查询执行的流程就是 二级缓存 -> 一级缓存 -> 数据库。
总结:
一级缓存作用域:sqlsession 范围的
二级缓存作用域:是多个sqlsesson共享,其作用域是同一个namespace,so是跨sqlsession的
参考:https://tech.meituan.com/2018/01/19/mybatis-cache.html
4、其他
下面的博客总结的很详细,推荐: http://svip.iocoder.cn/MyBatis/Interview/
4.1 #{} 和 ${} 的区别是什么?
- ${} 实际场景下,不推荐用。因为,可能有 SQL 注入的风险。
${}是 Properties 文件中的变量占位符,它可以用于 XML 标签属性值和 SQL 内部,属于字符串替换。例如将${driver}会被静态替换为com.mysql.jdbc.Driver:${}也可以对传递进来的参数原样拼接在 SQL 中
- #{} 是预编译处理,可以有效防止 SQL 注入,提高系统安全性。
#{} 是 SQL 的参数占位符,Mybatis 会将 SQL 中的 #{} 替换为 ? 号,在 SQL 执行前会使用 PreparedStatement 的参数设置方法,按序给 SQL 的 ? 号占位符设置参数值 。 4.2 最佳实践中,通常一个 XML 映射文件,都会写一个 Mapper 接口与之对应。请问,这个 Mapper 接口的工作原理是什么?Mapper 接口里的方法,参数不同时,方法能重载吗?
Mapper 接口,对应的关系如下:
- 接口的全限名,就是映射文件中的
"namespace"的值。 - 接口的方法名,就是映射文件中 MappedStatement 的
"id"值。 - 接口方法内的参数,就是传递给 SQL 的参数。
Mapper 接口是没有实现类的,当调用接口方法时,接口全限名 + 方法名拼接字符串作为 key 值,可唯一定位一个对应的 MappedStatement 。举例:com.mybatis3.mappers.StudentDao.findStudentById ,可以唯一找到"namespace" 为 com.mybatis3.mappers.StudentDao 下面 "id" 为 findStudentById 的 MappedStatement 。
总结来说,在 Mybatis 中,每一个 <select />、<insert />、<update />、<delete /> 标签,都会被解析为一个 MappedStatement 对象。
另外,Mapper 接口的实现类,通过 MyBatis 使用 JDK Proxy 自动生成其代理对象 Proxy ,而代理对象 Proxy 会拦截接口方法,从而“调用”对应的 MappedStatement 方法,最终执行 SQL ,返回执行结果。整体流程如下图:
其中,SqlSession 在调用 Executor 之前,会获得对应的 MappedStatement 方法。
4.3 Mapper 接口绑定有几种实现方式
- 第一种,通过 XML Mapper 里面写 SQL 来绑定。在这种情况下,要指定 XML 映射文件里面的
"namespace"必须为接口的全路径名。 - 第二种,通过注解绑定,就是在接口的方法上面加上
@Select、@Update、@Insert、@Delete注解,里面包含 SQL 语句来绑定。
4.4 在 Mapper 中如何传递多个参数
第一种,使用 Map 集合,装载多个参数进行传递
// Mapper 接口
List<Student> selectStudents(Map<String, Object> map);
// Mapper XML 代码
<select id="selectStudents" parameterType="Map" resultType="Student">
SELECT *
FROM students
LIMIT #{start}, #{end}
</select>第二种,保持传递多个参数,使用 @Param 注解
|
第三种,保持传递多个参数,不使用 @Param 注解
按照参数在方法中的位置,从 1 开始,逐个为 #{param1}、#{param2}、#{param3} 不断向下。
|
4.5 MyBatis 如何执行批量插入? 在面试中被问到过
方法一:单条sql插入,然后循环 ;我只回答了这个方法
@Test
public void testBatch() {
// 创建要插入的用户的名字的数组
List<String> names = new ArrayList<>();
names.add("小红");
names.add("小兰");
// 获得执行器类型为 Batch 的 SqlSession 对象,并且 autoCommit = false ,禁止事务自动提交
try (SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH, false)) {
// 获得 Mapper 对象
UserMapper mapper = session.getMapper(UserMapper.class);
// 循环插入
for (String name : names) {
mapper.insertUser(name);
}
// 提交批量操作
session.commit();
}
}方法二:对于这种方式,需要保证单条 SQL 不超过语句的最大限制 max_allowed_packet 大小,默认为 1 M 。
|
4.6 Mybatis 是如何进行分页的?分页插件的原理是什么?
- Mybatis 使用 RowBounds 对象进行分页,它是针对 ResultSet 结果集执行的内存分页,而非数据库分页。 === 不推荐使用
- 在 SQL 内直接书写带有数据库分页的参数来完成数据库分页功能 === 推荐
- 也可以使用分页插件来完成数据库分页。 === 推荐
后两个2、3都是基于数据库分页,差别在于前者是工程师手动编写分页条件,后者是插件自动添加分页条件。
分页插件的基本原理是使用 Mybatis 提供的插件接口,实现自定义分页插件。在插件的拦截方法内,拦截待执行的 SQL ,然后重写 SQL ,根据相应语法,添加对应的物理分页语句和物理分页参数。
举例:SELECT * FROM student ,拦截 SQL 后重写为:select * FROM student LIMI 0,10 。
分页插件有: MyBatis-Plus Mybatis-PageHelper
4.7 mybatis中<![CDATA[]]>的作用 面试被问到
mybatis 中 SQL 写在mapper.xml文件中,而xml解析 < 、>、<=、>= 时会出错,这时应该使用转义写法。
第一种写法(1):
原符号 < <= > >= & ' "
替换符号 < <= > >= & ' "
例如:sql如下: create_time >= #{startTime} and create_time <= #{endTime}
第二种写法(2):
大于等于 <![CDATA[ >= ]]>
小于等于 <![CDATA[ <= ]]>
例如:sql如下:create_time <![CDATA[ >= ]]> #{startTime} and create_time <![CDATA[ <= ]]> #{endTime} 在使用mybatis 时,sql是写在xml 映射文件中,如果写的sql中有一些特殊字符(如 < > & 等),在解析xml文件的时候会被转义,但我们不希望他被转义,所以要使用<![CDATA[ ]]>来解决。
<![CDATA[ ]]> 是什么,这是XML语法。在CDATA内部的所有内容都会被解析器忽略。