Mybatis的定义
MyBatis 是 Java 生态中非常著名的一款 ORM 框架,目前在一线互联网大厂中应用广泛,MyBatis已经成为了一个必会框架。
Mybatis的作用
解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。
提高了可维护性:因为SQL和代码的分离,sql写在xml里,便于统一管理和优化。
Mybatis与Hibernate的区别
1.SQL优化对比
Hibernate不需要编写大量的 SQL就可以完全映射,此外还提供 HQL(Hibernate Query Language)对 POJO 进行操作,查询时会查询所有字段,会有不必要的性能消耗。
MyBatis手动编写 SQL,所以调整方便,是针对响应的场景设计的 SQL,更灵活、 可控性更好、 更优化,特别适合很多大型网站需要上线检查SQL语句的场景。
2.开发速度对比
Hibernate的学习成本要比MyBatis高,MyBatis框架相对容易上手。
比如:
一个项目中用到最基本的增删改查,这样选择Hibernate的效率就要高些了,因为基本的SQL语句已经被封装好,可以节省大量时间。
但是对于一个大型项目复杂的查询语句较多,使用MyBatis就会加快很多,而且SQL语句管理也会很方便。
3.优劣势对比
Hibernate 优势
- Hibernate的DAO层开发比MyBatis简单,MyBatis 需要维护 SQL 和结果映射;
- Hibernate 数据库移植性很好,MyBatis 的数据库移植性不好,不同的数据库需要写不同 SQL;
- Hibernate 有更好的二级缓存机制,可以使用第三方缓存,MyBatis 本身提供的缓存机制不佳;
MyBatis优势
- MyBatis 可以进行更为细致的 SQL 优化,可以减少查询字段;
- MyBatis 容易掌握,而 Hibernate 门槛较高;
4.开发方面对比
MyBatis 是一个半自动映射的框架,因为 MyBatis 需要手动匹配 POJO、SQL 和映射关系;
Hibernate 是一个全表映射的框架,只需提供 POJO 和映射关系即可;
Mybatis的原理
MyBatis原理,如下图所示 :
上面中流程就是MyBatis内部核心流程,一共会经历8大步骤,下面我会详解8大步骤。
第一步:读取MyBatis配置文件
MyBatis-config.xml为MyBatis的全局配置文件,用于配置数据库连接信息。
如下所示:
<configuration> <!-- 和spring整合后 environments配置将废除 --> <environments default="development"> <environment id="development"> <!-- 使用jdbc事务管理 --> <transactionManager type="JDBC" /> <!-- 数据库连接池 --> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/test?characterEncoding=utf-8" /> <property name="username" value="root" /> <property name="password" value="123456" /> </dataSource> </environment> </environments> <mappers> <mapper resource="sqlMapper/userMapper.xml"/> </mappers> </configuration>
mapper.xml:sql的映射文件,配置了操作数据库的sql语句,此文件需在config.xml中加载。
比如:加载用户映射信息UserMapper.xml。
<mappers> <mapper resource="sqlMapper/userMapper.xml"/> </mappers>
第二步:加载映射文件
SQL的映射文件,配置了操作数据库的SQL语句,需要在MyBatis配置文件MyBatis-config.xml中加载。
MyBatis-config.xml 文件可以加载多个映射文件,每个文件对应数据库中的一张表。
如下所示:
<mappers> <mapper resource="sqlMapper/userMapper.xml"/> </mappers>
MyBatis-config.xml 加载用户表,映射文件为:UserMapper.xml。
第三步:构造会话工厂
通过MyBatis的环境配置信息构建会话工厂SqlSessionFactory,用来开启SqlSession。
SqlSessionFactory是MyBatis框架中的一个接口,主要负责MyBatis框架初始化操作及为开发人员提供SqlSession对象。
它有两个实现类:DefaultSqlSessionFactory和SqlSessionManager,其中SqlSessionManager已被废弃。
在Mybtis初始化的过程中首先会读取MyBatis的核心配置文件,一般创建SqlSessionFactory的代码如下:
//使用 SqlSessionFactoryBuilder 构建 SqlSessionFactory SqlSessionFactory SqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
SqlSessionFactoryBuilder通过Configuration对象生成SqlSessionFactory,用来开启SqlSession。
第四步:创建会话对象
由会话工厂创建SqlSession对象,该对象中包含了执行SQL语句的所有方法,SqlSession对象完成和数据库的交互。
SqlSession提供select、insert、update、delete方法,用于完成和数据库的交互。
第五步:Executor执行器
MyBatis底层定义了一个Executor接口来操作数据库,它将根据SqlSession传递的参数动态地生成需要执行的SQL语句,同时负责查询缓存的维护。
Execute执行器起到至关重要的作用,它是真正执行Java与数据库交互的东西,参与了整个SQL查询执行过程中。
主要有三种执行器:
- 简易执行器SIMPLE(不配置就是默认执行器);
- REUSE是一种重用预处理语句;
- BATCH批量更新批量专用处理器
public enum ExecutorType { SIMPLE, REUSE, BATCH }
第六步:MappedStatement对象
在Executor接口的执行方法中有一个MappedStatement类型的参数,该参数是对映射信息的封装,用于存储要映射的SQL语句的id、参数等信息。
在Executor中doQuery()方法返回了封装的结果集,如下所示:
@Override public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); stmt = prepareStatement(handler, ms.getStatementLog()); return handler.<E>query(stmt, resultHandler); } finally { closeStatement(stmt); } }
第七步:输入参数映射
输入映射,是在映射文件中通过parameterType指定输入参数的类型,类型可以是简单类型、hashmap、pojo的包装类型。
假设现在有个比较复杂的查询需求:完成用户信息的综合查询,需要传入查询条件很复杂。
可能包括:用户信息、其它信息,比如:商品、订单等,那么我们单纯的传入一个User就不行了,所以首先我们得根据查询条件,自定义一个新的pojo,在这个pojo中包含所有的查询条件。
package com.MyBatis.entity; public class UserQueryVo { //在这里添加所需要的查询条件 //用户查询条件,这里假设一个User就已经够了 private User user; public User getUser() { return user; } public void setUser(User user) { this.user = user; } //可以包装其他的查询条件,比如订单、商品等 //...... }
第八步:输出结果映射
使用resultType进行输出映射,输出结果类型可以是Map、List等集合类型,只有查询出来的列名和POJO中的属性名一致,该列才可以映射成功。
拆解,后续我将重点分析其核心源码,这样先全局再局部,这样更有利于掌握其核心原理实现,希望这个框架系列能对你有所用。
Mybatis的缓存
缓存就是内存中的数据,常常来自对数据库查询结果的保存,使用缓存可以避免频繁与数据库进行交互,从而提高查询响应速度。
MyBatis 缓存分为:一级缓存和二级缓存,如下图所示:
1.MyBatis一级缓存
MyBatis一级缓存是MyBatis提供的一种缓存机制,即在同一会话中MyBatis会自动将第一次查询的结果集缓存,后续的同一查询不再调用数据库查询,大大提升了效率。
MyBatis一级缓存是session级别的缓存,生命周期是一次会话,session关闭一级缓存就失效了。
MyBatis在Executor中创建了本地缓存(一级缓存),如下图所示:
大致的流程如下:
第一次查询用户id信息,先去缓存中查询是否有,如果没有,从数据库中查询用户信息,得到用户信息后在将用户信息储存到一级缓存中。
如果sqlSession去执行commit操作(插入、更新、删除),清空sqlSession中的一级缓存,保证缓存中始终保存的是最新的信息,避免脏读。
第二次查询用户id信息,先去缓存中查询,如缓存中有,直接从缓存中获取。
注意:两次查询须在同一个sqlsession中完成,否则将不会走MyBatis的一级缓存。
2.MyBatis二级缓存
二级缓存是mapper级别的缓存,同一个namespace公用这一个缓存,所以对SqlSession是共享的,二级缓存需要我们手动开启。
开启二级缓存后,会使用 CachingExecutor 装饰 Executor,进入一级缓存的查询流程前,先在 CachingExecutor 进行二级缓存的查询,具体的工作流程如下所示。
二级缓存开启后,同一个 namespace 下的所有操作语句,都影响着同一个 Cache,即二级缓存被多个 SqlSession 共享,是一个全局的变量。
当开启缓存后,数据的查询执行的流程就是 二级缓存 -> 一级缓存 -> 数据库。
MyBatis 是默认关闭二级缓存的,因为对于增删改操作频繁的话,那么二级缓存形同虚设,每次都会被清空缓存。
Mybatis配置文件
MyBatis配置文件主要分为两大类:MyBatis全局配置文件与MyBatis的mapper文件。
1.MyBatis全局配置
MyBatis-config.xml为MyBatis的全局配置文件,用于配置数据库连接信息。
MyBatis全局配置文件,格式如下:
<properties></properties><!-- 属性 --> <settings></settings> <!-- 设置--> <typeAliases></typeAliases><!-- 配置别名 --> <typeHandlers></typeHandlers><!-- 类型处理器 --> <objectFactory></objectFactory><!-- 对象工厂 --> <plugins></plugins><!-- 插件 --> <environments default=""><!-- 环境配置--> <environment id=""><!-- 环境变量 --> <transactionManager></transactionManager><!-- 事务管理器 --> <dataSource></dataSource><!-- 数据源 --> </environment> </environments> <databaseIdProvider></databaseIdProvider><!-- 数据库厂商标识 --> <mappers></mappers><!-- 映射器 -->
MyBatis全局配置示例如下:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <properties resource="database.properties"/> <environments default="MySqlDatabase" > <environment id="MySqlDatabase" > <transactionManager type="JDBC" /> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments> <mappers> <mapper resource="com/hxstrive/mybatis/dynamic_sql/demo5/UserMapper.xml" /> </mappers> </configuration>
2.Mappers配置
这个就是用来配置sql映射语句的xml文件了,mappers文件共有4种配置方式。
1、直接配置xml映射文件全限定名
<mappers>
<mapper resource="com/mikechen/mybatis/mapping/UserMapper.xml"/>
</mappers>
2、通过url配置本地或者远程映射文件路径
<mappers>
<mapper url="file://xxx.xxx.UserMapper.xml"></mapper>
</mappers>
123
3、通过配置Mapper接口的方式来配置
<mappers>
<mapper class="com.mikechen.mybatis.mapper.UserMapper"></mapper>
</mappers>
4、通过配置Mapper接口包名的形式来配置
<mappers>
<package name="com.mikechen.mybatis.mapper"/>
</mappers>
注意:在使用第3和第4两种方式配置的时候,需要把xml映射文件和Mapper接口文件放在同一个目录,而且必须同名
Mybatis的分页
MyBatis分页主要有:原生Limit分页、RowBounds分页、以及PageHelper插件等MyBatis分页方式。
原生SQL的Limit分页
1.Limit语法
SELECT * FROM table LIMIT stratIndex,pageSize
2.修改Mapper文件
<select id="selectUser" parameterType="map" resultType="user"> select * from user limit #{startIndex},#{pageSize} </select>
3.业务代码
在实际使用limit,是根据前端传进来页码和每页的条数,在sql使用limit进行查询。
//分页查询 , 两个参数startIndex , pageSize @Test public void testSelectUser() { SqlSession session = MybatisUtils.getSession(); UserMapper mapper = session.getMapper(UserMapper.class); int currentPage = 1; //第几页 int pageSize = 2; //每页显示几个 Map<String,Integer> map = new HashMap<String,Integer>(); map.put("startIndex",(currentPage-1)*pageSize); map.put("pageSize",pageSize); List<User> users = mapper.selectUser(map); for (User user: users){ System.out.println(user); } session.close(); }
使用PageHelper插件分页
PageHelper还是很好用的,主要分为如下三大步骤。
第一步:导入分页插件依赖
<!-- pagehelper 分页插件--> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.2.0</version> </dependency> <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.76</version> </dependency>
第二步:在mybaits-config.xml配置使用分页插件
<plugins> <!-- 配置mybatis分页插件PageHelper --> <!-- com.github.pagehelper为PageHelper类所在包名 --> <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin> </plugins>
第三步:业务层代码
@Test public void selectUserPageHelper() { SqlSession session = MybatisUtils.getSession(); UserMapper mapper = session.getMapper(UserMapper.class); //第二种,Mapper接口方式的调用,推荐这种使用方式。 PageHelper.startPage(1, 3); List<User> list = mapper.getUserInfo(); //用PageInfo将包装起来 PageInfo page = new PageInfo(list); for (User map: list){ System.out.println(map); } System.out.println("page:---"+page); session.close(); }
Mybatis使用场景
MyBatis适合数据量大(千万级),高并发的场景,表关联复杂度比较大,SQL需要优化的场景,项目要求对于数据库可控性好, 可深度调优。
陈睿mikechen
10年+大厂架构经验,资深技术专家,就职于阿里巴巴、淘宝、百度等一线互联网大厂。
关注「mikechen」公众号,获取更多技术干货!
后台回复【面试】即可获取《史上最全阿里Java面试题总结》,后台回复【架构】,即可获取《阿里架构师进阶专题全部合集》