MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生 SQL 查询,将接口和 Java 的实体类映射成数据库中的记录。
一、myBatis 优缺点
优点分析
- 灵活:MyBatis 不会对应用程序或数据库的现有设计强加任何限制。它使用简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO 为数据库中的记录。
- 易于学习:MyBatis 相对于其他 ORM 框架来说,学习曲线较低,因为它不需要开发人员学习新的领域特定语言(DSL)或复杂的 API。
- 性能:MyBatis 可以通过缓存和批量更新等技术来提高性能。
- 可定制性:MyBatis 提供了许多插件和扩展点,例如 Plugin 扩展点,它是用于拦截 MyBatis 执行前后操作的接口,可以通过实现该接口来自定义 MyBatis 的行为,这样开发者就可以根据需要进行定制。
缺点分析
MyBatis 有以下缺点:
- SQL 语句依赖:MyBatis 需要手动编写 SQL 语句,这意味着开发人员需要具备一定的 SQL 知识。此外,如果数据库模式发生变化,需要手动修改 SQL 语句,这可能会导致一些问题。
- XML 配置文件冗长:MyBatis 的配置文件通常比较冗长,这可能会导致一些维护问题。此外,如果使用注解配置,代码可能会变得混乱。
- 缺乏自动化创建:相比于其他 ORM 框架,MyBatis 缺乏自动化。例如,它不支持自动创建表和字段。
二、MyBatis 和 Hibernate 对比
- 对象关系映射(ORM)方式:Hibernate 是一个全自动的 ORM 框架,通过对象关系映射技术,将数据库表与 Java 对象之间的映射关系自动管理,不需要手动编写 SQL 语句。而 MyBatis 是一个半自动的 ORM 框架,需要开发者手动编写和管理 SQL 语句。
- 查询语言:Hibernate 使用 Hibernate Query Language(HQL)或 Criteria API 进行数据库查询,它们是面向对象的查询语言,类似于 SQL 语法。而 MyBatis 直接使用原生的 SQL 语句进行查询,可以更灵活地进行定制和优化。
- 学习曲线:由于 Hibernate 提供了全自动的 ORM 特性,对于开发者来说,学习成本相对较高,需要理解和掌握其复杂的特性和概念。相比之下,MyBatis 更接近于传统的 JDBC 编程模型,学习曲线相对较低,开发者可以更灵活地控制和优化 SQL 语句。
- 性能控制:由于 MyBatis 需要手动编写和优化 SQL 语句,开发者可以更精确地控制查询的性能。而 Hibernate 在某些情况下可能会生成复杂的 SQL 语句,性能方面可能不如 MyBatis 灵活。
MyBatis 和 Hibernate 都是优秀的持久层框架,它们的主要区别在于对象关系映射、查询语言、学习曲线、性能控制等方面。
三、MyBatis 的执行流程
- 读取配置文件:
- MyBatis 启动时,首先读取配置文件(通常是
mybatis-config.xml
),该文件包含了数据库连接信息、映射文件的位置、事务管理器配置、类型处理器等全局配置。
- MyBatis 启动时,首先读取配置文件(通常是
- 初始化 SqlSessionFactory:
- 根据配置文件创建
SqlSessionFactory
实例,它是 MyBatis 的核心工厂类,用于创建SqlSession
对象。
- 根据配置文件创建
- 创建 SqlSession:
- 通过
SqlSessionFactory.openSession()
方法打开一个新的SqlSession
,它代表和数据库的一次会话,用于执行 SQL 命令、获取映射器实例等。
- 通过
- 加载映射文件(Mapper XML 文件):
- MyBatis 会解析映射文件,这些文件定义了 SQL 查询语句以及如何将查询结果映射到 Java 对象。映射文件中的
<select|insert|update|delete>
标签对应了 SQL 语句。
- MyBatis 会解析映射文件,这些文件定义了 SQL 查询语句以及如何将查询结果映射到 Java 对象。映射文件中的
- Mapper 接口绑定:
- MyBatis 通过动态代理或者 cglib 技术,将 Mapper 接口与 XML 中定义的 SQL 映射关联起来,这样就可以通过接口方法调用执行 SQL。
- 执行 SQL:
- 通过
SqlSession
调用 Mapper 接口的方法,MyBatis 根据方法签名和映射配置,找到对应的 SQL 语句,并通过 JDBC 执行 SQL。
- 通过
- 参数处理:
- 在执行 SQL 之前,MyBatis 会对传入的参数进行类型转换和处理,这依赖于 TypeHandler。
- 结果映射:
- SQL 执行后,MyBatis 根据结果映射配置,将结果集中的每一行数据转换为对应的 Java 对象,并返回给调用者。
- 事务管理:
- SqlSession 提供了事务控制方法,如
commit()
和rollback()
,MyBatis 会根据配置管理事务的提交和回滚。
- SqlSession 提供了事务控制方法,如
- 资源清理:
- 使用完 SqlSession 后,需要通过
close()
方法关闭它,以释放数据库连接等资源。
- 使用完 SqlSession 后,需要通过
总之,MyBatis 的执行流程包括读取配置文件、创建 SqlSessionFactory、创建 SqlSession、执行 SQL 语句和返回结果等步骤。
四、${}和
#{}
${}
和 #{}
在 MyBatis 中都是用于 SQL 参数替换的符号,它们的区别主要体现在以下几个方面:
- 功能不同:
${}
是直接替换,而#{}
是预处理; - 使用场景不同:普通参数使用
#{}
,如果传递的是 SQL 命令或 SQL 关键字,需要使用${}
,但在使用前一定要做好安全验证; - 安全性不同:使用
${}
存在安全问题,如 SQL 注入,而#{}
则不存在安全问题。
所以,${}
和 #{}
在 MyBatis 中都是用于 SQL 参数替换的符号,然而使用 ${}
可能存在安全问题,所以在能用 #{}
时,尽量使用 #{}
时,否则可以考虑在充分验证了参数安全之后使用 ${}。
五、SQL 注入
SQL 注入即是指应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在应用程序中事先定义好的查询语句的结尾上添加额外的 SQL 语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息。 比如以下代码:
<select id="isLogin" resultType="com.example.demo.model.User">
select * from userinfo where username='${name}' and password='${pwd}'
</select>
sql 注入代码:“’ or 1='1”
六、动态 SQL
动态 SQL 是指可以根据不同的参数信息来动态拼接的不确定的 SQL 叫做动态 SQL,MyBatis 动态 SQL 的主要元素有:if、choose/when/otherwise、trim、where、set、foreach 等。
以 if 标签的使用为例:
<select id="findUser" parameterType="com.interview.entity.User" resultType="com.interview.entity.User">
select * from t_user where 1=1
<if test="id!=null">
and id = #{id}
</if>
<if test="username!=null">
and username = #{username}
</if>
<if test="password!=null">
and password = #{password}
</if>
</select>
当调用此方法时,如果传递 id 参数了,那么生成的 SQL 是这样的:
select * from t_user where 1=1 and id=n
如果不传递 id 参数,那么生成的 SQL 是这样的:
select * from t_user where 1=1
这就是动态 SQL,根据不同的参数生成不同的 SQL 语句。
七、MyBatis 二级缓存
MyBatis 二级缓存是 MyBatis 中非常重要的一个特性,它的作用是减少数据库的查询次数,提高系统性能。
二级缓存包含两级缓存:一级缓存和二级缓存。
- 一级缓存是 SqlSession 级别的,是 MyBatis 自带的缓存功能,并且无法关闭,因此当有两个 SqlSession 访问相同的 SQL 时,一级缓存也不会生效,需要查询两次数据库;
- 二级缓存是 Mapper 级别的,只要是同一个 Mapper,无论使用多少个 SqlSession 来操作,数据都是共享的,多个不同的 SqlSession 可以共用二级缓存,MyBatis 二级缓存默认是关闭的,需要使用时可手动开启,二级缓存也可以使用第三方的缓存,比如,使用 Ehcache 作为二级缓存。
一级缓存 VS 二级缓存
一级缓存和二级缓存的区别如下:
- 一级缓存是 SqlSession 级别的缓存,它的作用域是同一个 SqlSession,同一个 SqlSession 中的多次查询会共享同一个缓存。二级缓存是 Mapper 级别的缓存,它的作用域是同一个 Mapper,同一个 Mapper 中的多次查询会共享同一个缓存。
- 一级缓存是默认开启的,不需要手动配置。二级缓存需要手动配置,需要在 Mapper.xml 文件中添加 标签。
- 一级缓存的生命周期是和 SqlSession 一样长的,当 SqlSession 关闭时,一级缓存也会被清空。二级缓存的生命周期是和 MapperFactory 一样长的,当应用程序关闭时,二级缓存也会被清空。
- 一级缓存只能用于同一个 SqlSession 中的多次查询,不能用于跨 SqlSession 的查询。二级缓存可以用于跨 SqlSession 的查询,多个 SqlSession 可以共享同一个二级缓存。
- 一级缓存是线程私有的,不同的 SqlSession 之间的缓存数据不会互相干扰。二级缓存是线程共享的,多个 SqlSession 可以共享同一个二级缓存,需要考虑线程安全问题。
开启二级缓存
二级缓存默认是不开启的,我们需要手动开启二级缓存。 二级缓存开启需要两步:
- 在 mapper xml 中添加 标签;
- 在需要缓存的标签上设置 useCache=“true”。
完整示例实现如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mybatis.demo.mapper.StudentMapper">
<cache/>
<select id="getStudentCount" resultType="Integer" useCache="true">
select count(*) from student
</select>
</mapper>
MyBatis 中的设计模式
MyBatis 源码中的几个主要设计模式,即工厂模式、建造者模式、单例模式、适配器模式、代理模式、模板方法模式等,具体内容如下。
1.工厂模式
工厂模式想必都比较熟悉,它是 Java 中最常用的设计模式之一。工厂模式就是提供一个工厂类,当有客户端需要调用的时候,只调用这个工厂类就可以得到自己想要的结果,从而无需关注某类的具体实现过程。这就好比你去餐馆吃饭,可以直接点菜,而不用考虑厨师是怎么做的。
工厂模式在 MyBatis 中的典型代表是 SqlSessionFactory。SqlSession 是 MyBatis 中的重要 Java 接口,可以通过该接口来执行 SQL 命令、获取映射器示例和管理事务,而 SqlSessionFactory 正是用来产生 SqlSession 对象的,所以它在 MyBatis 中是比较核心的接口之一。
2.建造者模式
建造者模式指的是将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。也就是说建造者模式是通过多个模块一步步实现了对象的构建,相同的构建过程可以创建不同的产品。
例如,组装电脑,最终的产品就是一台主机,然而不同的人对它的要求是不同的,比如设计人员需要显卡配置高的;而影片爱好者则需要硬盘足够大的(能把视频都保存起来),但对于显卡却没有太大的要求,我们的装机人员根据每个人不同的要求,组装相应电脑的过程就是建造者模式。
建造者模式在 MyBatis 中的典型代表是 SqlSessionFactoryBuilder。普通的对象都是通过 new 关键字直接创建的,但是如果创建对象需要的构造参数很多,且不能保证每个参数都是正确的或者不能一次性得到构建所需的所有参数,那么就需要将构建逻辑从对象本身抽离出来,让对象只关注功能,把构建交给构建类,这样可以简化对象的构建,也可以达到分步构建对象的目的,而 SqlSessionFactoryBuilder 的构建过程正是如此。
3.单例模式
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一,此模式保证某个类在运行期间,只有一个实例对外提供服务,而这个类被称为单例类。
单例模式也比较好理解,比如一个人一生当中只能有一个真实的身份证号,每个收费站的窗口都只能一辆车子一辆车子的经过,类似的场景都是属于单例模式。单例模式在 MyBatis 中的典型代表是 ErrorContext。
ErrorContext 是线程级别的的单例,每个线程中有一个此对象的单例,用于记录该线程的执行环境的错误信息。
4.适配器模式
适配器模式是指将一个不兼容的接口转换成另一个可以兼容的接口,这样就可以使那些不兼容的类可以一起工作。 例如,最早之前我们用的耳机都是圆形的,而现在大多数的耳机和电源都统一成了方形的 typec 接口,那之前的圆形耳机就不能使用了,只能买一个适配器把圆形接口转化成方形的。
而这个转换头就相当于程序中的适配器模式,适配器模式在 MyBatis 中的典型代表是 Log。
MyBatis 中的日志模块适配了以下多种日志类型:
- SLF4J
- Apache Commons Logging
- Log4j 2
- Log4j
- JDK logging
5.代理模式
代理模式指的是给某一个对象提供一个代理对象,并由代理对象控制原对象的调用。
代理模式在生活中也比较常见,比如我们常见的超市、小卖店其实都是一个个“代理”,他们的最上游是一个个生产厂家,他们这些代理负责把厂家生产出来的产品卖出去。
代理模式在 MyBatis 中的典型代表是 MapperProxyFactory。
MapperProxyFactory 的 newInstance() 方法就是生成一个具体的代理来实现某个功能。
6.模板方法模式
模板方法模式是最常用的设计模式之一,它是指定义一个操作算法的骨架,而将一些步骤的实现延迟到子类中去实现,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。此模式是基于继承的思想实现代码复用的。
例如,我们喝茶的一般步骤都是这样的:
- 把热水烧开
- 把茶叶放入壶中
- 等待一分钟左右
- 把茶倒入杯子中
- 喝茶
整个过程都是固定的,唯一变的就是泡入茶叶种类的不同,比如今天喝的是绿茶,明天可能喝的是红茶,那么我们就可以把流程定义为一个模板,而把茶叶的种类延伸到子类中去实现,这就是模板方法的实现思路。
模板方法在 MyBatis 中的典型代表是 BaseExecutor,在 MyBatis 中 BaseExecutor 实现了大部分SQL 执行的逻辑。
7.装饰器模式
装饰器模式允许向一个现有的对象添加新的功能,同时又不改变其结构,这种类型的设计模式属于结构型模式,它是作为现有类的一个包装。
装饰器模式在生活中很常见,比如装修房子,我们在不改变房子结构的同时,给房子添加了很多的点缀;比如安装了天然气报警器,增加了热水器等附加的功能都属于装饰器模式。
装饰器模式在 MyBatis 中的典型代表是 Cache。
eExecutor 实现了大部分SQL 执行的逻辑。
7.装饰器模式
装饰器模式允许向一个现有的对象添加新的功能,同时又不改变其结构,这种类型的设计模式属于结构型模式,它是作为现有类的一个包装。
装饰器模式在生活中很常见,比如装修房子,我们在不改变房子结构的同时,给房子添加了很多的点缀;比如安装了天然气报警器,增加了热水器等附加的功能都属于装饰器模式。
装饰器模式在 MyBatis 中的典型代表是 Cache。
Cache 除了有数据存储和缓存的基本功能外(由 PerpetualCache 永久缓存实现),还有其他附加的 Cache 类,比如先进先出的 FifoCache、最近最少使用的 LruCache、防止多线程并发访问的 SynchronizedCache 等众多附加功能的缓存类。