一、Mybatis 介绍
MyBatis 本是apache的一个开源项目iBatis,2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAOs)。当前,最新版本是MyBatis 3.5.1 ,其发布时间是2019年4月8日。
MyBatis 是一款支持普通 SQL查询,存储过程和高级映射的优秀持久层框架。MyBatis 消除了几乎所有的 JDBC 代码和参数的手工设置以及结果集的检索。MyBatis 使用简单的 XML或注解用于配置和原始映射,将接口和 Java 的POJOs(Plain Ordinary Java Objects,普通的 Java对象)映射成数据库中的记录。
每个MyBatis应用程序主要都是使用SqlSessionFactory实例的,一个SqlSessionFactory实例可以通过SqlSessionFactoryBuilder获得。SqlSessionFactoryBuilder可以从一个xml配置文件或者一个预定义的配置类的实例获得。
用xml文件构建SqlSessionFactory实例是非常简单的事情。推荐在这个配置中使用类路径资源(classpath resource),但你可以使用任何Reader实例,包括用文件路径或file://开头的url创建的实例。MyBatis有一个实用类----Resources,它有很多方法,可以方便地从类路径及其它位置加载资源。
二、Mybatis的作用。为何不直接用JDBC
1️⃣JDBC 弊端
- 重复创建链接和释放链接,造成资源浪费。解决方式:使用连接池。
- 出现硬编码,体现在数据库驱动,url、用户名密码、sql。解决方式:使用配置文件。
- 结果获取不方便。解决方式:把结果放到一个POJO中。
2️⃣MyBatis 是一个支持普通 SQL 查询,存储过程和高级映射的优秀持久层框架。相对于 JDBC,MyBatis 有以下优点:
- 把 sql 语句从 Java 中独立出来,统一管理,便于维护和管理。
- 封装了底层的 JDBC,API 的调用,并且能够将结果集自动转换成 JavaBean 对象,简化了 Java 数据库编程的重复工作。
- 提供了缓存功能。
- 入参无需用对象封装(或者map封装),使用@Param。
三、Mybatis 解决 jdbc 编程的问题
- 数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。
解决:在 SqlMapConfig.xml 中配置数据链接池,使用连接池管理数据库链接。 - sql 与代码耦合,不易维护,实际应用 sql 变化的可能较大,sql 变动需要改变代码。
解决:将 sql 语句配置在 XXXXmapper.xml 文件中与代码分离。 - 向 sql 语句传参数麻烦,因为 where 条件不一定,可能多可能少,占位符需要和参数一一对应。
解决:Mybatis 自动将 java 对象映射至 sql 语句,通过 statement 中的 parameterType 定义输入参数的类型。 - 对结果集解析麻烦,sql 变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成 pojo 对象解析比较方便。
解决:Mybatis 自动将 sql 执行结果映射至 java 对象,通过 statement 中的 resultType 定义输出结果的类型。
四、Mybatis 的一级缓存和二级缓存
SqlSession 是 Mybatis 的核心处理类。
MyBatis 中的缓存分为两种:一级缓存sqlSession
和二级缓存mapper
。当使用同一个 sqlSession 时,查询到的数据可能是一级缓存;而当使用同一个 mapper 时,查询到的数据可能是二级缓存。一级缓存默认开启的。二级缓存需要配置才开启,当配置文件配置了cacheEnabled=true
时,就会开启二级缓存。
五、Mybatis DAO 接口为何不需要实现类
Mybatis 实现了 DAO 接口 与 xml 映射文件的绑定,自动生成接口的具体实现,使用起来变得更加省事和方便。
1️⃣Dao 接口,就是常说的 Mapper 接口:
①接口的全限名,就是映射文件中的 namespace 的值。
②接口的方法名,就是映射文件中 MappedStatement 的 id 值。
③接口方法内的参数,就是传递给 sql 的参数。
2️⃣Dao 接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为 key 值,可唯一定位一个 MappedStatement。例如:com.wg.mappers.ZooDao.selById
,可以唯一找到 namespace 为com.wg.mappers.ZooDao 下面 id = selById 的 MappedStatement。在 Mybatis 中,每一个 select、insert、update、delete 标签,都会被解析为一个 MappedStatement 对象。
3️⃣Dao 接口里的方法,是不能重载的,因为是全限名+方法名的保存和寻找策略。Dao 接口的工作原理是JDK的动态代理,Mybatis 运行时会使用 JDK 动态代理为 Dao 接口生成代理 proxy 对象(如使用 Spring 会注入到容器中),代理对象 proxy 会拦截接口方法,转而执行 MappedStatement 所代表的 sql,然后将 sql 执行结果返回。
六、Mybatis 不同的 xml 映射文件,id 是否可以重复
不同的 xml 映射文件,如果配置了 namespace,那么 id 可以重复;如果没有配置,那么 id 不能重复;毕竟 namespace 不是必须的,只是最佳实践而已。
原因就是 namespace+id 是作为 Map<String,MappedStatement> 的 key 使用的,如果没有 namespace,id 重复会导致数据互相覆盖。有了 namespace,id 就可以重复,namespace 不同,namespace+id 自然也就不同。
七、处理实体类中的属性名和表中的字段名不一致问题
八、 模糊查询like语句写法
九、什么情况用注解,什么情况用 xml 绑定
- 注解使用情况:sql 语句简单时
- xml 绑定使用情况:xml 绑定(@RequestMap用来绑定xml文件)
十、Mybatis是如何进行分页的?分页插件的原理是什么?
- Mybatis 使用 RowBounds 对象进行分页,它是针对 ResultSet 结果集执行的内存分页,而非物理分页。
- 也可以在 sql 内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。分页插件的基本原理是使用 Mybatis 提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的 sql,然后重写 sql,根据 dialect 方言,添加对应的物理分页语句和物理分页参数。
十一、Mybatis是如何将sql执行结果封装为目标对象并返回的?都有哪些映射形式?
- 使用 resultMap 标签,逐一定义列名和对象属性名之间的映射关系。
- 使用 sql 列的别名功能,将列别名书写为对象属性名,比如 AcctName AS NAME,对象属性名一般是小写,但是列名不区分大小写,Mybatis 会忽略列名大小写,智能找到与之对应对象属性名,甚至可以写成 AcctName AS NaMe,Mybatis 一样可以正常工作。有了列名与属性名的映射关系后,Mybatis 通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回。找不到映射关系的属性,是无法完成赋值的。
十二、如何在mapper中传递多个参数
第1种:
//DAO层的函数
public User selUser(String name,String area);
对应的xml,#{0}代表接收的是dao层中的第一个参数,#{1}代表dao层中第二参数,更多参数一致往后加即可。
<select id="selectUser" resultMap="BaseResultMap">
select * from userInfo where user_name = #{0} and user_area=#{1}
</select>
第2种: 使用 @param:
public interface usermapper {
user selUser(@param("userName") String userName,
@param("hashedPassword") String hashedPassword);
}
然后就可以在 xml 如下使用(推荐封装为一个map,作为单个参数传递给mapper):
<select id="selUser" resultType="user">
select id, username, hashedPassword
from userInfo
where userName = #{username}
and hashedPassword = #{hashedPassword}
</select>
十三、Mybatis 的动态 sql
- Mybatis 的动态 sql 可以在 xml 映射文件内,以标签的形式编写动态 sql,完成逻辑判断和动态拼接 sql 的功能。
- Mybatis 提供了 9 种动态 sql 标签:trim、where、set、foreach、if、choose、when、otherwise、bind。
- 执行原理:使用 OGNL 从 sql 参数对象中计算表达式的值,根据表达式的值动态拼接 sql,以此来完成动态 sql 的功能。
十四、#{} 和 ${} 的区别
十五、sql 预编译
sql 预编译指的是数据库驱动在发送 sql 语句和参数给 DBMS 数据库管理系统(Database Management System)之前对 sql 语句进行编译,这样 DBMS 执行 sql 时,就不需要重新编译。JDBC 中使用对象 PreparedStatement 来抽象预编译语句,使用预编译。为什么需要预编译:
- 预编译阶段可以优化 sql 的执行。
预编译之后的 sql 多数情况下可以直接执行,DBMS 不需要再次编译。越复杂的 sql,编译的复杂度将越大,预编译阶段可以合并多次操作为一个操作。 - 预编译语句对象可以重复利用。
sql 会预编译在数据库系统中。执行计划同样会被缓存,它允许数据库做参数化查询。使用预处理语句比普通查询更快,因为做的工作更少。把 sql 预编译后产生的 PreparedStatement 对象缓存下来,下次对于同一个 sql,可以直接使用该缓存的 PreparedState 对象。它拥有更佳的性能优势,因为数据库对 sql 的分析,编译,优化已经在第一次查询前完成了。 - PreparedStatement 可以防止 sql 注入式攻击
sql 注入攻击是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的 sql 语句段或命令,从而利用系统的 sql 引擎完成恶意行为的做法。
MyBatis 默认情况下,将对所有的 sql 进行预编译。
MySQL 的预编译源码在com.mysql.jdbc.ConnectionImpl中。
说明:
MyBatis 在调用 connection 进行 sql 预编译之前,会对 sql 进行动态解析,动态解析主要包含如下的功能:
- 占位符的处理
- 动态 sql 的处理
- 参数类型校验
MyBatis 强大的动态 sql 功能的具体实现就在此。
十六、Mybatis 为什么是半自动 ORM 映射工具
Hibernate 属于全自动 ORM 映射工具,使用 Hibernate 查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。而 Mybatis 在查询关联对象或关联集合对象时,需要手动编写 sql 来完成,所以,称之为半自动 ORM 映射工具。
十七、Ibatis 和 Mybatis
- Ibatis:2010年,apache 的 Ibatis 框架停止更新,并移交给了 google 团队,同时更名为 MyBatis。从2010年后 Ibatis 再没更新过,彻底变成了一个孤儿框架。一个没人维护的框架注定被 MyBatis 拍在沙滩上。
- Mybatis:Ibatis 的升级版本。
十八、Mybatis 架构
- mybatis 配置 SqlMapConfig.xml,此文件作为 mybatis 的全局配置文件,配置了 mybatis 的运行环境等信息。mapper.xml 文件即 sql 映射文件,文件中配置了操作数据库的sql语句。此文件需要在 SqlMapConfig.xml 中加载。
- 通过mybatis环境等配置信息构造 SqlSessionFactory 即会话工厂。
- 由会话工厂创建 sqlSession 即会话,操作数据库需要通过sqlSession进行。
- mybatis底层自定义了Executor执行器接口操作数据库,Executor接口有两个实现,一个是基本执行器、一个是缓存执行器。
- Mapped Statement也是mybatis一个底层封装对象,它包装了mybatis配置信息及sql映射信息等。mapper.xml文件中一个sql对应一个Mapped Statement对象,sql的id即是Mapped statement的id。
- Mapped Statement对sql执行输入参数进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql前将输入的java对象映射至sql中,输入参数映射就是jdbc编程中对preparedStatement设置参数。
- 输入映射(传入的参数)
支持的数据类型:
基本数据类型:基础类型以及包装类、String
POJO
Map
包装的POJO:一个pojo中有pojo属性
- Mapped Statement对sql执行输出结果进行定义,包括HashMap、基本类型、pojo,Executor通过Mapped Statement在执行sql后将输出结果映射至java对象中,输出结果映射过程相当于jdbc编程中对结果的解析处理过程。
- 输出映射(产生的结果类型)
基本数据类型:基础类型以及包装类、String
POJO
Map
List
十九、MyBatis xml配置文件层次结构
##properties元素
properties是一个配置属性的元素,让开发者能在配置文件的上下文中使用它,MyBatis提供3种配置方式:
- property子元素。
- properties配置文件。
- SqlSessionFactoryBuilder使用Properties文件构建。
property子元素
<property name="driver" value="com.mysql.jdbc.Driver"/>
properties配置文件
一般会使用一个单独的properties配置文件来配置属性值,以方便在多个配置文件中重复使用它们,也方便日后维护和随时修改。可以通过${key}的形式,取出在配置文件中配置的值。
<configuration>
<!-- 引入配置文件 -->
<properties resource="datasource.properties"/>
<environments default="dev">
<environment id="dev">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!-- 使用配置文件中的属性 -->
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
</dataSource>
</environment>
</environments>
</configuration>
SqlSessionFactoryBuilder使用Properties文件构建
出于安全考虑,properties配置文件中的账号密码等元素可能是加密的,这个时候就需要对加密的元素进行处理。
public static void func() throws Exception {
Properties properties = new Properties();
properties.load(Resources.getResourceAsStream("datasource.properties"));
// 对原账号密码解密
properties.setProperty("username", decode(properties.getProperty("username")));
properties.setProperty("password", decode(properties.getProperty("password")));
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
// SqlSessionFactoryBuilder可以使用一个InputStream和一个Properties构建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is, properties);
}
三种方式的优先级
MyBatis支持的3种配置方式可能同时出现,并且属性还会重复配置,MyBatis将按照下面的顺序来加载:
- 在properties元素体内指定的属性首先被读取。
- 根据 properties元素中的resource属性读取类路径下属性文件,或者根据url属性指定的路径读取属性文件,并覆盖已读取的同名属性。
- 读取作为build()方法参数传递的属性,并覆盖已读取的同名属性。
因此,通过build()方法参数传递的属性具有最高优先级,resource/url属性中指定的配置文件次之,最低优先级的是 properties属性中指定的属性。因此,我们尽量不要使用混合的方式来定义配置,首选的方式是使用properties文件。
二十、environments 环境变量
配置环境可以注册多个环境,每一个环境分为两大部分:一个是数据库源(dataSource)的配置,另外一个是数据库事务(transactionManager)的配置。
<environments default="dev">
<environment id="dev">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
</dataSource>
</environment>
</environments>
- default:表示默认使用哪个数据源
- id:表示数据源的名称
- transactionManager的事务类型type一共有三种:JDBC,采用JDBC方式管理事务,独立编码中我们常常使用;MANAGED,采用容器方式管理事务,在JNDI数据源中常用;自定义,由使用者自定义数据库事务管理办法,适用于特殊应用。
- property元素配置数据源的各类属性
- dataSource的type属性是提供我们对数据厍连接方式的配置:UNPOOLED(非连接池数据库)、POOLED(连接池数据库)、JNDI(JNDI数据源)、自定义数据源。
二十一、数据源
MyBatis内部为我们提供了3种数据源的实现方式:
- UNPOOLED,使用org.apache.ibatis.datasource.unpooled.UnpooledDataSource实现。
- POOLED,使用org.apache.ibatis.datasource.pooled.PooledDataSource实现。
- JNDI,使用org.apache.ibatis.datasource.jndi.JndiDataSourceFactory实现。
二十二、数据库事务
数据库事务是交由SqlSession去控制的,可以通过SqlSession提交或者回滚。在大部分的工作环境下,都会使用 Spring框架来控制它。