简介
MyBatis是一款持久层框架,用于简化JDBC开发
持久层:负责将数据保存到数据库的那一层代码
JavaEE三层架构:表现层、业务层、持久层
框架:一个半成品软件,是一套可重用的、通用的、软件基础代码模型
作用
MyBatis的主要作用为解决硬编码问题和操作繁琐问题
硬编码:在编写代码时由于代码内容可能与地址、用户名、密码等相关,因此可能会发生变动,后续维护困难
如JDBC操作时mysql中使用的数据库发生变化,用户名或密码发生变化
SQL语句的变化
操作繁琐:
如JDBC中执行sql语句时可能需要手动设置参数,结果集需要手动获取并装入集合
入门
1.导入jar包
使用MyBatis时,首先需要导入它的jar包,使用Maven则对应坐标为:
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>x.x.x</version>
</dependency>
<!--version需要手动更改版本-->
2.编写MyBatis核心配置文件
为解决硬编码问题,MyBatis将涉及硬编码的字符串信息全部装到了xml文件中,届时若需要更改维护则只需更改xml文件即可
在main包下的resources下创建xml文件(名称一般为mybatis-config.xml)
将以下内容复制到该文件中
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="org/mybatis/example/BlogMapper.xml"/>
</mappers>
</configuration>
其中,需要更改的内容为数据库的连接信息以及sql映射文件的名称
3.编写SQL映射文件
为解决sql的硬编码问题,相应sql也需要写到一个xml文件中
在main包下的resources下创建xml文件(名称一般为表名 + Mapper.xml,如UserMapper.xml)
将以下内容复制到该文件中
<?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="org.mybatis.example.BlogMapper">
<select id="selectBlog" resultType="Blog">
select * from Blog where id = #{id}
</select>
</mapper>
其中,需要更改的信息有
namespace:命名空间,可以储存一系列的sql语句,可以理解为一个包,届时使用sql语句时可以通过命名空间和id来找到对应sqs
id:sql语句的唯一标识,通过id可以找到对应sql,以便后续执行
resultType:结果类型,代表执行sql后返回的结果的数据类型
select语句
注:不同命名空间中id可以相同,在调用sql语句时有两种方式
全限定名:命名空间 + id
短名称:id
在id无重复时短名称可以正常使用,但当id在不同命名空间中重复出现时使用短名称会出错,因此一般使用全限定名
在编写好SQL映射文件后需要将该文件的名称填入到MyBatis核心配置文件中的resource中
4.编码
在解决完上述前置问题后,可以正式开始编写代码
4.1定义POJO类
定义一个类来代表相应的表
如查询User表,则需要定义User类并封装相应的字段信息
4.2加载核心配置文件,获取SqlSessionFactory对象
在main方法中复制以下代码
String resource = "mybatis-config.xml";//这里为核心配置文件的相对路径
InputStream inputStream = Resources.getResourceAsStream(resource);//需要抛出异常
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
最终得到sqlSessionFactory对象
4.3获取SqlSession对象,执行sql语句
使用sqlSessionFactory的方法openSession得到sqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
SqlSession sqlSession = sqlSessionFactory.openSession(true);//可以设置参数为true,开启自动提交事务
通过sqlSession对象执行sql语句
List selectList(String xxx)
该方法得到的结果为List集合,对应的sql语句应为查询了多个数据
Xxx selectOne(String xxx)
该方法得到的为单个对象,对应的sql语句应为查询了单个数据
其中,需要的参数为sql语句的全限定名或短名称
通过上述方法即可执行sql并得到结果集
最终释放资源即可
在idea中配置MySql数据库连接
在编辑SQL映射文件中sql语句时由于idea无法读取信息,会出现表名爆红的情况,可以将idea与MySQL数据库连接解决
1.找到Database
2.找到MySQL
3.建立连接
填写用户名、密码、连接的数据库即可
在配置完连接后,后续编写sql语句时也会有提示功能
插件下载
ieda中可以下载一个MyBatisX插件,在使用MyBatis时可以更为便捷
Mapper代理开发
目的
解决原生方式中的硬编码(全限定名)
简化后期执行SQL
步骤
1.定义与SQL映射文件同名的Mapper接口,并且将Mapper接口和SQL映射文件放置在同一目录下
正常的main包在编译过后,java包下的内容和resources下的内容会放在一起,且因为xml文件一般放置在resources中,所以想要接口和映射文件在同一目录,需要在resources中建立包名和结构与接口相同的包,并将映射文件放置进去
如图,UserMapper接口所在包的结构与对应的映射文件所在结构相同,在编译后它们会放置在同一目录下
注:
1.resources创建时不存在包这个概念,只有文件夹,创建时直接创建文件夹即可,且idea中创建文件夹时需要用com/itheima//mapper的形式来创建,这样创建出的文件夹才是分层的结构
2.在更改完映射文件的位置后,MyBatis核心配置文件中sql映射文件的地址也需要对应修改
2.设置SQL映射文件的namespace属性为Mapper接口全限定名
3.在Mapper接口中定义方法,方法名就是SQL映射文件中sql语句的id,并保持参数类型和返回值类型一致
注:返回的数据若为多个User对象,则这里返回值类型也需要改为List集合,这样到时候调用方法时会自动调用selectList方法,而不是selectOne
4.编码
4.1通过SqlSession的getMapper方法获取Mapper接口的代理对象
4.2调用对应方法完成sql的执行
5.提交事务,关闭资源
若调用的方法为增删改,则在完成sql后需要提交事务
调用完方法后,需要关闭资源
注:
如果Mapper接口名称和SQL映射文件名称相同,并在同一目录下,则可以通过使用包扫描 方式简化SQL映射文件的加载
如图,核心配置文件中加载sql映射文件时,可以直接加载mapper包(因为所有的映射文件和对应接口都会放在该包下)
Mapper接口中方法参数的传递
Mapper接口中的方法可以接收参数,到时候会传递给sql语句中的参数占位符,当传递的参数为多个时,
有以下三种传递方式
1.设置多个参数
使用@Param注解,到时候该参数会传递给名称和注解相同的参数占位符
2.传递对象
若传递的参数属于某一个类的属性,则可以直接传递一个对象
例:
Brand类里包含status、companyName等多个属性,则可以将这几个参数包装成一个Brand类传递给sql语句,到时候会到该对象里寻找与参数占位符名称相同的属性,并将其传递给参数占位符
3.传递集合
将参数包装成一个Map集合,键与参数名称相同,值为参数值,将其传递给sql语句
Mapper接口中方法参数的传递的底层原理
1.单个参数
1.1 POJO类型
直接使用,但要求属性名和参数占位符名称一致
注:在从pojo类中读写信息时,mybatis读取属性 读取的是get和set方法,如要读取user类中的id信息时,其会调用getId方法,无论此时是否有成员变量id。读取属性的过程为将get,set方法中的get和set去掉,剩余部分首字母变小写,得到的就是属性名
1.2 Map集合
直接使用,但要求键名和参数占位符名称一致
1.3 Collection
封装为map集合
其中键值对为:
map.put("arg0",collection集合);
map.put("collection",collection集合);
可以使用使用@Param注解,使用该注解后会替换Map集合中默认的arg键名
1.4 List
封装为map集合
其中键值对为:
map.put("arg0",list集合);
map.put("collection",list集合);
map.put("list",list集合);
可以使用使用@Param注解,使用该注解后会替换Map集合中默认的arg键名
1.5 Array
封装为map集合
其中键值对为:
map.put("arg0",数组);
map.put("array",数组);
可以使用使用@Param注解,使用该注解后会替换Map集合中默认的arg键名
1.6 其他类型
直接使用,且因为只有一个参数,因此参数名随意,参数占位符的名称随意
2.多个参数
传递多个参数时会将这些参数封装成Map集合
其中键值对为:
map.put("arg0",参数值1);
map.put("param1",参数值1);
map.put("arg1",参数值2);
map.put("param2",参数值2);
...
即每个参数都会创建两个键值对,但这些键值对的键名使用不方便,因此使用@Param注解,使用该注解后会替换Map集合中默认的arg键名
即变为
map.put(参数1的名称,参数值1);
map.put("param1",参数值1);
map.put(参数2的名称,参数值2);
map.put("param2",参数值2);
...
MyBatis核心配置文件
环境配置(environments)
environments:配置数据库连接环境信息。可以配置多个environment,通过default属性切换不同的environment
default
default:使用哪一个环境,其值应为某个environment的id
id
id:environment的环境id
注:我们通过default值来选择使用的数据库(default的值和哪个environment的id值相同,使用的就是哪个库),因此需要保证default至少要匹配一个环境ID
事务管理器(transactionManager)
transactionManager:设置事务管理器
属性type:设置事务管理的方式
type="JDBC|MANAGED"
JDBC:表示使用JDBC中原生的事务管理方式
MANAGED:被管理,例如交给Spring来进行事务管理
注:一般不使用MyBatis来管理事务
数据库连接池(dataSource)
dataSource:配置数据库连接池和和相应的数据库连接信息
属性:
type:设置数据源类型
type="POOLED|UNPOOLED|JBDI"
POOLED:表示使用数据库连接池
UNPOOLED:表示不使用数据库连接池
JBDI:表示使用上下文中的数据源
注:一般也不使用MyBatis管理数据库连接池,因此只需要配置好数据库连接信息即可
driver:注册驱动("com.mysql.jdbc.Driver")
ur:连接路径
username:用户名
password:密码
引入properties文件
我们可以引入properties文件,之后就可以在当前文件中使用${key}的方式访问value
<properties resource="jdbc.properties" />
其中jdbc.properties在resources包下,其内部是许多以键值对形式存在的数据,之后就可以在核心配置文件中使用${键名}的方式访问到对应的值
设置(settings)
setting:设置全局配置
该标签只有一个子标签<setting>,子标签有两个属性:
name,value
name的值有许多,且有相对应value的值
mapUnderscoreToCamelCase
将下划线映射为驼峰
name="mapUnderscoreToCamelCase" value="true|false"
value默认为false,设置其为true后就可以自动将下划线映射为驼峰,即emp_id映射为empId
lazyLoadingEnabled,aggressiveLazyLoading
lazyLoadingEnabled:延迟加载的全局开关。当开启时,所有的关联对象都会延迟加载,默认值为false
aggressiveLazyLoading:当开启时,任何方法的调用都会加载该对象的所有属性。否则每个属性会按需加载,默认值为false
这两个设置可以用于设置是否开启延迟加载,当lazyLoadingEnabled开启,aggressiveLazyLoading关闭时,延迟加载生效
类型别名(typeAliases)
typeAliases可以为Java类型设置一个别名,方便后续寻找
可以直接定位到某个类
如:
直接给类起别名的时候可以只写type,不写alias,则默认别名就是类名,且不区分大小写
如设置type为com.pojo.User,则默认别名就是user,不区分大小写
也可以指定一个包名
当指定包名,且无注解时,该包下所有类都会有默认的别名,如User的别名就是user,且不区分大小写(就算起了别名,也可以使用原名称)
映射器(mappers)
mappers:让MyBatis找到映射文件
有以下几种方式
一般使用最后一种方式,即Mapper代理开发中所说到的方式
但使用包的方式引入映射文件时,需要满足两个条件:
1.mapper接口和映射文件所在的包必须一致
2.mapper接口的名字和映射文件的名字必须一致
注意事项
在配置各个标签时,需要遵循前后顺序,如下
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!--数据库连接信息-->
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<!--加载sql映射文件-->
<mapper resource="org/mybatis/example/BlogMapper.xml"/>
</mappers>
</configuration>
SQL映射文件
标签介绍
namespace
namespace:命名空间,可以储存一系列的sql语句,可以理解为一个包,届时使用sql语句时可以通过命名空间和id来找到对应sql
select
select:查询
该标签有以下几个属性
id
id:sql语句的唯一标识,通过id可以找到对应sql,以便后续执行
parameterType
parameterType:用于设置参数类型,该参数可以忽略
resultType
resultType:结果类型,代表执行sql后返回的结果的数据类型,这里需要填写的类型应为对应类型的全类名,如com.pojo.User,但是在核心配置文件中设置别名后就可以简化为User了
注:这个结果类型可以是创建的pojo类,也可以是Integer,int等类型,且mybatis为许多类型都起了别名,如
int:_int、_integer
Integer:Integer、int
Map:map
String:string
且这些别名是不区分大小写的
返回map集合
当我们查询出来的数据没有对应的实体类时,我们可以返回一个map集合
届时查询出来的数据会以字段名为键,字段的值为值,封装到map集合中返回,且如果查询的字段中的数据为null,那么map集合中不会有相关的键值对。而查询到实体类时,即使字段数据为null,仍会有相关属性,且值为null
若我们查询出来的数据有多条时,并且要将每条数据转换为map集合时,可以用以下两种方式解决:
1.将mapper接口方法的返回值设置为泛是Map集合的List集合
2.将每条数据转换的map集合放在一个大的map中,但必须要使用@MapKey注解将查询的某个字段的值作为大的Map集合中的键
insert
insert:增添
该标签有以下几个属性
id
id:sql语句的唯一标识,通过id可以找到对应sql,以便后续执行
useGeneratedKeys
useGeneratedKeys:默认为false,设置为true后代表要返回主键
keyProperty
keyProperty:代表要将返回的主键传递给实体类的哪个属性(一般为id)
注:当配置了useGeneratedKeys和keyProperty两个属性,且从接口中传入的参数为一个对象时,在执行该sql语句后,会自动将增添的数据中的主键传给该对象中的某个属性(一般为id)
注:增删改返回的参数固定为受影响的行数(可以设置为void,即不处理返回的数据),因此想要获取自增的主键必须使用这两个属性
update
update:修改
该标签有以下几个属性
id
id:sql语句的唯一标识,通过id可以找到对应sql,以便后续执行
注:若接口中的返回值类型为int,则得到的是影响的行数
delete
delete:删除
该标签有以下几个属性
id
id:sql语句的唯一标识,通过id可以找到对应sql,以便后续执行
参数占位符
#{}
#{}:执行SQL时,会将#{}占位符替换为?,将来自动设置参数(无SQL注入问题)
${}
${}:拼SQL。会出现SQL注入问题。
使用时机
参数传递用#{}
若表名、列名不固定,只能用${}进行sql拼接
注:
当要传递多个参数时,方法中的多个参数需要用@Param()注解标记
@Param(xxx)类型 参数名
到时候这个参数就会传递给名称为xxx的参数占位符
例:下述@Param("status")int status接到的参数就会传递给#{status}
SQL语句中特殊字符处理
xml文件中的“<”等特殊字符可能会识别错误,出现问题
1.转义字符
使用xml文件自带的转义字符
2.<![CDATA[内容]]>
将符号放在以下内容中间,适用于出现大量特殊字符时
快捷键:idea中打出“CD”即可快速不全
注意事项
1.不同命名空间中id可以相同,在调用sql语句时有两种方式
全限定名:命名空间 + id
短名称:id
在id无重复时短名称可以正常使用,但当id在不同命名空间中重复出现时使用短名称会出错,因此一般使用全限定名
2.当sql语句中用到参数占位符时,对应Mapper集合中的方法要加对应参数,到时候调用方法时传入的参数会自动替换参数占位符
<?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="org.mybatis.example.BlogMapper">
<select id="selectBlog" resultType="Blog">
select * from Blog where id = #{id}
</select>
</mapper>
动态SQL
SQL语句会随着用户的输入或外部条件的变化而变化,我们称为动态SQL
if
if,类似于Java中的if,可以通过条件来判断是否执行sql语句
该标签只有一个属性,test
该属性代表条件语句,满足该条件则执行标签内的sql语句
如图
注:由于sql语句中where条件存在and等关键字,因此可能会出现语法错误(如if条件全部不满足),可以通过添加恒等式来解决(如上图),但一般更多使用where标签
where
我们可以使用where标签和if标签来解决多条件查询的问题
1.若where标签中有条件成立,会自动生成where关键字
2.会自动将where标签中前面多余的and去掉
3.若where标签中没有任何一个条件成立,则where没有任何功能
trim
trim可以更加灵活的去实现where的功能
trim,有四个属性
prefix、suffix:在标签中内容的前面或后面添加指定内容
prefixOverrides、suffixOverrides:在标签中内容的前面或后面去掉指定内容
trim、where、set
choose(when,otherwise)
当我们要实现从多个条件中选择一个时,可以使用choose标签
choose、when、otherwise,分别类似于Java中的if、else if、else
choose:类似于if
when:类似于else if,有test属性,代表条件,满足条件就拼接内部的SQL语句。至少一个
otherwise:类似于else,当所有when都未满足时,执行otherwise中的sql语句。最多一个
注:当有一个when的条件满足时,后面所有的when以及otherwise都不会再进行判断
注:上图中的otherwise是为了解决“用户没有传入任一条件”的情况下,sql语法的问题,实际上可以使用where标签解决
foreach
foreach:用于遍历数组或集合
该标签有以下几个属性
collection
collection:传入的数组或集合
注:当传入的是数组时,mybatis会将数组参数封装成一个Map集合,默认键名为array,值为数组
当传入的是List集合时,会自动封装成Map集合,默认键名为list
当传入的是数组或List集合时,collection的属性值需要为list或array,也可以在接口中使用@Param注解来改变map集合的键的名。当传入的是Map集合时,collection的属性值为该集合的名称
item
item:代表本次迭代得到的元素,在后面使用参数占位符时应使用相同的元素名(必选)
index
index:当传入的为除map以外的集合或者数组时,代表索引。当传入的为map集合时,代表键(可选)
separator
separator:代表每两次拼接中间的分隔符,且会自动在前后各加一个空格(可选)
open
open:在最开始拼接的字符串(可选)
close
close:在最后拼接的字符串(可选)
sql片段
可以记录一段sql,在需要用的地方使用include标签进行引用
注:sql片段标签应该在SQL映射文件中,即与select等标签同级
include标签则是可以在任何需要用到sql片段的地方均可使用
创建SqlSessionFactory代码优化
这串代码中,若有多个Servlet,那么代码将会重复多次,并且可能会创建多个SqlSessionFactory工厂,导致资源浪费,因此应只创建一次工厂
解决方法
代码重复:工具类
只创建一次工厂:静态代码块
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
public class SqlSessionFactoryUtils {
private static SqlSessionFactory sqlSessionFactory;
static {
try {
String resource = "mybatis-config.xml";//这里为核心配置文件的相对路径
InputStream inputStream = Resources.getResourceAsStream(resource);//需要抛出异常
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSessionFactory getSqlSessionFactory(){
return sqlSessionFactory;
}
}
注意事项
1.字段名和属性名不同
在使用Mapper接口的代理对象来执行方法时,如果数据库表中的字段名与实体类的属性名不一样,会导致无法自动封装数据
解决方法
1.1起别名
在编写sql语句时,给名称不一样的字段起别名,让别名和实体类的属性名一样
1.2设置全局映射
在核心配置文件的settings标签中设置
name="mapUnderscoreToCamelCase" value="true"
可以将所有的下划线自动映射为驼峰
1.3resultMap
定义一个resultMap标签来映射字段名和属性名
1.3.1定义<resultMap>
resultMap包含id和type两个属性
id:名称,唯一标签
type:类型,要和哪一个类型做映射
resultMap的子标签有四个,这里只用到两个
id:处理主键字段和实体类属性的映射关系
result:处理普通字段的和实体类属性的关系映射
这两个标签都有两个属性
column:字段名
property:属性名
1.3.2使用resultMap属性替换resultType
在<select>标签中,使用resultMap来替换resultType
例:
2.多对一
mysql中我们经常会将两张表关联起来,如一对多等关系
在一对多的关系中,我们需要创建两个实体类,分别对应两张表,
且在一的实体类中,应有一个成员变量,其类型为另一个实体类
在多的实体类中,应有一个成员变量,其类型为 泛型是另一个实体类的集合
之后我们在查询时,如查询员工表和部门表,查询到的数据为员工的信息及其对应部门的信息,但是此时我们设置的返回类型只有一个,如员工实体类,那么对应部门的信息我们是无法封装的
我们可以使用resultMap来自定义映射的方式解决,且有三种方式:
2.1级联
在设置映射时,我们可以将mysql中部门的相关字段 与 员工实体类中的部门对象的属性进行映射,如下图
property中的dept是员工实体类中部门对象的名称
2.2 association
resultMap除id和result外还有两个子标签
association:处理多对一,一对一的映射关系(处理 类型为实体类 的属性)
其有两个属性
property:设置需要处理映射关系的属性的属性名
javaType:设置要处理的属性的类型
有两个子标签
id:处理主键字段和实体类属性的映射关系
result:处理普通字段的和实体类属性的关系映射
这两个标签都有两个属性
column:字段名
property:属性名
2.3分步查询
我们可以将一次性查询员工及其所属部门信息的过程分成多步
1.查询员工信息
2.根据员工的部门id查询对应的部门
该方式也需要用到resultMap的association标签,但是用到的属性不完全相同
association,需要用到以下三个属性
property:设置需要处理映射关系的属性的属性名(用法与上个方法相同)
select:设置分步查询的sql的唯一标识(即下一个调用的sql语句的全类名)
column:将查询出的某个字段作为分步查询的sql的条件(即下一个调用的sql语句的参数)
注:
1.分步查询在开始第一步,即调用了resultMap对应的sql语句后,可以自动调用下一个sql语句,即select对应的sql语句
2.column为查询出来的某个字段,这个字段在员工实体类中不一定存在
延迟加载
分步查询的优点之一:可以实现延迟加载
即当进行分步查询时,可以只进行某一步的查询,而不是直接查询到底
如我们只查询员工的信息,不查询对应部门,则可以开启延迟加载,节省资源
开启方式:在核心配置文件的settings标签中设置lazyLoadingEnabled开启,aggressiveLazyLoading关闭(默认关闭,即可以不设置),开启后默认所有分步查询都只会查询一步
在设置了全局延迟加载后,我们可以在association中fetchType属性设置是否开启延迟加载
fetchType="eager(立即加载)| lazy(延迟加载)"
3.一对多
多对一查询时是查询员工及其对应部门的信息
一对多查询时是查询部门及其员工
同多对一,我们也需要解决查询到的数据与实体类中属性的不对应的情况,有以下两种方式:
3.1collection
resultMap的最后一个子标签
collection:处理一对多,多对多的映射关系(处理集合类型的属性)
collection与association的用法类似,有属性property和ofType
property:需要进行映射的属性(和association相同)
ofType:设置集合类型的属性中存储的数据的类型(即泛型的类型)
同样有子标签id,result,内有属性column和property
column:字段名
property:属性名
3.2分步查询
同多对一的分步查询,可以将查询部门及部门员工分为两步:
1.查询部门信息
2.查询该部门的员工信息
需要用到resultMap的collection,具体用法与使用association进行分步查询一致
也可以设置延迟加载
4.事务
当我们进行select操作时,不需要进行事务的管理,但当我们进行添加,删除操作时,需要手动提交事务
openSession():默认开启事务,进行增删改操作后需要使用sqlSession.commit();手动提交事务
openSession(true):设置为自动提交事务(关闭事务)
注解开发
当功能比较简单的时候,可以直接使用注解来代替配置文件完成开发,会更加方便
如图,不需要配置SQL映射文件,直接在对应接口方法上方加入注解即可
注:注解开发仅适用于完成简单功能,复杂功能还是使用配置文件
特殊SQL的执行
1.模糊查询
当我们进行模糊查询的时候,如果使用#{}来直接传递参数时,mybatis在解析SQL语句时会将#{}变为?占位符,并且在之后将其代替,但是模糊查询时我们会使用单引号将其包裹,如下图
那么在解析时,#{}变为?占位符后会在单引号中,此时?占位符会被识别为一个字符串,而不是占位符,这会导致之后mybatis调用方法去代替占位符时找不到?而报错
解决方案:
1.使用${},直接拼接
2.使用mysql的concat函数,这个函数可以拼接字符串
如图,concat会将两个%和#{}的内容拼接到一起并返回对应字符串
3.为两个%加上双引号,中间包裹#{}
一般使用第三种
2.批量删除
我们经常会用到批量删除的功能,此功能一般需要传输多个值,然后根据这些值删除数据
实现该功能的SQL语句有以下两种:
当我们使用第一种时,由于需要的参数的数目固定,是可以直接使用#{},但更多的时候删除的数目是不固定的,也就是要使用第二种SQL语句,此时需要传递的数据应为一个id数组
如果我们直接使用#{},由于#{}在使用时会自动在两边加上单引号,即数据为7,8的数组最后会变为'7,8',这并不符合mysql中in函数的语法,会报错
解决方案:
1.使用${}直接拼接
2.使用foreach来代替,设置起始为左括号,结尾为右括号,分割为逗号,然后遍历传递的数组即可
3.动态设置表名
当我们需要动态的设置表名时,由于#{}会自动的加单引号,因此我们只能使用${}
4.添加功能获取自增的主键
我们在执行添加功能后可能需要获得对应自增的主键,那么就需要在设置insert语句时设置对应的属性,即
useGeneratedKeys:默认为false,设置为true后代表要返回主键
keyProperty:代表要将返回的主键传递给实体类的哪个属性(一般为id)
通过这两个属性即可在调用增加方法后为对应的实体类的id属性赋值
Mybatis的缓存
一级缓存
一级缓存是sqlSession级别的,通过同一个sqlSession查询的数据会被缓存,下次执行相同的查询语句,就会从缓存中直接获取,不会从数据库重新访问。一级缓存是默认开启的
一级缓存失效的三种情况:
1.不同的sqlSession对应不同的一级缓存
2.同一个sqlSession两次查询期间进行了任何一次增删改操作(会自动清空该sqlSession的缓存)
3.同一个sqlSession两次查询期间手动清空了缓存
手动清空一级缓存:sqlSession.clearCache()
二级缓存
一级缓存是sqlSessionFactory级别的,通过同一个sqlSessionFactory创建的sqlSession查询的结果会被缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取
二级缓存开启的条件:
1.在核心配置文件中,设置全局配置属性cacheEnabled="true",默认为true,不需要设置
2.在sql映射文件中设置标签<cache/>
3.二级缓存必须在SqlSession关闭或提交之后有效
4.查询的数据所转换的实体类类型必须实现序列化的接口
使二级缓存失效的情况:两次查询之间执行任意的增删改操作,会使一级缓存和二级缓存同时失效
二级缓存的相关配置
注:这些配置一般使用默认的,不需要进行修改
在mapper配置文件中添加的cache标签可以设置一些属性:
1.eviction属性:缓存回收策略,默认的是 LRU。
LRU(Least Recently Used) – 最近最少使用的:移除最长时间不被使用的对象。
FIFO(First in First out) – 先进先出:按对象进入缓存的顺序来移除它们。 SOFT – 软引用:移除基于垃 圾回收器状态和软引用规则的对象。
WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
2.flushInterval属性:刷新间隔,单位毫秒,默认情况是不设置,也就是没有刷新间隔,缓存仅仅执行增删改操作时刷新
3.size属性:引用数目,正整数代表缓存最多可以存储多少个对象,太大容易导致内存溢出
4.readOnly属性:只读, true/false
true:只读缓存;会给所有调用者返回缓存对象的相同实例(即直接返回缓存的对象)。因此这些对象不能被修改。这提供了很重要的性能优势。
false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是 false。
MyBatis缓存查询的顺序
先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用。
如果二级缓存没有命中,再查询一级缓存
如果一级缓存也没有命中,则查询数据库
SqlSession关闭之后,一级缓存中的数据会写入二级缓存
整合第三方缓存EHCache
我们可以将二级缓存改为整合的第三方缓存EHCache
Mybatis的逆向工程
正向工程:先创建Java实体类,由框架负责根据实体类生成数据库表。Hibernate是支持正向工程的。
逆向工程:先创建数据库表,由框架负责根据数据库表,反向生成如下资源:
- Java实体类
- Mapper接口
- Mapper映射文件
注:通过逆向工程实现的实体类中不会包含其他实体类对象,即只能实现单表的实体类
创建逆向工程的步骤
1.添加依赖和插件
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<!-- junit测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- log4j日志 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
</dependencies>
<!-- 控制Maven在构建过程中相关配置 -->
<build>
<!-- 构建过程中用到的插件 -->
<plugins>
<!-- 具体插件,逆向工程的操作是以构建过程中插件形式出现的 -->
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.0</version>
<!-- 插件的依赖 -->
<dependencies>
<!-- 逆向工程的核心依赖 -->
<dependency>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-core</artifactId>
<version>1.3.2</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
2.创建MyBatis的核心配置文件
3.创建逆向工程的配置文件
注:在resources包下创建逆向工程的配置文件,文件名必须是 generatorConfig.xml
<?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>
<!--
targetRuntime: 执行生成的逆向工程的版本
MyBatis3Simple: 生成基本的CRUD(清新简洁版)
MyBatis3: 生成带条件的CRUD(奢华尊享版)
-->
<context id="DB2Tables" targetRuntime="MyBatis3Simple">
<!-- 数据库的连接信息 -->
<jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/db1?serverTimezone=UTC"
userId="root"
password="1234">
</jdbcConnection>
<!-- javaBean(实体类)的生成策略-->
<javaModelGenerator targetPackage="com.mybatis.pojo" targetProject=".\src\main\java">
<property name="enableSubPackages" value="true" />
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- SQL映射文件的生成策略 -->
<sqlMapGenerator targetPackage="com.mybatis.mapper" targetProject=".\src\main\resources">
<property name="enableSubPackages" value="true" />
</sqlMapGenerator>
<!-- Mapper接口的生成策略 -->
<javaClientGenerator type="XMLMAPPER"
targetPackage="com.mybatis.mapper" targetProject=".\src\main\java">
<property name="enableSubPackages" value="true" />
</javaClientGenerator>
<!-- 逆向分析的表 -->
<!-- tableName设置为*号,可以对应所有表,此时不写domainObjectName -->
<!-- domainObjectName属性指定生成出来的实体类的类名 -->
<table tableName="emp" domainObjectName="Emp"/>
<table tableName="dept" domainObjectName="Dept"/>
</context>
</generatorConfiguration>
4.执行MBG插件的generate目标
QBC查询
我们在创建逆向工程时,可以通过设置generatorConfig.xml文件中context标签的targetRuntime属性来选择是生成基本的增删改查还是带条件的增删改查
基本的增删改查较为简单,且只有五条sql语句
带条件的增删改查功能很全面,mapper中包含许多方法,其中
insert:根据条件添加,若添加的内容中包含null,则对应字段的值也为null
insertSelective:根据条件选择性添加,若添加的内容中包含null,则不会对 对应字段赋值,即会使用musql中 的默认值,若为该字段的默认值设置了值,则结果与insert方法不同
updateByExample:根据条件修改,若修改的内容中包含null,则对应字段会设置为null
updateByExampleSelective:根据条件选择性修改,若修改的内容中包含null,则不会修改对应字段
Exmaple
mapper中很多需要条件的方法接收的参数为xxxExample类型,即若实体类为Emp,则对应条件类型为EmpExample
若直接传入一个null,代表没有条件
使用example时,我们首先需要调用方法createCriterid()创建条件查询,
之后链式调用and + 属性名 + 条件 方法即可
and:代表多个条件之间以and连接,因此可以直接链式编程
属性名:代表我们要对哪一个属性进行条件限制,如Id代表要对Id进行条件限制
条件:代表要进行什么条件限制,如Like代表模糊匹配,EqualTo代表要求属性名与什么相同
例:example.createCriteria().andEmpNameEqualTo("张三").andAgeGreaterThanOrEqualTo(20);
该条件对应的是:EmpName要与"张三"相同,且Age要大于等于20
之后将对应的example作为参数提供给方法即可
若需要使用or来连接条件,则需要调用or()方法,然后调用andxxx即可
例:example.createCriteria().andEmpNameEqualTo("张三").andAgeGreaterThanOrEqualTo(20);
example.or().andDidIsNotNull();
该条件中Did不是null的条件就会和前面的条件以or连接
@Test
public void testMBG(){
try {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new
SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = sqlSessionFactory.openSession(true);
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
//查询所有数据
/*List<Emp> list = mapper.selectByExample(null);
list.forEach(emp -> System.out.println(emp));*/
//根据条件查询
/*EmpExample example = new EmpExample();
example.createCriteria().andEmpNameEqualTo("张三").andAgeGreaterThanOrEqualTo(20);
example.or().andDidIsNotNull();
List<Emp> list = mapper.selectByExample(example);
list.forEach(emp -> System.out.println(emp));*/
mapper.updateByPrimaryKeySelective(new Emp(1,"admin",22,null,"456@qq.com",3));
} catch (IOException e) {
e.printStackTrace();
}
}
分页插件
分页是一个十分常用的功能,且在实现分页功能时通常需要进行许多的逻辑判断等操作,为简化这些操作,可以使用分页插件
使用步骤
1.添加依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
2.配置分页插件
在Mybatis的核心配置文件中配置插件
<plugins>
<!--设置分页插件-->
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>
在配置完分页插件后,只要我们开启分页功能,那么只要我们发送查询请求后,分页插件就会自动拦截请求,并且为我们的查询语句加上limit(分页)语句
分页插件的使用
在进行查询功能之前需要开启分页功能:
PageHelper.startPage(int pageNum, int pageSize)
pageNum:当前页的页码
pageSize:每页显示的条数
在查询获取list集合后,可以获取分页相关数据:
PageInfo pageInfo = new PageInfo<>(List list, int navigatePages)
list:分页之后的数据
navigatePages:导航分页的页码数,即显示在页面上的页码,如下图
这里显示了5个页码,即navigatePages为5
所得的pageInfo中就包含许多处理好的分页相关的数据,如下:
PageInfo{ pageNum=8, pageSize=4, size=2, startRow=29, endRow=30, total=30, pages=8, list=Page{count=true, pageNum=8, pageSize=4, startRow=28, endRow=32, total=30, pages=8, reasonable=false, pageSizeZero=false}, prePage=7, nextPage=0, isFirstPage=false, isLastPage=true, hasPreviousPage=true, hasNextPage=false, navigatePages=5, navigateFirstPage4, navigateLastPage8, navigatepageNums=[4, 5, 6, 7, 8] }
pageNum:当前页的页码
pageSize:每页显示的条数
size:当前页显示的真实条数
startRow:
endRow:
total:总记录数
pages:总页数
prePage:上一页的页码
nextPage:下一页的页码
isFirstPage/isLastPage:是否为第一页/最后一页
hasPreviousPage/hasNextPage:是否存在上一页/下一页
navigatePages:导航分页的页码数
navigatepageNums:导航分页的页码,[1,2,3,4,5]
其内部有一个list,值为Page对象的数据,Page对象里面包含部分分页相关数据,且该对象可以在开启分页功能时获得
//获取mapper
String resource = "mybatis-config.xml";//这里为核心配置文件的相对路径
InputStream inputStream = Resources.getResourceAsStream(resource);//需要抛出异常
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
//开启分页功能(可以在此处获取Page对象)
Page<Object> page = PageHelper.startPage(1,4);
//调用查询功能
List<Emp> emps = mapper.selectAll();
//获取分页相关数据
PageInfo pageInfo = new PageInfo(emps,4);
System.out.println(pageInfo);