Mybatis技术手册
- Mybatis技术手册
- 基础
- 1、Mybatis的一级、二级缓存
- 2、Mybatis 的一级缓存原理(sqlsession 级别)
- 3、二级缓存原理(Mapper 级别)
- 4、resultType和resultMap的区别?
- 5、为什么说Mybatis是半自动 ORM 映射工具?它与全自动的区别在哪里?
- 6、MyBatis 动态sql有什么用?执行原理?有哪些动态sql?
- 7、MyBatis如何获取自动生成的(主)键值?
- 8、传统JDBC开发存在的问题
- 9、
#{}和${}
的区别 - 10、MyBatis实现一对一,一对多有几种方式,怎么操作的?
- 11、Mybatis是否可以映射Enum枚举类?
- 12、在mapper中如何传递多个参数
- 13、Mybatis中如何指定使用哪一种Executor执行器?
- 14、Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?
- 15、为什么需要预编译
- 16、Mybatis都有哪些Executor执行器?它们之间的区别是什么?
- 17、Mybatis中如何指定使用哪一种Executor执行器?
- 18、MyBatis的框架架构设计是怎么样的
- 19、模糊查询like语句该怎么写
- 20、Mybatis 是如何进行分页的?分页插件的原理是什么?
- 基础
Mybatis技术手册
基础
1、Mybatis的一级、二级缓存
- 一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。
- 二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口(可用来保存对象的状态),可在它的映射文件中配置;
- 对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear
2、Mybatis 的一级缓存原理(sqlsession 级别)
Mybatis的一级缓存是指在SqlSession级别的缓存,即同一个SqlSession中执行的多次相同SQL语句,只会查询一次数据库并缓存结果,后续的查询操作会直接从缓存中获取数据,而不会再次查询数据库。一级缓存默认是开启的,可以通过SqlSession的clearCache()方法来清空缓存。
一级缓存的实现原理如下:
当SqlSession执行查询操作时,Mybatis会通过MappedStatement获取到SQL语句、参数映射关系以及结果映射关系等信息,然后通过Executor去执行SQL语句,并将查询结果缓存到一级缓存中。一级缓存是一个Map对象,其中键值对的键是由SQL语句、参数和环境等因素组成的一个唯一标识符,值是查询结果对象。
当执行相同的SQL语句时,Mybatis会先从一级缓存中查找是否存在缓存结果,如果存在,则直接返回缓存结果。如果不存在,则通过Executor去查询数据库,并将查询结果缓存到一级缓存中。
需要注意的是,一级缓存是基于SqlSession的,不同的SqlSession之间的一级缓存是互不干扰的。同时,一级缓存的生命周期也是和SqlSession相同的,即当SqlSession关闭时,一级缓存也会被清空。
总体来说,Mybatis的一级缓存是通过Map对象实现的,通过在SqlSession级别缓存查询结果来提高系统的性能和响应速度。但是需要注意的是,一级缓存的使用是有局限性的,例如当执行更新或删除操作时,Mybatis会清空一级缓存,因此在这种情况下,一级缓存并不能提高系统的性能。
3、二级缓存原理(Mapper 级别)
Mybatis的二级缓存是指在Mapper级别的缓存,即多个SqlSession共享同一个Mapper的缓存。二级缓存默认是关闭的,可以在Mapper.xml文件中通过标签进行配置和开启。
二级缓存的实现原理如下:
当SqlSession执行查询操作时,如果开启了二级缓存,则Mybatis会先从二级缓存中查找是否存在缓存结果,如果存在,则直接返回缓存结果。如果不存在,则通过Executor去查询数据库,并将查询结果缓存到二级缓存中。
二级缓存是基于Mapper的,Mybatis会为每个Mapper创建一个对应的Cache对象,用于缓存该Mapper的查询结果。Cache对象是一个接口,提供了缓存的基本操作方法,例如putObject、getObject、removeObject等。
在Mybatis中,Cache对象的实现方式是可插拔的,用户可以自定义Cache的实现方式,例如使用Ehcache、Redis等第三方缓存框架来实现二级缓存。
需要注意的是,二级缓存的使用需要谨慎,因为多个SqlSession共享同一个Mapper的缓存,可能会导致数据的脏读、数据一致性等问题。因此,建议只在查询频率较高、数据更新频率较低的情况下使用二级缓存,并且需要注意在更新或删除操作后及时清空缓存,以保证数据的一致性。
总体来说,Mybatis的二级缓存是通过Cache对象实现的,通过在Mapper级别缓存查询结果来提高系统的性能和响应速度。但是需要注意的是,二级缓存的使用需要谨慎,并且需要根据具体的业务需求进行配置和使用。
4、resultType和resultMap的区别?
resultType和resultMap都是Mybatis中用于映射查询结果的配置属性,但是它们的使用方式和作用有所不同。
- resultType:resultType是指查询结果的类型,通常是一个JavaBean或者基本数据类型。当resultType被配置时,Mybatis会自动将查询结果映射成对应的JavaBean或者基本数据类型,并返回结果集。
例如:
<select id="selectUserById" resultType="com.example.User">
select * from user where id = #{id}
</select>
上面的配置中,resultType被设置为com.example.User,表示查询结果将会被映射成User类型的对象。
- resultMap:resultMap是指查询结果的映射规则,用于将查询结果映射成JavaBean或者其他复杂类型。通常需要手动配置resultMap,定义查询结果与JavaBean之间的映射关系。
例如:
<resultMap id="userMap" type="com.example.User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="password" column="password"/>
<result property="age" column="age"/>
</resultMap>
<select id="selectUserById" resultMap="userMap">
select * from user where id = #{id}
</select>
上面的配置中,定义了一个名为userMap的映射规则,用于将查询结果映射成User类型的对象。在select语句中使用resultMap属性引用了userMap,表示查询结果需要按照userMap的规则进行映射。
总体来说,resultType和resultMap都用于映射查询结果,但是resultType适用于简单的查询场景,而resultMap适用于复杂的映射场景。需要根据具体的业务需求选择合适的配置属性。
5、为什么说Mybatis是半自动 ORM 映射工具?它与全自动的区别在哪里?
Mybatis在查询关联对象或关联集合对象时,需要手动编写sql来完成,所以称之为半自动ORM映射工具。
6、MyBatis 动态sql有什么用?执行原理?有哪些动态sql?
MyBatis动态sql可以在Xml映射文件内,以标签的形式编写动态sql。执行原理是根据表达式的值完成逻辑判断并动态拼接 sql 的功能。MyBatis提供了9种动态sql标签:trim、where、set、foreach、if、choose、when、otherwise、bind。
7、MyBatis如何获取自动生成的(主)键值?
在insert标签中使用useGeneratedKeys和keyProperty两个属性来获取自动生成的主键值。
<insert id="insertUser" useGeneratedKeys="true" keyProperty="userId" >
insert into user(
user_name, user_password, create_time)
values(#{userName}, #{userPassword} , #{createTime, jdbcType= TIMESTAMP})
</insert>
8、传统JDBC开发存在的问题
- 频繁创建数据库连接对象、释放,容易造成系统资源浪费,影响系统性能。可以使用连接池解决这个问题。但是使用jdbc需要自己实现连接池。
- sql语句定义、参数设置、结果集处理存在硬编码。实际项目中sql语句变化的可能性较大,一旦发生变化,需要修改java代码,系统需要重新编译,重新发布,不好维护。
- 使用preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不一定,可能多也可能少,修改sql还要修改代码,系统不易维护。
结果集处理存在重复代码,处理麻烦。如果可以映射成Java对象会比较方便。
9、#{}和${}
的区别
- #{}是占位符,预编译处理;${}是拼接符,字符串替换,没有预编译处理。
- Mybatis在处理#{}时,#{}传入参数是以字符串传入,会将SQL中的#{}替换为?号,调用PreparedStatement的set方法来赋值。
- Mybatis在处理时 ,是 原 值 传 入 , 就 是 把 {}时,是原值传入,就是把时,是原值传入,就是把{}替换成变量的值,相当于JDBC中的Statement编译
- 变量替换后,#{} 对应的变量自动加上单引号 ‘’;变量替换后,${} 对应的变量不会加上单引号 ‘’
- #{} 可以有效的防止SQL注入,提高系统安全性;${} 不能防止SQL 注入
- #{} 的变量替换是在DBMS 中;${} 的变量替换是在 DBMS 外
10、MyBatis实现一对一,一对多有几种方式,怎么操作的?
有联合查询和嵌套查询。联合查询是几个表联合查询,只查询一次,通过在resultMap里面的association,collection节点配置一对一,一对多的类就可以完成。
嵌套查询是先查一个表,根据这个表里面的结果的外键id,去再另外一个表里面查询数据,也是通过配置association,collection,但另外一个表的查询通过select节点配置。
11、Mybatis是否可以映射Enum枚举类?
Mybatis可以映射枚举类,不单可以映射枚举类,Mybatis可以映射任何对象到表的一列上。映射方式为自定义一个TypeHandler,实现TypeHandler的setParameter()和getResult()接口方法。
TypeHandler有两个作用,一是完成从javaType至jdbcType的转换,二是完成jdbcType至javaType的转换,体现为setParameter()和getResult()两个方法,分别代表设置sql问号占位符参数和获取列查询结果。
12、在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类里面的成员属性。
这种方法直观,需要建一个实体类,扩展不容易,需要加属性,但代码可读性强,业务逻辑处理方便,推荐使用。
13、Mybatis中如何指定使用哪一种Executor执行器?
在Mybatis配置文件中,在设置(settings)可以指定默认的ExecutorType执行器类型,也可以手动给DefaultSqlSessionFactory的创建SqlSession的方法传递ExecutorType类型参数,如SqlSession openSession(ExecutorType execType)。
配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements);BATCH 执行器将重用语句并执行批量更新。
14、Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?
Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection指的就是一对多查询。在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false。
它的原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理。
15、为什么需要预编译
定义:SQL 预编译指的是数据库驱动在发送 SQL 语句和参数给 DBMS 之前对 SQL 语句进行编译,这样 DBMS 执行 SQL 时,就不需要重新编译。
为什么需要预编译 JDBC 中使用对象 PreparedStatement 来抽象预编译语句,使用预编译。预编译阶段可以优化 SQL 的执行。预编译之后的 SQL 多数情况下可以直接执行,DBMS 不需要再次编译,越复杂的SQL,编译的复杂度将越大,预编译阶段可以合并多次操作为一个操作。同时预编译语句对象可以重复利用。把一个 SQL 预编译后产生的 PreparedStatement 对象缓存下来,下次对于同一个SQL,可以直接使用这个缓存的 PreparedState 对象。Mybatis默认情况下,将对所有的 SQL 进行预编译。
16、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批处理相同。
作用范围:Executor的这些特点,都严格限制在SqlSession生命周期范围内。
17、Mybatis中如何指定使用哪一种Executor执行器?
在Mybatis配置文件中,在设置(settings)可以指定默认的ExecutorType执行器类型,也可以手动给DefaultSqlSessionFactory的创建SqlSession的方法传递ExecutorType类型参数,如SqlSession openSession(ExecutorType execType)。
配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements);BATCH 执行器将重用语句并执行批量更新。
18、MyBatis的框架架构设计是怎么样的
这张图从上往下看。MyBatis的初始化,会从mybatis-config.xml配置文件,解析构造成Configuration这个类,就是图中的红框。
(1)加载配置:配置来源于两个地方,一处是配置文件,一处是Java代码的注解,将SQL的配置信息加载成为一个个MappedStatement对象(包括了传入参数映射配置、执行的SQL语句、结果映射配置),存储在内存中。
(2)SQL解析:当API接口层接收到调用请求时,会接收到传入SQL的ID和传入对象(可以是Map、JavaBean或者基本数据类型),Mybatis会根据SQL的ID找到对应的MappedStatement,然后根据传入参数对象对MappedStatement进行解析,解析后可以得到最终要执行的SQL语句和参数。
(3)SQL执行:将最终得到的SQL和参数拿到数据库进行执行,得到操作数据库的结果。
(4)结果映射:将操作数据库的结果按照映射的配置进行转换,可以转换成HashMap、JavaBean或者基本数据类型,并将最终结果返回。
19、模糊查询like语句该怎么写
(1)’%${question}%’
可能引起SQL注入,不推荐
(2)"%"#{question}"%"
注意:因为#{…}
解析成sql语句时候,会在变量外侧自动加单引号’ ',所以这里 % 需要使用双引号" ",不能使用单引号 ’ ',不然会查不到任何结果。
(3)CONCAT(’%’,#{question},’%’)
使用CONCAT()函数,推荐
(4)使用bind标签
<select id="listUserLikeUsername" resultType="com.jourwon.pojo.User">
<bind name="pattern" value="'%' + username + '%'" />
select id,sex,age,username,password from person where username LIKE #{pattern}
</select>
20、Mybatis 是如何进行分页的?分页插件的原理是什么?
- Mybatis 使用 RowBounds 对象进行分页,也可以直接编写 sql 实现分页,也可以使用Mybatis 的分页插件。
- 分页插件的原理:实现 Mybatis 提供的接口,实现自定义插件,在插件的拦截方法内拦截待执行的 sql,然后重写 sql。
select * from student,拦截 sql 后重写为:select t.* from (select * from student)t limit 0,10