MyBatis详解
1. MyBatis的优点
- 不屏蔽SQL,意味着可以更为精确地定位SQL语句,可以对其进行优化和改造,这有利于互联网系统性能的提高,符合互联网需要性能优化的特点
- 提供强大的,灵活的映射机制,方便Java开发者使用,提供动态的SQL功能,允许我们根据不同条件组装SQL,这个功能远比其他工具或者Java编码的可读性和可靠性高得多,满足各种应用系统的同时,也满足了需求经常变化的互联网应用的要求
- 在MyBatis中,提供了使用Mapper的接口编程,只要一个接口和一个xml就能创建映射器,进一步简化我们的操作,使得很多的框架API在MyBatis中消失,开发者能更加集中业务逻辑
2. MyBatis四个核心组件
-
SqlSessionFactoryBuilder(构造器)
它会根据配置或者代码来生成SqlSessionFactory,采用的是分布构建的Builder模式
-
SqlSessionFactory(工厂接口)
依靠它来生成SqlSession,使用的是工厂模式
-
SqlSession(会话)
一个既可以发送SQL执行返回结果,也可以获取Mapper的接口,在现有的技术中,一般我们会让其在业务逻辑代码中"消失",而使用的是MyBatis提供的SQL Mapper接口编程技术,它能够提高代码的可读性和可维护性
-
SQL Mapper(映射器)
MyBatis新设计存在的组件,它是由一个Java接口和XML文件(或注解)构成,需要给出对应的SQL和映射规则,它负责发送SQL去执行,并返回结果
2.1 SqlSessionFactory
在MyBatis中生成SqlSessionFactory有两种方式:
- 读取配置的XML方式★★★★
- 通过Java代码的形式(不推荐)
通过XML构建SqlSessionFactory
在MyBatis里面的XML:
-
基础配置文件
配置最基本的上下文参数和运行环境
命名为mybatis-config.xml,放在工程路径下
-
映射文件
配置映射关系,SQL,参数等信息
2.2 SqlSession
在MyBatis中,SqlSession是核心接口,在MyBatis中有两个实现类
-
DefaultSqlSession 单线程
-
SqlSessionManager 多线程
SqlSession的作用就类似于JDBC中Connection对象,代表一个连接资源的启用
SqlSession的作用
- 获取Mapper接口
- 发送SQL给数据库
- 控制数据库事务
//SqlSession事务控制的伪代码
//定义SqlSession
SqlSession sqlSession = null;
try{
//打开SqlSession会话
sqlSession = SqlSessionFactory.openSession();
//some code....
sqlSession.commit(); //提交事务
}catch(Exception e){
sqlSession.rollback(); //回滚事务
}finally{
//在finally语句中确保资源被顺利关闭
if(sqlSession != null){
sqlSession.close();
}
}
2.3 映射器
映射器是MyBatis中最重要,最复杂的组件,它是由一个接口和XML文件(或注解)组成
映射器可以配置
- 描述映射规则
- 提供SQL语句,并可以配置SQL的参数类型,返回类型,缓存刷新等信息
- 配置缓存
- 提供动态SQL
实现映射器的两种方式
- XML方式
- 注解方式
在实现映射器之前需要先定义POJO
映射器的主要作用就是将SQL查询到的结果映射为一个POJO,或者将POJO的数据插入到数据库中,并定义一些关于缓存的重要内容
开发的只是一个接口,而不是一个实现类,接口实际上不能直接运行,但是MyBatis运用了动态代理的技术是接口能够运行起来,入门阶段只需要了解MyBatis会为这个接口生成一个动态代理对象,代理对象回去处理相关逻辑
利用XML实现映射器★★★★
利用XML去实现映射器分为两部分
- 接口
- XML
<?xml version="1.0" encoding="UTF-8" ?>
<!--MyBatis的DTD约束-->
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--
mapper:核心根标签
namespace属性:名称空间
-->
<mapper namespace="studentMapper">
<select id="findAll" resultType="student">
select * from student;
</select>
</mapper>
注意: 这里并没有配置SQL执行后和student的对应关系,它是通过自动映射的功能,MyBatis在默认情况下提供自动映射
只要SQL返回的列名column能够和POJO的属性名对应起来
SqlSession发送SQL
@Test
public void findById(){
SqlSession sqlSession = factory.openSession();
Student student = sqlSession.selectOne("studentMapper.findById",1);
System.out.println("student = " + student);
sqlSession.close();
}
//这是MyBatis沿用iBatis所留下来的方式
用Mapper接口发送SQL★★★★
@Test
public void findById(){
SqlSession sqlSession = factory.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student student = mapper.findById(1);
System.out.println("student = " + student);
sqlSession.close();
}
//利用Mapper 发送SQL
//SqlSession的getMapper方法获取一个Mapper接口,就可以调用它的方法
//因为XML文件和接口注解定义的SQL都可以通过"全限定名+方法名"查找,
//所以MyBatis会启用相应的SQL进行运行,并返回结果
对比两种发送SQL方式
- 使用Mapper接口编程可以消除SqlSession带来的功能性代码,提高可读性,完全面向对象的语言,更能提高业务的逻辑
- 使用Mapper接口编程,IDE会提示错误和校验,sqlSession.selectOne()这种只有在运行中才能知道是否产生错误
3. MyBatis的配置
//此处省略一万字
引入映射器
映射器是MyBatis最复杂的最核心的组件
映射器定义命名空间(namespace)的方法,命名空间对应的是一个接口的全路径,而不是实现类
<!--定义Mapper映射规则和SQL语句-->
<?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.blue_ocean.mapper.UserMapper">
<select id="findAll" resultType="user">
select * from user
</select>
</mapper>
-
用文件路径引入映射器
<mappers> <mapper resource="com.blue_ocean.mapper.UserMapper.xml"/> </mappers>
-
用包名引入映射器
<mappers> <package name="com.blue_ocean.mapper"/> </mappers>
-
用类注册引入映射器
<mappers> <mapper class="com.blue_ocean.mapper.UserMapper"/> </mappers>
-
用userMapper.xml引入映射器
<mappers> <mapper url="com.blue_ocean.mapper.UserMapper.xml"/> </mappers>
4. 映射器Mapper
映射器是MyBatis最复杂的最核心的组件
在映射器中我们可以配置参数,各类的SQL语句,存储过程,缓存,级联等复杂的内容,并且通过简易的映射规则映射到指定的POJO或许和其他对象上,映射器能有效地消除JDBC底层的代码
在MyBatis应用程序开发,映射器的开发工作量占绝大部分,
XML和注解:
- 面对复杂性:SQL会显得无力,尤其是长SQL
- 注解的可读性比较差
- 在功能上注解丢失了XML上下文相互引用的功能,基于实用情况,XML更好用
概述
映射器的配置元素
元素名称 | 描述 | 备注 |
---|---|---|
select | 查询语句,最常用,最复杂的元素之一 | 可以自定义参数,返回结果集等 |
insert | 插入语句 | 执行后返回一个整数,代表插入的条数 |
update | 更新语句 | 执行后返回一个整数,代表更新的条数 |
delete | 删除语句 | 执行后返回一个整数,代表删除的条数 |
sql | 定义一部分SQL,然后在别的地方引用 | 例如:一个表列名,一次定义,多次使用 |
resultMap | 用来描述从数据库结果集中来加载对象,它是最复杂,最强大的元素 | 它将提供映射规则 |
cache | 给定命名空间的缓存配置 | ---- |
select元素
元素 | 说明 | 备注(仅列出常用的) |
---|---|---|
id | id和Mapper的命名空间组合一起是唯一的,供MyBatis调用 | 不唯一,就会抛出异常 |
parameterType | 可以给出类的全命名,也可以给出别名,但是别名必须是MyBatis内部的 | 可以选择JavaBean,Map等简单的参数类型传递给SQL |
resultType | - 定义类的全路径,在允许自动匹配的情况下,结果集将通过JavaBean的规范映射;- 或定义为int,double,float,map等参数 - 也可以使用别名,但是要符合命名规范, | 不能和resultMap同时使用 |
resultMap | 它是映射集的引用,将执行强大的映射机制,我们可以使用resultType和resultMap其中一个,resultMap能提供自定义映射规则的机会O | MyBatis最复杂的元素,可以配置映射规则,级联,typeHandler |
insert元素
元素 | 说明 | 备注 |
---|---|---|
id | SQL编号,用于识别这条SQL | 命名空间+id+databaseId唯一 |
parameterType | 参数类型,同select元素 | 和select一样,可以是多个参数 |
useGeneratedKeys | 是否启用JDBC的getGeneratedKeys方法来取出由数据库内部生成的主键 | 默认值为false |
keyProperty | MyBatis会通过getGeneratedKeys的返回值,或者通过insert语句的selectKey子元素设置它的键值,如果是复合主键,要把每一个名称用逗号隔开 | (仅对insert和update有用)唯一的标记一个属性,默认值unset |
keyColumn | 通过生成的键值设置表中的列名,这个设置仅在某些数据库中式必须的,当主键列不是表中的第一列时需要设置,如果是复合主键,需要把每一个名称用逗号(,)隔开 | (仅对insert和update有用)唯一的标记一个属性,不能和keyProperty连用 |
主键回填
<insert id="add" parameterType="student">
insert into student(name,age) values (#{name},#{age});
</insert>
<!--没有插入id列,因为MySQL中表格采用了主键自增,数据库会生成相对应的主键,
有时候我们可能还需要用到这个主键,此时利用useGeneratedKeys属性,值设为true,
此时需要同时用keyProperty和keyColumn,告诉系统把生成的主键当在哪个属性-->
resultMap元素
<resultMap id="StudentMapper" type="Student">
<!--配置学生基本信息-->
<id column="sid" property="sid"/>
<result column="name" property="name"/>
<result column="age" property="age"/>
<!--配置学生所属班级信息 private Classes classes;-->
<association property="classes" javaType="Classes">
<id column="cid" property="cid"/>
<result column="cname" property="cname"/>
</association>
</resultMap>
resultMap元素的属性
- id代表这个resultMap的标识
- type代表需要映射的POJO
- 这里可以使用MyBatis定义好的类的别名
- 也可以使用自定义的类的全限定类名
- property :代表POJO的属性名称
- column:代表数据库SQL的列名
5. 级联
级联简介
MyBatis的级联分为3种
-
鉴别器(discriminstor):根据某些条件决定采用具体实现类基连的方案
-
一对一(association)
-
一对多(collection)
-
每个级联元素中的属性id的配置和POJO的实体配置中的id一一对应形成级联,这是级联关键所在
-
在级联的元素中association是通过javaType的定义去声明实体映射的
-
collection 中是使用ofType进行声明的
-
级联的N+1问题
问题描述
☆ 当级联成功后,有时候引发性能问题,比如一个简单的查询就关联很多无关数据
★ 假设现在有N个关联关系完成了级联,那么要是在加入一个关联关系,就变成了N+1级联,所有的级联SQL都会被执行
在高性能的互联网系统中是不允许的
提出解决办法:延迟加载
延迟加载
MyBatis支持延迟加载(需要就加载,不需要不加载),这些不常用的级联数据可以采用延迟加载
配置项 | 作用 | 配置说明 | 默认值 |
---|---|---|---|
lazyLoadingEnabledy | 延迟加载的全局开关,所有的关联对象都会延迟加载,在特定的关联关系中,可通过设置fetchType属性来覆盖该项的开关状态 | true|false | false |
aggressiveLazyLoading | 当启用时,对任意延迟属性的调用会使带有延迟加载的属性的对象完整加载;反之,这种属性按需加载 | true|false | 3.4.1后为false |
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="true"/>
</settings>
<!-- 这两个属性是配置全局变量,也可以配置局部变量-->
<!-- 在association和collection中 配置fetchType-->
<!-- eager 获得当前POJO后立即加载对应的数据 -->
<!-- lazy 获得当前POJO后延迟加载对应的数据 -->
6. 动态SQL
概述
MyBatis提供对SQL语句动态的组装能力,使用XML元素,大量的判断都可以在MyBatis的映射XML里面配置,体现了MyBatis的灵活,高度可配置性和维护性
动态SQL的元素
元素 | 作用 | 备注 |
---|---|---|
if | 判断语句 | 单条件分支判断 |
choose(when,otherwise) | 相当于Java中的switch和case语句 | 多条件分支判断 |
trim(where,set) | 辅助元素,用于处理特定的SQL拼装问题 | 用于处理SQL拼装的问题 |
foreach | 循环语句 | 在in语句等列举条件常用 |
<!-- 利用test的属性去判断字符串-->
<select id="findByCondition" parameterType="Student" resultType="Student">
select * from student
<where>
<if test="id != null">
and id = #{id}
</if>
<if test="name != null and name != ''">
and name = #{name}
</if>
<if test="age != null and age != ''">
and age = #{age}
</if>
</where>
</select>
<select id="findByIdCondition" parameterType="int" resultType="Student">
Select * from student
<foreach collection="list" open="where id IN (" close=")" item="id" separator=",">
#{id}
</foreach>
</select>