文章目录
1 MyBatis 简介
1.1 MyBatis 历史
MyBatis 最初是 Apache 的一个开源项目 iBatis ,2010年6月这个项目由 Apache Software Foundation 迁移到了 Google Code 。随着开发团队转投 Google Code 旗下, iBatis 3.x 正式更名为 MyBatis 。代码于2013年11月迁移到 Github 。
iBatis 一词来源于“internet”和“abatis”的组合,是一个基于 Java 的持久层框架。 iBatis 提供的持久层框架包括 SQL Maps 和 DAO(Data Access Objects,数据访问对象)。
1.2 MyBatis 特性
- 支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架
- 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集
- 可以使用简单的 XML 或注解用于配置和原始映射,将接口和 Java 的 POJO(Plain Old Java Objects,普通的 Java 对象)映射成数据库中的记录
- 一个半自动的 ORM(Object Relation Mapping,对象关系映射)框架
1.3 ORM
ORM(Object Relationship Mapping)对象关系映射。
- 对象:Java 的实体类对象
- 关系:关系型数据库
- 映射:二者之间的对应关系
Java 概念 | 数据库概念 |
---|---|
类 | 表 |
属性 | 字段 / 列 |
对象 | 记录 / 行 |
1.4 和其它持久层技术对比
- JDBC
- SQL 夹杂在 Java 代码中耦合度高,导致硬编码内伤
- 维护不易且实际开发需求中 SQL 有变化,频繁修改的情况多见
- 代码冗长,开发效率低
- Hibernate 和 JPA
- 操作简便,开发效率高
- 程序中的长难复杂 SQL 需要绕过框架
- 内部自动生产的 SQL ,不容易做特殊优化
- 基于全映射的全自动框架,大量字段的 POJO 进行部分映射时比较困难
- 反射操作太多,导致数据库性能下降
- MyBatis
- 轻量级,性能出色
- SQL 和 Java 编码分开,功能边界清晰,Java 代码专注业务、SQL 语句专注数据
- 开发效率稍逊于 HIbernate ,但是完全能够接受
2 搭建 MyBatis 环境
2.1 创建 Maven 工程
-
打包方式:jar
-
引入依赖
-
<dependencies> <!-- MyBatis 核心 --> <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> <!-- MySQL 驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.3</version> </dependency> </dependencies>
-
2.2 创建 MyBatis 的核心配置文件
- 习惯上命名为 mybatis-config.xml ,这个文件名仅仅只是建议,并非强制要求。
- 将来整合 Spring 之后,这个配置文件可以省略,所以操作时可以直接复制、粘贴。
- 核心配置文件主要用于配置连接数据库的环境以及 MyBatis 的全局配置信息。
- 核心配置文件存放的位置是 src/main/resources 目录下
核心配置文件的具体配置如下:
<?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="123456"/>
</dataSource>
</environment>
</environments>
<!-- 引入映射文件 -->
<mappers>
<mapper resource="mappers/UserMapper.xml"/>
</mappers>
</configuration>
2.3 创建 mapper 接口
MyBatis 中的 mapper 接口相当于以前的 dao 。但是区别在于,mapper 仅仅是接口,不需要提供实现类。
public interface UserMapper {
/**
* 添加用户信息
*/
int insertUser();
}
2.4 创建 MyBatis 的映射文件
- 映射文件的命名规则:
- 表所对应的实体类的类名 + Mapper.xml
- 例如:表 t_user,映射的实体类为 User ,所对应的映射文件为 UserMapper.xml
- 因此一个映射文件对应一个实体类,对应一张表的操作
- MyBatis 的映射文件用于编写 SQL ,访问以及操作表中的数据
- MyBatis 的映射文件存放的位置是 src/main/resources/mappers 目录下
- MyBatis 中可以面向接口操作数据,要保证两个一致:
- mapper 接口的全类名和映射文件的命名空间(namespace)保持一致
- mapper 接口中方法的方法名和映射文件中编写 SQL 的标签的 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">
<mapper namespace="com.sumo.mybatis.mapper.UserMapper">
<insert id="insertUser">
INSERT INTO t_user values (null, '苏一', '123', 23, '女')
</insert>
</mapper>
2.5 通过JUnit 测试功能
// 读取 MyBatis 的核心配置文件
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
// 创建 SqlSessionFactoryBuilder 对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 通过核心配置文件所对应的字节输入流创建工厂类 SqlSessionFactory ,生产SqlSession 对象
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
// 创建 SqlSession 对象,此时通过 SqlSession 对象所操作的 SQL 都必须手动提交或回滚事务
// SqlSession sqlSession = sqlSessionFactory.openSession();
// 创建 SqlSession 对象,此时通过 SqlSession 对象所操作的 SQL 都会自动提交
SqlSession sqlSession = sqlSessionFactory.openSession(true);
// 通过代理模式创建 UserMapper 接口的代理实现类对象
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 调用 UserMapper 接口中的方法,就可以根据 UserMapper 的全类名匹配元素文件,通过调用的方法名匹配映射文件中的 SQL 标签,并执行标签中的 SQL 语句
int result = userMapper.insertUser();
// 提交事务
// sqlSession.commit();
System.out.println("结果L " + result);
- SqlSession:代表 Java 程序和数据库之间的会话。(HttpSession 是 Java 程序和浏览器之间的会话)
- SqlSessionFactory:是“生产”SqlSession 的“工厂”。
- 工厂模式:如果创建某一个对象,使用的过程基本固定,那么就可以把创建这个对象的 相关代码封装到一个“工厂类”中,以后都使用这个工厂类来“生产”需要的对象。
2.6 加入 Log4j 日志功能
-
加入依赖
-
<!-- Log4j 日志 --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
-
-
加入 Log4j 的配置文件
-
Log4j 的配置文件名为 log4j.xml ,存放的位置是 src/main/resources 目录下
-
<?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>
-
日志的级别
- FATAL(致命) > ERROR(错误) > WARN (警告) > INFO (信息) > DEBUG (调试)
- 从左到右打印的内容越来越详细
-
3 核心配置文件
核心配置文件中的标签必须按照固定的顺序:
- properties
- settings
- typeAliases
- typeHandlers
- objectFactory
- objectWrapperFactory
- reflectorFactory
- plugins
- environments
- databaseIdProvider
- mappers
3.1 配置文件模板
<?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>
<properties resource="">
<property name="" value=""/>
</properties>
<settings>
<setting name="" value=""/>
</settings>
<typeAliases>
<typeAlias type=""/>
<package name=""/>
</typeAliases>
<environments default="">
<environment id="">
<transactionManager type=""/>
<dataSource type="">
<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>
<mappers>
<mapper resource=""/>
<package name=""/>
</mappers>
</configuration>
-
properties
-
引入 properties 文件,此时可以使用 ${属性名} 的方式访问属性值;
-
<properties resource="jdbc.properties"/>
-
也可以在 properties 元素的子元素中设置属性值。
-
<properties resource="jdbc.properties"> <property name="" value=""/> <property name="" value=""/> </properties>
-
-
settings :MyBatis 的全局配置
-
一个配置完整的 settings 元素的示例如下:
-
<settings> <setting name="cacheEnabled" value="true"/> <setting name="lazyLoadingEnabled" value="true"/> <setting name="multipleResultSetsEnabled" value="true"/> <setting name="useColumnLabel" value="true"/> <setting name="useGeneratedKeys" value="false"/> <setting name="autoMappingBehavior" value="PARTIAL"/> <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/> <setting name="defaultExecutorType" value="SIMPLE"/> <setting name="defaultStatementTimeout" value="25"/> <setting name="defaultFetchSize" value="100"/> <setting name="safeRowBoundsEnabled" value="false"/> <!-- 将下划线(_)映射为驼峰 --> <setting name="mapUnderscoreToCamelCase" value="false"/> <setting name="localCacheScope" value="SESSION"/> <setting name="jdbcTypeForNull" value="OTHER"/> <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/> </settings
-
-
typeAliases :类型别名(typeAliases)可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。
-
<typeAliases> <!-- typeAlias:设置某个具体的类型的别名 属性: type:需要设置别名的类型的全类名 alias:设置此类型的别名,若不设置此属性,该类型拥有默认的别名,即类名且不区分大小写 若设置此属性,此时该类型的别名只能使用alias所设置的值 --> <typeAlias type=""/> <typeAlias type="" alias="abc"/> <!-- 以包为单位,设置改包下所有的类型都拥有默认的别名,即类名且不区分大小写 --> <package name="com.atguigu.mybatis.bean"/> </typeAliases>
-
-
typeHandlers
-
objectFactory
-
plugins
-
environments
-
<!-- environments:设置多个连接数据库的环境 属性: default:设置默认使用的环境的id --> <environments default="mysql_test"> <!-- environment:设置具体的连接数据库的环境信息 属性: id:设置环境的唯一标识,可通过environments标签中的default设置某一个环境的id,表示默认使用的环境 --> <environment id="mysql_test"> <!-- transactionManager:设置事务管理方式 属性: type:设置事务管理方式,type="JDBC|MANAGED" type="JDBC":设置当前环境的事务管理都必须手动处理 type="MANAGED":设置事务被管理,例如 Spring 中的 AOP --> <transactionManager type="JDBC"/> <!-- dataSource:设置数据源 属性: type:设置数据源的类型,type="POOLED|UNPOOLED|JNDI" type="POOLED":使用数据库连接池,即会将创建的连接进行缓存,下次使用可以从缓存中直接获取,不需要重新创建 type="UNPOOLED":不使用数据库连接池,即每次使用连接都需要重新创建 type="JNDI":调用上下文中的数据源 --> <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>
-
-
databaseIdProvider
-
mappers
-
<!--引入映射文件--> <mappers> <mapper resource="UserMapper.xml"/> <!-- 以包为单位,将包下所有的映射文件引入核心配置文件 注意:此方式必须保证 mapper 接口和 mapper 映射文件必须在相同的包下 --> <package name="com.sumo.mybatis.mapper"/> </mappers>
-
3.2 映射文件模板
<?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="">
</mapper>
注意:
- 映射文件的命名规则
- 表所对应的实体类的类名+Mapper.xml
- 例如:表 t_user ,映射的实体类为 User ,所对应的映射文件为 UserMapper.xml
- 因此一个映射文件对应一个实体类,对应一张表的操作
- MyBatis 映射文件用于编写 SQL ,访问以及操作表中的数据
- MyBatis 映射文件存放的位置是 src/main/resources/mappers 目录下
- MyBatis 中可以面向接口操作数据,要保证两个一致
- mapper 接口的全类名和映射文件的 namespace (命名空间)保持一致
- mapper 接口中方法的方法名和映射文件中编写 SQL 的标签的 id 属性保持一致
4 MyBatis 的增删查改
4.1 添加
<insert id="insertUser">
INSERT INTO t_user
VALUES (null, 'admin', '123456', 23, '男', '[email protected]')
</insert>
4.2 删除
<delete id="deleteUser">
DELETE
FROM t_user
WHERE id = 3
</delete>
4.3 修改
<update id="updateUser">
UPDATE t_user
SET username = 'suyi'
WHERE id = 2
</update>
4.4 查询一个实体类对象
<select id="getUserById" resultType="User">
SELECT *
FROM t_user
WHERE id = 2
</select>
4.5 查询集合
<select id="getAllUser" resultType="User">
SELECT *
FROM t_user
</select>
注意:
- 查询的标签 select 必须设置属性 resultType 或 resultMap ,用于设置实体类和数据库表的映射关系
- resultType :自动映射,用于属性名和表中字段名一致的情况
- resultMap :自定义映射,用于一对多或多对一或字段名和属性名不一致的情况
- 当查询的数据为多条时,不能使用实体类作为返回值,只能使用集合,否则会抛出异常
TooManyResultsException
;但是若查询的数据只有一条,可以使用实体类或集合作为返回值
5 MyBatis 获取参数值的两种方式
MyBatis 获取参数值的两种方式:
${}
:本质是 字符串拼接#{}
:本质是 占位符赋值
${}
使用字符串拼接的方式拼接 SQL ,若为字符串类型或日期类型的字段进行赋值时,需要手动加单引号;但是 #{}
使用占位符赋值的方式拼接 SQL ,此时为字符串类型或日期类型的字段进行赋值时,可以自动添加单引号。
5.1 单个字面量类型的参数
若 mapper 接口中的方法参数为单个的字面量类型:
- 可以使用
${}
和#{}
,以任意的名称获取参数的值,注意${}
需要手动加单引号。
<select id="getUserByUsername" resultType="User">
SELECT *
FROM t_user
WHERE username = #{username}
</select>
5.2 多个字面量类型的参数
若 mapper 接口中的方法参数为多个时:
- MyBatis 会自动将这些参数放在一个 Map 集合中
- 以 arg0, arg1, … 为键,以参数为值
- 以 param1, param2, … 为键,以参数为值
- 需要通过
${}
和#{}
访问 map 集合的键l来获取相对应的值,注意${}
需要手动加单引号。
<select id="checkLogin" resultType="User">
SELECT *
FROM t_user
WHERE username = #{arg0}
AND password = #{arg1}
</select>
5.3 Map 集合类型的参数
若 mapper 接口中的方法需要的参数为多个时:
- 可以手动创建 Map 集合,将这些数据放在 Map 中,只需要通过
${}
和#{}
访问 map 集合的键就可以获取相对应的值,注意${}
需要手动加单引号。
<select id="checkLoginByMap" resultType="User">
SELECT *
FROM t_user
WHERE username = #{username}
AND password = #{password}
</select>
5.4 实体类类型的参数
若 mapper 接口中的方法参数为实体类对象时:
- 可以使用
${}
和#{}
,通过访问实体类对象中的属性名获取属性值,注意${}
需要手动加单引号。
<insert id="insertUser">
INSERT INTO t_user
VALUES (null, #{username}, #{password}, #{age}, #{sex}, #{email})
</insert>
5.5 使用 @Param 标识参数
可以通过 @Param
注解标识 mapper 接口中的方法参数:
- MyBatis 会自动将这些参数放在 Map 集合中
- 以
@Param
注解的 value 属性值为键,以参数为值 - 以 param1, param2, … 为键,以参数为值
- 以
- 需要通过
${}
和#{}
访问 Map 集合的键就可以获取相对应的值, 注意${}
需要手动加单引号。
<select id="login" resultType="User">
SELECT *
FROM t_user
WHERE username = #{username}
AND password = #{password}
</select>
6 MyBatis 的各种查询功能
6.1 查询一个实体类对象
/**
* 根据 id 查询用户信息
*/
User getUserById(@Param("id") Integer id);
<select id="getUserById" resultType="User">
SELECT *
FROM t_user
WHERE id = #{id}
</select>
6.2 查询一个 List 集合
/**
* 查询所有用户信息
*/
List<User> getAllUser();
<select id="getAllUser" resultType="User">
SELECT *
FROM t_user
</select>
6.3 查询单个数据
/**
* 查询用户信息记录数
*/
Integer getCount();
<select id="getCount" resultType="Integer">
SELECT COUNT(*)
FROM t_user
</select>
6.4 查询一条数据为 Map 集合
/**
* 根据 id 查询用户信息,返回 Map 集合
*/
Map<String, Object> getUserByIdToMap(@Param("id") Integer id);
<select id="getUserByIdToMap" resultType="Map">
SELECT *
FROM t_user
WHERE id = #{id}
</select>
6.5 查询多条数据为 Map 集合
-
方式一:将表中的数据以 Map 集合的方式查询,一条数据对应一个 Map ;若有多条数据,就会产生多个 Map 集合,此时可以将这些 Map 放在一个 List 集合中进行获取
-
/** * 查询所有用户信息,返回 Map 集合 */ List<Map<String, Object>> getAllUserToMap1();
-
<select id="getAllUserToMap1" resultType="Map"> SELECT * FROM t_user </select>
-
// 结果 [ {password=123456, sex=男, id=2, age=23, [email protected], username=suyi}, {password=123456, sex=男, id=4, age=23, [email protected], username=admin}, {password=123456, sex=女, id=5, age=24, [email protected], username=yyy} ]
-
-
方式二:将表中的数据以 Map 集合的方式查询,一条数据对应一个 Map ;若有多条数据,就会产生多个 Map 集合,并且最终要以一个 Map 的方式返回数据,此时需要通过
@MapKey
注解设置 Map 集合的键,值是每条数据所对应的 Map 集合,-
/** * 查询所有用户信息,返回 Map 集合 */ @MapKey("id") Map<String, Object> getAllUserToMap2();
-
<select id="getAllUserToMap2" resultType="Map"> SELECT * FROM t_user </select>
-
// 结果 { 2={password=123456, sex=男, id=2, age=23, [email protected], username=suyi}, 4={password=123456, sex=男, id=4, age=23, [email protected], username=admin}, 5={password=123456, sex=女, id=5, age=24, [email protected], username=yyy} }
-
7 特殊 SQL 的执行
7.1 模糊查询
/**
* 根据用户名进行模糊查询用户信息
*/
List<User> getUserByLike(@Param("username") String username);
<select id="getUserByLike" resultType="User">
SELECT *
FROM t_user
WHERE username LIKE '%${username}%'
或
SELECT *
FROM t_user
WHERE username LIKE CONCAT('%', #{username}, '%')
或
SELECT *
FROM t_user
WHERE username LIKE "%"#{username}"%"
</select>
7.2 批量删除
/**
* 批量删除
*/
int deleteMore(@Param("ids") String ids);
<delete id="deleteMore">
DELETE
FROM t_user
WHERE id IN (${ids})
</delete>
7.3 动态设置表名
/**
* 查询指定表名的用户信息
*/
List<User> getUserByTableName(@Param("tableName") String tableName);
<select id="getUserByTableName" resultType="User">
SELECT *
FROM ${tableName}
</select>
7.4 添加功能获取自增的主键
/**
* 添加用户信息
*/
int insertUser(User user);
<!--
useGeneratedKeys:设置使用自增的主键
keyProperty:因为增删改有统一的返回值是受影响的行数,因此只能将获取的自增的主键放在传输的参数对象的某个属性中
-->
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
INSERT INTO t_user
VALUES (NULL, #{username}, #{password}, #{age}, #{sex}, #{email})
</insert>
8 自定义映射 resultMap
8.1 处理字段和属性的映射关系
若字段名和实体类中的属性名不一致,但是字段名符合数据库的规则(使用 _
),实体类中的属性名符合 Java 的规则(使用驼峰命名),此时可通过以下的方式处理字段名和实体类中的属性的映射关系:
-
为字段起别名
-
在 MyBatis 的核心配置文件中设置一个全局配置信息 mapUnderscoreToCamelCase ,可以在查询表中数据时,自动将带
_
的字段名转换为驼峰 -
通过 resultMap 设置自定义映射
-
resultMap :设置自定义映射
- 属性:
- id :表示自定义映射的唯一标识
- type :查询的数据要映射的实体类的类型
- 子标签:
- id :设置主键的映射关系
- 属性:
- property :设置映射关系中实体类中的属性名
- column :设置映射关系中表中的字段名
- 属性:
- result :设置普通字段的映射关系
- 属性:
- property :设置映射关系中实体类中的属性名
- column :设置映射关系中表中的字段名
- 属性:
- association :设置多对一的映射关系
- collection:设置一对多的映射关系
- id :设置主键的映射关系
- 属性:
-
<resultMap id="empMap" type="Emp"> <id property="eid" column="eid"/> <result property="empName" column="emp_name"/> <result property="age" column="age"/> <result property="sex" column="sex"/> <result property="email" column="email"/> </resultMap> <select id="getAllEmp" resultMap="empMap"> SELECT * FROM t_emp </select>
-
8.2 多对一映射处理
查询员工信息以及员工所对应的部门信息。
8.2.1 级联赋值
<resultMap id="empAndDeptMapOne" type="Emp">
<id property="eid" column="eid"/>
<result property="empName" column="emp_name"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
<result property="email" column="email"/>
<result property="dept.did" column="did"/>
<result property="dept.deptName" column="dept_name"/>
</resultMap>
<select id="getEmpAndDept" resultMap="empAndDeptMapOne">
SELECT *
FROM t_emp
LEFT JOIN t_dept on t_emp.did = t_dept.did
WHERE eid = #{eid}
</select>
8.2.2 association
<resultMap id="empAndDeptMapTwo" type="Emp">
<id property="eid" column="eid"/>
<result property="empName" column="emp_name"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
<result property="email" column="email"/>
<association property="dept" javaType="Dept">
<id property="did" column="did"/>
<result property="deptName" column="dept_name"/>
</association>
</resultMap>
<select id="getEmpAndDept" resultMap="empAndDeptMapOne">
SELECT *
FROM t_emp
LEFT JOIN t_dept on t_emp.did = t_dept.did
WHERE eid = #{eid}
</select>
8.2.3 分步查询
-
第一步:查询员工信息
-
/** * 根据 eid 查询员工信息以及员工所对应的部门信息(分步查询1) */ Emp getEmpAndDeptByEidOfStepOne(@Param("eid") Integer eid);
-
<resultMap id="empAndDeptByEidOfStepOneMap" type="Emp"> <id property="eid" column="eid"/> <result property="empName" column="emp_name"/> <result property="age" column="age"/> <result property="sex" column="sex"/> <result property="email" column="email"/> <!-- select :查询某个属性的值的 sql 的标识(namespace.sqlId 或 mapper 接口全类名.方法名) column :将查询结果中的某个字段设置为分步查询的条件 --> <association property="dept" select="com.sumo.mybatis.mapper.DeptMapper.getDeptByDIdOfStepTwo" column="did"/> </resultMap> <select id="getEmpAndDeptByEidOfStepOne" resultMap="empAndDeptByEidOfStepOneMap"> SELECT * FROM t_emp WHERE eid = #{eid} </select>
-
-
第二步:根据员工所对应的部门 id 查询部门信息
-
/** * 根据 did 查询部门信息(分步查询2) */ Dept getDeptByDIdOfStepTwo(@Param("did") Integer did);
-
<select id="getDeptByDIdOfStepTwo" resultType="Dept"> SELECT * FROM t_dept WHERE did = #{did} </select>
-
8.3 一对多映射处理
8.3.1 collection
<resultMap id="deptAndEmpByDidMap" type="Dept">
<id property="did" column="did"/>
<result property="deptName" column="dept_name"/>
<collection property="emps" ofType="Emp">
<id property="eid" column="eid"/>
<result property="empName" column="emp_name"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
<result property="email" column="email"/>
</collection>
</resultMap>
<select id="getDeptAndEmpByDid" resultMap="deptAndEmpByDidMap">
SELECT *
FROM t_dept
LEFT JOIN t_emp ON t_dept.did = t_emp.did
WHERE t_dept.did = #{did}
</select>
8.3.2 分步查询
-
第一步:查询部门信息
-
/** * 根据部门 id 查询部门信息以及部门中的所有员工信息(分步查询1) */ Dept getDeptAndEmpByDIdOfStepOne(@Param("did") Integer did);
-
<resultMap id="deptAndEmpByDIdOfStepOneMap" type="Dept"> <id property="did" column="did"/> <result property="deptName" column="dept_name"/> <collection property="emps" select="com.sumo.mybatis.mapper.EmpMapper.getDeptAndEmpByDidOfStepTwo" column="did"/> </resultMap> <select id="getDeptAndEmpByDIdOfStepOne" resultMap="deptAndEmpByDIdOfStepOneMap"> SELECT * FROM t_dept WHERE did = #{did} </select>
-
-
第二步:根据部门 id 查询部门中的所有员工
-
/** * 根据部门 id 查询员工信息(分步查询2) */ Emp getDeptAndEmpByDidOfStepTwo(@Param("did") Integer did);
-
<select id="getDeptAndEmpByDidOfStepTwo" resultType="Emp"> SELECT * FROM t_emp WHERE did = #{did} </select>
-
分步查询的优点:可以实现延迟加载,但是必须在核心配置文件中设置全局配置信息:
- lazyLoadingEnabled :
- 延迟加载的全局开关,当开启时,所有关联对象都会延迟加载
- aggressiveLazyLoading :
- 当开启时,任何方法的调用都会加载该对象的所有属性。 否则,每个属性会按需加载,此时就可以实现按需加载,获取的数据是什么,就只会执行相应的 sql
- 通过 association 和 collection 中的 fetchType 属性设置当前的分步查询是否使用延迟加载,fetchType=“lazy(延迟加载)|eager(立即加载)”
9 动态 SQL
MyBatis 框架的动态 SQL 技术是一种根据特定条件动态拼装 SQL 语句的功能,它存在的意义是为了解决拼接 SQL 语句字符串时的痛点问题。
9.1 if
if 标签可通过 test 属性的表达式进行判断,若表达式的结果为 true ,则标签中的内容会执行;反之标签中的内容不会执行。
<select id="getEmpByConditionsOne" resultType="Emp">
SELECT *
FROM t_emp
WHERE 1=1
<if test="empName != null and empName != ''">
AND emp_name = #{empName}
</if>
<if test="age != null and age != ''">
AND age = #{age}
</if>
<if test="sex != null and sex != ''">
AND sex = #{sex}
</if>
<if test="email != null and email != ''">
AND email = #{email}
</if>
</select>
其中,where 后的 1=1 是为了保证 SQL 语句的正确性。
9.2 where
where 和 if 一般结合使用:
- 若 where 标签中的 if 条件都不满足,则 where 标签没有任何功能,即不会添加 where 关键字
- 若 where 标签中的 if 条件满足,则 where 标签会自动添加 where 关键字,并将条件前方多余的 and 或 or 去掉
- 注意:where 标签不能去掉条件后方多余的 and 或 or
<select id="getEmpByConditionsTwo" resultType="Emp">
SELECT *
FROM t_emp
<where>
<if test="empName != null and empName != ''">
emp_name = #{empName}
</if>
<if test="age != null and age != ''">
AND age = #{age}
</if>
<if test="sex != null and sex != ''">
AND sex = #{sex}
</if>
<if test="email != null and email != ''">
AND email = #{email}
</if>
</where>
</select>
9.3 trim
trim 用于去掉或添加标签中的内容,常用属性:
- prefix | suffix :在 trim 标签中的内容的前面或后面添加某些内容
- prefixOverrides | suffixOverrides :在 trim 标签中的内容的前面或后面去掉某些内容
当 trim 标签中没有内容时,没有任何效果。
<select id="getEmpByConditions" resultType="Emp">
SELECT *
FROM t_emp
<trim prefix="WHERE" suffixOverrides="AND | OR">
<if test="empName != null and empName != ''">
emp_name = #{empName} AND
</if>
<if test="age != null and age != ''">
age = #{age} AND
</if>
<if test="sex != null and sex != ''">
sex = #{sex} OR
</if>
<if test="email != null and email != ''">
email = #{email}
</if>
</trim>
</select>
9.4 choose、when、otherwise
choose 、when 、otherwise 相当于 if…else if…else ,when 最少有一个,otherwise 最多有一个。
<select id="getEmpByChoose" resultType="Emp">
SELECT *
FROM t_emp
<where>
<choose>
<when test="empName != null and empName != ''">
emp_name = #{empName}
</when>
<when test="age != null and age != ''">
age = #{age}
</when>
<when test="sex != null and sex != ''">
sex = #{sex}
</when>
<when test="email != null and email != ''">
email = #{email}
</when>
<otherwise>
did = 1
</otherwise>
</choose>
</where>
</select>
9.5 foreach
属性:
- collection :设置要循环的数组或集合
- item :表示集合或数组中的每一个数据
- separator :设置循环体之间的分隔符
- open :设置 foreach 标签所循环的所有内容的开始符
- close :设置 foreach 标签所循环的所有内容的结束符
<delete id="deleteMoreByArray">
DELETE
FROM t_emp
WHERE eid IN
<foreach collection="eids" item="eid" separator="," open="(" close=")">
#{eid}
</foreach>
</delete>
<delete id="deleteMoreByArray">
DELETE
FROM t_emp
WHERE
<foreach collection="eids" item="eid" separator="OR">
eid = #{eid}
</foreach>
</delete>
9.6 SQL 片段
SQL 片段,可以记录一段公共 SQL 片段,在使用的地方通过 include 标签进行引入。
<sql id="empColumns">
eid, emp_name, age, sex, email, did
</sql>
SELECT
<include refid="empColumns"/>
FROM t_emp
10 MyBatis 的缓存
10.1 MyBatis 的一级缓存
一级缓存是 SqlSession 级别的,通过同一个 SqlSession 查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问, 一级缓存默认开启 。
使一级缓存失效的四种情况:
- 不同的 SqlSession 对应不同的一级缓存
- 同一个 SqlSession 但是查询条件不同
- 同一个 SqlSession 的两次查询期间执行了任何一次增删改操作
- 同一个 SqlSession 的两次查询期间手动清空了缓存
10.2 MyBatis 的二级缓存
二级缓存是 SqlSessionFactory 级别,通过同一个 SqlSessionFactory 创建的 SqlSession 查询的结果会被缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取。
二级缓存开启的条件:
- 在核心配置文件中,设置全局配置属性 cacheEnabled=“true” ,默认为 true,不需要设置
- 在映射文件中设置标签
<cache />
- 二级缓存必须在 SqlSession 关闭或提交之后有效
- 查询的数据所转换的实体类类型必须实现序列化的接口
使二级缓存失效的情况:
- 两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效
10.3 二级缓存的相关配置
在 mapper 配置文件中添加的 <cache />
标签可以设置一些属性:
- type :指定二级缓存的具体实现
- eviction :缓存回收策略
- LRU(Least Recently Used, 最近最少使用的):移除最长时间不被使用的对象
- FIFO(First in First out,先进先出):按对象进入缓存的顺序来移除它们
- SOFT(软引用):移除基于垃圾回收器状态和软引用规则的对象
- WEAK(弱引用):更积极地移除基于垃圾收集器状态和弱引用规则的对象
- 默认的是 LRU
- flushInterval :刷新间隔,单位毫秒
- 默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新
- size :引用数目,正整数
- 代表缓存最多可以存储多少个对象,太大容易导致内存溢出
- readOnly :只读,true / false
- true :只读缓存。会给所有调用者返回缓存对象的相同实例,因此这些对象不能被修改,这提供了很重要的性能优势。
- false :读写缓存。会返回缓存对象的拷贝(通过序列化),这会慢一些,但是安全,因此默认是 false 。
10.4 MyBatis 缓存查询的顺序
- 先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用
- 如果二级缓存没有命中,再查询一级缓存
- 如果一级缓存也没有命中,则查询数据库
- SqlSession 关闭之后,一级缓存中的数据会写入二级缓存
10.5 整合第三方缓存 EhCache
-
加入依赖
-
<!-- MyBatis 和 EhCache 的整合包 --> <dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-ehcache</artifactId> <version>1.2.1</version> </dependency> <!-- slf4j 日志门面的一个具体实现 --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency>
-
-
各 jar 包的功能介绍
-
名称 作用 mybatis-ehcache MyBatis 和 EhCache 的整合包 ehcache EhCache 核心包 slf4j-api SLF4J 日志门面包 logback-classic 支持 SLF4J 日志门面接口的一个具体实现
-
-
创建 EhCache 的配置文件 ehcache.xml
-
<?xml version="1.0" encoding="utf-8" ?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd"> <!-- 磁盘保存路径 --> <diskStore path="D:\sumo\ehcache"/> <defaultCache maxElementsInMemory="1000" maxElementsOnDisk="10000000" eternal="false" overflowToDisk="true" timeToIdleSeconds="120" timeToLiveSeconds="120" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> </defaultCache> </ehcache>
-
配置文件说明
-
属性名 作用 是否必须 maxElementsInMemory 在内存中缓存的 element 的最大数目 是 maxElementsOnDisk 在磁盘上缓存的 element 的最大数目,若是 0 表示无穷大 是 eternal 设定缓存的 elements 是否永远不过期。如果为 true ,则缓存的数据始终有效,如果为 false ,那么还要根据 timeToIdleSeconds、timeToLiveSeconds 来进行判断 是 overflowToDisk 设定当内存缓存溢出的时候是否将过期的 element 缓存到磁盘上 是 timeToIdleSeconds 当缓存在 EhCache 中的数据前后两次访问的时间超过 timeToIdleSeconds 的属性取值时,这些数据便会删除,默认值是 0 ,也就是可闲置时间无穷大 否 timeToLiveSeconds 缓存 element 的有效生命期,默认是 0 ,也就是 element 存活时间无穷大 否 diskSpoolBufferSizeMB DiskStore(磁盘缓存)的缓存区大小,默认是 30MB,每个 Cache 都应该有自己的一个缓冲区 否 diskPersistent 在 VM 重启的时候是否启用磁盘保存 EhCache 中的数据,默认是 false 否 diskExpiryThreadIntervalSeconds 磁盘缓存的清理线程运行间隔,默认是 120 秒。每个 120 s,相应的线程会进行一次 EhCache 中数据的清理工作 否 memoryStoreEvictionPolicy 当内存缓存达到最大,有新的 element 加入的时 候, 移除缓存中 element 的策略。 默认是 LRU(最 近最少使用),可选的有 LFU(最不常使用)和 FIFO(先进先出) 否
-
-
-
设置二级缓存的类型
-
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
-
-
加入 logback 日志
-
存在 SLF4J 时,作为简易日志的 log4j 将失效,此时需要借助 SLF4J 的具体实现 logback 来打印日志
-
创建 logback 的配置文件 logback.xml
-
<?xml version="1.0" encoding="UTF-8"?> <configuration debug="true"> <!-- 指定日志输出的位置 --> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <!-- 日志输出的格式 --> <!-- 按照顺序分别是:时间、日志级别、线程名称、打印日志的类、日志主体内容、换行 --> <pattern>[%d{HH:mm:ss.SSS}] [%-5level] [%thread] [%logger] [%msg]%n</pattern> </encoder> </appender> <!-- 设置全局日志级别。日志级别按顺序分别是:DEBUG > INFO > WARN > ERROR --> <!-- 指定任何一个日志级别都只打印当前级别和后面级别的日志 --> <root level="DEBUG"> <!-- 指定打印日志的 appender ,这里通过“STDOUT”引用了前面配置的 appender --> <appender-ref ref="STDOUT" /> </root> <!-- 根据特殊需求指定局部日志级别 --> <logger name="com.sumo.mybatis.mapper" level="DEBUG"/> </configuration>
-
-
11 MyBatis 的逆向工程
- 正向工程:先创建 Java 实体类,由框架负责根据实体类生成数据库表,Hibernate 是支持正向工程的。
- 逆向工程:先创建数据库表,由框架负责根据数据库表,反向生成如下资源:
- Java 实体类
- Mapper 接口
- Mapper 映射文件
11.1 创建逆向工程
-
添加依赖和插件
-
<!-- 依赖MyBatis核心包 --> <dependencies> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.7</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> <!-- 数据库连接池 --> <dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.2</version> </dependency> <!-- MySQL 驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.8</version> </dependency> </dependencies> </plugin> </plugins> </build>
-
-
创建 MyBatis 的核心配置文件(用于测试功能)
-
创建逆向工程的配置文件,文件名必须是 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.jdbc.Driver" connectionURL="jdbc:mysql://localhost:3306/mybatis" userId="root" password="123456"/> <!-- JavaBean 的生成策略--> <javaModelGenerator targetPackage="com.sumo.mybatis.entity" targetProject=".\src\main\java"> <property name="enableSubPackages" value="true"/> <property name="trimStrings" value="true"/> </javaModelGenerator> <!-- SQL 映射文件的生成策略 --> <sqlMapGenerator targetPackage="com.sumo.mybatis.mapper" targetProject=".\src\main\resources"> <property name="enableSubPackages" value="true"/> </sqlMapGenerator> <!-- Mapper 接口的生成策略 --> <javaClientGenerator type="XMLMAPPER" targetPackage="com.sumo.mybatis.mapper" targetProject=".\src\main\java"> <property name="enableSubPackages" value="true"/> </javaClientGenerator> <!-- 逆向分析的表 --> <!-- tableName 设置为 * 号,可以对应所有表,此时不写 domainObjectName --> <!-- domainObjectName 属性指定生成出来的实体类的类名 --> <table tableName="t_emp" domainObjectName="Emp"/> <table tableName="t_dept" domainObjectName="Dept"/> </context> </generatorConfiguration>
-
-
执行 MBG 插件的 generate 目标
11.2 QBC 查询
QBC :Query By Condition ,根据条件查询。
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);
EmpExample example = new EmpExample();
// 创建条件对象,通过 andXXX() 方法为SQL添加查询添加,每个条件之间是 and 关系
example.createCriteria().andEmpNameEqualTo("yhh").andAgeEqualTo(24);
// 将之前添加的条件通过 or 拼接其他条件
example.or().andAgeIsNotNull();
List<Emp> list = mapper.selectByExample(example);
list.forEach(emp -> System.out.println(emp));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
12 MyBatis 的分页插件
12.1 分页插件使用步骤
-
添加依赖
-
<!-- 分页插件 --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.2.0</version> </dependency>
-
-
配置分页插件
-
在 MyBatis 的核心配置文件中配置插件:
-
<plugins> <!-- 设置分页插件 --> <plugin interceptor="com.github.pagehelper.PageInterceptor"/> </plugins>
-
12.2 分页插件的使用
-
在查询功能之前使用
PageHelper.startPage(int pageNum, int pageSize)
开启分页功能- pageNum :当前页的页码
- pageSize :每页显示的条数
-
在查询获取 List 集合之后,使用
PageInfo<E> pageInfo = new PageInfo<>(List<E> list, int navigatePages)
获取分页相关数据- list :分页之后的数据
- navigatePages :导航分页的页码数
-
分页相关数据
-
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, navigateFirstPage=4, navigateLastPage=8, navigatepageNums=[4, 5, 6, 7, 8] }
-
常用数据:
- pageNum :当前页的页码
- pageSize :每页显示的条数
- size :当前页显示的真实条数
- total :总记录数
- pages :总页数
- prePage :上一页的页码
- nextPage :下一页的页码
- isFirstPage/isLastPage :是否为第一页/最后一页
- hasPreviousPage/hasNextPage :是否存在上一页/下一页
- navigatePages :导航分页的页码数
- navigatepageNums :导航分页的页码,[1, 2, 3, 4, 5]
-
的核心配置文件中配置插件:
-
<plugins> <!-- 设置分页插件 --> <plugin interceptor="com.github.pagehelper.PageInterceptor"/> </plugins>
12.2 分页插件的使用
-
在查询功能之前使用
PageHelper.startPage(int pageNum, int pageSize)
开启分页功能- pageNum :当前页的页码
- pageSize :每页显示的条数
-
在查询获取 List 集合之后,使用
PageInfo<E> pageInfo = new PageInfo<>(List<E> list, int navigatePages)
获取分页相关数据- list :分页之后的数据
- navigatePages :导航分页的页码数
-
分页相关数据
-
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, navigateFirstPage=4, navigateLastPage=8, navigatepageNums=[4, 5, 6, 7, 8] }
-
常用数据:
- pageNum :当前页的页码
- pageSize :每页显示的条数
- size :当前页显示的真实条数
- total :总记录数
- pages :总页数
- prePage :上一页的页码
- nextPage :下一页的页码
- isFirstPage/isLastPage :是否为第一页/最后一页
- hasPreviousPage/hasNextPage :是否存在上一页/下一页
- navigatePages :导航分页的页码数
- navigatepageNums :导航分页的页码,[1, 2, 3, 4, 5]
-