技术栈知识点巩固——Mybatis
MyBatis
-
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL 、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object ,普通的 Java 对象)映射成数据库中的记录。 -
MyBatis 专注于SQL 本身,是实体类与SQL 语句之间建立的映射关系(不是实体类与表),是一个足够灵活的dao 层优化方案,适用于性能要求高,需求多变的项目 。
Mybatis 工作原理

MyBatis框架的优点和缺点
优点
- 与
JDBC 相比,减少了50% 以上的代码量 - 最简单的持久化[框架、小巧简单易学
SQL 代码从程序代码中彻底分离出来,可重用- 提供
XML 标签,支持编写动态SQL - 提供映射标签,支持对象与数据库的
ORM 字段关系映射
缺点
SQL 语句编写工作量大,熟练度要高- 数据库移植性差,比如
Mysql 移植到Oracle ,SQL 语句会有差异,要适配各种数据库
Mybatis 中 #{}和 ${}的区别是什么?
什么是SQL注入 ,如何避免。
- 恶意拼接查询
- 传入非法参数
- 过滤输入内容就是在数据提交到数据库之前,就把用户输入中的不合法字符剔除掉。
- 在使用参数化查询的情况下,数据库服务器不会将参数的内容视为 SQL 语句的一部分来进行处理,而是在数据库完成 SQL 语句的编译之后,才套用参数运行。
说一下 mybatis 的一级缓存和二级缓存
- 一级缓存:默认是开启的,
sql session 级别的。 - 二级缓存:默认是关闭的,是
Mapper 级别的。
MyBatis 定义的接口,怎么找到实现的?
-
Mapper 接口添加到MapperRegistry 中的一个HashMap 中。用Mapper 接口的Class 对象作为Key ,用Mapper 接口的 Class 对象作为 Key , 以一个携带Mapper 接口作为属性的MapperProxyFactory 实例作为value 。 -
在session.getMapper() 的时候,会生成一个Mapper 接口的代理对象、Mybatis 为了完成 Mapper 接口的实现,运用了Jdk 代理模式。 -
在代理类执行相应的方法的时候,会于数据库进行交互操作,进行增删改查。得到ResultSet ,解析ResultSet 映射成JavaBean 。 -
简易版Mybatis 请看:https://blog.csdn.net/qq_37248504/article/details/108371768
Mybatis的底层实现原理

- 构建
SqlSessionFactory 通过xml 方式、或者通过Java 代码方式。 - 从
SqlSessionFactory 中获取SqlSession 。 - 从
SqlSession 中获取Mapper - 调用
Mapper 的方法。 - 详细内容请见:https://blog.csdn.net/qq_37248504/article/details/108352168
PageHelper分页插件的使用
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
PageHelper.startPage(pageNum,pageSize);
- 原理解释:
Spring Boot 在启动的时候,为 SqlSessionFactory 添加了 PageInterceptor ,这个拦截器会在 SQL 执行之前添加分页信息,分页信息是通过 Page 对象传递到当前线程中的,查询完成之后,会将查询的结果加上分页信息封装成 PageInfo 对象传给前端来解析。
Mybatis 代码生成器插件
<build>
<plugins>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.6</version>
<configuration>
<configurationFile>src/main/resources/mybatis/generator-config.xml</configurationFile>
<overwrite>true</overwrite>
</configuration>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<context id="MySQLTables" targetRuntime="MyBatis3">
<plugin type="org.mybatis.generator.plugins.EqualsHashCodePlugin"/>
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3307/test?useUnicode=yes&characterEncoding=UTF-8&useSSL=false"
userId="root"
password="root">
</jdbcConnection>
<javaModelGenerator targetPackage="com.li.springbootproject.domain.test" targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
<property name="trimStrings" value="true"/>
</javaModelGenerator>
<sqlMapGenerator targetPackage="mapper.test" targetProject="src/main/resources">
<property name="enableSubPackages" value="true"/>
</sqlMapGenerator>
<javaClientGenerator type="XMLMAPPER" targetPackage="com.li.springbootproject.mapper.test"
targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
</javaClientGenerator>
<table schema="test" tableName="table_class" domainObjectName="TableClazzDO"></table>
</context>
</generatorConfiguration>
Mybatis批量插入返回主键
- 在
Mysql 数据库中支持批量插入,所以只要配置useGeneratedKeys 和keyProperty 就可以批量插入并返回主键了。
Mybatis Executor

BaseExecutor :实现了Executor 的全部方法,包括对缓存,事务,连接提供了一系列的模板方法, 这些模板方法中留出来了四个抽象的方法等待子类去实现SimpleExecutor : 特点是每次执行完毕后都会将创建出来的statement 关闭掉,他也是默认的执行器类型ReuseExecutor : 在它在本地维护了一个容器,用来存放针对每条sql 创建出来的statement ,下次执行相同的sql 时,会先检查容器中是否存在相同的sql ,如果存在就使用现成的,不再重复获取BatchExecutor : 特点是进行批量修改,她会将修改操作记录在本地,等待程序触发提交事务,或者是触发下一次查询时,批量执行修改
Mybatis动态Sql
<if> :条件语句<where> :当和if 标签配合的时候,不用显示的声明类似where 1=1 这种无用的条件<choose><when><otherwise> :选择分支<foreach> :遍历传入的集合对象<include> :减少代码的重写<set> :更新语句<trim> :格式化标签
动态Sql 执行原理
- 构建
SqlSessionFactory 对象时,解析mapper.xml 文件,XMLStatementBuilder.parseStatementNode() 根据不同的SQL 标签生成对应的MappedStatement 对象,存放在Configuration.mappedStatements 集合中。 然后遍历主标签里所有节点生成对应的SQL 子节点对象(SqlNode )。XMLScriptBuilder.parseScriptNode() 解析每个标签生成SqlNode 对象。 - 执行
Executor.query() 方法,根据keyStatementId 获取到对应的MappedStatement 对象,获取BoundSql 对象时。遍历当前SqlSource.rootSqlNode 对象。执行SqlNode.apply() ,把动态sql 拼接到DynamicContext.sqlBuilder 字段中。 - 使用
sqlSourceParser.parse() 方法替换sql 中 ${} , #{} 为 ? ,并且把大括号中的值存放在 ParameterMappingTokenHandler.parameterMappings 集合中。
Mybatis分页方式
- 逻辑分页:使用
Mybatis 自带的RowBounds 进行分页,它是一次性查询很多数据,然后在数据中进行检索。一次性查询很多数据,如何在结果中检索分页的数据。这样做需要消耗大量的内存、有内存溢出的风险、对数据库压力较大。 - 物理分页:自己手写分页
sql 或使用分页插件PageHelper ,去数据库查询指定条数的分页数据。从数据库查询指定条数的数据,有效的防止了一次性全部查询出所有数据所带来风险。
当实体类属性名和表中的字段名不一样
- 在
Mapper 映射文件中使用resultMap 来自定义映射规则
Mapper接口
- 接口的全限名,就是映射文件中的
namespace 的值;接口的方法名,就是映射文件中 Mapper 的 Statement 的 id 值; 接口方法内的参数,就是传递给 sql 的参数。 Mapper 接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为key 值,可唯一定位一个 MapperStatement 。在 Mybatis 中,每一个标签,都会被解析为一个MapperStatement 对象。
Mapper接口里的方法
Mapper 接口里的方法,是不能重载的,因为是使用全限名+方法名的保存和寻找策略。Mapper 接口的工作原理是 JDK 动态代理,Mybatis 运行时会使用 JDK 动态代理为 Mapper 接口生成代理对象 proxy ,代理对象会拦截接口方法,转而执行 MapperStatement 所代表的 sql ,然后将 sql 执行结果返回。
Mybatis插件
Mybatis 可以编写针对Executor 、StatementHandler 、ParameterHandler 、ResultSetHandler 四个接口的插件,mybatis 使用JDK 的动态代理为需要拦截的接口生成代理对象,然后实现接口的拦截方法,所以当执行需要拦截的接口方法时,会进入拦截方法(AOP 面向切面编程的思想)
Mybatis where 标签
- 使用 Where 标签解决
where 1=1 and 这个问题
ExecutorType.BATCH
-
Mybatis 内置的ExecutorType 有3 种,默认为simple ,该模式下它为每个语句的执行创建一个新的预处理语句,单条提交sql ; 而batch 模式重复使用已经预处理的语句,并且批量执行所有更新语句,显然batch 性能将更优; -
batch 模式也有自己的问题,比如在Insert 操作时,在事务没有提交之前,是没有办法获取到自增的id ,这在某型情形下是不符合业务要求的
private void multiInsertWithSqlSession(List<OrgTempDO> list) {
SqlSessionFactory sqlSessionFactory = SpringContextUtils.getBean(SqlSessionFactory.class);
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
InsertTestMapper mapper = sqlSession.getMapper(InsertTestMapper.class);
List<List<OrgTempDO>> lists = splitList(list, splitSize);
StopWatch stopWatch = new StopWatch("totalTask");
stopWatch.start();
for (List<OrgTempDO> orgTempList : lists) {
mapper.insertOrgTempBatch(orgTempList);
}
sqlSession.commit();
sqlSession.close();
stopWatch.stop();
System.out.println("写入 150000 条执行耗时:{}" + stopWatch.getTotalTimeMillis());
}
|