MyBatis是什么?
Mybatis是一个优秀的持久层框架,也是一个半ORM(对象关系映射)框架,它内部封装了JDBC,开发时只需要关注SQL语句本身,不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程,提高了开发效率。
ORM是什么
ORM(Object Relational Mapping),对象关系映射,是一种为了解决关系型数据库数据与简单Java对象(POJO)的映射关系的技术。简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系型数据库中。一般情况下,一个持久化类和一个表对应,类的每个实例对应表中的一条记录,类的每个属性对应表的每个字段。
为什么说Mybatis是半自动ORM映射工具?
Mybatis在查询关联对象或关联集合对象时,需要手动编写sql来完成,所以称之为半自动ORM映射工具。
总结
传统的 jdbc 是手工的,需要程序员加载驱动、建立连接、创建 Statement 对象、定义SQL语句、处理返回结果、关闭连接等操作。
Hibernate 是自动化的,内部封装了JDBC,连 SQL 语句都封装了,理念是即使开发人员不懂SQL语言也可以进行开发工作,向应用程序提供调用接口,直接调用即可。
Mybatis 是半自动化的,是介于 jdbc 和 Hibernate之间的持久层框架,也是对 JDBC 进行了封装,不过将SQL的定义工作独立了出来交给用户实现,负责完成剩下的SQL解析,处理等工作。
Mybatis的实现机制/MyBatis编程步骤是什么样的?
Mybatis的实现机制
1.读取 Mybatis的全局配置文件 mybatis-config.xml并加载映射文件
2.创建 SqlSessionFactory 对象。
3.通过 SqlSessionFactory 获取 SqlSession 对象。
4.通过SqlSession实例直接运行映射的sql语句,执行数据库操作。
5.执行成功,则使用 SqlSession 提交事务。
6.执行失败,则使用 SqlSession 回滚事务。
7.最终,关闭 session 会话。
mybatis-config.xml文件中包括一系列配置信息,其中包括标签 <mapper>,此标签配置了映射节点,映射节点内部定义了SQL语句。
Mybatis将 SQL的定义工作独立出来,让用户自定义,而 SQL的解析,执行等工作交由 Mybatis处理执行。
详细解释
MyBatis应用程序根据XML配置文件创建SqlSessionFactory,SqlSessionFactory在根据配置,配置来源于两个地方,一处是配置文件,一处是Java代码的注解,获取一个SqlSession。SqlSession包含了执行sql所需要的所有方法,可以通过SqlSession实例直接运行映射的sql语句,完成对数据的增删改查和事务提交等,用完之后关闭SqlSession。
Hibenate 的实现机制
1、构建 Configuration实例,初始化该实例中的变量
2、加载 hibenate.cfg.xml 文件到内存
3、通过 hibenate.cfg.xml 文件中的 mapping 节点配置并加载 xxx.hbm.xml 文件至内存
4、利用 Configuration实例构建 SessionFactory 实例
5、由SessionFactory 实例构建 session实例
6、由 session实例创建事务操作接口 Transaction 实例
7、执行查询操作
JDBC编程有哪些不足之处,MyBatis是如何解决这些问题的?
1、数据库连接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库连接池可解决此问题。
解决:在mybatis-config.xml中配置数据链接池,使用连接池管理数据库连接。
2、耦合度太高,sql是和java代码写在一起的,不方便后期的修改和维护
解决:将Sql语句配置在XXXXmapper.xml文件中与java代码分离。
3、向sql语句传参数麻烦, 占位符需要和参数一一对应。
解决: Mybatis自动将java对象映射至sql语句。
4、对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析比较方便。
解决:Mybatis自动将sql执行结果映射至java对象。
补充:
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个;数据库连接池其实就是一个容器(集合),存放着数据库连接。
连接池基本的思想是当用户需要访问数据库时,并非建立一个新的连接,而是先去连接池里看看有没有已经建立的空闲连接对象,如果有从连接池中取出一个进行使用,没有就新创建一个连接对象进行使用。使用完后,也并非将连接关闭,而是将连接放回连接池中,以供下一个请求访问使用。连接的建立、断开、销毁都由连接池自身来管理。同时,还可以通过设置连接池的参数来控制连接池中的初始连接数、连接的上下限数以及每个连接的最大使用次数、最大空闲时间等等。也可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。
Mybatis优缺点
优点
与传统的数据库访问技术相比,ORM有以下优点:
1、基于SQL语句编程,相当灵活
2、SQL写在XML里,解除sql与程序代码的耦合,便于统一管理;
3、提供XML标签, 支持编写动态SQL语句,并可重用
4、与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接,只要JDBC 支持的数据库MyBatis都支持
5、支持对象与数据库的ORM字段关系映射;
6、能够与Spring很好的集成
缺点
1、SQL语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求
2、SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库
MyBatis框架适用场景
MyBatis专注于SQL本身,是一个足够灵活的DAO层解决方案。
对性能的要求很高,或者需求变化较多的项目,如互联网项目,MyBatis将是不错的选择。因为Mybatis的sql调优方便,需求变化多是因为mybatis的sql语句和java代码是分开的,所以耦合度低,方便后期的修改和维护
请说说MyBatis的工作原理

MyBatis执行八步走
1、读取MyBatis的核心配置文件。mybatis-config.xml为MyBatis的全局配置文件,用于配置数据库连接、属性、类型别名、类型处理器、插件、环境配置、映射器(mapper.xml)等信息,这个过程中有一个比较重要的部分就是映射文件其实是配在这里的;这个核心配置文件最终会被封装成一个Configuration对象
2、加载映射文件。映射文件即SQL映射文件,该文件中配置了操作数据库的SQL语句,映射文件是在mybatis-config.xml中加载;可以加载多个映射文件。常见的配置的方式有两种,一种是package扫描包,一种是mapper找到配置文件的位置。
<!-- 使用包路径,扫描包下所有的接口,这种方式比较方便 -->
<package name="com.mybatis.demo"/>
<!-- resource:使用相对路径的资源引用-->
<!-- url:使用绝对类路径的资源引用-->
<!-- class:使用映射器接口实现类的完全限定类名-->
<mapper resource="xxx.xml"/>
3、构造会话工厂获取SqlSessionFactory。这个过程其实是用建造者设计模式使用SqlSessionFactoryBuilder对象构建的,SqlSessionFactory的最佳作用域是应用作用域。
//2. 创建SqlSessionFactory对象实际创建的是DefaultSqlSessionFactory对象
SqlSessionFactory builder = new SqlSessionFactoryBuilder().build(inputStream);
4、创建会话对象SqlSession。由会话工厂创建SqlSession对象,对象中包含了执行SQL语句的所有方法,每个线程都应该有它自己的 SqlSession 实例。SqlSession的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。
//3. 创建SqlSession对象实际创建的是DefaultSqlSession对象
SqlSession sqlSession = builder.openSession();
5、Executor执行器。是MyBatis的核心,负责SQL语句的生成和查询缓存的维护,它将根据SqlSession传递的参数动态地生成需要执行的SQL语句,同时负责查询缓存的维护
SimpleExecutor -- SIMPLE 就是普通的执行器。
ReuseExecutor-执行器会重用预处理语句(PreparedStatements)
BatchExecutor --它是批处理执行器
6、MappedStatement对象。MappedStatement是对解析的SQL的语句封装,一个MappedStatement代表了一个sql语句标签,如下:
<!--一个动态sql标签就是一个`MappedStatement`对象-->
<select id="selectUserList" resultType="com.mybatis.User">
select * from t_user
</select>
7、输入参数映射。输入参数类型可以是基本数据类型,也可以是Map、List、POJO类型复杂数据类型,这个过程类似于JDBC的预编译处理参数的过程,有两个属性 parameterType和parameterMap
8、封装结果集。可以封装成多种类型可以是基本数据类型,也可以是Map、List、POJO类型复杂数据类型。封装结果集的过程就和JDBC封装结果集是一样的。也有两个常用的属性resultType和resultMap。
/**
* Mybatis测试
*/
public class MybatisTest {
public static void main(String[]args) throws Exception {
// 1.加载配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//2. 创建SqlSessionFactory对象实际创建的是DefaultSqlSessionFactory对象
SqlSessionFactory builder = new SqlSessionFactoryBuilder().build(inputStream);
//3. 创建SqlSession对象实际创建的是DefaultSqlSession对象
SqlSession sqlSession = builder.openSession();
//4. 创建代理对象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//5. 执行查询语句
List<User> users = mapper.selectUserList();
//6. 释放资源
sqlSession.close();
inputStream.close();
}
}
通过分析Mybatis的执行流程,我们可以发现它和JDBC基本大同小异,比较明显的地方就是:
- 注册驱动获取链接的部分都抽取到了核心配置文件mybatis-config.xml中。
- sql语句抽取到了映射文件mapper.xml中。
至于其他的部分,如执行sql预编译、执行查询、封装结果集等都是抽取到了其他的类中来完成这些操作。通过对JDBC执行步骤来对比分析MyBatis的执行的流程,总体上来看它们的执行步骤基本是一样的,所以大家是不是觉得MyBatis这个框架其实也挺简单的,总结下其实就是:
加载解析配置文件(核心配置文件和映射文件)
处理参数
执行查询
封装结果集
MyBatis的功能架构(框架架构设计)是怎样的

Mybatis功能框架
我们把Mybatis的功能架构分为三层:
API接口层:提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。
数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。 它主要的目的是根据调用的请求完成一次数据库操作。
基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。
为什么需要预编译
定义
SQL 预编译指的是数据库驱动在发送 SQL 的参数给数据库管理系统DBMS 之前对 SQL 语句进行编译,这样 DBMS 执行 SQL 时,就不需要重新编译。
为什么需要预编译
防止SQL注入
预编译之后的 SQL 多数情况下可以直接执行,DBMS 不需要再次编译,越复杂的SQL,编译的复杂度将越大,节省性能(Mybatis的预编译会将编译后的SQL缓存起来,再次遇到相同的SQL会直接使用不会再次编译,Mybatis默认情况下会对所有的SQL进行预编译)
预编译阶段可以对sql语句进行优化;
Mybatis都有哪些Executor执行器?它们之间的区别是什么?
Mybatis有三种基本的Executor执行器,SimpleExecutor、ReuseExecutor、BatchExecutor。
SimpleExecutor:每执行一次update(这里包括下面的update是指更新,包括增删改)或select,就创建一个Statement对象, 用完立刻关闭Statement对象。(statement的重要作用就是设置sql参数然后执行sql)
ReuseExecutor:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map内,供下一次使用。简言之,就是重复使用Statement对象。
BatchExecutor:执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同。简单来说就是 BATCH 执行器将重用语句并执行批量更新。
作用范围:Executor的这些特点,都严格限制在SqlSession生命周期范围内。
Mybatis中如何指定使用哪一种Executor执行器?
在Mybatis配置文件中,在设置(settings)可以指定默认的ExecutorType执行器类型
可以在SqlSessionFactory的创建SqlSession时传递ExecutorType类型参数
SqlSession SqlSession=SqlSessionFactory.openSession(ExecutorType execType)配置默认的执行器
SIMPLE 就是普通的执行器;
REUSE 执行器会重用预处理语句(prepared statements);
BATCH 执行器将重用语句并执行批量更新。
ExecutorType介绍
mybatis提供三种sql执行器,分别是SIMPLE、REUSE、BATCH。
SIMPLE是默认执行器,根据对应的sql直接执行,不会做一些额外的操作。
REUSE是可重用执行器,重用对象是Statement(即该执行器会缓存同一个sql的Statement,省去Statement的重新创建,优化性能)(即会重用预处理语句)
BATCH执行器会重用预处理语句,并执行批量更新。
即ExecutorType有三种值,即SIMPLE、REUSE、BATCH。
ExecutorType执行效果
ExecutorType值为SIMPLE、REUSE,可通过insert、update、delete方法的返回值判断sql是否执行成功,返回非0表示执行sql成功的条数,返回0表示sql执行失败
ExecutorType值为BATCH,insert、update、delete方法返回值一直会是负数-2147482646,在该模式下insert、update、delete返回值将无任何意义,不能作为判断sql执行成功的判断依据
Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?
Mybatis仅支持association关联对象和collection关联集合对象的延迟加载, association指的就是一对一,collection指的就是一对多查询。
在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false
<settings>
<!--开启全局延迟加载功能-->
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
一对多例子
假设存在用户、订单两张表,可以查询用户(User)及用户对应的订单(Order)列表(一对多);用户信息作为主体,而订单信息不是立即需要获取到的情况下(也就是当我只是需要查询用户信息但是不是立马查询用户信息锁关联的所有订单信息的时候我就不需要把订单信息都查出来,只需要查用户信息就好),MyBatis提供延迟加载的策略,发送SQL执行语句时,只查询用户信息,当需要使用到订单信息时,即user.getOrderList()时,才会发送获取订单信息的SQL查询订单信息 (需要用到对应的信息时,才执行相关SQL);
一对一例子
public class Person {
private int age;
private String name;
private List<SonOrDaughter> children;
...snip getter setter tostring and so on...
}
正常的查询是将age,name,以及关联的children一次查询出来,但是,对于children的查询是需要额外的数据库交互的,并且实际业务场景中也许只是需要获取某个人的年龄而已,此时额外的一次数据库查询就是完全不必要的,因此就需要使用懒加载,那么懒加载原理是什么呢?答案是动态代理,只需要返回给用户的是一个PersonProxy就可以了,既然是懒加载,就要确定什么时候加载,什么时候加载其实就是确定什么时候用就可以了,这个用其实就是方法调用了,因此,只需要确定了哪些方法被调用认为是用了,比如调用getChildren方法代表要获取children集合了,此时就执行加载工作,这个拦截的工作在代理对象中完成,最终完成数据库操作,加载对象。
MyBatis延迟加载的本质:通过动态代理的形式,创建了目标对象(User)的代理对象,拦截了对象的getting方法,在执行getting方法时,进入拦截器的invoke方法,当发现需要延迟加载时,会把之前存放好的SQL语句进行执行,并调用对象(User)调用set方法存值,然后调用对象本身的get方法取值
扩展
什么是延迟加载?
延迟加载就是在需要⽤到数据时才进⾏加载,不需要⽤到数据时就不加载数据。延迟加载也称懒加载。
扩展
什么是延迟加载?
延迟加载就是在需要⽤到数据时才进⾏加载,不需要⽤到数据时就不加载数据。延迟加载也称懒加载。
延迟加载的缺点
因为只有当需要⽤到数据时,才会进⾏数据库查询,这样在⼤批量数据查询时,因为查询⼯作也要消耗时间,所以可能造成⽤户等待时间变⻓,造成⽤户体验下降。
模糊查询like语句该怎么写
(1)'%${question}%' 可能引起SQL注入所以不推荐
(2)CONCAT函数 推荐
<select id="selectDepts" parameterType="java.lang.String" resultType="Dept">
select * from tbl_dept where deptName like concat('%',#{name},'%')
</select>

(3) "%"#{question}"%"
注意:因为#{…}解析成sql语句时候,会在变量外侧自动加单引号 '',所以这里 % 需要使用双引号" ",不能使用单引号 ’ ',不然会查不到任何结果。
(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><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>
在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注解的作用是给参数命名,参数命名后就能根据名字得到参数值,正确的将参数传入sql语句中
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.test.User" resultMap="UserResultMap">
select * from user
where user_name = #{userName} and dept_id = #{deptId}
</select>
#{}里面的名称对应的是 User类里面的成员属性。
Mybatis如何执行批量操作
使用foreach标签
foreach的主要用在构建in条件中,它可以在SQL语句中进行迭代一个集合。foreach标签的属性主要有item,index,collection,open,separator, close。
1.item 表示集合中每一个元素进行迭代时的别名,随便起的变量名;
2.index指定一个名字,用于表示在迭代过程中,每次迭代到的位置,不常用;
3.open表示该语句以什么开始,常用“(”;
4.separator表示在每次进行迭代之间以什么符号作为分隔符,常用“,”;
5.close 表示以什么结束,常用“)”。
在使用foreach的时候最关键的也是最容易出错的就是collection属性,该属性是必须指定的,但是在不同情况下,该属性的值是不一样的,主要有一下3种情况:
1、如果传入的是单参数且参数类型是一个List的时候,collection属性值为list
2、如果传入的是单参数且参数类型是一个array数组的时候,collection的属性值为array
3、如果传入的参数是多个的时候,我们就需要把它们封装成一个Map了, 当然单参数也可以封装成map,实际上如果你在传入参数的时候,在MyBatis里面也是会把它封装成一个Map的,map的key就是参数名,所以这个时候collection属性值就是传入的List或array对象在自己封装的map里面的key
<!-- 接收list参数,循环着组装sql语句,注意for循环的写法
separator=";" 代表着每次循环完,在sql后面放一个分号 -->
<update id="updateForeachByUserId" parameterType="java.util.List">
<foreach collection="list" item="item" separator=";">
UPDATE lp_user_test_batch
SET
user_name = #{item.userName,jdbcType=VARCHAR},
user_age = #{item.userAge,jdbcType=INTEGER},
type = #{item.type,jdbcType=INTEGER},
update_time = #{item.updateTime,jdbcType=TIMESTAMP}
WHERE user_id = #{item.userId,jdbcType=VARCHAR}
</foreach>
</update>
当实体类中的属性名和表中的字段名不一样 ,怎么办 ?
第1种
通过在查询的sql语句中定义字段名的别名,让字段名的别名和实体类的属性名一致。
<select id=”selectorder” parametertype=”int” resultetype=”me.gacl.domain.order”>
select order_id id, order_no orderno ,order_price price form orders where
order_id=#{id};
</select>
第2种
通过<resultMap>来映射字段名和实体类属性名的一一对应的关系。
<select id="getOrder" parameterType="int" resultMap="orderresultmap">
select * from orders where order_id=#{id}
</select>
<resultMap type=”me.gacl.domain.order” id=”orderresultmap”>
<!–用id属性来映射主键字段–>
<id property=”id” column=”order_id”>
<!–用result属性来映射非主键字段,property为实体类属性名,column为数据表中的属性–>
<result property = “orderno” column =”order_no”/>
<result property=”price” column=”order_price” />
</reslutMap>