一、定义
MyBatis是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
官网:https://mybatis.org/mybatis-3/zh_CN/index.html
二、核心概念
1、SqlSessionFactoryBuilder
2、SqlSessionFactory
3、SqlSession
4、插件
(1)定义
MyBatis允许通过使用插件在映射语句执行过程中的某一点进行拦截。
默认情况下,MyBatis允许使用插件来拦截的方法调用有:
A、Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)(执行器拦截器)
允许拦截和自定义MyBatis执行器的行为。例如,可以添加缓存、日志记录或审计功能到执行器中。这些拦截器可以在MyBatis执行的不同阶段扩展或修改其行为。您可以通过实现MyBatis提供的相应接口并在MyBatis配置文件中进行配置来实现这些拦截器
B、ParameterHandler (getParameterObject, setParameters)(参数拦截器)
允许在将参数设置到SQL语句之前修改或验证它们。例如,可以对作为参数传递的敏感信息进行加密或解密。
C、ResultSetHandler (handleResultSets, handleOutputParameters)(结果集拦截器)
可以在将结果集返回给应用程序之前修改或分析它们。例如,可以对结果集数据进行转换或执行额外的计算。
D、StatementHandler (prepare, parameterize, batch, update, query)(语句拦截器)
可以在SQL语句执行之前修改或增强它们。例如,可以向WHERE子句添加额外的条件或记录执行的语句。分页等
(2)自定义插件
在MyBatis中,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可对某个方法进行拦截。
A、Executor方法的拦截
示例代码:对query执行过程的拦截
@Intercepts({@Signature(type= Executor.class,method="query",args={MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class })})
public class QueryExecutorPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 执行query方法
Object obj=invocation.proceed();
// 修改执行结果
List<Object> list= JSONUtil.parseArray(obj);
list.add(new Category().setName("测试插件1"));
return list;
}
@Override
public Object plugin(Object target) {
// 包装目标对象的:包装:为目标对象创建一个代理对象
return Interceptor.super.plugin(target);
}
@Override
public void setProperties(Properties properties) {
// 将插件注册时的property属性设置进来
Interceptor.super.setProperties(properties);
}
}
B、StatementHandler方法的拦截
示例代码:对sql语句的修改
@Intercepts({@Signature(type = StatementHandler.class,method ="prepare",args = {Connection.class,Integer.class})})
public class StatementPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler= PluginUtils.realTarget(invocation.getTarget());
MetaObject metaObject= SystemMetaObject.forObject(statementHandler);
BoundSql boundSql= (BoundSql) metaObject.getValue("delegate.boundSql");
String originSql=boundSql.getSql();
System.out.println("原始sql:"+originSql);
// 对原始sql进行改写
metaObject.setValue("delegate.boundSql.sql",originSql.replace("?","8888888"));
return invocation.proceed();
}
}
或者
@Intercepts({@Signature(type = StatementHandler.class,method ="prepare",args = {Connection.class,Integer.class})})
public class StatementPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler= (StatementHandler) invocation.getTarget();
BoundSql boundSql= statementHandler.getBoundSql();
String originSql=boundSql.getSql();
System.out.println("原始sql:"+originSql);
// 直接对原始sql进行改写
ReflectUtil.setFieldValue(boundSql,"sql",originSql.replace("?","9878948"));
return invocation.proceed();
}
}
C、ParameterHandler方法的拦截
@Intercepts({@Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class)})
public class ParameterPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 修改参数值
return null;
}
}
D、ResultSetHandler方法的拦截
@Intercepts({@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = Statement.class)})
public class ResultSetPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 修改结果集
return null;
}
}
E、测试
a、添加插件
public class MyBatisConfig {
@Bean
public SqlSessionFactory sqlSessionFactory(){
DriverManagerDataSource dataSource=new DriverManagerDataSource();
dataSource.setUrl("jdbc:mysql://xxx:xxx/xxx?characterEncoding=utf-8&useSSL=false");
dataSource.setUsername("xxx");
dataSource.setPassword("xxx");
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
org.apache.ibatis.session.Configuration config=new org.apache.ibatis.session.Configuration(environment);
config.addMapper(CategoryMapper.class);
// 添加插件
config.addInterceptor(new QueryExecutorPlugin());
config.addInterceptor(new ParameterPlugin());
config.addInterceptor(new StatementPlugin());
return new SqlSessionFactoryBuilder().build(config);
}
}
b、测试代码
public class TestMyBatis {
public static void main(String[] args) {
AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(MyBatisConfig.class);
SqlSessionFactory factory= (SqlSessionFactory) context.getBean("sqlSessionFactory");
try(SqlSession sqlSession=factory.openSession()){
CategoryMapper categoryMapper=sqlSession.getMapper(CategoryMapper.class);
categoryMapper.insert(new Category().setName("222222"));
List<Category> list=categoryMapper.selectList();
System.out.println(JSONUtil.toJsonPrettyStr(list));
sqlSession.commit();// 默认mybatis是不会自动提交的,需要手动自动提交修改才会生效
}
}
}
(3)分页插件
1>、定义
引入该分页插件对返回数据进行分页。
官网:如何使用分页插件 (pagehelper.github.io)
2>、使用
A、引入依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.8</version>
</dependency>
B、编写配置
@Bean
public SqlSessionFactory sqlSessionFactory(){
DriverManagerDataSource dataSource=new DriverManagerDataSource();
dataSource.setUrl("xxx");
dataSource.setUsername("xx");
dataSource.setPassword("xxx");
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
org.apache.ibatis.session.Configuration config=new org.apache.ibatis.session.Configuration(environment);
config.addMapper(CategoryMapper.class);
// 【关键】添加分页插件
Properties properties=new Properties();
properties.setProperty("helperDialect","mysql");// 指定分页插件使用哪种方言
properties.setProperty("reasonable","true");// 分页合理化参数,默认值为false。当该参数设置为 true 时,pageNum<=0 时会查询第一页, pageNum>pages(超过总数时),会查询最后一页。默认false 时,直接根据参数进行查询
properties.setProperty("supportMethodsArguments","true");//支持通过 Mapper 接口参数来传递分页参数
properties.setProperty("params","count=countSql");// 为了支持startPage(Object params)方法,增加了该参数来配置参数映射,用于从对象中根据属性名取值
PageInterceptor pageInterceptor=new PageInterceptor();
pageInterceptor.setProperties(properties);
config.addInterceptor(pageInterceptor);
return new SqlSessionFactoryBuilder().build(config);
}
C、测试代码
public static void main(String[] args) {
AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(MyBatisConfig.class);
SqlSessionFactory factory= (SqlSessionFactory) context.getBean("sqlSessionFactory");
try(SqlSession sqlSession=factory.openSession()){
CategoryMapper categoryMapper=sqlSession.getMapper(CategoryMapper.class);
categoryMapper.insert(new Category().setName("222222"));
List<Category> list=categoryMapper.selectList();
System.out.println(JSONUtil.toJsonPrettyStr(list));
System.out.println("---------分页查询:方式1:RowBounds(不需要引入依赖)----------");
List<Category> listPage=categoryMapper.selectListPage(new RowBounds(0,1));
System.out.println(JSONUtil.toJsonPrettyStr(listPage));
System.out.println("---------分页查询:方式2:PageHelper----------");
PageHelper.startPage(1,2);
List<Category> list2=categoryMapper.selectList();
System.out.println(JSONUtil.toJsonPrettyStr(list2));
sqlSession.commit();// 默认mybatis是不会自动提交的,需要手动自动提交修改才会生效
}
}
5、类型转换器TypeHandler
(1)常用的TypeHandler
(2)自定义TypeHandler
可以通过继承BaseTypeHandler来自定义TypeHandler
三、使用
1、Spring使用MyBatis
(0)数据库中已存在category表
(1)引入依赖
<!-- mybatis依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.16</version>
</dependency>
(2)编写配置类MyBatisConfig
@ComponentScan(basePackages = "org.example.mybatis")
@Configuration
public class MyBatisConfig {
@Bean
public SqlSessionFactory sqlSessionFactory(){
DriverManagerDataSource dataSource=new DriverManagerDataSource();
dataSource.setUrl("jdbc:mysql://xxx:xxx/xxx?characterEncoding=utf-8&useSSL=false");
dataSource.setUsername("xxx");
dataSource.setPassword("xxx");
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
org.apache.ibatis.session.Configuration config=new org.apache.ibatis.session.Configuration(environment);
config.addMapper(CategoryMapper.class);
return new SqlSessionFactoryBuilder().build(config);
}
}
(3)编写测试类
public class TestMyBatis {
public static void main(String[] args) {
AnnotationConfigApplicationContext context=new AnnotationConfigApplicationContext(MyBatisConfig.class);
SqlSessionFactory factory= (SqlSessionFactory) context.getBean("sqlSessionFactory");
try(SqlSession sqlSession=factory.openSession()){
CategoryMapper categoryMapper=sqlSession.getMapper(CategoryMapper.class);
categoryMapper.insert(new Category().setName("222222"));
List<Category> list=categoryMapper.selectList();
System.out.println(JSONUtil.toJsonPrettyStr(list));
sqlSession.commit();// 默认mybatis是不会自动提交的,需要手动自动提交修改才会生效
}
}
}
四、原理
1、分页插件PageHelper原理
(1)大体过程
PageHelper实现了一个分页拦截器:PageInterceptor。
(2)方法调用过程
- PageHelper.startPage(1,2)(1表示页码,2表示每页显示数量)
- startPage(pageNum, pageSize, DEFAULT_COUNT)(DEFAULT_COUNT表示是否进行count查询,默认为true)
- startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero)
- setLocalPage(page)(设置分页参数)
- LOCAL_PAGE.set(page)(Local_PAGE的声明:ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>(),所有这里是为每个线程都设置了一个page分页参数)
- setLocalPage(page)(设置分页参数)
- startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero)
- startPage(pageNum, pageSize, DEFAULT_COUNT)(DEFAULT_COUNT表示是否进行count查询,默认为true)
- PageInterceptor intercept
- if (!dialect.skip(ms, parameter, rowBounds))(判断是否需要分页)
- PageHelper skip
- Page page = pageParams.getPage(parameterObject, rowBounds)(获取 Page 参数)
- Page page = PageHelper.getLocalPage()
- Page page = pageParams.getPage(parameterObject, rowBounds)(获取 Page 参数)
- PageHelper skip
- resultList = ExecutorUtil.pageQuery(dialect, executor,ms, parameter, rowBounds, resultHandler, boundSql, cacheKey)(是,进行物理分页)
- resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql)(否则,rowBounds用参数值,不使用分页插件处理时,仍然支持默认的内存分页)
- if (!dialect.skip(ms, parameter, rowBounds))(判断是否需要分页)
参考地址:
【【Java面试】说说PageHelper分页的原理】 https://www.bilibili.com/video/BV1xy421a7rf/?share_source=copy_web&vd_source=8933594f62a26ba490437ea3b533d038
2、MyBatis的缓存机制
(1)定义
(2)一级缓存
A、定义
B、 失效场景
(3)二级缓存
A、定义
B、开启二级缓存的方式
C、二级缓存失效场景
3、MyBatis的延迟加载
(1)定义
(2)程序中设置延迟加载的方式
A、全局设置
B、局部设置
(3)原理
4、MyBatis的数据库连接池
(1)定义
(2)使用
5、MyBatis涉及的设计模式
(1)建造者模式
(2)工厂模式
(3)单例模式
(4)代理模式
(5)组合模式
(6)模版模式
(7)责任链模式
(8)装饰器模式
(9)适配器模式
6、MyBatis和Hibernate的区别
【【Java面试】说说MyBatis与Hibernate的区别】 https://www.bilibili.com/video/BV1ew411E7jJ/?share_source=copy_web&vd_source=8933594f62a26ba490437ea3b533d038
五、关于MyBatisPlus
1、定义
MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
2、特性
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
- 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
- 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
- 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
- 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
- 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
- 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
- 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
- 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
- 内置性能分析插件:可输出 SQL 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
- 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作