目录
编辑 1.4 为什么要使用MyBatis – 现有持久化技术的对比
一、Mybatis简介
1.1 MyBatis简介
- MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架
- MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集
- MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和Java的POJO(Plain Ordinary Java Object,普通的Java对象)映射成数据库中的记录
- Mybatis 是一个 半自动的ORM(Object Relation Mapping)框架
1.2 如何下载MyBatis
下载网址:https://github.com/mybatis/mybatis-3
1.4 为什么要使用MyBatis – 现有持久化技术的对比
JDBC
- SQL夹在Java代码块里,耦合度高导致硬编码内伤
- 维护不易且实际开发需求中sql有变化,频繁修改的情况多见
Hibernate笨重和JPA - 长难复杂SQL,对于Hibernate而言处理也不容易
- 内部自动生产的SQL,不容易做特殊优化
- 基于全映射的全自动框架,大量字段的POJO进行部分映射时比较困难。导致数据库性能下降
MyBatis - 对开发人员而言,核心sql还是需要自己优化
- sql和java编码分开,功能边界清晰,一个专注业务、一个专注数据
二、开发环境的准备
- 创建一个Java工程,导入以下jar包
<!--导入MySQL的驱动包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>
<!--导入MyBatis的jar包-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</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>
2.导入log4j 的配置文件log4j.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
<param name="Encoding" value="UTF-8" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m (%F:%L) \n" />
</layout>
</appender>
<logger name="java.sql">
<level value="debug" />
</logger>
<logger name="org.apache.ibatis">
<level value="info" />
</logger>
<root>
<level value="debug" />
<appender-ref ref="STDOUT" />
</root>
</log4j:configuration>
2.2 创建数据库和表
2.3 创建Employee类
public class Employee {
private Integer id;
private String lastName;
private String email;
private Double salary;
private Integer deptId;
public Employee() {
}
public Employee(Integer id, String lastName, String email, Double salary, Integer deptId) {
this.id = id;
this.lastName = lastName;
this.email = email;
this.salary = salary;
this.deptId = deptId;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Double getSalary() {
return salary;
}
public void setSalary(Double salary) {
this.salary = salary;
}
public Integer getDeptId() {
return deptId;
}
public void setDeptId(Integer deptId) {
this.deptId = deptId;
}
@Override
public String toString() {
return "Employee{" +
"id=" + id +
", lastName='" + lastName + '\'' +
", email='" + email + '\'' +
", salary=" + salary +
", deptId=" + deptId +
'}';
}
}
2.4 创建EmployeeMapper(Dao)接口
public interface EmployeeMapper {
Employee getEmployeeById(Integer id);
}
2.5 创建Mybatis的sql映射文件
- 参考MyBatis的官方文档创建EmployeeMapper.xml映射文件
- 完成两个绑定
- <mapper>标签中的namespace属性必须指定成Mapper接口的全类名
- Mapper映射文件中的增删改查标签的id必须指定成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">
<!--namespace属性:设置为接口的全类名-->
<mapper namespace="com.atguigu.mybatis.dao.EmployeeMapper">
<!--
id属性:设置为接口中的方法名
resultType属性:设置为方法的返回值的类型的全类名
-->
<select id="getEmployeeById" resultType="com.atguigu.mybatis.entities.Employee">
select id,last_name lastName,email,salary,dept_id deptId
from employees
where id = #{id}
</select>
</mapper>
2.6 创建MyBatis的全局配置文件
参考MyBatis的官网文档创建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="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!--设置Mapper映射文件(sql映射文件)-->
<mappers>
<mapper resource="EmployeeMapper.xml"/>
</mappers>
</configuration>
2.7 测试
参考MyBatis的官方文件创建测试代码
public class MyBatisTest {
/*
测试HelloWorld
*/
@Test
public void testHelloWorld() throws IOException {
//设置MyBatis全局配置文件的路径
String resource = "mybatis-config.xml";
//读取类路径下的配置文件得到输入流
InputStream inputStream = Resources.getResourceAsStream(resource);
//创建SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//获取SqlSession对象,相当于Connection对象
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
//获取Mapper代理对象
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
//调用EmployeeMapper中获取一个对象的方法
Employee employeeById = mapper.getEmployeeById(1);
System.out.println(employeeById);
} finally {
//关闭sqlSession
sqlSession.close();
}
}
}
三、MyBatis全局配置文件
3.1 MyBatis全局配置文件简介
MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:
3.2 properties属性
1.可外部配置且可动态替换的,既可以在典型的 Java 属性文件中配置,亦可通过 properties 元素的子元素来配置
<properties>
<property name="jdbc.username" value="hanzong"/>
<property name="jdbc.password" value="123456"/>
</properties>
2.然而properties的作用并不单单是这样,你可以创建一个资源文件,名为jdbc.properties的文件,将四个连接字符串的数据在资源文件中通过键值 对(key=value)的方式放置,不要任何符号,一条占一行
a-> jdbc.properties
jdbc.username=root
jdbc.password=root
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.driver=com.mysql.jdbc.Driver
b-> 通过properties标签引入jdbc.properties文件
<!--
resource属性:引入类路径下的属性文件
url属性:引入网络或其他指定路径下的属性文件
-->
<properties resource="jdbc.properties"></properties>
c-> 在environment元素的dataSource元素中为其动态设置
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
3.如果一个属性在不只一个地方进行了配置,那么,MyBatis 将按照下面的顺序来加载:
a.首先读取在 properties 元素体内指定的属性。
b.然后根据 properties 元素中的 resource 属性读取类路径下属性文件,或根据 url 属性指定的路径读取属性文件,并覆盖之前读取过的同名属性。
c.最后读取作为方法参数传递的属性,并覆盖之前读取过的同名属性。
3.3 settings设置
这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。 下表描述了设置中各项常见设置的含义、默认值等
设置名 |
描述 |
默认值 |
cacheEnabled |
缓存的全局开关 |
true |
lazyLoadingEnabled |
延迟加载的全局开关 |
false |
aggressiveLazyLoading |
开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载 |
false (在 3.4.1 及之前的版本中默认为 true) |
autoMappingBehavior |
指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示关闭自动映射;PARTIAL 只会自动映射没有定义嵌套结果映射的字段。 FULL 会自动映射任何复杂的结果集(无论是否嵌套)。 |
partial |
mapUnderscoreToCamelCase |
是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn |
false |
3.4 typeAliases 别名处理
1.通过子标签typeAlias设置别名
<typeAliases>
<!--
子标签typeAlias:用来给某些类指定别名
type属性:指定起别名的类的全类名
alias属性:指定别名,如果没有指定则是类命的首字母小写,但是别名大小写不敏感
-->
<typeAlias type="com.atguigu.mybatis.entities.Employee" alias="employee"></typeAlias>
</typeAliases>
2.通过子标签package设置别名
<typeAliases>
<!--
子标签package:通过指定包名给包下所有的类起别名
name属性:指定包名
-->
<package name="com.atguigu.mybatis.entities"/>
</typeAliases>
3.MyBatis已经取好的别名
3.5 typeHandlers 类型处理器
1.无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型
2.MyBatis中提供的类型处理器:
3.日期和时间的处理,JDK1.8以前一直是个头疼的问题。我们通常使用JSR310规范领导者Stephen Colebourne创建的Joda-Time来操作。1.8已经实现全部的JSR310规范了
4.日期时间处理上,我们可以使用MyBatis基于JSR310(Date and Time API)编写的各种日期时间类型处理器。
5. MyBatis3.4以前的版本需要我们手动注册这些处理器,以后的版本都是自动注册的,如需注册,需要下载mybatistypehandlers-jsr310,并通过如下方式注册
6.自定义类型转换器
- 我们可以重写类型处理器或创建自己的类型处理器来处理不支持的或非标准的类 型
- 步骤
- 实现org.apache.ibatis.type.TypeHandler接口或者继承org.apache.ibatis.type.BaseTypeHandler
- 指定其映射某个JDBC类型(可选操作)
- 在mybatis全局配置文件中注册
3.6 plugins 插件机制
- 插件是MyBatis提供的一个非常强大的机制,我们可以通过插件来修改MyBatis的一些核心行为。插件通过动态代理机制,可以介入四大对象的任何一个方法的执行
- 四大对象:
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
3.7 environments 环境配置
- MyBatis可以配置多种环境,比如开发、测试和生产环境需要有不同的配置
- 每种环境使用一个environment标签进行配置并通过id属性指定唯一标识符
- 可以通过environments标签中的default属性指定一个环境的标识符来快速的切换环境
- environment-指定具体环境
- id:指定当前环境的唯一标识
- transactionManager和dataSource都必须有
<environments default="development">
<!--开发环境-->
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
<!--生产环境-->
<environment id="online">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
5.transactionManager
type: JDBC | MANAGED | 自定义
JDBC:使用了 JDBC 的提交和回滚设置,依赖于从数据源得到的连接来管理事务范 围。 JdbcTransactionFactory
MANAGED:不提交或回滚一个连接、让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 ManagedTransactionFactory
自定义:实现TransactionFactory接口,type=全类名/别名
6.dataSource
type: UNPOOLED | POOLED | JNDI | 自定义
UNPOOLED:不使用连接池, UnpooledDataSourceFactory
POOLED:使用连接池, PooledDataSourceFactory
JNDI: 在EJB 或应用服务器这类容器中查找指定的数据源
自定义:实现DataSourceFactory接口,定义数据源的获取方式
7.可以通过org.apache.ibatis.session. Configuration查询上述信息
8.实际开发中我们使用Spring管理数据源,并进行事务控制的配置来覆盖上述配置
3.8 databaseIdProvider数据库厂商标识
1.MyBatis 可以根据不同的数据库厂商执行不同的语句
<!--设置数据库厂商标识-->
<databaseIdProvider type="DB_VENDOR">
<property name="MySQL" value="mysql"/>
<property name="Oracle" value="oracle"/>
</databaseIdProvider>
2.type: DB_VENDOR, 使用MyBatis提供的VendorDatabaseIdProvider解析数据库厂商标识。也 可以实现DatabaseIdProvider接口来自定义。
VendorDatabaseIdProvider会通过 DatabaseMetaData#getDatabaseProductName() 返回的字符串进行设置。由于通常情况下这个字符串都非常长而且相同产品的不同版本会返回不同的值,所以最好通过设置属性别名来使其变短。
name:数据库厂商标识
value:为标识起一个别名,方便SQL语句使用databaseId属性引用
3.配置了databaseIdProvider后,在SQL映射文件中的增删改查标签中使用databaseId
来指定数据库标识的别名
<select id="getEmployeeById" resultType="employee" databaseId="mysql">
select id,last_name,email,salary,dept_id
from employees
where id = #{id}
</select>
4.MyBatis匹配规则如下:
- 如果没有配置databaseIdProvider标签,那么databaseId=null
- 如果配置了databaseIdProvider标签,使用标签配置的name去匹配数据库信息,匹配上则设置databaseId=配置指定的值,否则依旧为null
- 如果databaseId不为null,他只会执行配置databaseId的sql语句
- MyBatis 会加载不带 databaseId 属性和带有匹配当前数据库databaseId 属性的所有语句。如果同时找到带有 databaseId 和不带databaseId 的相同语句,则后者会被舍弃。
3.9 mappers 映射器
- 用来在mybatis初始化的时候,告诉mybatis需要引入哪些Mapper映射文件
- mapper标签:逐个注册SQL映射文件
- resource属性:引入类路径下的文件
- url 属性:引入网络路径或者是磁盘路径下的文件
- class属性:设置Mapper接口的全类名
- 如果有SQL映射文件,要求Mapper接口与 SQL映射文件同名同包。
- 如果没有SQL映射文件 ,使用注解在接口的方法上写SQL语句
<mappers>
<mapper resource="com/atguigu/mybatis/dao/EmployeeMapper.xml"/>
</mappers>
3. package标签:批量注册SQL映射文件
name 属性:设置Mapper接口所在的包名
a.有SQL映射文件 ,要求Mapper接口与 SQL映射文件同名同包。
b.没有SQL映射文件,使用注解在接口的方法上写SQL语句。
<mappers>
<package name="com.atguigu.mybatis.dao"/>
</mappers>
四、 MyBatis 映射文件
4.1 Mybatis映射文件简介
- MyBatis 的真正强大在于它的映射语句,也是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 就是针对 SQL 构建的,并且比普通的方法做的更好。
- SQL 映射文件只有很少的几个顶级元素(按照应被定义的顺序列出):
- cache – 该命名空间的缓存配置。
- cache-ref – 引用其它命名空间的缓存配置。
- resultMap – 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。
parameterMap – 老式风格的参数映射。此元素已被废弃,并可能在将来被移除!请使用行内参数映射。文档中不会介绍此元素。- sql – 可被其它语句引用的可重用语句块。
- insert – 映射插入语句。
- update – 映射更新语句。
- delete – 映射删除语句。
- select – 映射查询语句。
4.2 Mybatis映射CRUD
4.2.1 select
1.Mapper接口方法
Employee getEmployeeById(Integer id)
2.Mapper映射文件
<select id="getEmployeeById" resultType="employee" databaseId="mysql">
select id,last_name,email,salary,dept_id
from employees
where id = #{id}
</select>
3.属性说明
- id:指定接口中的方法名。
- parameterType:设置传入的参数的类型,也可以不指定,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数。
- resultType:设置方法的返回值的类型。注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。 resultType 和 resultMap 之间只能同时使用一个。
- resultMap:设置对外部 resultMap高级结果集标签的引用。
- flushCache:将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:false。
- useCache:将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true。
- databaseId:如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有,则不带的会被忽略。
4.2.2 insert
1.Mapper接口方法
void addEmployee(Employee employee)
2.Mapper映射文件
<!--
parameterType属性:设置请求参数的类型的全类名,该属性也可以不指定,
MyBatis 可以通过类型处理器推断出具体传入语句的参数
-->
<insert id="addEmployee" parameterType="com.atguigu.mybatis.entities.Employee">
insert into employees(last_name,email,salary,dept_id)
values(#{lastName},#{email},#{salary},#{deptId})
</insert
4.2.3 update
1.Mapper接口方法
void updateEmployee(Employee employee)
2.Mapper映射文件
<update id="updateEmployee">
update employees set
last_name=#{lastName},
email=#{email},
salary=#{salary},
dept_id=#{deptId}
where id=#{id}
</update>
4.2.4 delete
1.Mapper接口方法
void deleteEmployeeById(Integer id);
2.Mapper映射文件
<delete id="deleteEmployeeById">
delete from employees where id = #{id}
</delete>
3.insert、update、delete标签属性说明
- flushCache:将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:(对 insert、update 和 delete 语句)true。
- useGeneratedKeys:(仅适用于 insert 和 update)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的自动递增字段),默认值:false。
- keyProperty:(仅适用于 insert 和 update)指定能够唯一识别对象的属性,MyBatis 会使用 getGeneratedKeys 的返回值或 insert 语句的 selectKey 子元素设置它的值,默认值:未设置(unset)。如果生成列不止一个,可以用逗号分隔多个属性名称。
- keyColumn:(仅适用于 insert 和 update)设置生成键值在表中的列名,在某些数据库(像 PostgreSQL)中,当主键列不是表中的第一列的时候,是必须设置的。如果生成列不止一个,可以用逗号分隔多个属性名称。
4.3 主键生成方式、获取主键值
4.3.1 主键生成方式
- 支持主键自增,例如MySQL数据库
- 不支持主键自增,例如Oracle数据库
4.3.2 获取主键值
1.若数据库支持自动生成主键的字段(比如 MySQL 和 SQL Server),则可以设置 useGeneratedKeys=”true”,然后再把 keyProperty 设置到目标属性上。
<insert id="addEmployee" useGeneratedKeys="true" keyProperty="id">
insert into employees(last_name,email,salary,dept_id)
values(#{lastName},#{email},#{salary},#{deptId})
</insert
2.而对于不支持自增型主键的数据库(例如 Oracle),则可以使用 selectKey 子元素:selectKey 元素将会首先运行,id 会被设置,然后插入语句会被调用。
<insert id="addEmployee" databaseId="oracle">
<selectKey order="BEFORE" keyProperty="id" resultType="integer">
select employee_seq.nextval from dual
</selectKey>
insert into oracle_employees(id,last_name,email,salary,dept_id)
values(#{id},#{lastName},#{email},#{salary},#{deptId})
</insert>
4.4 参数传递
4.4.1 参数传递的方式
1.单个普通类型参数
可以接受基本类型,包装类型,字符串类型等。这种情况MyBatis可直接使用这个参数,不需 要经过任何处理。
2.多个参数
任意多个参数,都会被MyBatis重新包装成一个Map传入。Map的key是param1,param2,或者arg0,arg1…,值就是参数的值。
3.命名参数
为参数使用@Param起一个名字,MyBatis就会将这些参数封装进map中,key就是我们自己指定的名字。
4.javaBean
当这些参数属于我们业务javaBean时,我们直接传递javaBean。
5.Map
我们也可以封装多个参数为map,直接传递。
6.Collection/Array
会被MyBatis封装成一个map传入, Collection对应的key是collection,Array对应的key是array. 如果确定是List集合,key还可以是list。
4.4.2 参数的获取方式
- #{key}:获取参数的值,预编译到SQL中。安全。
- ${key}:获取参数的值,拼接到SQL中。有SQL注入问题。
4.5 select查询的几种情况
1.查询单行数据返回单个对象
Employee getEmployeeById(Integer id);
2.查询多行数据返回对象的集合
List<Employee> getEmployees();
3.查询单行数据返回Map集合
- 数据库中的字段名为key
- 数据库中的字段值为value
Map<String,Object> getEmployeeByIdReturnMap(Integer id );
4.查询多行数据返回Map集合
@MapKey("id") //指定使用数据库中的那个字段的值作为map的key
Map<Integer,Employee> getEmployeesReturnMap();
4.6 resultType自动映射
- autoMappingBehavior默认是PARTIAL,开启自动映射的功能。唯一的要求是结果集列名和POJO属性名一致。
- 如果autoMappingBehavior设置为none则会取消自动映射。
- 数据库字段命名规范,POJO属性符合驼峰命名法,如A_COLUMN,aColumn,我们可以开启自动驼峰命名规则映射功能,mapUnderscoreToCamelCase=true。
4.7 resultMap自定义映射
- resultMap,实现高级结果集映射
- id :用于完成主键值的映射
- result :用于完成普通列的映射
- association :一个复杂的类型关联;许多结果将包成这种类型
- collection : 复杂类型的集合
4.7.1 id&result 中的属性
属性 |
描述 |
property |
映射到列结果的字段或属性。比如,你可以这样映射一些简单的东西:“username”,或者映射到一些复杂的东西上:“address.street.number” |
column |
数据库中的列名,或者是列的别名。一般情况下,这和传递给 resultSet.getString(columnName) 方法的参数一样 |
javaType |
一个 Java 类的全限定名,或一个类型别名,如果你映射到一个 JavaBean,MyBatis 通常可以推断类型。 |
jdbcType |
JDBC 类型 |
typeHandler |
使用这个属性,你可以覆盖默认的类型处理器。 这个属性值是一个类型处理器实现类的全限定名,或者是类型别名 |
<!--
resultMap属性:引用高级结果集映射,设置为resultMap标签的id属性值
-->
<select id="getEmployeeById" resultMap="myResult">
select id,last_name,email,salary,dept_id
from employees
where id = #{id}
</select>
<resultMap id="myResult" type="com.atguigu.mybatis.entities.Employee">
<id property="id" column="id"></id>
<result property="lastName" column="last_name"></result>
<result property="email" column="email"></result>
<result property="salary" column="salary"></result>
<result property="deptId" column="dept_id"></result>
</resultMap
4.7.2 association
- POJO中的属性可能会是一个对象,我们可以使用联合查询,并以级联属性的方式封装对象。使用association标签定义对象的封装规则。
a.使用级联的方式给部门属性赋值
<select id="getEmployeeAndDept" resultMap="myEmp">
SELECT e.*,d.id d_id,d.name d_name
FROM employees e
LEFT JOIN departments d
ON e.dept_id = d.id
WHERE e.id = #{id};
</select>
<resultMap id="myEmp" type="com.atguigu.mybatis.entities.Employee">
<id property="id" column="id"></id>
<result property="lastName" column="last_name"></result>
<result property="email" column="email"></result>
<result property="salary" column="salary"></result>
<!--通过级联方式给部门属性赋值-->
<result property="dept.id" column="d_id"></result>
<result property="dept.name" column="d_name"></result>
</resultMap>
b.通过association标签给部门属性赋值
<resultMap id="myEmp2" type="com.atguigu.mybatis.entities.Employee">
<id property="id" column="id"></id>
<result property="lastName" column="last_name"></result>
<result property="email" column="email"></result>
<result property="salary" column="salary"></result>
<!--通过association标签给部门属性赋值
property属性:指定Employee中部门的属性名
javaType属性:指定属性的类型
-->
<association property="dept" javaType="com.atguigu.mybatis.entities.Department">
<id property="id" column="d_id"></id>
<result property="name" column="d_name"></result>
</association>
</resultMap>
4.7.3 association 分步查询
实际的开发中,对于每个实体类都应该有具体的增删改查方法,也就是DAO层, 因此,对于查询员工信息并且将对应的部门信息也查询出来的需求,就可以通过分步的方式完成查询。
- 先通过员工的id查询员工信息
- 再通过查询出来的员工信息中的外键(部门id)查询对应的部门信息
- 将部门信息设置到员工中
<?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">
<!--namespace属性:设置接口的全类名-->
<mapper namespace="com.atguigu.mybatis.dao.DepartmentMapper">
<select id="getDepartmentById" resultType="com.atguigu.mybatis.entities.Department">
select id,name
from departments
where id = #{id}
</select>
</mapper>
<select id="getEmployeeAndDeptByStep" resultMap="myEmp3">
select id,last_name,email,salary,dept_id
from employees
where id = #{id}
</select>
<!--
分步查询:
1.根据员工的id查询员工信息
2.根据员工的部门id查询部分信息
3.将部门信息设置到员工中
-->
<resultMap id="myEmp3" type="com.atguigu.mybatis.entities.Employee">
<id property="id" column="id"></id>
<result property="lastName" column="last_name"></result>
<result property="email" column="email"></result>
<result property="salary" column="salary"></result>
<!--通过association标签分步查询给部门属性赋值
property属性:指定Employee中部门的属性名
select属性:指定调用那个接口的那个方法查询部门信息
column属性:指定将那个字段的值传入到select中调用的方法中
-->
<association property="dept" select="com.atguigu.mybatis.dao.DepartmentMapper.getDepartmentById"
column="dept_id"></association>
</resultMap
4.7.4 association 分步查询使用延迟加载
在分步查询的基础上,可以使用延迟加载来提升查询的效率,只需要在全局的
settings中进行如下的配置:
<settings>
<!-- 开启延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 设置加载的数据是按需加载-->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
在association标签中也可以设置延迟加载,将覆盖全局配置
<resultMap id="myEmp3" type="com.atguigu.mybatis.entities.Employee">
<id property="id" column="id"></id>
<result property="lastName" column="last_name"></result>
<result property="email" column="email"></result>
<result property="salary" column="salary"></result>
<!--通过association标签分步查询给部门属性赋值
property属性:指定Employee中部门的属性名
select属性:指定调用那个接口的那个方法查询部门信息
column属性:指定将那个字段的值传入到select中调用的方法中
fetchType属性:是否使用延迟加载
lazy: 使用延迟加载
eager:关闭延迟加载
-->
<association property="dept" select="com.atguigu.mybatis.dao.DepartmentMapper.getDepartmentById"
column="dept_id" fetchType="lazy"></association>
</resultMap
4.7.5 collection
POJO中的属性可能会是一个集合对象,我们可以使用联合查询,并以级联属性的方式封装对象,使用collection标签定义对象的封装规则
<select id="getDepartmentAndEmps" resultMap="myDept">
SELECT d.id d_id,d.name d_name,e.*
FROM departments d
LEFT JOIN employees e
ON d.id = e.dept_id
WHERE d.id = #{id}
</select>
<resultMap id="myDept" type="com.atguigu.mybatis.entities.Department">
<id property="id" column="d_id"></id>
<result property="name" column="d_name"></result>
<!--
property属性:集合属性的属性名
ofType属性:集合中元素的类型
-->
<collection property="emps" ofType="com.atguigu.mybatis.entities.Employee">
<id property="id" column="id"></id>
<result property="lastName" column="last_name"></result>
<result property="email" column="email"></result>
<result property="salary" column="salary"></result>
</collection>
</resultMap>
4.7.6 collection 分步查询
实际的开发中,对于每个实体类都应该有具体的增删改查方法,也就是DAO层, 因此,对于查询部门信息并且将对应的所有的员工信息也查询出来的需求,就可以通过分步的方式完成查询。
- 先通过部门的id查询部门信息
- 再通过部门id作为员工的外键查询对应的员工信息.。
- 将所有员工设置到部门中
<select id="getEmployeesByDeptId" resultType="com.atguigu.mybatis.entities.Employee">
select id,last_name,email,salary,dept_id
from employees
where dept_id = #{deptId}
</select
<select id="getDepartmentAndEmpsByStep" resultMap="myDept2">
select id,name
from departments
where id = #{id}
</select>
<!--
分步查询:
1.根据部门的id查询部门信息
2.根据部门id查询出该部门下所有的员工
3.将员工设置到部门中
-->
<resultMap id="myDept2" type="com.atguigu.mybatis.entities.Department">
<id property="id" column="id"></id>
<result property="name" column="name"></result>
<!--
property属性:集合属性的属性名
select属性:调用那个接口的那个方法查询员工信息
column属性:将那个字段的值传入到select属性调用的方法中
-->
<collection property="emps" select="com.atguigu.mybatis.dao.EmployeeMapper.getEmployeesByDeptId"
column="id"></collection>
</resultMap
4.7.7 collection 分步查询使用延迟加载
<collection property="emps" select="com.atguigu.mybatis.dao.EmployeeMapper.getEmployeesByDeptId"
column="id" fetchType="lazy"></collection
4.7.8 扩展: 分步查询多列值的传递
- 如果分步查询时,需要传递给调用的查询中多个参数,则需要将多个参数封装成Map来进行传递,语法如下: {k1=v1, k2=v2....}
- 在所调用的查询方法取值时就要参考Map的取值方式,需要严格的按照封装map时所用的key来取值.
五、 MyBatis 动态SQL
5.1 MyBatis动态SQL简介
- 动态 SQL是MyBatis强大特性之一。极大的简化我们拼装SQL的操作
- 动态 SQL 元素和使用 JSTL 或其他类似基于 XML 的文本处理器相似
- MyBatis 采用功能强大的基于 OGNL 的表达式来简化操作
if
choose (when, otherwise)
trim (where, set)
foreach
4.OGNL( Object Graph Navigation Language )对象图导航语言,这是一种强大的
表达式语言,通过它可以非常方便的来操作对象属性。 类似于我们的EL,SpEL等
访问对象属性: person.name
调用方法: person.getName()
调用静态属性/方法: @java.lang.Math@PI
@java.util.UUID@randomUUID()
调用构造方法: new com.atguigu.bean.Person(‘admin’).name
运算符: +,-*,/,%
逻辑运算符: in,not in,>,>=,<,<=,==,!=
注意:xml中特殊符号如 ” , > , < 等这些都需要使用转义字符
5.2 if where
- if用于完成简单的判断.解决SQL语句中where关键字以及条件前面的and
- where用于或者or的问题
<select id="getEmployeeByConditionIf" resultType="com.atguigu.mybatis.entities.Employee">
select id,last_name,email,salary
from employees
<where>
<if test="id!=null">
id=#{id}
</if>
<if test="lastName!=null&&lastName!=""">
and last_name=#{lastName}
</if>
<if test="email!=null and email!=''">
and email=#{email}
</if>
<if test="salary!=null">
and salary=#{salary}
</if>
</where>
</select>
5.3 trim
trim 可以在条件判断完的SQL语句前后添加或者去掉指定的字符
prefix: 添加前缀
prefixOverrides: 去掉前缀
suffix: 添加后缀
suffixOverrides: 去掉后缀
<select id="getEmployeeByConditionIf" resultType="com.atguigu.mybatis.entities.Employee">
select id,last_name,email,salary
from employees
<trim prefix="where" suffixOverrides="and">
<if test="id!=null">
id=#{id} and
</if>
<if test="lastName!=null&&lastName!=""">
last_name=#{lastName} and
</if>
<if test="email!=null and email!=''">
email=#{email} and
</if>
<if test="salary!=null">
salary=#{salary}
</if>
</trim>
5.4 set
set 主要是用于解决修改操作中SQL语句中可能多出逗号的问题
<update id="updateEmployeeByConditionSet">
update employees
<set>
<if test="lastName!=null and lastName!=''">
last_name = #{lastName},
</if>
<if test="email!=null and email!=''">
email = #{email},
</if>
<if test="salary!=null">
salary = #{salary},
</if>
</set>
where id = #{id}
</update
5.5 choose(when、otherwise)
choose 主要是用于分支判断,类似于java中的switch case,只会满足所有分支中的一个
<select id="getEmployeeByConditionChoose" resultType="com.atguigu.mybatis.entities.Employee">
select id,last_name,email,salary
from employees
<where>
<choose>
<when test="id!=null">
id = #{id}
</when>
<when test="lastName!=null">
last_name = #{lastName}
</when>
<when test="email!=null">
email = #{email}
</when>
<otherwise>
salary = #{salary}
</otherwise>
</choose>
</where>
</select
5.6 foreach
1.foreach 主要用于循环迭代
collection: 要迭代的集合
item: 当前从集合中迭代出的元素
open: 开始字符
close:结束字符
separator: 元素与元素之间的分隔符
index:
迭代的是List集合: index表示的当前元素的下标
迭代的Map集合: index表示的当前元素的key
<select id="getEmployeesByConditionForeach" resultType="com.atguigu.mybatis.entities.Employee">
select id,last_name,email,salary
from employees
where id in
<foreach collection="ids" open="(" close=")" separator="," item="id">
#{id}
</foreach>
</select>
5.7 sql
sql 标签是用于抽取可重用的sql片段,将相同的,使用频繁的SQL片段抽取出来,单独定义,方便多次引用.
抽取SQL:
<sql id="selectSql">
select id,last_name,email,salary
from employees
</sql>
引用SQL:
<include refid="selectSql"></include>
六、MyBatis 缓存机制
6.1 缓存机制简介
- MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。缓存可以极大的提升查询效率
- MyBatis系统中默认定义了两级缓存
一级缓存
二级缓存
- 默认情况下,只有一级缓存(SqlSession级别的缓存,也称为本地缓存)开启。
- 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
- 为了提高扩展性。MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存
6.2 一级缓存的使用
- 一级缓存(local cache), 即本地缓存, 作用域默认为sqlSession。当 Session flush 或 close 后, 该 Session 中的所有 Cache 将被清空。
- 本地缓存不能被关闭, 但可以调用 clearCache() 来清空本地缓存, 或者改变缓存的作用域.
- 在mybatis3.1之后, 可以配置本地缓存的作用域. 在 mybatis的全局配置文件中配置。
设置名 |
描述 |
默认值 |
localCacheScope |
MyBatis 利用本地缓存机制(Local Cache)防止循环引用和加速重复的嵌套查询。 默认值为 SESSION,会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地缓存将仅用于执行语句,对相同 SqlSession 的不同查询将不会进行缓存。 |
SESSION |
4.一级缓存的工作机制
同一次会话期间只要查询过的数据都会保存在当前SqlSession的一个Map中
key: hashCode+查询的SqlId+编写的sql查询语句+参数
6.3 一级缓存失效的几种情况
- 不同的SqlSession对应不同的一级缓存
- 同一个SqlSession但是查询条件不同
- 同一个SqlSession两次查询期间执行了任何一次增删改操作
- 同一个SqlSession两次查询期间手动清空了缓存
- 同一个SqlSession两次查询期间提交了事务
6.4 二级缓存的使用
- 二级缓存(second level cache),全局作用域缓存
- 二级缓存默认不开启,需要手动配置
- MyBatis提供二级缓存的接口以及实现,缓存实现要求POJO实现Serializable接口
- 二级缓存在 SqlSession 关闭或提交之后才会生效
- 二级缓存使用的步骤:
- 全局配置文件中开启二级缓存<setting name="cacheEnabled" value="true"/>
- 需要使用二级缓存的映射文件处使用cache配置缓存<cache />
- 注意:POJO需要实现Serializable接口
二级缓存相关的属性
- eviction=“FIFO”:缓存回收策略:
LRU – 最近最少使用的:移除最长时间不被使用的对象。
FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
软引用:软引用需要用SoftReference类来实现,对于只有软引用的对象来说,当系统内存足够时它不会被回收,当系统内存空间不足时它会被回收。软引用通常用在对内存敏感的程序中。
WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
弱引用:弱引用需要用WeakReference类来实现,它比软引用的生存期更短,对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管JVM的内存空间是否足够,总会回收该对象占用的内存。
默认的是 LRU。
- flushInterval:刷新间隔,单位毫秒
默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新
- size:引用数目,正整数
代表缓存最多可以存储多少个对象,太大容易导致内存溢出
- readOnly:只读,true/false
true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。
false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是 false。
6.5 缓存的相关属性设置
1.全局setting的cacheEnabled:
配置二级缓存的开关,一级缓存一直是打开的。
2.select标签的useCache属性:
这个属性是用来配置这个select是否使用二级缓存,默认为true。
一级缓存一直是使用的。
3.所有的增删改查标签中的flushCache属性:
增删改默认flushCache=true。sql执行以后,会同时清空一级和二级缓存。
查询默认 flushCache=false。
4.sqlSession.clearCache():只是用来清除一级缓存。
七、 PageHelper分页插件
7.1 PageHelper 分页插件简介
- PageHelper是MyBatis中非常方便的第三方分页插件
- 官方文档:
https://github.com/pagehelper/Mybatis-PageHelper/blob/master/README_zh.md
3.我们可以对照官方文档的说明,快速的使用插件
7.2 PageHelper的使用步骤
1.导入以下jar包
<!-- pagehelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.0.0</version>
</dependency>
2.在MyBatis全局配置文件中配置分页插件
<plugins>
<!-- com.github.pagehelper 为 PageInterceptor类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor
">
<!-- 设置数据库类型 Oracle,Mysql,MariaDB,SQLite,Hsqldb,PostgreSQL 六种数据库-->
<property name="helperDialect" value="mysql"/>
</plugin>
</plugins>
3.使用PageHelper提供的方法进行分页
4.可以使用更强大的PageInfo封装返回结果
7.3 Page对象的使用
1.在查询之前通过PageHelper.startPage(页码,条数)设置分页信息,该方法返回Page对象
@Test
public void testPageHelper() throws IOException {
//设置MyBatis全局配置文件的路径
String resource = "mybatis-config.xml";
//读取类路径下的配置文件得到输入流
InputStream inputStream = Resources.getResourceAsStream(resource);
//创建SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//获取SqlSession对象,相当于Connection对象
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
//获取Mapper代理对象
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
//设置分页信息,每页显示3条记录,获取第1页
Page<Object> page = PageHelper.startPage(1, 3);
//获取所有员工
List<Employee> employees = mapper.getEmployees();
System.out.println("当前页是:"+page.getPageNum());
System.out.println("每页显示的条数是:"+page.getPageSize());
System.out.println("总页数是:"+page.getPages());
System.out.println("总记录数是:"+page.getTotal());
System.out.println("当前页中的记录有:");
for (Employee employee : employees) {
System.out.println(employee);
}
} finally {
//关闭sqlSession
sqlSession.close();
}
}
7.4 PageInfo对象的使用
在查询完数据后,使用PageInfo对象封装查询结果,可以获取更详细的分页信息以及可以完成分页逻辑
@Test
public void testPageHelper() throws IOException {
//设置MyBatis全局配置文件的路径
String resource = "mybatis-config.xml";
//读取类路径下的配置文件得到输入流
InputStream inputStream = Resources.getResourceAsStream(resource);
//创建SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//获取SqlSession对象,相当于Connection对象
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
//获取Mapper代理对象
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
//设置分页信息
Page<Object> page = PageHelper.startPage(4, 2);
//获取所有员工
List<Employee> employees = mapper.getEmployees();
//创建PageInfo对象设置每页只显示5个页码
PageInfo<Employee> pageInfo = new PageInfo<>(employees, 5);
System.out.println("当前页是:"+pageInfo.getPageNum());
System.out.println("每页显示的条数是:"+pageInfo.getPageSize());
System.out.println("总页数是:"+pageInfo.getPages());
System.out.println("总记录数是:"+pageInfo.getTotal());
System.out.println("是否有上一页:"+pageInfo.isHasPreviousPage());
System.out.println("上一页是:"+pageInfo.getPrePage());
System.out.println("是否有下一页:"+pageInfo.isHasNextPage());
System.out.println("下一页是:"+pageInfo.getNextPage());
System.out.println("是否是第一页:"+pageInfo.isIsFirstPage());
System.out.println("是否是最后一页:"+pageInfo.isIsLastPage());
System.out.println("导航页的第一个页码是:"+pageInfo.getNavigateFirstPage());
System.out.println("导航页的最后一个页码是:"+pageInfo.getNavigateLastPage());
System.out.println("导航页的总页码是:"+pageInfo.getNavigatePages());
System.out.println("当前页中的记录有:");
for (Employee employee : employees) {
System.out.println(employee);
}
System.out.println("页码信息:");
int[] navigatepageNums = pageInfo.getNavigatepageNums();
for (int navigatepageNum : navigatepageNums) {
System.out.print(navigatepageNum+" ");
}
} finally {
//关闭sqlSession
sqlSession.close();
}