Mybatis简介

1. Mybatis的特性

  • Mybatis支持定制化SQL,存储过程以及高级映射的优秀的持久层框架。
  • MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。
  • MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和ava的POJO(Plain Old JavaObjects,普通的Java对象)映射成数据库中的记录。
  • MyBatis是一个半自动的ORM (Object Relation Mapping)框架。

2. Mybatis的下载

Mybatis的下载地址:https://github.com/mybatis/mybatis-3

image-20221020171807640

image-20221020171834802

Mybatis的快速入门

  1. 新建maven工程
  2. 打包方式修改成jar
1
<packaging>jar</packaging>
  1. 引入依赖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<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>8.0.16</version>
</dependency>
<!--实体类注解替代getter,setter-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>
</dependencies>

MySQL不同版本的注意事项

1、驱动类driver-class-name

MySQL 5版本使用jdbc5驱动,驱动类使用:com.mysql.jdbc.Driver

MySQL 8版本使用jdbc8驱动,驱动类使用:com.mysql.cj.jdbc.Driver

2、连接地址url MySQL 5版本的url: jdbc:mysql://localhost:3306/ssm

MySQL 8版本的url: jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC

否则运行测试用例报告如下错误:

java.sql.SQLException: The server time zone value ‘Öйú±ê׼ʱ¼ä’ is unrecognized or represents more

  1. 数据库创建t_user
1
2
3
4
5
6
7
8
CREATE TABLE `t_user` (
`id` int PRIMARY KEY AUTO_INCREMENT,
`username` varchar(20),
`password` varchar(20),
`age` int,
`gender` char(1),
`email` varchar(50)
);
  1. 创建对应的实体类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.atguigu.mybatis.pojo;

// lombok提供的注解
@AllArgsConstructor //提供全参的构造方法
@NoArgsConstructor //提供无参的构造方法
@Data //提供getter,setter方法
public class User {
private Integer id;
private String username;
private String password;
private Integer age;
private String gender;
private String email;
}
  1. 创建Mapper接口
1
2
3
4
5
6
7
8
package com.atguigu.mybatis.mapper;

public interface UserMapper {
/**
* 添加用户信息
*/
int insertUser();
}
  1. 创建Mybatis的核心配置文件在src/main/resources目录下。

mybatis-config.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!--引入映射文件-->
<mappers>
<mapper resource="mappers/UserMapper.xml"/>
</mappers>
</configuration>
  1. 创建MyBatis的映射文件

相关概念:ORM(Object Relationship Mapping)对象关系映射。

  • 对象:Java的实体类对象

  • 关系:关系型数据库

  • 映射:二者之间的对应关系

  • 映射文件的命名规则: 表所对应的实体类的类名+Mapper.xml

    例如:表t_user,映射的实体类为User,所对应的映射文件为UserMapper.xml

    因此一个映射文件对应一个实体类,对应一张表的操作 MyBatis映射文件用于编写SQL,访问以及操作表中的数据MyBatis映射文件存放的位置是src/main/resources/mappers目录下

  • MyBatis中可以面向接口操作数据,要保证两个一致

    • mapper接口的全类名和映射文件的命名空间(namespace)保持一致

    • mapper接口中方法的方法名和映射文件中编写SQL的标签的id属性保持一致

1
2
3
4
5
6
7
8
9
10
<?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.atguigu.mybatis.mapper.UserMapper">
<!--int insertUser();-->
<insert id="insertUser">
insert into t_user values(null,'admin','123456',23,'男','12345@qq.com')
</insert>
</mapper>

小提示:可以在IDEA中安装上MybatisX插件。安装后接口和Mapper就会对应起来可以方便跳转。

  1. 添加日志记录

添加依赖

1
2
3
4
5
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>

加入log4j的配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?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(调试)

从左到右打印的内容越来越详细

Mybatis核心文件详解

核心配置文件中的标签必须按照固定的顺序: properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,refl ectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
<?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文件,此后就可以在当前文件中使用${key}的方式访问value-->
<properties resource="jdbc.properties"/>

<!--
typeAliases:设置类别名,即为具体的一个类型设置一个别名。
在Mybatis的范围中,就可以使用别名表示一个具体的类
-->
<typeAliases>
<!--
type:需要取别名的类
alias:设置某个类的别名,若不设置,就取类名为别名(不区分大小写)。
-->
<!--<typeAlias type="com.atguigu.mybatis.pojo.User"/>-->
<!--通过包设置别名,指定包下所有的类将全部拥有默认别名-->
<package name="com.atguigu.mybatis.pojo"/>
</typeAliases>

<!--
environments:设置连接数据库的环境
属性:
default:设置默认使用的环境ID
-->
<environments default="development">
<!--
environment:设置一个具体的数据库环境
属性:
id:设置环境的唯一标识
-->
<environment id="development">
<!--
transactionManager:设置事务管理器
属性:
type:设置事物管理的方式
type="JDBC|MANAGED"
JDBC:表示使用JDBC中原生的事务管理方式
MANAGED:被管理,例如Spring
-->
<transactionManager type="JDBC"/>
<!--
dataSource:设置数据源
属性:
type:设置数据源的类型
type="POOLED|UNPOOLED|JNDI"
POOLED:使用数据库连接池
UNPOOLED:不使用数据库连接池
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>
<!--引入映射文件-->
<mappers>
<!--<mapper resource="mappers/UserMapper.xml"/>-->
<!--
以包为单位引入映射文件
要求:
1、mapper接口所在的包要和映射文件所在的包一致
2、mapper接口要和映射文件的名字一致
-->
<package name="com.atguigu.mybatis.mapper"/>
</mappers>
</configuration>

Mybatis获取参数的两种方式

MyBatis获取参数值的两种方式:${}#{}

${}的本质就是字符串拼接,#{}的本质就是占位符赋值

${}使用字符串拼接的方式拼接sql,若为字符串类型或日期类型的字段进行赋值时,需要手动加引号。

但是#{}使用占位符赋值的方式拼接sql,此时为字符串类型或日期类型的字段进行赋值时, 可以自动加引号。

${}有可能会存在SQL注入风险。推荐使用#{}

1. 单个参数

若mapper接口中的方法参数为单个的字面量类型

此时可以使用${}和#{}以任意的名称获取参数的值,注意${}需要手动加引号。

1
User getUserByUserName(@Param("username")String username);
1
2
3
4
5
6
7
<select id="getUserByUserName" resultType="com.atguigu.mybatis.pojo.User">
select * from t_user where userName = #{username}
</select>
<!--不推荐-->
<select id="getUserByUserName" resultType="com.atguigu.mybatis.pojo.User">
select * from t_user where userName = "${username}"
</select>

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static SqlSession getSqlSession(){
SqlSession sqlSession = null;
try {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
// 设置自动提交
sqlSession = sqlSessionFactory.openSession(true);
} catch (IOException e) {
throw new RuntimeException(e);
}
return sqlSession;
}
@Test
public void testgetUserByUserName() {
SqlSession sqlSession = getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.getUserByUserName("root");
System.out.println(user);
sqlSession.close();
}

2. 多个参数

若mapper接口中的方法参数为多个时,此时MyBatis会自动将这些参数放在一个map集合中

以arg0,arg1…为键,以参数为值;以param1,param2…为键,以参数为值;

因此只需要通过${}和#{}访问map集合的键就可以获取相对应的值。${}需要手动加引号。

1
User checkLogin(String username,String password);
1
2
3
4
5
6
7
8
9
10
11
12
13
<select id="checkLogin" resultType="com.atguigu.mybatis.pojo.User">
select * from t_user where userName = #{arg0} AND password = #{arg1}
</select>
<!--或-->
<select id="checkLogin" resultType="com.atguigu.mybatis.pojo.User">
select * from t_user where userName = #{param1} AND password = #{param2}
</select>
<!--推荐-->
<!--该种方式需要Mapper接口中使用@Param()注解,且名称必须一致-->
<!--User checkLogin(@Param("username") String username,@Param("password") String password);-->
<select id="checkLogin" resultType="com.atguigu.mybatis.pojo.User">
select * from t_user where userName = #{username} AND password = #{password}
</select>

测试代码

1
2
3
4
5
6
7
8
@Test
public void testCheckLogin() {
SqlSession sqlSession = getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.checkLogin("admin","123456");
System.out.println(user);
sqlSession.close();
}

@Param注解是用于传递参数,从而可以与SQL中的的字段名相对应,如果只有单个参数可以省略。

  • SQL注入示例演示
1
2
3
<select id="checkLogin" resultType="com.atguigu.mybatis.pojo.User">
select * from t_user where userName = "${username}" AND password = "${password}"
</select>
1
2
3
4
5
6
7
8
@Test
public void testCheckLogin() {
SqlSession sqlSession = getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> user = userMapper.checkLogin("admin","abc\" or \"1\"=\"1");
System.out.println(user);
sqlSession.close();
}

实际上我并没有输入正确的账号和密码,却可以获取所有的用户数据。

1
2
3
4
Preparing: select * from t_user where userName = "admin" AND password = "abc" or "1"="1" (BaseJdbcLogger.java:137) 
DEBUG 10-21 22:56:37,987 ==> Parameters: (BaseJdbcLogger.java:137)
DEBUG 10-21 22:56:38,020 <== Total: 7 (BaseJdbcLogger.java:137)
[User(id=1, username=admin, password=123456, age=23, gender=男, email=12345@qq.com), User(id=2, username=root, password=123456, age=23, gender=男, email=12345@qq.com), User(id=3, username=heima, password=123456, age=23, gender=男, email=12345@qq.com), User(id=4, username=guigu, password=123456, age=23, gender=男, email=12345@qq.com), User(id=5, username=zhangsan, password=123456, age=23, gender=男, email=12345@qq.com), User(id=6, username=lisi, password=123456, age=23, gender=男, email=12345@qq.com), User(id=7, username=wangwu, password=123456, age=23, gender=男, email=12345@qq.com)]

3. map做为参数

若mapper接口中的方法需要的参数为多个时,此时可以手动创建map集合,将这些数据放在 map中。可以使用@Param()注解

只需要通过${}和#{}访问map集合的键就可以获取相对应的值。${}需要手动加引号。

1
User checkLogin2(Map map);
1
2
3
<select id="checkLogin2" resultType="com.atguigu.mybatis.pojo.User">
select * from t_user where userName = #{username} AND password = #{password}
</select>
1
2
3
4
5
6
7
8
9
10
11
@Test
public void testCheckLogin() {
SqlSession sqlSession = getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
Map map = new HashMap<>();
map.put("username","admin");
map.put("password","123456");
User user = userMapper.checkLogin2(map);
System.out.println(user);
sqlSession.close();
}

如果方法中有多个参数并且其中含有map时,可以使用arg0.键param.键调用。

1
2
User checkLogin2(Map map,Map map1);
//User checkLogin2(@Param("m1") Map map,@Param("m2") Map map1);
1
2
3
4
5
6
7
8
9
10
11
12
@Test
public void testCheckLogin() {
SqlSession sqlSession = getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
Map map = new HashMap<>();
map.put("username","admin");
Map map1 = new HashMap<>();
map1.put("password","123456");
User user = userMapper.checkLogin2(map,map1);
System.out.println(user);
sqlSession.close();
}
1
2
3
4
<select id="checkLogin2" resultType="com.atguigu.mybatis.pojo.User">
select * from t_user where userName = #{arg0.username} AND password = #{arg1.password}
</select>
<!--select * from t_user where userName = #{m1.username} AND password = #{m2.password}-->

了解:如果参数类型是list时,可以通过下标获取。

4. 实体类型做为参数

若mapper接口中的方法参数为实体类对象时

此时可以使用${}和#{},通过访问实体类对象中的属性名获取属性值,注意${}需要手动加引号

1
boolean insertUser(User user);
1
2
3
<insert id="insertUser">
insert into t_user values(null,#{username},#{password},#{age},#{gender},#{email})
</insert>
1
2
3
4
5
6
7
8
@Test
public void testInsertUser() {
SqlSession sqlSession = getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
boolean flag = userMapper.insertUser(new User(null, "test", "123456", 23, "男", "123@qq.com"));
System.out.println("结果:" + flag);
sqlSession.close();
}

5. @Param注解

@Param注解是用于传递参数,从而可以与SQL中的的字段名相对应,如果只有单个参数可以省略。

建议:所有参数类型都加上@Param(),养成良好习惯。

Mybatis的查询功能

1. 查询一个实体类对象

1
User getUserById(@Param("id") int id);
1
2
3
<select id="getUserById" resultType="User">
select * from t_user where id = #{id}
</select>

如果查询结果出现的记录数大于1条会报TooManyResultsException异常。

2. 查询一个list集合

1
List<User> getUserList();
1
2
3
<select id="getUserList" resultType="User">
select * from t_user
</select>

3. 查询单个数据

1
int getCount();
1
2
3
<select id="getCount" resultType="_integer">
select count(id) from t_user
</select>

4. 查询一条数据为map

1
Map<String, Object> getUserToMap(@Param("id") int id);
1
2
3
<select id="getUserToMap" resultType="map">
select * from t_user where id = #{id}
</select>

如果是Map接收返回值,字段为null值不会返回(Map中没有字段为null值的键和值)

5. 查询多条数据为map集合

5.1 方式一:使用List包着Map

1
List<Map<String, Object>> getAllUserToMap();
1
2
3
<select id="getAllUserToMap" resultType="map">
select * from t_user
</select>

5.2 方式二:使用注解

将表中的数据以map集合的方式查询,一条数据对应一个map;若有多条数据,就会产生多个map集合,并且最终要以一个map的方式返回数据,此时需要通过@MapKey注解设置map集合的键,值是每条数据所对应的map集合。

1
2
@MapKey("id")
Map<String, Object> getAllUserToMap();
1
2
3
4
5
6
7
8
9
10
11
<!--Map<String, Object> getAllUserToMap();-->
<!--
{
1={password=123456, sex=男, id=1, age=23, username=admin},
2={password=123456, sex=男, id=2, age=23, username=张三},
3={password=123456, sex=男, id=3, age=23, username=张三}
}
-->
<select id="getAllUserToMap" resultType="map">
select * from t_user
</select>

特殊的SQL执行

1. 模糊查询

1
List<User> testMohu(@Param("mohu") String mohu);
1
2
3
4
5
6
<!--List<User> testMohu(@Param("mohu") String mohu);-->
<select id="testMohu" resultType="User">
<!--select * from t_user where username like '%${mohu}%'-->
<!--select * from t_user where username like concat('%',#{mohu},'%')-->
select * from t_user where username like "%"#{mohu}"%"
</select>

2. 批量删除

1
int deleteMore(@Param("ids") String ids);
1
2
3
<delete id="deleteMore">
delete from t_user where id in (${ids})
</delete>

3. 动态设置表名

1
List<User> getAllUser(@Param("tableName") String tableName);
1
2
3
<select id="getAllUser" resultType="User">
select * from ${tableName}
</select>

4. 添加功能获取自增的主键

1
2
3
4
5
6
7
8
9
/**
* 添加用户信息
* @param user
* @return
* useGeneratedKeys:设置使用自增的主键
* keyProperty:因为增删改有统一的返回值是受影响的行数,因此只能将获取的自增的主键放在传输的参
数user对象的某个属性中
*/
int insertUser(User user);
1
2
3
4
<!--int insertUser(User user);-->
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
insert into t_user values(null,#{username},#{password},#{age},#{sex})
</insert>

自定义ResultMap

1. 下划线转驼峰

若字段名和实体类中的属性名不一致,但是字段名符合数据库的规则(使用_),实体类中的属性名符合Java的规则(使用驼峰)。

此时也可通过以下两种方式处理字段名和实体类中的属性的映射关系

  • 可以通过为字段起别名的方式,保证和实体类中的属性名保持一致

    1
    2
    3
    <select id="getByEmpId" resultType="com.atguigu.mybatis.pojo.Emp">
    select emp_id empId,emp_name empName,age,gender from t_emps where emp_Id = #{empId}
    </select>
  • 可以在MyBatis的核心配置文件中设置一个全局配置信息mapUnderscoreToCamelCase,可以在查询表中数据时,自动将_类型的字段名转换为驼峰

    例如:字段名user_name,设置了mapUnderscoreToCamelCase,此时字段名就会转换为 userName

    1
    2
    3
    <settings>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

2. ResultMap处理字段和属性的映射关系

resultMap:设置自定义映射

属性:

  • id:表示自定义映射的唯一标识。
  • type:查询的数据要映射的实体类的类型。

子标签:

  • id:设置主键的映射关系
  • result:设置普通字段的映射关系
  • association:设置多对一、一对一的映射关系(设置实体类型属性)
  • collection:设置一对多的映射关系(设置集合类型属性)

子标签属性:

  • property:设置映射关系中实体类中的属性名

  • column:设置映射关系中表中的字段名

1
2
3
4
5
6
7
8
9
10
11
<resultMap id="userMap" type="User">
<id property="id" column="id"></id>
<result property="userName" column="user_name"></result>
<result property="password" column="password"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
</resultMap>
<select id="testMohu" resultMap="userMap">
select id,user_name,password,age,sex from t_user where user_name like
concat('%',#{mohu},'%')
</select>

3. 多对一映射处理

查询员工所对应的部门信息

3.1 方式一:级联方式处理映射关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<resultMap id="empDeptMap" type="Emp">
<id column="eid" property="eid"></id>
<result column="ename" property="ename"></result>
<result column="age" property="age"></result>
<result column="sex" property="sex"></result>
<!--dept是Emp中的属性,类型是Dept;did和dname是Dept中的属性-->
<result column="did" property="dept.did"></result>
<result column="dname" property="dept.dname"></result>
</resultMap>

<!--Emp getEmpAndDeptByEid(@Param("eid") int eid);-->
<select id="getEmpAndDeptByEid" resultMap="empDeptMap">
select emp.*,dept.*
from t_emp emp
left join t_dept dept on emp.did = dept.did
where emp.eid = #{eid}
</select>

3.2 方式二:使用association处理映射关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<resultMap id="empDeptMap" type="Emp">
<id column="eid" property="eid"></id>
<result column="ename" property="ename"></result>
<result column="age" property="age"></result>
<result column="sex" property="sex"></result>
<association property="dept" javaType="Dept">
<id column="did" property="did"></id>
<result column="dname" property="dname"></result>
</association>
</resultMap>

<!--Emp getEmpAndDeptByEid(@Param("eid") int eid);-->
<select id="getEmpAndDeptByEid" resultMap="empDeptMap">
select emp.*,dept.*
from t_emp emp
left join t_dept dept on emp.did = dept.did
where emp.eid = #{eid}
</select>

3.3 方式三:分步查询

1
2
3
4
5
6
/**
* 1.查询员工信息
* @param eid
* @return
*/
Emp getEmpByStep(@Param("eid") int eid);

association的属性详情

  • select:设置分步查询,查询某个属性的值的sql的标识(namespace.sqlId)
  • column:将sql以及查询结果中的某个字段设置为分步查询的条件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<resultMap id="empDeptStepMap" type="Emp">
<id column="eid" property="eid"></id>
<result column="ename" property="ename"></result>
<result column="age" property="age"></result>
<result column="sex" property="sex"></result>
<!--
select:设置分步查询,查询某个属性的值的sql的标识(namespace.sqlId)
column:将sql以及查询结果中的某个字段设置为分步查询的条件
-->
<!--2.使用查询员工信息得到的部门did,执行第二步,传入did查询部门信息-->
<!--column使用的是查询出来的字段-->
<association property="dept"
select="com.atguigu.MyBatis.mapper.DeptMapper.getEmpDeptByStep"
column="did">
</association>
</resultMap>

<!--Emp getEmpByStep(@Param("eid") int eid);-->
<select id="getEmpByStep" resultMap="empDeptStepMap">
select * from t_emp where eid = #{eid}
</select>
1
2
3
4
5
6
/**
* 3.根据员工所对应的did查询部门信息
* @param did
* @return
*/
Dept getEmpDeptByStep(@Param("did") int did);
1
2
3
4
<!--Dept getEmpDeptByStep(@Param("did") int did);-->
<select id="getEmpDeptByStep" resultType="Dept">
select * from t_dept where did = #{did}
</select>

分别查询的优势,延迟加载(懒加载)

但是必须在核心配置文件中设置全局配置信息:

lazyLoadingEnabled:延迟加载的全局开关。当开启时,所有关联对象都会延迟加载

aggressiveLazyLoading:当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载

此时就可以实现按需加载,获取的数据是什么,就只会执行相应的sql。此时可通过association和 collection中的fetchType属性设置当前的分步查询是否使用延迟加载, fetchType=”lazy(延迟加载)|eager(立即加载)”

一对多映射处理

方式一:collection

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<resultMap id="deptEmpMap" type="Dept">
<id property="did" column="did"></id>
<result property="dname" column="dname"></result>
<!--
ofType:设置collection标签所处理的集合属性中存储数据的类型
-->
<collection property="emps" ofType="Emp">
<id property="eid" column="eid"></id>
<result property="ename" column="ename"></result>
<result property="age" column="age"></result>
<result property="sex" column="sex"></result>
</collection>
</resultMap>

<!--Dept getDeptEmpByDid(@Param("did") int did);-->
<select id="getDeptEmpByDid" resultMap="deptEmpMap">
select dept.*,emp.* from t_dept dept left join t_emp emp on dept.did =
emp.did whe
</select>

方式二:分步查询

1
2
3
4
5
6
/**
* 分步查询部门和部门中的员工
* @param did
* @return
*/
Dept getDeptByStep(@Param("did") int did);
1
2
3
4
5
6
7
8
9
10
11
12
13
<resultMap id="deptEmpStep" type="Dept">
<id property="did" column="did"></id>
<result property="dname" column="dname"></result>
<collection property="emps"
fetchType="eager"
select="com.atguigu.MyBatis.mapper.EmpMapper.getEmpListByDid"
column="did">
</collection>
</resultMap>
<!--Dept getDeptByStep(@Param("did") int did);-->
<select id="getDeptByStep" resultMap="deptEmpStep">
select * from t_dept where did = #{did}
</select>
1
2
3
4
5
6
/**
* 根据部门id查询员工信息
* @param did
* @return
*/
List<Emp> getEmpListByDid(@Param("did") int did);
1
2
3
4
<!--List<Emp> getEmpListByDid(@Param("did") int did);-->
<select id="getEmpListByDid" resultType="Emp">
select * from t_emp where did = #{did}
</select>

Mybatis动态Sql

Mybatis框架的动态SQL技术是一种根据特定条件动态拼装SQL语句的功能,它存在的意义是为了 解决拼接SQL语句字符串时的痛点问题。

1. if

if标签可通过test属性的表达式进行判断,若表达式的结果为true,则标签中的内容会执行;反之标签中的内容不会执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
<!--List<Emp> getEmpListByCondition(Emp emp);-->
<select id="getEmpListByMoreTJ" resultType="Emp">
select * from t_emp where 1=1
<if test="ename != '' and ename != null">
and ename = #{ename}
</if>
<if test="age != '' and age != null">
and age = #{age}
</if>
<if test="sex != '' and sex != null">
and sex = #{sex}
</if>
</select>

2. where

where和if一般结合使用:

  • 若where标签中的if条件都不满足,则where标签没有任何功能,即不会添加where关键字。
  • 若where标签中的if条件满足,则where标签会自动添加where关键字,并将条件最前方多余的and去掉。

注意:where标签不能去掉条件最后多余的and

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<select id="getEmpListByMoreTJ2" resultType="Emp">
select * from t_emp
<where>
<if test="ename != '' and ename != null">
ename = #{ename}
</if>
<if test="age != '' and age != null">
and age = #{age}
</if>
<if test="sex != '' and sex != null">
and sex = #{sex}
</if>
</where>
</select>

3. trim

trim用于去掉或添加标签中的内容

常用属性:

prefix:在trim标签中的内容的前面添加某些内容。

prefixOverrides:在trim标签中的内容的前面去掉某些内容。

suffix:在trim标签中的内容的后面添加某些内容。

suffixOverrides:在trim标签中的内容的后面去掉某些内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<select id="getEmpListByMoreTJ" resultType="Emp">
select * from t_emp
<trim prefix="where" suffixOverrides="and">
<if test="ename != '' and ename != null">
ename = #{ename} and
</if>
<if test="age != '' and age != null">
age = #{age} and
</if>
<if test="sex != '' and sex != null">
sex = #{sex}
</if>
</trim>
</select>

4. choose、when、otherwise

choose、when、 otherwise相当于if…else if..else

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!--List<Emp> getEmpListByChoose(Emp emp);-->
<select id="getEmpListByChoose" resultType="Emp">
select <include refid="empColumns"></include> from t_emp
<where>
<choose>
<when test="ename != '' and ename != null">
ename = #{ename}
</when>
<when test="age != '' and age != null">
age = #{age}
</when>
<when test="sex != '' and sex != null">
sex = #{sex}
</when>
<when test="email != '' and email != null">
email = #{email}
</when>
</choose>
</where>
</select>

5. foreach

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!--int insertMoreEmp(List<Emp> emps);-->
<insert id="insertMoreEmp">
insert into t_emp values
<foreach collection="emps" item="emp" separator=",">
(null,#{emp.ename},#{emp.age},#{emp.sex},#{emp.email},null)
</foreach>
</insert>

<!--int deleteMoreByArray(int[] eids);-->
<delete id="deleteMoreByArray">
delete from t_emp where
<foreach collection="eids" item="eid" separator="or">
eid = #{eid}
</foreach>
</delete>

<!--int deleteMoreByArray(int[] eids);-->
<delete id="deleteMoreByArray">
delete from t_emp where eid in
<foreach collection="eids" item="eid" separator="," open="(" close=")">#{eid}
</foreach>
</delete>

6. Sql片段

1
2
3
4
<sql id="empColumns">
eid,ename,age,sex,did
</sql>
select <include refid="empColumns"></include> from t_emp

Mybatis缓存

1. Mybatis一级缓存(默认开启)

一级缓存是SqlSession级别的,通过同一个SqlSession查询的数据会被缓存,下次查询相同的数据,就 会从缓存中直接获取,不会从数据库重新访问。

使一级缓存失效的四种情况:

  • 不同的SqlSession对应不同的一级缓存。

  • 同一个SqlSession但是查询条件不同

  • 同一个SqlSession两次查询期间执行了任何一次增删改操作

  • 同一个SqlSession两次查询期间手动清空了缓存

    1
    sqlSession.clearCache();

一级缓存示例:

1
2
3
4
5
6
7
8
9
10
11
@Test
public void TestGetByEmpId() {
SqlSession sqlSession = getSqlSession();
// 都是从同一个sqlSession中获取的
EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class);
List<Emp> emp1 = empMapper.getByEmpId(1);
List<Emp> emp2 = empMapper.getByEmpId(1);
System.out.println(emp1);
System.out.println(emp2);
sqlSession.close();
}

在看记录可以知道,sql只执行了一次,第二次是从缓存中获取的。

1
2
3
4
5
DEBUG 10-25 20:08:44,458 ==>  Preparing: select emp_id empid,emp_name empName,age,gender from t_emps where emp_Id = ? (BaseJdbcLogger.java:137) 
DEBUG 10-25 20:08:44,489 ==> Parameters: 1(Integer) (BaseJdbcLogger.java:137)
DEBUG 10-25 20:08:44,522 <== Total: 1 (BaseJdbcLogger.java:137)
[Emp(empId=1, empName=张三, age=12, gender=男, deptId=null, dept=null)]
[Emp(empId=1, empName=张三, age=12, gender=男, deptId=null, dept=null)]

2. MyBatis的二级缓存

二级缓存是SqlSessionFactory级别,通过同一个SqlSessionFactory创建的SqlSession查询的结果会被缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取。

二级缓存开启的条件:

  • 在核心配置文件中,设置全局配置属性cacheEnabled="true",默认为true,不需要设置。
  • 在映射文件中设置<cache/>标签。
  • 二级缓存必须在SqlSession关闭或提交之后有效。
  • 查询的数据所转换的实体类类型必须实现序列化的接口(implements Serializable)。

使二级缓存失效的情况:

两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Test
public void testEacheTwo() throws IOException {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession1 = sqlSessionFactory.openSession(true);
EmpMapper mapper1 = sqlSession1.getMapper(EmpMapper.class);
List<Emp> emp1 = mapper1.getByEmpId(1);
System.out.println(emp1);
sqlSession1.close();
SqlSession sqlSession2 = sqlSessionFactory.openSession(true);
EmpMapper mapper2 = sqlSession2.getMapper(EmpMapper.class);
List<Emp> emp2 = mapper2.getByEmpId(1);
System.out.println(emp2);
sqlSession2.close();
}

2.1 二级缓存的相关配置

在mapper配置文件中添加的cache标签可以设置一些属性:

  • eviction属性:缓存回收策略,默认的是 LRU。

    • LRU(Least Recently Used) – 最近最少使用的:移除最长时间不被使用的对象。
    • FIFO(First in First out) – 先进先出:按对象进入缓存的顺序来移除它们。
    • SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
    • WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
  • flushInterval属性:刷新间隔,单位毫秒。

    默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。

  • size属性:引用数目,正整数。

    代表缓存最多可以存储多少个对象,太大容易导致内存溢出。

  • readOnly属性:只读, true/false

    true:只读缓存;会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。

    false:读写缓存;会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是 false。

3. 缓存查询的顺序

先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用。

如果二级缓存没有命中,再查询一级缓存。

如果一级缓存也没有命中,则查询数据库。

SqlSession关闭之后,一级缓存中的数据会写入二级缓存。


分页插件

  1. 添加依赖
1
2
3
4
5
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
  1. 配置分页插件

在Mybatis核心文件中配置插件

1
2
3
4
<plugins>
<!--设置分页插件-->
<plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>

1. 分页插件的使用

  1. 在查询功能之前使用PageHelper.startPage(int pageNum, int pageSize)开启分页功能

  2. 在查询获取list集合之后,使用PageInfo pageInfo = new PageInfo<>(List list, int navigatePages)获取分页相关数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void testGetUserAll() {
SqlSession sqlSession = getSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// PageHelper.startPage(int pageNum, int pageSize)
// pageNum:当前页的页码。
// pageSize:没页显示的条数。
PageHelper.startPage(1, 3);
List<User> users = userMapper.getUserAll();
// PageInfo<T> pageInfo = new PageInfo<>(List<T> list, int navigatePages);
// list:分页之后的数据
// navigatePages:导航分页的页码数
PageInfo pageInfo = new PageInfo<>(users);
System.out.println(pageInfo);
sqlSession.close();
}
1
2
3
4
5
6
7
8
9
10
11
pageNum:当前页的页码
pageSize:每页显示的条数
size:当前页显示的真实条数
total:总记录数
pages:总页数
prePage:上一页的页码
nextPage:下一页的页码
isFirstPage/isLastPage:是否为第一页/最后一页
hasPreviousPage/hasNextPage:是否存在上一页/下一页
navigatePages:导航分页的页码数
navigatepageNums:导航分页的页码,[1,2,3,4,5]