Java 企业级应用开发 part 1 - J2EE part 1

记录一下 J2EE 的 SSM 学习

  • MyBatis
  • configuration
  • mappers
  • MyBatis cache

简介

MyBatis 是持久层的一个 ORM 框架,移去了 JDBC 的繁琐操作以及 SQL 对代码的强绑定,采用 xml 的 SQL mapper 文件解决了这个问题,并能够进行从 SQL 结果到 实体类的转换

简单使用

1
2
3
4
5
6
7
use mybatis;
create table users(
uid int primary key auto_increment,
uname varchar(20) not null,
uage int not null
);
insert into users(uid,uname,uage) values(null,'张三',20),(null,'李四',18);

这个例子是基于 Maven 构建的,首先是配置依赖,这里是老版本了,实际上可以在 这里 搜,然后直接 copy dependency

pom.xml

pom.xml
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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.itheima</groupId>
<artifactId>mybatistest</artifactId>
<version>1.0-SNAPSHOT</version>

<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<!-- <scope>test</scope>-->
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>

</project>

这个 build part 实际上按这个目录写代码上不需要配置的

配置 MyBatis

db.properties
db.properties
1
2
3
4
mysql.driver=com.mysql.cj.jdbc.Driver
mysql.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false
mysql.username=root
mysql.password=mysql

应该不需要过多解释,数据库连接也就是这些东西

  • 指定驱动
  • 指定 JDBC URL,也就是 host, port 以及 database(可选)
  • 用户名 + 密码

这个属性名(整个等号前面)实际上是任意的,在这个版本里还没有自动注入执行,只是为了语义

mybatis-config.xml
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
23
24
25
26
27
<?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="db.properties"/>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<!-- 数据库连接相关配置 ,db.properties文件中的内容-->
<dataSource type="POOLED">
<property name="driver" value="${mysql.driver}"/>
<property name="url" value="${mysql.url}"/>
<property name="username" value="${mysql.username}"/>
<property name="password" value="${mysql.password}"/>
</dataSource>
</environment>
</environments>
<!-- mapping文件路径配置 -->
<mappers>
<mapper resource="mapper/UserMapper.xml"/>

</mappers>

</configuration>

不在代码里配置,就得在 xml 里配置

<properties> 导入了我们的配置(记录的属性),随后的 <environments> 包含所有可能的环境,即不同的数据库配置,以及事务管理器配置,<dataSource> 中是主要的配置部分,将我们在 db.properties 中的属性加载过去

<mappers> 中配置了 mapper 部分,也即 SQL 到接口的映射配置,有几个 mapper 就有几条 <mapper>

environment 一个是通过 <environments>default 切换,一个是代码里构建 SqlSessionFactory 的时候指定

UserMapper.xml
UserMapper.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?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为映射的根节点-->
<!-- mapper为映射的根节点,namespace指定Dao接口的完整类名
mybatis会依据这个接口动态创建一个实现类去实现这个接口,
而这个实现类是一个Mapper对象-->
<mapper namespace="com.itheima.pojo.User">
<!--id ="接口中的方法名"
  parameterType="传入的参数类型"
  resultType = "返回实体类对象,使用包.类名"-->
<select id="findById" parameterType="int"
resultType="com.itheima.pojo.User">
select * from users where uid = #{id}
</select>
</mapper>

其中 <mapper> 中包含具体的映射关系(其实在 mybatis-config.xml 中我们就可以写这些,但是这样太耦合了,不推荐),这一元素必须要包含 namespace,这是为了解决 id 的命名空间问题,同一个 mapper 不能有相同的 id

  • id 是这一查询的接口名称(唯一标识)
  • parameterType 是传入参数类型
  • resultType 是传回结果类型,通常是一个 实体类,例如我们这里的 User
  • <select> 等标签内是一句动态解析的 SQL,不仅是参数可以动态,语句事实上也可以是动态的

关于这个参数传递,实际上对于这里 #{} 内部是任意的,这里是一个特殊情况,只有一个原子类型参数传递
对于一般情况,若传入类型直接为 实体类,那么 #{} 中的名称会直接解析为属性名,按照对象对应属性填充;若传入多个参数,且接口函数上无 @Param 注解标注,那么只能使用 #{0},#{1} 这样的顺序编号方式,按序读取参数;若传入接口函数上有注解标注或采用了 JVM 参数 -parameters 参数,则直接按函数接口上的命名对应填充
对于一个 mapper,在使用时我们更多讲它理解为一个函数,输入参数,生成 SQL 查询,返回结果,这也与他的接口设计模式相符合

User.sql
User.java
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
package com.itheima.pojo;

public class User {
private int uid; //用户id
private String uname; //用户姓名
private int uage; //用户年龄

public int getUid() {
return uid;
}

public void setUid(int uid) {
this.uid = uid;
}

public String getUname() {
return uname;
}

public void setUname(String uname) {
this.uname = uname;
}

public int getUage() {
return uage;
}

public void setUage(int uage) {
this.uage = uage;
}
}

这是实体类的定义,ORM 通过这些 getter, setter 函数来进行提取与修改,而避免直接对类成员的访问,这符合 OOP 的思想,且有更多参数的可操作性,例如对返回格式的调整以及参数验证

调用 MyBatis
UserTest.java
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
package Test;

import com.itheima.pojo.User;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

import java.io.IOException;
import java.io.Reader;

public class UserTest {
@Test
public void userFindByIdTest() {
//读取文件名
String resources = "mybatis-config.xml";
//创建流
Reader reader = null;
try {
//读取mybatis-config.xml文件内容到reader对象中
reader = Resources.getResourceAsReader(resources);
} catch (IOException e) {
e.printStackTrace();
}
//初始化mybatis数据库,创建SqlSessionFactory类的实例
SqlSessionFactory sqlMapper = new
SqlSessionFactoryBuilder().build(reader);
//创建SqlSession实例
SqlSession session = sqlMapper.openSession();
//传入参数查询,返回结果
User user = session.selectOne("findById", 2);
//输出结果
System.out.println(user.getUname());
//关闭session
session.close();
}
}

我们主要看这个类方法里面的代码,首先我们通过 Resources 类的 getResourceAsReader 方法,传入本地的配置文件路径来获取一个文件流的 Reader,并通过 SqlSessionFactoryBuilderbuild 方法构建一个 SqlSessionFactory

  • SqlSessionFactory 这个类是大多数 SQL 操作的开始

我们在这里首先通过 openSession 方法打开一个数据库 session(连接),再通过 session 上的 selectOne 方法通过 statemenet参数(id) 选出我们配置的 mapper 的一个接口,在这里,2 是传入参数,这只是单参数,单对象输出(One,对应的有 List)的传递方法,该方法调用 mapper 接口,返回了我们指定的类型

需要注意的一点是,有时候我们运行导入的项目会遇到找不到 mybatis-config.xml 的问题,这是因为 Java 编译很大程度靠 IDE(编译参数)的调节,有的项目没有在 pom.xml 中配置 resources 目录,会导致这一目录在编译后不被复制到编译目录中,导致这个问题发生
因此要么配置 resources,要么在 src/main/resources 目录上右键,选择 Mark Director As > Resources Root

核心配置

mapper 语法

除去一大堆关于 mybatis-config.xml 的克苏鲁选项以及 SqlSessionFactoryBuilderSqlSessionFactory,等大量 api 之外,重要的其实只有 mapper 中的一些配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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.itheima.mapper.StudentMapper">
<resultMap type="com.itheima.pojo.Student" id="studentMap">
<id property="id" column="sid"/>
<result property="name" column="sname"/>
<result property="age" column="sage"/>
</resultMap>
<select id="findAllStudent" resultMap="studentMap">
select * from t_student
</select>
</mapper>

这个配置文件中,使用了一个 resultMap,这通常用于处理当数据库字段和实体类属性名称不一致的问题,我们通过这个 map 映射字段,在一个查询中,我们指定 resultMap 参数为我们 resultMapid

  • <result> 标签指定返回结果中的字段映射
  • <id> 标签指定数据表的主键

常用的查询类型有

  • <select>
  • <insert>
  • <update>
  • <delete>
  • <sql>
1
2
3
4
<insert id="addUser" parameterType="com.itheima.pojo.User"
keyProperty="uid" useGeneratedKeys="true" >
insert into users(uid,uname,uage) values(#{uid},#{uname},#{uage})
</insert>

这个 insert 中有几个特殊的字段,分别是 keyPropertyuseGeneratedKeys,这是用于获取指定自动增长字段的值并写回传入对象的 uid 属性中

这常在我们做注册操作时使用,在注册时,用户 id 往往自动生成,但是我们可能需要获取这些 id 进行一些后续步骤,而避免二次查询

SqlSessionFactory 封装

在一个项目中,这部分往往可以封装并复用

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
package com.itheima.utils;

import java.io.Reader;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

/**
* 工具类
*/
public class MyBatisUtils {
private static SqlSessionFactory sqlSessionFactory = null;

// 初始化SqlSessionFactory对象
static {
try {
// 使用MyBatis提供的Resources类加载MyBatis的配置文件
Reader reader =
Resources.getResourceAsReader("mybatis-config.xml");
// 构建SqlSessionFactory工厂
sqlSessionFactory =
new SqlSessionFactoryBuilder().build(reader);
} catch (Exception e) {
e.printStackTrace();
}
}

// 获取SqlSession对象的静态方法
public static SqlSession getSession() {
return sqlSessionFactory.openSession();
}
}

动态 SQL

一切都是为了不手动拼接字符串

有如下的表

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
use mybatis;
create table users(
uid int primary key auto_increment,
uname varchar(20) not null,
uage int not null
);
insert into users(uid,uname,uage) values(null,'张三',20),(null,'李四',18);

CREATE TABLE t_student(
sid INT PRIMARY KEY AUTO_INCREMENT,
sname VARCHAR(50),
sage INT
);
INSERT INTO t_student(sname,sage) VALUES('Lucy',25);
INSERT INTO t_student(sname,sage) VALUES('Lili',20);
INSERT INTO t_student(sname,sage) VALUES('Jim',20);

# 创建一个名称为t_customer的表
CREATE TABLE t_customer (
id int(32) PRIMARY KEY AUTO_INCREMENT,
username varchar(50),
jobs varchar(50),
phone varchar(16)
);
# 插入3条数据
INSERT INTO t_customer VALUES ('1', 'joy', 'teacher', '13733333333');
INSERT INTO t_customer VALUES ('2', 'jack', 'teacher', '13522222222');
INSERT INTO t_customer VALUES ('3', 'tom', 'worker', '15111111111');

<if>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<select id="findCustomerByNameAndJobs"
parameterType="com.itheima.pojo.Customer"
resultType="com.itheima.pojo.Customer">
select * from t_customer
<!--where 1=1 -->
<!-- <where> -->
<trim prefix="where" prefixOverrides="and">
<if test="username !=null and username !=''">
and username like concat('%',#{username}, '%')
</if>
<if test="jobs !=null and jobs !=''">
and jobs= #{jobs}
</if>
</trim>
<!--</where> -->
</select>

这里 <if> 标签上的 test 属性表示当何时需要填充标签中的语句,在这个例子中,当 test 满足时才添加查询条件,也因此,if 常用于可变 where 查询条件的情况,即通过不同的字段组合进行查询。通过 <if> 标签,我们可以实现一个 mapper 查询实现动态的 SQL 生成

<trim>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<update id="updateCustomerByTrim"
parameterType="com.itheima.pojo.Customer">
update t_customer
<trim prefix="set" suffixOverrides=",">
<if test="username !=null and username !=''">
username=#{username},
</if>
<if test="jobs !=null and jobs !=''">
jobs=#{jobs},
</if>
<if test="phone !=null and phone !=''">
phone=#{phone},
</if>
</trim>
where id=#{id}
</update>

我们结合目前为止给出的两个例子,<trim> 标签上的 prefix 表示若内容存在,则以这一标签作为前缀,prefixOverridessuffixOverrides 是需要被 trim 掉的多余前缀或后缀,例如这个例子的末尾多余逗号

<choose>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<select id="findCustomerByNameOrJobs"
parameterType="com.itheima.pojo.Customer"
resultType="com.itheima.pojo.Customer">
select * from t_customer where 1=1
<choose>
<!--条件判断 -->
<when test="username !=null and username !=''">
and username like concat('%',#{username}, '%')
</when>
<when test="jobs !=null and jobs !=''">
and jobs= #{jobs}
</when>
<otherwise>
and phone is not null
</otherwise>
</choose>
</select>

和 if-else 类似,选择其一满足 <when>test 属性的

<foreach>

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
<!--<foreach>元素使用 -->
<select id="findByArray" parameterType="java.util.Arrays"
resultType="com.itheima.pojo.Customer">
select * from t_customer where id in
<foreach item="id" index="index" collection="array"
open="(" separator="," close=")">
#{id}
</foreach>
</select>

<!--<foreach>元素使用 -->
<select id="findByList" parameterType="java.util.Arrays"
resultType="com.itheima.pojo.Customer">
select * from t_customer where id in
<foreach item="id" index="index" collection="list"
open="(" separator="," close=")">
#{id}
</foreach>
</select>

<!--<foreach>元素使用 -->
<select id="findByMap" parameterType="java.util.Map"
resultType="com.itheima.pojo.Customer">
select * from t_customer where jobs=#{jobs} and id in
<foreach item="roleMap" index="index" collection="id"
open="(" separator="," close=")">
#{roleMap}
</foreach>
</select>

结合这里的三个 mapper 例子以及对应 service 部分的代码

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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package Test;

import com.itheima.pojo.Customer;
import com.itheima.pojo.Student;
import com.itheima.utils.MyBatisUtils;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MyBatisTest {
private SqlSessionFactory sqlSessionFactory;
private SqlSession sqlSession;

@Before
public void init() {
//定义读取文件名
String resources = "mybatis-config.xml";
//创建流
Reader reader = null;
try {
//读取mybatis-config.xml文件到reader对象中
reader = Resources.getResourceAsReader(resources);
//初始化mybatis,创建SqlSessionFactory类的对象
SqlSessionFactory sqlMapper = new
SqlSessionFactoryBuilder().build(reader);
//创建session对象
sqlSession = sqlMapper.openSession();
} catch (IOException e) {
e.printStackTrace();
}
}

/**
* 省略其他代码
*/

/**
* 根据客户id批量查询客户信息
*/
@Test
public void findByArrayTest() {
// 获取SqlSession
SqlSession session = MyBatisUtils.getSession();
// 创建数组,封装查询id
Integer[] roleIds = {2, 3};
// 执行SqlSession的查询方法,返回结果集
List<Customer> customers = session.selectList("com.itheima.mapper"
+ ".CustomerMapper.findByArray", roleIds);
// 输出查询结果信息
for (Customer customer : customers) {
// 打印输出结果
System.out.println(customer);
}
// 关闭SqlSession
session.close();
}

/**
* 根据客户id批量查询客户信息
*/
@Test
public void findByListTest() {
// 获取SqlSession
SqlSession session = MyBatisUtils.getSession();
// 创建List集合,封装查询id
List<Integer> ids = new ArrayList<Integer>();
ids.add(1);
ids.add(2);
// 执行SqlSession的查询方法,返回结果集
List<Customer> customers = session.selectList("com.itheima.mapper"
+ ".CustomerMapper.findByList", ids);
// 输出查询结果信息
for (Customer customer : customers) {
// 打印输出结果
System.out.println(customer);
}
// 关闭SqlSession
session.close();
}

/**
* 根据客户id批量查询客户信息
*/
@Test
public void findByMapTest() {
// 获取SqlSession
SqlSession session = MyBatisUtils.getSession();
// 创建List集合,封装查询id
List<Integer> ids = new ArrayList<Integer>();
ids.add(1);
ids.add(2);
ids.add(3);
Map<String, Object> conditionMap = new HashMap<String, Object>();
conditionMap.put("id", ids);
conditionMap.put("jobs", "teacher");
// 执行SqlSession的查询方法,返回结果集
List<Customer> customers = session.selectList("com.itheima.mapper"
+ ".CustomerMapper.findByMap", conditionMap);
// 输出查询结果信息
for (Customer customer : customers) {
// 打印输出结果
System.out.println(customer);
}
// 关闭SqlSession
session.close();
}


@After
public void destory() {
//提交事务
sqlSession.commit();
//关闭事务
sqlSession.close();
}
}

这里的 foreach,针对的是传入的一系列相同类型参数,第三个例子实际并不好,虽然传入 Map 类型参数,但是 foreach 最终是针对 Map 上一个 key 的 value,而这个 value 事实上是个 List

  • item 属性是每一个迭代对象的变量名(当我们需要自定义内容时会使用到,而默认就是直接 value + separator 填充)
  • index 属性是迭代的索引,对于列表/数组来说就是下标,
  • open/close 分别对应整体的前缀和后缀

值得注意的是 MyBatis 默认会将非 Map 参数封装为 Maparraylist 是这个 Map 对象的键名,这是固定的。

关联映射

一对一查询

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
<mapper namespace="com.itheima.mapper.PersonMapper">
<!-- 嵌套查询:通过执行另外一条SQL映射语句来返回预期的特殊类型 -->
<select id="findPersonById" parameterType="Integer"
resultMap="IdCardWithPersonResult">
SELECT *
from tb_person
where id = #{id}
</select>
<resultMap type="Person" id="IdCardWithPersonResult">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
<!-- 一对一:association使用select属性引入另外一条SQL语句 -->
<association property="card" column="card_id" javaType="IdCard"
select="com.itheima.mapper.IdCardMapper.findCodeById"/>
</resultMap>

<!-- 嵌套结果:使用嵌套结果映射来处理重复的联合结果的子集 -->
<select id="findPersonById2" parameterType="Integer"
resultMap="IdCardWithPersonResult2">
SELECT p.*, idcard.code
from tb_person p,
tb_idcard idcard
where p.card_id = idcard.id
and p.id = #{id}
</select>
<resultMap type="Person" id="IdCardWithPersonResult2">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
<association property="card" javaType="IdCard">
<id property="id" column="card_id"/>
<result property="code" column="code"/>
</association>
</resultMap>

</mapper>
  • 在第一个 mapper 中,我们在 <resultMap> 中添加了 <association> 标签,进行二次查询,首先我们获取到 card_id,随后我们使用 card_id 指定了一条 select 语句,即 select="com.itheima.mapper.IdCardMapper.findCodeById"。这一方法的缺点是需要二次查询,好处是可以进行 lazy load 的配置,也就是只有当我们从 Person 上调用 Person.getCard() 才会执行这条查询。
  • 在第二个 mapper 中,我们在 <association> 元素里添加了和上面一样的 <id><result> 标签,这是因为我们通过等值连接进行了查询,直接将结果映射到 IdCard 对象上

值得注意的是这里的 javaTypetype 都只写了类名,而非完整的带包名版本,这是因为配置了 MyBatis 的 <typeAliases>

1
2
3
4
5
6
7
8
9
10
11
12
13
<!--使用扫描包的形式定义别名 -->
<typeAliases>
<package name="com.itheima.pojo"/>
</typeAliases>

<settings>
<!-- 打开延迟加载的开关 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 将积极加载改为消息加载,即按需加载 -->
<setting name="aggressiveLazyLoading" value="false"/>
<!-- 开启二级缓存-->
<setting name="cacheEnabled" value="true"/>
</settings>

一对多查询

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
<?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.itheima.mapper.UsersMapper">
<!-- 一对多:查看某一用户及其关联的订单信息
注意:当关联查询出的列名相同,则需要使用别名区分 -->
<resultMap type="Users" id="UserWithOrdersResult">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="address" column="address"/>
<!-- 一对多关联映射:collection
ofType表示属性集合中元素的类型,List<Orders>属性即Orders类-->
<collection property="ordersList" ofType="Orders">
<id property="id" column="orders_id"/>
<result property="number" column="number"/>
</collection>
</resultMap>
<select id="findUserWithOrders" parameterType="Integer"
resultMap="UserWithOrdersResult">
SELECT u.*,o.id as orders_id,o.number
from tb_user u,tb_orders o
WHERE u.id=o.user_id
and u.id=#{id}
</select>
</mapper>

<collection> 标签内部指定了 <id><result> 元素,将一次的查询结果映射并封装成 ofType 存到 property (ordersList) 上,同样可以采用 select 属性指定查询语句来进行嵌套查询而不是连接

多对多查询

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
<?xml version="1.0" encoding="UTF8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.mapper.OrdersMapper">
<!-- 多对多嵌套查询:通过执行另外一条SQL映射语句来返回预期的特殊类型 -->
<select id="findOrdersWithPorduct" parameterType="Integer"
resultMap="OrdersWithProductResult">
select * from tb_orders WHERE id=#{id}
</select>
<resultMap type="Orders" id="OrdersWithProductResult">
<id property="id" column="id"/>
<result property="number" column="number"/>
<collection property="productList" column="id" ofType="Product"
select="com.itheima.mapper.ProductMapper.findProductById">
</collection>
</resultMap>

<!-- 多对多嵌套结果查询:查询某订单及其关联的商品详情 -->
<select id="findOrdersWithPorduct2" parameterType="Integer"
resultMap="OrdersWithPorductResult2">
select o.*,p.id as pid,p.name,p.price
from tb_orders o,tb_product p,tb_ordersitem oi
WHERE oi.orders_id=o.id
and oi.product_id=p.id
and o.id=#{id}
</select>
<!-- 自定义手动映射类型 -->
<resultMap type="Orders" id="OrdersWithPorductResult2">
<id property="id" column="id"/>
<result property="number" column="number"/>
<!-- 多对多关联映射:collection -->
<collection property="productList" ofType="Product">
<id property="id" column="pid"/>
<result property="name" column="name"/>
<result property="price" column="price"/>
</collection>
</resultMap>

</mapper>

这里的两个 mapper 通过 <collection> 标签实现对于多个查询结果的封装和映射。

  • 第一个 mapper 中,<collection> 标签上的 select 属性指定了查询语句,ofType 指定每一个查询结果的类型,最后这些结果会被封装成一个 List 并 set 到 property 上,这个方式属于二次(嵌套)查询
  • 第二个 mapper 中,<collection> 标签内部指定了 <id><result> 元素,将一次的查询结果映射并封装成 ofType

一级缓存

对于同一 SqlSession 在无 update,delete,insert 操作期间,任何的查询都将被缓存(默认开启)

二级缓存

对于同一 Mapper 的相同 SQL 语句,对于 不同的 SqlSession 也将被缓存(手动开启),在 Mapper 的 namespace 下,用 <cache>...</cache> 标签包裹即可开启二级缓存