Mybatis

整理了有关mybatis框架的知识,方便查阅和学习

MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索等jdbc繁杂的过程代码。

Mybatis通过xml或注解的方式将要执行的各种statement(statement、preparedStatement、CallableStatement)配置起来,并通过java对象和statement中的sql进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射成java对象并返回。

Mybatis优势

JDBC 问题总结

JDBC编程步骤:

  1. 加载数据库驱动

  2. 创建并获取数据库连接

  3. 创建jdbc statement 语句对象

  4. 设置sql语句

  5. 设置sql语句参数(preparedStatement)

  6. 使用statement执行sql并获取结果

  7. 对sql执行结果进行解析处理

  8. 释放资源(resultSet、preparement、connection)

主要问题:

  1. 数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。

  2. Sql语句在代码中硬编码,造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。

  3. 使用preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不一定,可能多也可能少,修改sql还要修改代码,系统不易维护。

  4. 对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成pojo对象解析比较方便。

Mybatis VS Hibernate

Hibernate
Mybatis

以对象为中心的ORM

以数据库中心

不适合大吞吐量

极为方便的ORM操作,以及HQL

需要写SQL,较为冗余

简单查询支持好,但复杂SQL以及多表查询较差

复杂SQL支持好

学习成本高,SQL优化困难

学习成本低,针对原生SQL进行优化即可

内部实现复杂,拓展难度大

内部实现简单

随项目增长维护成本逐渐升高

维护成本相对较低

Mybatis和hibernate不同,它不完全是一个ORM框架,因为MyBatis需要程序员自己编写Sql语句,不过mybatis可以通过XML或注解方式灵活配置要运行的sql语句,并将java对象和sql语句映射生成最终执行的sql,最后将sql执行的结果再映射生成java对象。

Mybatis学习门槛低,简单易学,程序员直接编写原生态sql,可严格控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,例如互联网软件、企业运营类软件等,因为这类软件需求变化频繁,一但需求变化要求成果输出迅速。但是灵活的前提是mybatis无法做到数据库无关性,如果需要实现支持多种数据库的软件则需要自定义多套sql映射文件,工作量大。

Hibernate对象/关系映射能力强,数据库无关性好,对于关系模型要求高的软件(例如需求固定的定制化软件)如果用hibernate开发可以节省很多代码,提高效率。但是Hibernate的学习门槛高,要精通门槛更高,而且怎么设计O/R映射,在性能和对象模型之间如何权衡,以及怎样用好Hibernate需要具有很强的经验和能力才行。

总之,按照用户的需求在有限的资源环境下只要能做出维护性、扩展性良好的软件架构都是好架构,所以框架只有适合才是最好。

MyBatis架构

img
  1. mybatis配置,SqlMapConfig.xml,mybatis的全局配置文件,配置了mybatis的运行环境等信息。mapper.xml文件即sql映射文件,文件中配置了操作数据库的sql语句。此文件需要在SqlMapConfig.xml中加载。

  2. 通过mybatis环境等配置信息构造SqlSessionFactory即会话工厂

  3. 由会话工厂创建sqlSession即会话,操作数据库需要通过sqlSession进行。

  4. mybatis底层自定义了Executor执行器接口操作数据库,Executor接口有两个实现,一个是基本执行器、一个是缓存执行器。

  5. MappedStatement也是mybatis一个底层封装对象,它包装了mybatis配置信息sql映射信息等。mapper.xml文件中一个sql对应一个MappedStatement对象sql的id即是MappedStatement的id

  6. MappedStatement对sql执行输入参数进行定义,包括HashMap、基本类型、pojoExecutor通过MappedStatement在执行sql前将输入的java对象映射至sql中,输入参数映射就是jdbc编程中对preparedStatement设置参数。

  7. MappedStatement对sql执行输出结果进行定义,包括HashMap、基本类型、pojoExecutor通过MappedStatement在执行sql后将输出结果映射至java对象中,输出结果映射过程相当于jdbc编程中对结果的解析处理过程。

环境构建

官方网址:https://github.com/mybatis/mybatis-3/releases

  • mybatis-3.2.7.jar----mybatis的核心包

  • lib----mybatis的依赖包

  • mybatis-3.2.7.pdf----mybatis使用手册

添加maven依赖

mybatis默认使用log4j进行日志输出,需要在resources下添加log4j配置文件log4j.properties

核心配置文件编写:

在classpath/resources下创建SqlMapConfig.xml,如下:

配置sql映射文件

User.xml:

注意:

#{}表示一个占位符号,通过#{}可以实现preparedStatement向占位符中设置值,自动进行java类型和jdbc类型转换,#{}可以有效防止sql注入。#{}可以接收简单类型值或pojo属性值。 如果parameterType传输单个简单类型值,#{}括号中可以是value或其它名称。

${}表示拼接sql串,通过${}可以将parameterType 传入的内容拼接在sql中且不进行jdbc类型转换, ${}可以接收简单类型值或pojo属性值,如果parameterType传输单个简单类型值,${}括号中只能是value。

mysql的主键生成策略:

使用uuid:select uuid()

使用 ast_insert_id()寒素, select AST_INSERT_ID(),返回auto_increment自增列新记录id值。

书写Java代码

在sql映射文件中已经包含了增删改查的sql语句,现在可以通过代码完成增删改查的操作了。

查找多个:

插入:

更新:

删除:

selectOne查询一条记录,如果使用selectOne查询多条记录则抛出异常:

MyBatis 解决的问题

  1. 数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。

    解决:在SqlMapConfig.xml中配置数据链接池,使用连接池管理数据库链接。

  2. Sql语句写在代码中造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。

    解决:将Sql语句配置在XXXXmapper.xml文件中与java代码分离。

  3. 向sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需要和参数一一对应。

    解决:Mybatis自动将java对象映射至sql语句,通过statement中的parameterType定义输入参数的类型。

  4. 对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析比较方便。

    解决:Mybatis自动将sql执行结果映射至java对象,通过statement中的resultType定义输出结果的类型。

MyBatis常见类的作用

SqlSessionFactoryBuilder

SqlSessionFactoryBuilder用于创建SqlSessionFacotySqlSessionFacoty一旦创建完成就不需要SqlSessionFactoryBuilder了,因为SqlSession是通过SqlSessionFactory生产,所以可以将SqlSessionFactoryBuilder当成一个工具类使用,最佳使用范围是方法范围即方法体内局部变量。

SqlSessionFactory

是一个接口,接口中定义了openSession()的不同重载方法,SqlSessionFactory的最佳使用范围是整个应用运行期间,一旦创建后可以重复使用,通常以单例模式管理SqlSessionFactory。

SqlSession

SqlSession是一个面向用户的接口,封装了对数据库的操作,如:查询、插入、更新、删除等。

通过SqlSessionFactory创建SqlSession,而SqlSessionFactory是通过SqlSessionFactoryBuilder进行创建。

每个线程都应该有它自己的SqlSession实例。SqlSession的实例不能共享使用,它也是线程不安全的。因此最佳的范围是请求或方法范围。绝对不能将SqlSession实例的引用放在一个类的静态字段或实例字段中。

打开一个 SqlSession;使用完毕就要关闭它。通常把这个关闭操作放到 finally 块中以确保每次都能执行关闭。如下:

使用MyBatis开发DAO

使用MyBatis开发Dao有两种方式:

  1. 使用原始的Dao开发方式

  2. 使用Mapper接口开发方式

原始Dao开发

interface: UserDao

实现:

测试类:

原始Dao开发中存在以下问题:

  • Dao方法体存在重复代码:通过SqlSessionFactory创建SqlSession,调用SqlSession的数据库操作方法

  • 调用sqlSession的数据库操作方法需要指定statement的id,这里存在硬编码,不利于开发维护。

MyBatis模板工具类

如果你要添加Transaction,请修改Template类

Mapper动态代理方式

开发规范

Mapper接口开发方法只需要程序员编写Mapper接口(相当于Dao接口),由Mybatis框架根据接口定义创建接口的动态代理对象,代理对象的方法体同上边Dao接口实现类方法。

Mapper接口开发需要遵循以下规范:

  1. Mapper.xml文件中的namespaceMapper接口的类路径相同。

  2. Mapper接口方法名和Mapper.xml中定义的每个statement的id相同

  3. Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同

  4. Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同

定义Mapper接口

UserMapper.java:

Mapper.xml

加载映射文件

编写测试类

SqlMapConfig.xml

配置内容:

  • properties(属性)

  • settings(全局配置参数)

  • typeAliases(类型别名)

  • typeHandlers(类型处理器)

  • objectFactory(对象工厂)

  • plugins(插件)

  • environments(环境集合属性对象)

  • environment(环境子属性对象)

  • transactionManager(事务管理)

  • dataSource(数据源)

  • mappers(映射器)

properties

将数据库信息提取到 db.properties中,然后引入到myBatis的核心配置文件中进行配置

MyBatis 将按照下面的顺序来加载属性:

  • 在 properties 元素体内定义的属性首先被读取。

  • 然后会读取properties 元素中resource或 url 加载的属性,它会覆盖已读取的同名属性。

typeAliases

类型别名,MyBatis支持的类型别名:

别名
映射的类型

_byte

byte

_long

long

_short

short

_int

int

_integer

int

_double

double

_float

float

_boolean

boolean

string

String

byte

Byte

long

Long

short

Short

int

Integer

integer

Integer

double

Double

float

Float

boolean

Boolean

date

Date

decimal

BigDecimal

bigdecimal

BigDecimal

map

Map

作用:

在MyBatis中的类型属性可以使用一上的类型别名替代,比如parameterType="int"就同parameterType="java.lang.Integer"是一样的

好处,简化了开发

自定以类型别名:

类型别名的定义在MyBatis SqlMapConfig.xml的核心配置文件中!!!

mappers

配置映射器。

  • 使用相对类路径的资源:<mapper resource="sqlmap/User.xml" />

  • 使用mapper接口类路径:<mapper class="me.feathers.mapper.UserMapper"/>

  • 自动注册指定包下的所有mapper接口,<package name="me.feathers.mapper"/>

输入映射和输出映射

Mapper.xml映射文件中定义了操作数据库的sql,每个sql是一个statement,映射文件是mybatis的核心。

parameterType 输入类型

  1. 传递简单类型

  2. 传递pojo对象

  3. 传递pojo包装类型

传递pojo包装类型:

开发中通过pojo传递查询条件 ,查询条件是综合的查询条件,不仅包括用户查询条件还包括其它的查询条件(比如将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数。

UserVo (Value Object):

Mapper接口方法:

Mapper配置文件:

resultType 输出类型

  1. 输出简单类型

  2. 输出pojo类型

  3. 输出pojo列表

这里不再演示,请查看上面的代码。

resultMap

resultType可以将查询结果映射为pojo,但需要pojo的属性名和sql查询的列名一致方可映射成功。如果查询字段与pojo的属性名不一致,可以通过resultMap将字段名和属性名一一对应,完成映射。

<id/>标签:此属性表示查询结果集的唯一标识,非常重要。如果是多个字段为复合唯一约束则定义多个<id />

  • Property:表示User类的属性。

  • Column:表示sql查询出来的字段名。Column和property放在一块儿表示将sql查询出来的字段映射到指定的pojo类属性上。

  • <result />:普通结果,即pojo的属性。

如果列名和表名一致,可以不写。

动态sql

通过mybatis提供的各种标签方法实现动态拼接sql。

if

之前在传递参数到sql中时,如果参数为null,则会发生错误,为了避免查询参数不合法的情况,我们可以使用if进行有效性判断。

注意,字符串参数一定要进行空字符串判断

where

上面使用了1=1 来保证sql语句的完成性,我们也可以使用where标签替代。

备注:<where />可以自动处理第一个and。

foreach

当向sql传递数组或List,mybatis使用foreach进行解析:

sql片段:

Sql 片段

xml中的sql可能会发生大量重复的sql片段,我们可以将他们抽取到sql标签中,然后使用include引用这些sql片段。

抽出条件

使用include引用:

关联查询

一对一关联查询

预备工作

有两张表,分别为妻子和丈夫, 属于一对一的关系。要求根据丈夫查找对应的妻子。

方式一

一对一自动映射,试用resultType,定义夫妻信息po类,该类包含了丈夫和妻子的信息,即所要查的信息。

现在我们要查询所有的夫妻对,有如下sql语句:

定义sql映射:

这样就将所需要的信息封装到实体类中了。

测试类:

这种方式在开发中最为常用,不仅可以处理这种一对一的表关系,根据订单查用户也是属于一对一的查询,此种方式也是试用的。

方式二

一对一手动映射,定义一个resultMap,用于映射一对一查询结果。

sql语句仍然不变:select w.wife_name, w.age wife_age, hus.* from husband hus join wife w on w.hus_id = HUS.hus_id;

association:表示进行关联查询单条记录

property:表示将关联查询数据库column列的值放入到property属性中

javaType:表示关联查询的结果类型析

一对多级联查询

一对多关联映射同一对一关联映射的方式二相同,试用自定义的ResultMap

缓存

Mybatis的缓存是针对SQL查询结果的缓存,如果执行了两个完全相同的SQL(同一个SQL,同样的SQL参数),在有缓存的情况下,将不会再执行一次SQL,而是直接使用缓存的结果。

一级缓存

一级缓存是是SqlSession级别的缓存,也就是说,如果Mapper处于同一个namespace下,并且处于同一个SqlSession下,缓存才会生效。

在一级缓存下,所有的缓存数据存储到SqlSession对象中。

image-20220307193905552

一级缓存的原理:

  1. 实际缓存结构是一个map,放置在SqlSession中

  2. 当有一个SQL需要缓存时,将会按照mapperID + offset + limit + sql + sql所有参数的形式生成key

  3. 使用这个key存储到map中,其值为查询结果

  4. 当同一个SqlSession再次发出相同的SQL,就从缓存中取出数据。

  5. 如果两次相同的操作中存在commit操作(包括增、删、改),SqlSession的一级缓存区域将会被全部清空。下次再去缓存中查询不到,将会从数据库查询。

二级缓存

**二级缓存是以namespace为标记的缓存,可以是由一个SQLSessionFactory创建的SqlSession之间共享的缓存。**默认不开启。

二级缓存是Mapper级别的

在二级缓存下,每个namespace都会生成一个Cache对象,独立于SqlSession,并与所有的SqlSession共享。

image-20220307194041014

开启二级缓存(总开关):

分开关,在要开启二级缓存的mapper中添加如下标签:

二级缓存不一定使用内存存储,且查询结果类必须实现Serializable接口。

二级缓存的原理:

  1. MyBatis的二级缓存是通过CacheExecutor实现的,CacheExecutor是Executor的代理对象

  2. 所有的查询操作,在CacheExecutor中都会匹配缓存中是否存在,不存在则查询数据库

  3. 每一个Mapper都会生成对应的一个MapperCache对象,用于缓存数据,他也是一个Map结构

  4. 不同的Sql在map中使用如下格式的key存储:MapperId + offset + limit + sql + sql所有参数

三级缓存

一级缓存与二级缓存都是Mybatis自带的缓存机制,解决不了在分布式环境下的缓存问题。所以可以使用三级缓存,三级缓存一般使用第三方缓存中间件(Memcached、Redis、ehCache)。

比如,使用ehcache作为缓存机制,就需要引入第三方jar mybatis-ehcache

Spring整合MyBatis

  1. SqlSessionFactory对象应该放到spring容器中作为单例存在。

  2. 传统dao的开发方式中,应该从spring容器中获得sqlsession对象。

  3. Mapper代理形式中,应该从spring容器中直接获得mapper的代理对象。

  4. 数据库的连接以及数据库连接池事务管理都交给spring容器来完成。

db.properties 省略

参考模块代码:spring-mybatis

逆向工程

mybatis-generator

Mybatis需要程序员手动编写SQL,表过多时,会出现大量的重复工作,所以,mybatis提供了逆向工程。可以根据数据表生成 Mapper接口,SqlMapper映射文件,实体类等。

Maven插件方式具体操作:

  1. 添加maven依赖mybatis-generator-core

  2. 依赖maven插件mybatis-generator-maven-plugin

  3. 使用 idea mybatis plugins 插件 new 一个Mybatis GeneratorFile

  4. 配置这个配置文件

  5. 执行maven插件即可mybatis-generator:generate

mybatis-generator-gui

可视化界面generator: https://github.com/zouzg/mybatis-generator-gui.git

最后更新于

这有帮助吗?