1. 什么是 Mybatis?
- Mybatis 是一个半 ORM(对象关系映射)框架,它外部封装了 JDBC,开发时只须要关注 SQL 语句自身,不须要破费精力去解决加载驱动、创立连贯、创立 statement 等繁冗的过程。程序员间接编写原生态 sql,能够严格控制 sql 执行性能,灵便度高
- MyBatis 能够应用 XML 或注解来配置和映射原生信息,将 POJO 映射成数据库中的记录,防止了简直所有的 JDBC 代码和手动设置参数以及获取后果集
- 通过 xml 文件或注解的形式将要执行的各种 statement 配置起来,并通过 java 对象和 statement 中 sql 的动静参数进行映射生成最终执行的 sql 语句,最初由 mybatis 框架执行 sql 并将后果映射为 java 对象并返回(从执行 sql 到返回 result 的过程)
2. Mybaits 的长处?
- 基于 SQL 语句编程,相当灵便,不会对应用程序或者数据库的现有设计造成任何影响,SQL 写在 XML 里,解除 sql 与程序代码的耦合,便于对立治理;提供 XML 标签,反对编写动静 SQL 语句,并可重用
- 与 JDBC 相比,缩小了 50% 以上的代码量,打消了 JDBC 大量冗余的代码,不须要手动开关连贯
- 很好的与各种数据库兼容(因为 MyBatis 应用 JDBC 来连贯数据库,所以只有 JDBC 反对的数据库 MyBatis 都反对)
- 可能与 Spring 很好的集成
- 提供映射标签,反对对象与数据库的 ORM 字段关系映射;提供对象关系映射标签,反对对象关系组件保护 -
3. MyBatis 框架的毛病?
- SQL 语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写 SQL 语句的功底有肯定要求
- SQL 语句依赖于数据库,导致数据库移植性差,不能随便更换数据库
4. MyBatis 框架实用场合?
MyBatis 专一于 SQL 自身,是一个足够灵便的 DAO 层解决方案
对性能的要求很高,或者需要变动较多的我的项目,如互联网我的项目,MyBatis 将是不错的抉择
5. MyBatis 与 Hibernate 有哪些不同?
- Mybatis 和 hibernate 不同,它不齐全是一个 ORM 框架,因为 MyBatis 须要程序员本人编写 Sql 语句
- Mybatis 间接编写原生态 sql,能够严格控制 sql 执行性能,灵便度高,非常适合对关系数据模型要求不高的软件开发,因为这类软件需要变动频繁,一但需要变动要求迅速输入成绩。然而灵便的前提是 mybatis 无奈做到数据库无关性,如果须要实现反对多种数据库的软件,则须要自定义多套 sql 映射文件,工作量大
- Hibernate 对象 / 关系映射能力强,数据库无关性好,对于关系模型要求高的软件,如果用 hibernate 开发能够节俭很多代码,提高效率
6. MyBatis 和其它长久化层技术比照
-
JDBC
- SQL 夹杂在 Java 代码中耦合度高,导致硬编码外伤
- 保护不易且理论开发需要中 SQL 有变动,频繁批改的状况多见
- 代码简短,开发效率低
-
Hibernate 和 JPA
- 操作简便,开发效率高
- 程序中的长难简单 SQL 须要绕过框架
- 外部自动生产的 SQL,不容易做非凡优化
- 基于全映射的全自动框架,大量字段的 POJO 进行局部映射时比拟艰难。
- 反射操作太多,导致数据库性能降落
-
MyBatis
- 轻量级,性能杰出
- SQL 和 Java 编码离开,性能边界清晰。Java 代码专一业务、SQL 语句专一数据
- 开发效率稍逊于 HIbernate,然而齐全可能承受
7. 谈谈 MyBatis 和 JPA 的区别
-
ORM 映射不同:
MyBatis 是半自动的 ORM 框架,提供数据库与后果集的映射;
JPA(默认采纳 Hibernate 实现)是全自动的 ORM 框架,提供对象与数据库的映射。
-
可移植性不同:
JPA 通过它弱小的映射构造和 HQL 语言,大大降低了对象与数据库的耦合性;
MyBatis 因为须要写 SQL,因而与数据库的耦合性间接取决于 SQL 的写法,如果 SQL 不具备通用性而用了很多数据库的个性 SQL 的话,移植性就会升高很多,移植时老本很高。
-
SQL 优化上的区别:
因为 Mybatis 的 SQL 都是写在 XML 里,因而优化 SQL 比 Hibernate 不便很多。
而 Hibernate 的 SQL 很多都是主动生成的,无奈间接保护 SQL。虽有 HQL,但性能还是不迭 SQL 弱小,见到报表等简单需要时 HQL 就无能为力,也就是说 HQL 是有局限的 Hhibernate 尽管也反对原生 SQL,但开发模式上却与 ORM 不同,须要转换思维,因而应用上不是十分不便。总之写 SQL 的灵便度上 Hibernate 不迭 Mybatis。
8. MyBatis 输入输出反对的类型有哪些?
parameterType:
MyBatis 反对多种输入输出类型,包含:
简略的类型,如整数、小数、字符串等;汇合类型,如 Map 等;自定义的 JavaBean。
其中,简略的类型,其数值间接映射到参数上。对于 Map 或 JavaBean 则将其属性依照名称映射到参数上。
9. MyBatis 里如何实现一对多关联查问?
MyBatis 实现一对多有 联结查问 和 嵌套查问。联结查问是几个表联结查问,只查问一次,通过在 resultMap 外面的 collection 节点配置一对多的类就能够实现;嵌套查问是先查一个表,依据这个表外面的后果的外键 id,去再另外一个表外面查问数据,也是通过配置 collection,但另外一个表的查问通过 select 节点配置。
一对多:例如:依据部门 id 查找部门以及部门中的员工信息
须要查问一对多、多对一的关系,须要在“一”的 pojo 中退出 List< 多 > 属性,在“多”的 pojo 中退出“一”。
也就是说,在 Dept 类中,要退出 private List<Emp> emps;
;在 Emp 类中,要退出 private Dept dept;
。而后给他们各自增加 get、set 办法,重写结构器和 toString()
public class Dept {
private Integer did;
private String deptName;
private List<Emp> emps;
//... 结构器、get、set 办法等
}
办法 1:collection(联结查问)
DeptMapper 接口
public interface DeptMapper {
/**
* 获取部门以及部门中所有的员工信息
*/
Dept getDeptAndEmp(@Param("did") Integer did);
}
DeptMapper.xml
<resultMap id="deptAndEmpResultMap" type="Dept">
<id property="did" column="did"></id>
<result property="deptName" column="dept_name"></result>
<!--
collection:解决一对多的映射关系
ofType:示意该属性对应的汇合中存储数据的类型
-->
<collection property="emps" ofType="Emp">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
<result property="email" column="email"></result>
</collection>
</resultMap>
<!-- Dept getDeptAndEmp(@Param("did") Integer did);-->
<select id="getDeptAndEmp" resultMap="deptAndEmpResultMap">
select * from t_dept left join t_emp on t_dept.did = t_emp.did where t_dept.did = #{did} </select>
办法 2:分步查问(嵌套查问)
(1)查问部门信息
DeptMapper 接口
public interface DeptMapper {
/**
* 分步查问 查问部门及其所有的员工信息
* 第一步 查问部门信息
*/
Dept getDeptAndEmoByStepOne(@Param("did") Integer did);
}
DeptMapper.xml
<!-- 分步查问 -->
<resultMap id="deptAndEmoByStepOneMap" type="Dept">
<id property="did" column="did"></id>
<result property="deptName" column="dept_name"></result>
<collection property="emps"
select="com.atguigu.mybatis.mapper.EmpMapper.getDeptAndEmpByStepTwo"
column="did">
</collection>
</resultMap>
<!-- Dept getDeptAndEmoByStepOne(@Param("did") Integer did);-->
<select id="getDeptAndEmoByStepOne" resultMap="deptAndEmoByStepOneMap">
select * from t_dept where did = #{did} </select>
(2)依据部门 id 查问部门中的所有员工
EmpMapper
public interface EmpMapper {
/**
* 分步查问 查问部门及其所有的员工信息
* 第一步 查问部门信息
* 第二步 依据查问员工信息
*/
List<Emp> getDeptAndEmpByStepTwo(@Param("did") Integer did);
}
EmpMapper.xml
<!-- 分步查问 -->
<!-- List<Emp> getDeptAndEmpByStepTwo(@Param("did") Integer did);-->
<select id="getDeptAndEmpByStepTwo" resultType="Emp">
select * from t_emp where did = #{did} </select>
10. MyBatis 获取参数值的两种形式 – #{} 和 ${} 的区别是什么
{} 是预编译解决,${} 是字符串替换。
Mybatis 在解决 #{} 时,会将 sql 中的 #{} 替换为 ? 号,调用 PreparedStatement 的 set 办法来赋值;Mybatis 在解决 ${} 时,就是把 ${} 替换成变量的值。
应用 #{} 能够无效的避免 SQL 注入,进步零碎安全性。
- ${}的实质就是字符串拼接
-
{}的实质就是占位符赋值
${}应用字符串拼接的形式拼接 sql,若为字符串类型或日期类型的字段进行赋值时,须要手动加单引号;
然而 #{}应用占位符赋值的形式拼接 sql,此时为字符串类型或日期类型的字段进行赋值时,能够主动增加单引号(尽量应用这一种)。
- 应用 #设置参数时,MyBatis 会创立预编译的 SQL 语句,而后在执行 SQL 时 MyBatis 会为预编译 SQL 中的占位符(?)赋值。预编译的 SQL 语句执行效率高,并且能够避免注入攻打。
- 应用 $ 设置参数时,MyBatis 只是创立一般的 SQL 语句,而后在执行 SQL 语句时 MyBatis 将参数间接拼入到 SQL 里。这种形式在效率、安全性上均不如前者,然而能够解决一些非凡状况下的问题。例如,在一些动静表格(依据不同的条件产生不同的动静列)中,咱们要传递 SQL 的列名,依据某些列进行排序,或者传递列名给 SQL 都是比拟常见的场景,这就无奈应用预编译的形式了。
总结:分成两种状况进行解决
- 实体类类型的参数 (若 mapper 接口中的办法参数为实体类对象时此时能够应用 ${} 和 #{},通过拜访实体类对象中的属性名获取属性值,留神 ${}须要手动加单引号)
-
应用 @Param 标识参数
``java public interface ParameterMapper { /** * 增加用户信息 */ int insertUser(User user); }</pre> <pre class="prettyprint hljs less">public interface ParameterMapper { /** * 验证登录(应用 @Param)*/ User checkLoginByParam(@Param("username") String username, @Param("password") String password); }
11. 既然 ${}不平安,为什么还须要用它,什么时候会用到它?
它能够解决一些非凡状况下的问题。例如,在一些动静表格(依据不同的条件产生不同的动静列)中,咱们要传递 SQL 的列名,依据某些列进行排序,或者传递列名给 SQL 都是比拟常见的场景,这就无奈应用预编译的形式了。
批量删除时:
只能应用 ${},如果应用 #{},则解析后的 sql 语句为 delete from t_user where id in ('1,2,3')
,这样是将 1,2,3 看做是一个整体,只有 id 为 1,2,3 的数据会被删除。正确的语句应该是 delete from t_user where id in (1,2,3)
,或者 delete from t_user where id in ('1','2','3')
12. MyBatis 的 xml 文件和 Mapper 接口是怎么绑定的?
是通过 xml 文件中 <mapper>
根标签的 namespace 属性进行绑定的,即 namespace 属性的值须要配置成接口的全限定名称,MyBatis 外部就会通过这个值将这个接口与这个 xml 关联起来。
-
MyBatis 中能够面向接口操作数据,要保障两个统一
- mapper 接口的全类名和映射文件的命名空间(namespace)保持一致
-
mapper 接口中办法的办法名和映射文件中编写 SQL 的标签的 id 属性保持一致
package com.atguigu.mybatis.mapper; public interface UserMapper { /** 增加用户信息 */ int insertUser();}
<?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="com.atguigu.mybatis.mapper.UserMapper"> <!--int insertUser();--> <insert id="insertUser"> insert into t_user values(null,'张三','123',23,'女') </insert> </mapper>
13. MyBatis 分页和本人写的分页哪个效率高?
本人写的分页效率高。
在 MyBatis 中,咱们能够通过分页插件实现分页,也能够通过分页 SQL 本人实现分页。其中,分页插件的原理是,拦挡查问 SQL,在这个 SQL 根底上主动为其增加 limit 分页条件。它会大大的进步开发的效率,然而无奈对分页语句做出有针对性的优化,比方分页偏移量很大的状况,而这些在本人写的分页 SQL 里却是能够灵便实现的。
14. 理解 MyBatis 缓存机制吗?
MyBatis 的缓存分为一级缓存和二级缓存。
14.1、MyBatis 的一级缓存
- 一级缓存是 SqlSession 级别的,通过同一个 SqlSession 查问的数据会被缓存,下次查问雷同的数据,就会从缓存中间接获取,不会从数据库从新拜访
使一级缓存生效的四种状况:
- 不同的 SqlSession 对应不同的一级缓存
- 同一个 SqlSession 然而查问条件不同
- 同一个 SqlSession 两次查问期间执行了任何一次增删改操作
- 同一个 SqlSession 两次查问期间手动清空了缓存
14.2、MyBatis 的二级缓存
- 二级缓存是 SqlSessionFactory 级别,通过同一个 SqlSessionFactory 创立的 SqlSession 查问的后果会被缓存;尔后若再次执行雷同的查问语句,后果就会从缓存中获取
二级缓存开启的条件
cacheEnabled="true"
<cache />
使二级缓存生效的状况: 两次查问之间执行了任意的增删改,会使一级和二级缓存同时生效
没有提交 sqlsession 时,数据会保留在一级缓存中,提交后,会保留在二级缓存中。
14.3、MyBatis 缓存查问的程序
- 先查问二级缓存,因为二级缓存中可能会有其余程序曾经查出来的数据,能够拿来间接应用
- 如果二级缓存没有命中,再查问一级缓存
- 如果一级缓存也没有命中,则查询数据库
- SqlSession 敞开之后,一级缓存中的数据会写入二级缓存
15. 当实体类中的属性名和表中的字段名不一样,怎么办?
若 字段名 (数据库里的名字例如 emp_name)和 实体类中的属性名 不统一,然而字段名合乎数据库的规定(应用 _
),实体类中的属性 名合乎 Java 的规定(应用驼峰),此时也可通过以下两种形式解决字段名和实体类中的属性的映射关系
<resultMap>
15.1 用起别名的形式保障字段名与属性名统一
和 sql 中一样,用字段名 属性名(如 emp_name empName)来使二者统一。
<!-- List<Emp> getAllEmp();-->
<select id="getAllEmp" resultType="Emp">
select eid, emp_name empName, age, sex, email from t_emp
</select>
15.2 逐个设置 resultMap 映射关系
在 resultMap 中,一一对应地设置属性名 -> 字段名,再在 select 标签中增加 resultMap=“对应 resultMap 的 id”
<!--
resultMap 设置自定义映射关系
id 惟一标识
type 映射的实体类型
子标签:id 设置主键的映射关系,result 设置其余的映射关系
property 设置映射关系中的属性名,必须是 type 属性所设置的实体类类型的属性名
column 设置映射关系中的字段名,必须是 sql 语句查问进去的字段名
如果应用 resultMap,就所有属性都须要设置
-->
<resultMap id="empResultMap" type="Emp">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
<result property="email" column="email"></result>
</resultMap>
<select id="getAllEmp" resultMap="empResultMap">
select * from t_emp
</select>
16. 含糊查问 like 语句该怎么写?
select * from t_user where username like "%"#{username}"%"
是最罕用的
SQLMapper 接口:
public interface SQLMapper {
/**
* 依据用户名含糊查问用户信息
*/
List<User> getUserByLike(@Param("username") String username);
}
SQLMapper.xml:
<!-- List<User> getUserByLike(@Param("username") String username);-->
<!-- 应用 #{},因为包含在单引号里,会被认为是字符串的一部分:select * from t_user where username like '%#{username}%'-->
<!-- 三种形式 -->
<select id="getUserByLike" resultType="User">
<!-- 第一种 select * from t_user where username like '%${username}%'
第二种 select * from t_user where username like concat('%', #{username}, '%')-->
<!-- 第三种 举荐应用 -->
select * from t_user where username like "%"#{username}"%"
</select>
17. 通常一个 Xml 映射文件,都会写一个 Dao 接口与之对应,请问,这个 Dao 接口的工作原理是什么?Dao 接口里的办法,参数不同时,办法能重载吗?
- Dao 接口即 Mapper 接口。接口的全限名,就是映射文件中的 namespace 的值;接口的办法名,就是映射文件中 Mapper 的 Statement 的 id 值;接口办法内的参数,就是传递给 sql 的参数。
- Mapper 接口是没有实现类的,当调用接口办法时,接口全限名 + 办法名拼接字符串作为 key 值,可惟一定位一个 MapperStatement。在 Mybatis 中,每一个
<select>
、<insert>
、<update>
、<delete>
标签,都会被解析为一个 MapperStatement 对象。 - 举例:
com.mybatis3.mappers.StudentDao.findStudentById
,能够惟一找到 namespace 为com.mybatis3.mappers.StudentDao
上面 id 为 findStudentById 的 MapperStatement。 - Mapper 接口里的办法,是不能重载的,因为是应用 全限名 + 办法名 的保留和寻找策略。Mapper 接口的工作原理是 JDK 动静代理,Mybatis 运行时会应用 JDK 动静代理为 Mapper 接口生成代理对象 proxy,代理对象会拦挡接口办法,转而执行 MapperStatement 所代表的 sql,而后将 sql 执行后果返回。
18. Mybatis 是如何进行分页的?分页插件的原理是什么?
Mybatis 应用 RowBounds 对象进行分页,它是针对 ResultSet 后果集执行的内存分页,而非物理分页。能够在 sql 内间接书写带有物理分页的参数来实现物理分页性能,也能够应用分页插件来实现物理分页。
分页插件的基本原理是应用 Mybatis 提供的插件接口,实现自定义插件,在插件的拦挡办法内拦挡待执行的 sql,而后重写 sql,依据 dialect 方言,增加对应的物理分页语句和物理分页参数。
19. Mybatis 是如何将 sql 执行后果封装为指标对象并返回的?都有哪些映射模式?
第一种是应用 <resultMap>
标签,逐个定义数据库列名和对象属性名之间的映射关系。
第二种是应用 sql 列的别名性能,将列的别名书写为对象属性名。
有了列名与属性名的映射关系后,Mybatis 通过反射创建对象,同时应用反射给对象的属性逐个赋值并返回,那些找不到映射关系的属性,是无奈实现赋值的。
20. 如何执行批量删除和插入?
用 foreach(罕用)
-
foreach 罕用属性:
,
利用场景 1:通过数组实现批量删除(用的多)
DynamicSqlMapper 接口
public interface DynamicSQLMapper {
/**
* 通过数组实现批量删除
*/
int deleteMoreByArray(@Param("eids") Integer[] eids);
}
DynamicSqlMapper.xml
<!-- int deleteMoreByArray(Integer[] eids);-->
<delete id="deleteMoreByArray">
<!-- 办法 2:-->
delete from t_emp where
<foreach collection="eids" item="eid" separator="or">
eid = #{eid} </foreach>
</delete>
测试类:
/**
* 5、foreach
*/
@Test public void testDeleteMoreByArray(){SqlSession sqlSession = SqlSessionUtils.getSqlSession();
DynamicSQLMapper mapper = sqlSession.getMapper(DynamicSQLMapper.class);
int result = mapper.deleteMoreByArray(new Integer[]{7, 8, 9});
System.out.println(result);
}
利用场景 2:通过 list 汇合实现批量增加(用的多)
DynamicSqlMapper 接口
public interface DynamicSQLMapper {
/**
* 通过 list 汇合实现批量增加
*/
int insertMoreByList(@Param("emps") List<Emp> emps);
}
DynamicSqlMapper.xml
<!-- int insertMoreByList(List<Emp> emps);-->
<!-- DynamicSqlMapper 接口不加 @Paras 注解会报错:Parameter 'emps' not found. Available parameters are [arg0, collection, list]-->
<!-- int insertMoreByList(@Param("emps") List<Emp> emps);-->
<insert id="insertMoreByList">
insert into t_emp values
<foreach collection="emps" item="emp" separator=",">
(null, #{emp.empName}, #{emp.age}, #{emp.sex}, #{emp.email}, null)
</foreach>
</insert>
测试类:
@Test
public void testInsertMoreByList(){SqlSession sqlSession = SqlSessionUtils.getSqlSession();
DynamicSQLMapper mapper = sqlSession.getMapper(DynamicSQLMapper.class);
Emp emp1 = new Emp(null, "Mary", 23, "女", "11111@qq.com");
Emp emp2 = new Emp(null, "Linda", 23, "女", "1144111@qq.com");
Emp emp3 = new Emp(null, "Jackoline", 23, "女", "1122111@qq.com");
List<Emp> emps = Arrays.asList(emp1, emp2, emp3);
System.out.println(mapper.insertMoreByList(emps));
}
21. 增加性能获取自增的主键、如何获取主动生成的 (主) 键值
-
应用场景
- t_clazz(clazz_id,clazz_name)
- t_student(student_id,student_name,clazz_id)
- 增加班级信息
- 获取新增加的班级的 id
- 为班级调配学生,行将某学生的班级 id 批改为新增加的班级的 id
-
在 mapper.xml 中设置两个属性
- useGeneratedKeys:设置应用自增的主键
-
keyProperty:因为增删改有对立的返回值是受影响的行数,因而只能将获取的自增的主键放在传输的参数 user 对象的某个属性中
/** * 增加用户信息 * @param user * @date 2022/2/27 15:04 */ void insertUser(User user);
<!--void insertUser(User user);--> <!-- 办法的返回值是固定的 useGeneratedKeys 设置以后标签中的 sql 应用了自增的主键 (id) keyProperty 将自增的主键的值 赋值给 传输到映射文件中的参数的某个属性(user.id)--> <insert id="insertUser" useGeneratedKeys="true" keyProperty="id"> insert into t_user values (null,#{username},#{password},#{age},#{sex},#{email}) </insert>
// 测试类 @Test public void insertUser() {SqlSession sqlSession = SqlSessionUtils.getSqlSession(); SQLMapper mapper = sqlSession.getMapper(SQLMapper.class); User user = new User(null, "ton", "123", 23, "男", "123@321.com"); mapper.insertUser(user); System.out.println(user); // 输入:user{id=10, username='ton', password='123', age=23, sex='男', email='123@321.com'},自增主键寄存到了 user 的 id 属性中 }
22. 在 mapper 中如何传递多个参数?
应用 @Param 标识参数
-
能够通过 @Param 注解标识 mapper 接口中的办法参数,此时,会将这些参数放在 map 汇合中
- 以 @Param 注解的 value 属性值为键,以参数为值;
- 以 param1,param2…为键,以参数为值;
-
只须要通过 ${}和 #{}拜访 map 汇合的键就能够获取绝对应的值,留神 ${}须要手动加单引号
ParameterMapper 接口:
public interface ParameterMapper { /** * 验证登录(应用 @Param)*/ User checkLoginByParam(@Param("username") String username, @Param("password") String password); }
对应在 ParameterMapper.xml 中配置。
<!-- 以 @Param 的值为键,参数为值; 或以 "param1"/"param2" 为键,参数为值 --> <!-- User checkLoginByParam(@Param("username") String username, @Param("password") String password);--> <select id="checkLoginByParam" resultType="User"> select * from t_user where username = #{username} and password = #{password} </select>
多个参数封装成 map
-
若 mapper 接口中的办法须要的参数为多个时,此时能够手动创立 map 汇合,将这些数据放在 map 中只须要通过 ${}和 #{}拜访 map 汇合的键就能够获取绝对应的值,留神 ${}须要手动加单引号
ParameterMapper 接口:
public interface ParameterMapper { /** * 验证登录 */ User checkLoginByMap(Map<String, Object> map); }
对应在 ParameterMapper.xml 中配置。
<!-- User checkLoginByMap(Map<String, Object> map);--> <select id="checkLoginByMap" resultType="User"> select * from t_user where username = #{username} and password = #{password} </select>
23. Mybatis 动静 sql 有什么用?执行原理?有哪些动静 sql?
实质是一系列的标签
Mybatis 动静 sql 能够在 Xml 映射文件内,以标签的模式编写动静 sql,执行原理是依据表达式的值 实现逻辑判断并动静拼接 sql 的性能。
Mybatis 提供了 9 种动静 sql 标签:trim | where | set | foreach | if | choose | when | otherwise | bind。
- Mybatis 框架的动静 SQL 技术是一种依据特定条件动静拼装 SQL 语句的性能,它存在的意义是为了解决拼接 SQL 语句字符串时的痛点问题
if(罕用)
if 标签可通过 test 属性的表达式进行判断,若表达式的后果为 true,则标签中的内容会执行; 反之标签中的内容不会执行
- 在 where 前面增加一个恒成立条件
1=1
- 这个恒成立条件并不会影响查问的后果
-
这个
1=1
能够用来拼接and
语句,例如:当 empName 为 null 时- 如果不加上恒成立条件,则 SQL 语句为
select * from t_emp where and age = ? and sex = ? and email = ?
,此时where
会与and
连用,SQL 语句会报错 - 如果加上一个恒成立条件,则 SQL 语句为
select * from t_emp where 1= 1 and age = ? and sex = ? and email = ?
,此时不报错
- 如果不加上恒成立条件,则 SQL 语句为
利用场景:多条件查问
24. Xml 映射文件中,除了常见的 select|insert|updae|delete 标签之外,还有哪些标签?
<resultMap>
、<parameterMap>
、<sql>
、<include>
、<selectKey>
,加上动静 sql 的 9 个标签,其中 <sql>
为 sql 片段标签,通过 <include>
标签引入 sql 片段,<selectKey>
为不反对自增的主键生成策略标签。
25. Mybatis 的 Xml 映射文件中,不同的 Xml 映射文件,id 是否能够反复?
不同的 Xml 映射文件,如果配置了 namespace,那么 id 能够反复;如果没有配置 namespace,那么 id 不能反复;
起因就是 namespace+id 是作为 Map<String, MapperStatement>
的 key 应用的,如果没有 namespace,就剩下 id,那么,id 反复会导致数据相互笼罩。有了 namespace,天然 id 就能够反复,namespace 不同,namespace+id 天然也就不同。
然而,在以前的 Mybatis 版本的 namespace 是可选的,不过新版本的 namespace 曾经是必须的了。
26. 为什么说 Mybatis 是半自动 ORM 映射工具?它与全自动的区别在哪里?
Hibernate 属于全自动 ORM 映射工具,应用 Hibernate 查问关联对象或者关联汇合对象时,能够依据对象关系模型间接获取,所以它是全自动的。而 Mybatis 在查问关联对象或关联汇合对象时,须要手动编写 sql 来实现,所以,称之为半自动 ORM 映射工具。
27. MyBatis 实现一对多有几种形式, 怎么操作的?
有联结查问和嵌套查问。联结查问是几个表联结查问,只查问一次,通过在 resultMap 外面的 collection 节点配置一对多的类就能够实现;嵌套查问是先查一个表,依据这个表外面的后果的外键 id,去再另外一个表外面查问数据,也是通过配置 collection,但另外一个表的查问通过 select 节点配置。
28. 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() 办法的调用。这就是提早加载的基本原理。
当然了,不光是 Mybatis,简直所有的包含 Hibernate,反对提早加载的原理都是一样的。
28. 什么是 MyBatis 的接口绑定?有哪些实现形式?
接口绑定,就是在 MyBatis 中任意定义接口,而后把接口外面的办法和 SQL 语句绑定,咱们间接调用接口办法就能够,这样比起原来了 SqlSession 提供的办法咱们能够有更加灵便的抉择和设置。
接口绑定有两种实现形式,一种是通过注解绑定,就是在接口的办法下面加上 @Select、@Update 等注解,外面蕴含 Sql 语句来绑定;另外一种就是通过 xml 外面写 SQL 来绑定,在这种状况下,要指定 xml 映射文件外面的 namespace 必须为接口的全路径名。当 Sql 语句比较简单时候,用注解绑定,当 SQL 语句比较复杂时候,用 xml 绑定,个别用 xml 绑定的比拟多。
28. 应用 MyBatis 的 mapper 接口调用时有哪些要求?
Mapper 接口办法名和 mapper.xml 中定义的每个 sql 的 id 雷同
Mapper 接口办法的输出参数类型和 mapper.xml 中定义的每个 sql 的 parameterType 的类型雷同
Mapper 接口办法的输入参数类型和 mapper.xml 中定义的每个 sql 的 resultType 的类型雷同
Mapper.xml 文件中的 namespace 即是 mapper 接口的类门路
29. 简述 Mybatis 的插件运行原理,以及如何编写一个插件?
Mybatis 仅能够编写针对 ParameterHandler、ResultSetHandler、StatementHandler、Executor 这 4 种接口的插件,Mybatis 应用 JDK 的动静代理,为须要拦挡的接口生成代理对象以实现接口办法拦挡性能,每当执行这 4 种接口对象的办法时,就会进入拦挡办法,具体就是 InvocationHandler 的 invoke() 办法,当然,只会拦挡那些你指定须要拦挡的办法。
编写插件:实现 Mybatis 的 Interceptor 接口并复写 intercept() 办法,而后在给插件编写注解,指定要拦挡哪一个接口的哪些办法即可,记住,别忘了在配置文件中配置你编写的插件。
30. 多对一映射解决
如:查问员工信息以及员工所对应的部门信息
须要查问一对多、多对一的关系,须要在“一”的 pojo 中退出 List< 多 > 属性,在“多”的 pojo 中退出“一”。
也就是说,在 Dept 类中,要退出 private List<Emp> emps
;;在 Emp 类中,要退出 private Dept dept;
。而后给他们各自增加 get、set 办法,重写结构器和 toString()
办法 1:应用 association 解决映射关系
- association:解决多对一的映射关系
- property:须要解决多对一的映射关系的属性名
-
javaType:该属性的类型
<resultMap id="empAndDeptResultMapTwo" type="Emp"> <id property="eid" column="eid"></id> <result property="empName" column="emp_name"></result> <result property="age" column="age"></result> <result property="sex" column="sex"></result> <result property="email" column="email"></result> <association property="dept" javaType="Dept"> <id property="did" column="did"></id> <result property="deptName" column="dept_name"></result> </association> </resultMap> <!--Emp getEmpAndDept(@Param("eid")Integer eid);--> <select id="getEmpAndDept" resultMap="empAndDeptResultMapTwo"> select * from t_emp left join t_dept on t_emp.eid = t_dept.did where t_emp.eid = #{eid} </selec>
办法 2:分步查问(用的较多)
1. 查问员工信息
- select:设置散布查问的 sql 的惟一标识(namespace.SQLId 或 mapper 接口的全类名. 办法名)
- column:设置分步查问的条件,也就是下一步查谁,查员工的部门
<pre class=”prettyprint hljs less”>//EmpMapper 接口里的办法
/**
- 通过分步查问,员工及所对应的部门信息
- 分步查问第一步:查问员工信息
*/
Emp getEmpAndDeptByStepOne(@Param(“eid”) Integer eid);</pre>
EmpMapper.xml
<resultMap id="empAndDeptByStepResultMap" type="Emp">
<id property="eid" column="eid"></id>
<result property="empName" column="emp_name"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
<result property="email" column="email"></result>
<!--
select: 设置分步查问的 sql 的惟一标识(namespace.SQLId 或 mapper 接口的全类名. 办法名)column:分步查问的条件,也就是下一步查谁,查员工的部门
fetchType: 当开启了全局的提早记录后,可通过此属性手动管制提早加载的成果
fetchType:"lazy/eager" lazy 示意提早加载,eager 示意立刻加载
-->
<association property="dept"
select="com.atguigu.mybatis.mapper.DeptMapper.getEmpAndDeptByStepTwo"
column="did"
fetchType="eager">
</association>
</resultMap>
<!--Emp getEmpAndDeptByStepOne(@Param("eid") Integer eid);-->
<select id="getEmpAndDeptByStepOne" resultMap="empAndDeptByStepResultMap">
select * from t_emp where eid = #{eid} </select>
2. 查问部门信息
//DeptMapper 里的办法
/**
* 通过分步查问,员工及所对应的部门信息
* 分步查问第二步:通过 did 查问员工对应的部门信息
*/
Dept getEmpAndDeptByStepTwo(@Param("did") Integer did);</pre>
<pre class="prettyprint hljs dust"><!-- 此处的 resultMap 仅是解决字段和属性的映射关系 -->
<resultMap id="EmpAndDeptByStepTwoResultMap" type="Dept">
<id property="did" column="did"></id>
<result property="deptName" column="dept_name"></result>
</resultMap>
<!--Dept getEmpAndDeptByStepTwo(@Param("did") Integer did);-->
<select id="getEmpAndDeptByStepTwo" resultMap="EmpAndDeptByStepTwoResultMap">
select * from t_dept where did = #{did} </select>
31、提早加载
-
分步查问的长处:能够实现提早加载,然而必须在外围配置文件中设置全局配置信息:
lazyLoadingEnabled aggressiveLazyLoading
- 此时就能够实现按需加载,获取的数据是什么,就只会执行相应的 sql。此时可通过 association 和 collection 中的 fetchType 属性设置以后的分步查问是否应用提早加载,fetchType=“lazy(提早加载)|eager(立刻加载)”
mybatis-config.xml
<settings>
<!-- 开启提早加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
@Test
public void getEmpAndDeptByStepOne() {SqlSession sqlSession = SqlSessionUtils.getSqlSession();
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
Emp emp = mapper.getEmpAndDeptByStepOne(1);
System.out.println(emp.getEmpName());
}
- 敞开提早加载,两条 SQL 语句都运行了
- 开启提早加载,只运行获取 emp 的 SQL 语句
通过 fetchType 参数,能够手动管制提早加载或立刻加载,否则依据全局配置的属性决定是提早加载还是立刻加载。
@Test
public void getEmpAndDeptByStepOne() {SqlSession sqlSession = SqlSessionUtils.getSqlSession();
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
Emp emp = mapper.getEmpAndDeptByStepOne(1);
System.out.println(emp.getEmpName());
System.out.println("----------------");
System.out.println(emp.getDept());
}