学习目标
- 能够使用
<Result>
对SQL返回结果和实体类进行映射 - 能够实现一种插入数据后立即获取ID的方案
- 能够说出OGNL表达式中
#
和$
的区别 - 能够说出
<if>
、<where>
、<set>
、<foreach>
标签的应用场景 - 能够掌握一对一的复杂映射
- 能够掌握一对多的复杂映射
1 单表操作案例
1.1 环境搭建(02_01)
1. 初始化数据库
CREATE SCHEMA `study_mybatis_02_01` DEFAULT CHARACTER SET utf8 ;
use study_mybatis_02_01 ;
create table t_user (
id int primary key auto_increment,
user_name varchar(20) not null,
birthday date,
sex char(1) default '男',
home_address varchar(50)
);
insert into t_user values (null, '孙悟空','1980-10-24','男','花果山水帘洞');
insert into t_user values (null, '白骨精','1992-11-12','女','白虎岭白骨洞');
insert into t_user values (null, '猪八戒','1983-05-20','男','福临山云栈洞');
insert into t_user values (null, '玉面狐','1995-03-22','女','积雷山摩云洞');
insert into t_user values (null, '玉兔精','2010-02-12','女','天竺国皇宫');
insert into t_user values (null, '豹子精','2008-05-03','男','隐雾山折岳洞');
2. 初始化项目
-
创建mavn模块
<groupId>com.ithiema</groupId> <artifactId>study_mybatis_02_01</artifactId> <version>1.0-SNAPSHOT</version>
-
修改pom.xml
<?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.example</groupId> <artifactId>study_mybatis_02_01</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <!--junit--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <!--mybatis依赖--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.5</version> </dependency> <!--mysql数据库驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.38</version> </dependency> <!--日志输出 log4j--> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> </dependencies> <build> <!--重新指定资源文件--> <resources> <!--指定src/main/java下的xml文件和properties文件作为资源文件--> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> <include>**/*.properties</include> </includes> </resource> <!--指定src/main/resources下的xml文件和properties文件作为资源文件--> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.xml</include> <include>**/*.properties</include> </includes> </resource> </resources> </build> </project>
-
添加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> <!-- 指定使用log4j输出日志--> <settings> <setting name="logImpl" value="LOG4J"/> </settings> <!-- 别名配置 --> <typeAliases> <package name="com..entity"/> </typeAliases> <!-- mybatis环境的配置 --> <environments default="default"> <environment id="default"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/study_mybatis_02_01?useUnicode=true&characterEncoding=utf-8"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <mappers> <package name="com.example.dao" /> </mappers> </configuration>
-
添加log4j配置文件
- src/main/log4j.properties
log4j.rootLogger=DEBUG, stdout log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
1.2 案例之resultMap标签
1. 需求
利用Mybatis框架,从MySQL中查询t_user表中的所有数据并打印
2. 代码实现
User实体类
- com.example.entity.User.java
public class User {
private Integer id;
private String userName;
private Date birthday;
private String sex;
private String homeAddress;
// 构造方法、setter、 getter、 toString方法省略……
}
UserMapper接口
- com.example.dao.UserMapper.java
/**
数据访问层方法
*/
public interface UserMapper {
/**
* 查询所有
* @return
*/
List<User> selectAll();
}
映射文件
和UserMapper.java相同的包下创建映射文件。
- src/main/java/com/example/dao/UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.dao.UserMapper">
<!--
定义映射
id:映射的名字
type:实体类
-->
<resultMap id="userResultMap" type="User">
<!--
定义字段映射,id和result标签分别表示主键字段映射和非主键字段映射
property 实体类中的属性名
column SQL返回结果的列名
如果property和cloumn的值相同,可以省略
-->
<!-- <id property="id" column="id"/>-->
<result property="userName" column="user_name"/>
<!-- <result property="birthday" column="birthday"/>-->
<!-- <result property="sex" column="sex"/>-->
<result property="homeAddress" column="home_address"/>
</resultMap>
<select id="selectAll" resultMap="userResultMap">
select * from t_user
</select>
</mapper>
3. 测试
- com.example.dao.UserMapperTest.java
public class UserMapperTest {
private SqlSession session;
private UserMapper userMapper;
/**
* 执行单元测试前,先打开会话且获取userMapper
* 测试方法中直接使用userMapper
*/
@Before
public void before() throws IOException {
//1. 得到输入流对象
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//2. 构造会话工厂建造类
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//3. 通过建造类得到会话工厂
SqlSessionFactory factory = builder.build(inputStream);
//4. 通过会话工厂得到会话对象
session = factory.openSession();
//5. 会话对象得到UserMapper接口的代理对象
userMapper = session.getMapper(UserMapper.class);
}
/**
* 测试方法执行完成后,先提交事务,再关闭会话
*/
@After
public void after() {
// 提交事务
session.commit();
// 关闭会话
session.close();
}
@Test
public void selectAll() {
List<User> users = userMapper.selectAll();
for (User user : users) {
System.out.println(user);
}
}
}
4. 总结
当SQL查询返回数据的列与实体类的属性不一致的时候可以使用<ResultMap>
进行字段映射,具体步骤如下:
-
编写Result映射
<!-- 定义映射 id:映射的名字 type:实体类 --> <resultMap id="userResultMap" type="User"> <!-- 定义字段映射,id和result标签分别表示主键字段映射和非主键字段映射 property 实体类中的属性名 column SQL返回结果的列名 如果property和cloumn的值相同,可以省略 --> <id property="id" column="id"/> <result property="userName" column="user_name"/> <result property="birthday" column="birthday"/> <result property="sex" column="sex"/> <result property="homeAddress" column="home_address"/> </resultMap>
如果property和cloumn的值相同,可以省略,所以可以进行简化,如下:
<resultMap id="userResultMap" type="User"> <result property="userName" column="user_name"/> <result property="homeAddress" column="home_address"/> </resultMap>
-
在
<select>
标签中使用ResultMap<select id="selectAll" resultMap="userResultMap"> select * from t_user </select>
1.3 案例之获取自增主键
1. 需求
向t_user表中插入1条数据,并输出mysql自动生成的id值
名字:红孩儿
生日:2020-11-11
性别:男
住址:号山火云洞
2. 代码实现
UserMapper接口
UserMapper.java文件中加入下面两个方法声明
/**
* 插入
*
* @param user
* @return
*/
int insert01(User user);
/**
* 插入
*
* @param user
* @return
*/
int insert02(User user);
映射文件
UserMapper.xml中加入下面两映射
<!--####################插入后获取ID方案一-->
<insert id="insert01">
<!--
resultType:主键的类型
keyProperty:实体类中的主键字段
order: AFTER表示SQL操作完成以后获取ID
-->
<selectKey resultType="int" keyProperty="id" order="AFTER" >
select LAST_INSERT_ID()
</selectKey>
INSERT INTO t_user(id,user_name,birthday, sex,home_address)
VALUES (null, #{userName}, #{birthday}, #{sex}, #{homeAddress})
</insert>
<!--####################插入后获取ID方案二-->
<!--
useGeneratedKeys:自动生成主键ID
keyProperty: 实体类中主键的属性名
-->
<insert id="insert02" useGeneratedKeys="true" keyProperty="id" >
INSERT INTO t_user(id,user_name,birthday, sex,home_address)
VALUES (null, #{userName}, #{birthday}, #{sex}, #{homeAddress})
</insert>
3. 测试
在UserMapperTest.java文件中加入下面两个测试方法
@Test
public void insert01(){
User user = new User(null,"红孩儿",Date.valueOf("2020-11-11"),"男","号山火云洞");
userMapper.insert01(user);
System.out.println(user);
}
@Test
public void insert02(){
User user = new User(null,"红孩儿",Date.valueOf("2020-11-11"),"男","号山火云洞");
userMapper.insert02(user);
System.out.println(user);
}
4. 总结
插入数据后获取自增主键的方案有两种,分别是使用<selectKey>
和useGeneratedKeys
属性
方案一
属性 | 说明 |
---|---|
keyColumn | 主键的列名,一般无需指定 |
keyProperty | 主键对应的类对象的属性名 |
resultType | 返回的主键数据类型 |
order | 获取主键的时机 AFTER(记录插入后再获取主键值),BEFORE(记录插入前获取主键值,使用较少,了解即可) |
方案二
属性 | 说明 |
---|---|
useGeneratedKeys | true 使用数据库自动生成的主键 |
keyColumn | 表中主键的列名,一般无需指定,只有在某些数据库(如PostgreSQL)中主键不是表中的第一列时才需要使用此属性 |
keyProperty | 实体类中主键的属性名 |
1.4 案例之OGNL表达式
1. 需求
查询t_user表中前n条数据(根据id升序排序)并打印,n是一个方法参数,方法的接口已经给出,如下所示:
/**
* 查询前n个数据,根据ID升序排序
*
* @return
*/
List<User> selectTopN(@Param("n") String n);
2. 代码实现
映射文件
UserMapper.xml中加入下面映射
<select id="selectTopN" resultMap="userResultMap">
select * from t_user order by id asc limit ${n}
</select>
3. 测试
@Test
public void selectTopN(){
List<User> users = userMapper.selectTopN("3");
System.out.println("n=3\t返回数量:"+users.size());
System.out.println("查询结果:"+users);
users = userMapper.selectTopN("5");
System.out.println("n=5\t返回数量:"+users.size());
System.out.println("查询结果:"+users);
users = userMapper.selectTopN("100");
System.out.println("n=100\t返回数量:"+users.size());
System.out.println("查询结果:"+users);
}
4. 总结
Mybatis 使用ognl 表达式获取参数, 有两种格式: #{} 和 ${}
#{} : 占位符
pojo对象 #{对象中的属性名} -- 获取pojo中的属性
基本数据类型 #{参数名} -- 获取基本类型数据,参数名通过@Param("xxxx")指定
${} : 拼接符
pojo对象 ${对象中的属性名} -- 获取pojo中的属性
基本数据类型 ${参数名} -- 获取基本类型数据,参数名通过@Param("xxxx")指定
注意:
- 使用${}会有SQL注入的风险,所以生产中尽量使用#{}
- 在分页查询中,如果传入字符串类型分页参数,则只能使用
${}
,
#和$的区别【面试点】:
#:占位符,通过praparedStatment对象预编译处理
$:sql拼接,通过statement对象处理,没有预编译处理,有sql注入的风险
2 动态SQL案例
Mybatis动态SQL的官方说明地址: https://mybatis.org/mybatis-3/dynamic-sql.html
2.1 案例之if/where标签
1. 需求
根据名字和性别来查询数据,只有当查询条件不为null且不是空字符串时,才作为查询条件。
方法声明如下:
/**
* 根据名字和性别查询
* @param userName 空字符串或null则不作为查询条件
* @param sex 空字符串或null则不作为查询条件
* @return
*/
List<User> selectByUserNameAndSex(@Param("userName") String userName,@Param("sex") String sex);
用例1
名字:孙悟空 性别:男
select * from t_user where user_name = '孙悟空' and sex='男'
用例2
名字:null 性别:男
select * from t_user where sex = '男'
用例3
名字:null 性别:null
select * from t_user
2. 代码实现
映射文件
UserMapper.xml中加入下面映射
<!--
where:
1. where标签就相当于SQL语句中where关键字
2. 去掉多余的and、or关键字
if:
判断条件是否为真,如果为真则将if中字符串拼接到SQL语句中
-->
<select id="selectByUserNameAndSex" resultMap="userResultMap">
select * from t_user
<where>
<if test="userName!=null and userName!=''">
user_name=#{userName}
</if>
<if test="sex!=null and sex!=''">
and sex=#{sex}
</if>
</where>
</select>
3. 测试
@Test
public void selectByUserNameAndSex(){
List<User> users = null;
users =userMapper.selectByUserNameAndSex("孙悟空","男");
System.out.println("========完成,条件:('孙悟空','男')==========");
users =userMapper.selectByUserNameAndSex("","男");
System.out.println("========完成,条件:('',男)==========");
users =userMapper.selectByUserNameAndSex("孙悟空",null);
System.out.println("========完成,条件:(孙悟空,null)==========");
users =userMapper.selectByUserNameAndSex(null,"");
System.out.println("========完成,条件:(null,'')==========");
}
4. 总结
if标签
判断条件是否为真,如果为真则将if中字符串拼接到SQL语句中
where标签
1-where标签就相当于SQL语句中where关键字
2-去掉多余的and、or关键字
2.2 案例之if/set标签
1. 需求
更新对象的非null属性。
方法声明如下:
/**
* 根据ID更新非空字段
* @param user
* @return
*/
int updateByIdSelective(User user);
2. 代码实现
映射文件
UserMapper.xml中加入下面映射
<!--
set
1.一般与if标签配合使用
2.set用在update语句中,相当于set关键字
3.会自动去掉能多余的逗号
-->
<update id="updateByIdSelective">
update t_user
<set>
<if test="userName!=null">
user_name = #{userName},
</if>
<if test="birthday!=null">
birthday = #{birthday},
</if>
<if test="sex!=null">
sex = #{sex},
</if>
<if test="homeAddress!=null">
home_address = #{homeAddress},
</if>
</set>
where id=#{id}
</update>
3. 测试
@Test
public void updateByIdSelective(){
// 获取第1条记录
User user = userMapper.selectTopN("1").get(0);
System.out.println("原始数据:"+user);
// 更新生日和住址
userMapper.updateByIdSelective(new User(user.getId(),null,Date.valueOf("2020-01-01"),null,"传智修道院"));
user = userMapper.selectTopN("1").get(0);
System.out.println("生日和住址不为空,结果:"+user);
}
4. 总结
set标签
- 一般与if标签配合使用
- set用在update语句中,相当于set关键字
- 会去掉多余的逗号
2.3 案例之foreach标签
1. 需求
-
批量向t_user表中插入数据
方法声明如下:
/** * 批量插入 * @param users * @return */ int insertRecords(@Param("users")List<User> users);
-
根据ID批量删除数据
方法声明如下:
/** * 根据id批量删除 * @param ids * @return */ int deleteByIdS(@Param("ids")Integer[] ids);
2. 代码实现
映射文件
UserMapper.xml中加入下面映射
<insert id="insertRecords">
insert into t_user values
<!--
foreach 遍历操作
collection 参数名,要求必须是Iterable对象或数组或Map
item 表示遍历过程中每个元素的变量
separator 遍历完每个元素后添加的分割符
-->
<foreach collection="users" item="user" separator=",">
(#{user.id}, #{user.userName}, #{user.birthday}, #{user.sex}, #{user.homeAddress})
</foreach>
</insert>
<delete id="deleteByIdS">
delete from t_user where id in
<!--
open 遍历开始前添加的符号
close 遍历完成后添加的符号
-->
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</delete>
3. 测试
- 测试批量插入
@Test
public void insertRecords(){
// 获取第1条记录
List<User> users = Arrays.asList(
new User(null, "黄风怪", Date.valueOf("1980-10-10"), "男", "黄风岭黄风洞"),
new User(null, "黑熊精", Date.valueOf("1988-05-05"), "男", "黑风山黑风洞")
);
int result = userMapper.insertRecords(users);
System.out.println("批量插入数据,条数:"+result);
}
- 测试批量 删除
@Test
public void deleteByIdS(){
int result = userMapper.deleteByIdS(new Integer[]{1, 2, 3});
System.out.println("批量删除数据,条数:"+result);
}
4. 总结
foreach标签属性
foreach标签的属性 | 作用 |
---|---|
collection | 参数名,要求必须是Iterable对象或数组或Map |
index(了解即可) | 元素下标 |
item | 表示遍历过程中每个元素的变量 |
separator | 每次遍历后添加分隔符 |
open | 遍历前添加什么符号 |
close | 遍历后添加什么符号 |
特殊场景(了解即可):如果需要遍历的是Map(或者元素是Map.Entry类型的Collection),则index表示key,item表示value。
2.4 案例之sql/include标签
1. 需求
-
根据名字和住址进行模糊数据查询。
方法声明如下:
/** * 模糊查询 * @param condition 查询条件 * @return */ List<User> selectByConditon(@Param("condition")String condition);
-
根据名字和住址进行模糊数据统计。
方法声明如下:
/** * 模糊统计 * @param condition 查询条件 * @return */ int countByConditon(@Param("condition")String condition);
要求:上面两个功能中拼接where条件的逻辑是一样的,因此要做到复用。
2. 代码实现
映射文件
UserMapper.xml中加入下面映射
<!--定义可重用的代码块-->
<sql id="conditionSql">
<if test="condition!=null and condition!=''">
where user_name like concat('%',#{condition},'%')
or home_address like concat('%',#{condition},'%')
</if>
</sql>
<!--根据条件查询-->
<select id="selectByConditon" resultMap="userResultMap">
select * from t_user
<!--引用代码块-->
<include refid="conditionSql"/>
</select>
<!--根据条件统计-->
<select id="countByConditon" resultType="int">
select count(1) from t_user
<!--引用代码块-->
<include refid="conditionSql"/>
</select>
3. 测试
@Test
public void selectByConditon(){
List<User> result = userMapper.selectByConditon("精");
System.out.println("根据条件模糊查询,结果:"+result);
}
@Test
public void countByConditon(){
int result = userMapper.countByConditon("精");
System.out.println("根据条件模糊统计,结果:"+result);
}
4. 总结
- sql标签:定义一段可以重用的SQL语句
- include标签: 引用上面定义的SQL语句
3 复杂映射案例
当SQL查询的结果集复杂,不能(或不方便)使用一个类结构来接收这些数据,我们就称这种映射是复杂映射。复杂映射一般分为两种:一对一、一对多。
一对一映射通过<assosication>
标签实现;一对多映射通过<collection>
标签实现。
3.1 环境搭建(02_02)
1. 初始化数据库
CREATE SCHEMA `study_mybatis_02_02` DEFAULT CHARACTER SET utf8 ;
use study_mybatis_02_02 ;
-- 创建用户基本表
drop table if exists t_user;
create table t_user (
id int primary key auto_increment,
username varchar(20) not null,
birthday date,
sex char(1) default '男',
address varchar(50)
);
-- 插入用户表
insert into t_user values (1, '孙悟空','1980-10-24','男','花果山水帘洞');
insert into t_user values (2, '白骨精','1992-11-12','女','白虎岭白骨洞');
insert into t_user values (3, '猪八戒','1983-05-20','男','福临山云栈洞');
insert into t_user values (4, '玉面狐','1995-03-22','女','积雷山摩云洞');
insert into t_user values (5, '玉兔精','2010-02-12','女','天竺国皇宫');
insert into t_user values (6, '豹子精','2008-05-03','男','隐雾山折岳洞');
-- 用户扩展信息表,一个用户对应一条用户扩展信息
drop table if exists t_user_info;
create table t_user_info (
id int primary key auto_increment,
user_id int not null,
height double,
weight double,
married tinyint(1),
UNIQUE KEY `UNIQUE_USERID` (`user_id`)
);
-- 插入用户扩展信息表
insert into t_user_info values(null,1,185,90,1),(null,2,170,60,0);
-- 创建订单表
drop table if exists t_order;
create table t_order(
id int primary key auto_increment , -- 主键
user_id int not null, -- 用户id
goods varchar(100), -- 商品名称
create_time datetime, -- 下单时间
note varchar(100) -- 备注
);
-- 添加订单数据
insert into t_order values(null, 1,'金箍棒', now(), '可伸缩,能放到耳朵里'),(null, 1,'紧箍咒', now(), '咒语接收邮箱:sunwukong@itcast.com'),(null, 1,'筋斗云', now(), '高配版:一个筋斗十万八千里');
insert into t_order values(null, 3, '九齿钉钯',now(), '高配版,玄铁材质的'),(null, 3,'小娘子', now(), '要有高小姐那种气质');
-- 创建技能表
drop table if exists t_skill;
CREATE TABLE `t_skill` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`name` varchar(64) NOT NULL COMMENT '名称',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COMMENT='技能表';
-- 添加技能数据
INSERT INTO `t_skill` VALUES (1,'飞天术'),(2,'变幻术'),(3,'隐形术'),(4,'摄魂术');
-- 创建用户技能关联表
drop table if exists t_user_skill;
CREATE TABLE `t_user_skill` (
`user_id` int(11) NOT NULL COMMENT '学生id',
`skill_id` int(11) NOT NULL COMMENT '课程id',
PRIMARY KEY (`user_id`,`skill_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户技能关联表';
-- 添加用户技能关联数据
INSERT INTO `t_user_skill` VALUES (1,1),(1,2),(1,3),(2,1),(2,2),(2,3),(2,4);
2. 初始化项目
- 解压缩资料中提供的study_mybatis_02_02.zip。
- study_mybatis_02_02文件夹复制到项目根目录。
- 导入module。
3.2 一对一案例(association)
1. 需求
根据ID查询用户信息(t_user),要求将用户拓展信息(t_user_info)一起查询出来并打印到控制台。
select tu.*,
tui.id ui_id,
tui.user_id ui_user_id,
tui.height ui_height,
tui.weight ui_weight,
tui.married ui_married
from t_user tu
join t_user_info tui on tu.id = tui.user_id
where tu.id=#{id}
2. 代码实现
实体类(源代码已给出)
UserMapper接口
添加下面方法
/**
* 根据ID查询用户基本信息和拓展信息
* @param id
* @return
*/
User selectUserAndInfoById(@Param("id") Integer id);
UserMapper接口映射
添加下面映射
<!--
一对一映射
1. association标签,用于表示数据查询中的1对1映射
2. 此时,resultMap和association都需要指定所有字段的映射,不能省略
3. SQL查询的结果集中不能有重复的字段,如果有重复字段,则映射过程中只有最后一个字段可以取到正确的值,所以需要给名称重复的字段使用别名。
-->
<resultMap id="userResultMap" type="User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="birthday" column="birthday"/>
<result property="sex" column="sex"/>
<result property="address" column="address"/>
<association property="userInfo">
<id property="id" column="ui_id"/>
<result property="userId" column="ui_user_id"/>
<result property="height" column="ui_height"/>
<result property="weight" column="ui_weight"/>
<result property="married" column="ui_married"/>
</association>
</resultMap>
<select id="selectUserAndInfoById" resultMap="userResultMap">
select tu.*,
tui.id ui_id,
tui.user_id ui_user_id,
tui.height ui_height,
tui.weight ui_weight,
tui.married ui_married
from t_user tu
join t_user_info tui on tu.id = tui.user_id
where tu.id=#{id}
</select>
3. 测试
@Test
public void selectUserAndInfoById(){
User user = userMapper.selectUserAndInfoById(1);
System.out.println("根据id查询用户基本信息和拓展信息,结果:"+user);
}
4. 总结
association标签,用于表示数据查询中的1对1映射
association标签的属性 | 说明 |
---|---|
property | 类的属性名 |
注意
-
resultMap和association都需要指定所有字段的映射,不能省略,这里与1.2章节不同
-
SQL查询的结果集中不能有重复的字段,如果有重复字段,则映射过程中只有第一个字段可以取到正确的值,所以需要给名称重复的字段使用别名。
3.3 一对多案例(collection)
1. 需求
根据用户ID查询用户信息(t_user)和订单信(t_order),并打印到控制台。
SELECT tu.*,
tor.id o_id,
tor.user_id o_user_id,
tor.goods o_goods,
tor.create_time o_create_time,
tor.note o_note
FROM t_user tu join t_order tor on tu.id=tor.user_id
where tu.id=#{id}
2. 代码实现
实体类(源代码已给出)
UserMapper接口
添加下面方法
/**
* 根据ID查询用户基本信息和订单信息
* @param id
* @return
*/
User selectUserAndOrderById(@Param("id") Integer id);
UserMapper接口映射
添加下面映射
<!--
一对多映射
1. collection标签,用于表示数据查询中的1对多映射
2. 此时,resultMap和collection都需要指定所有字段的映射,不能省略(同association)
3. SQL查询的结果集中不能有重复的字段,如果有重复字段,则映射过程中只有第一个字段可以取到正确的值,所以需要给名称重复的字段使用别名。(同association)
-->
<resultMap id="userAndOrderResultMap" type="User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="birthday" column="birthday"/>
<result property="sex" column="sex"/>
<result property="address" column="address"/>
<!--
collection 映射成一个集合
porperty 类属的属性名
ofType 集合内元素的类型
-->
<collection property="orders" ofType="Order" >
<id property="id" column="o_id"/>
<result property="userId" column="o_user_id"/>
<result property="goods" column="o_goods"/>
<result property="createTime" column="o_create_time"/>
<result property="note" column="o_note"/>
</collection>
</resultMap>
<select id="selectUserAndOrderById" resultMap="userAndOrderResultMap">
SELECT tu.*,
tor.id o_id,
tor.user_id o_user_id,
tor.goods o_goods,
tor.create_time o_create_time,
tor.note o_note
FROM t_user tu join t_order tor on tu.id=tor.user_id
where tu.id=#{id}
</select>
3. 测试
@Test
public void selectUserAndOrderById(){
User user = userMapper.selectUserAndOrderById(1);
System.out.println("根据id查询用户基本信息和订单信息,完成~");
System.out.println("用户:"+user.getUsername());
for (Order order : user.getOrders()) {
System.out.println("订单"+order.getId()+":"+order);
}
}
4.总结
collection标签,用于表示数据查询中的1对多的映射
collection的属性 | 说明 |
---|---|
property(同association) | 类的属性名 |
ofType | 集合中每个元素的类型 |
注意
-
(同association)resultMap和collection都需要指定所有字段的映射,不能省略,这里与2.2章节不同
-
(同association)SQL查询的结果集中不能有重复的字段,如果有重复字段,则映射过程中只有第一个字段可以取到正确的值,所以需要给名称重复的字段使用别名。
3.3 思考案例
1. 需求
如何对下面sql语句的查询结果进行映射呢?
SELECT tu.*,ts.id s_id,ts.name s_name
FROM t_user tu
join t_user_skill tus on tu.id = tus.user_id
join t_skill ts on tus.skill_id = ts.id;
2. 代码实现
在实际生产中,我们可以根据业务场景采用下面两个方案(二选一)来解决上面的问题:
方案一:一个用户包含多个技能
/**
* 查询所有的用户和技能,映射为User对象列表
**/
List<User> selectUserAndSkill();
<!--将结果集映射为User对象,一个用户对象包含多个Skill对象 -->
<resultMap id="userAndSkilResultMap" type="User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="birthday" column="birthday"/>
<result property="sex" column="sex"/>
<result property="address" column="address"/>
<collection property="skills" ofType="Skill">
<id property="id" column="s_id"/>
<result property="name" column="s_name"/>
</collection>
</resultMap>
<select id="selectUserAndSkill" resultMap="userAndSkilResultMap">
SELECT tu.*,ts.id s_id,ts.name s_name
FROM t_user tu
join t_user_skill tus on tu.id = tus.user_id
join t_skill ts on tus.skill_id = ts.id
</select>
方案二:一个技能可以被多个用户拥有
/**
* 查询所有的用户和技能,映射为Skill对象列表
**/
List<Skill> selectSkillAndUser();
<!--将结果集映射为Skill对象,一个技能对象包含多个User对象 -->
<resultMap id="skillAndUserResultMap" type="Skill">
<id property="id" column="s_id"/>
<result property="name" column="s_name"/>
<collection property="users" ofType="User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="birthday" column="birthday"/>
<result property="sex" column="sex"/>
<result property="address" column="address"/>
</collection>
</resultMap>
<select id="selectSkillAndUser" resultMap="skillAndUserResultMap">
SELECT tu.*,ts.id s_id,ts.name s_name
FROM t_user tu
join t_user_skill tus on tu.id = tus.user_id
join t_skill ts on tus.skill_id = ts.id
</select>
3. 测试
@Test
public void selectUserAndSkill(){
List<User> users = userMapper.selectUserAndSkill();
System.out.println("查询所有用户基本信息和技能信息(selectUserAndSkill),完成~");
for (User user : users) {
System.out.println("=====================");
System.out.println("用户:"+user.getUsername());
for (Skill skill : user.getSkills()) {
System.out.println("技能"+skill.getId()+":"+skill.getName());
}
}
}
@Test
public void selectSkillAndUser(){
List<Skill> skills = userMapper.selectSkillAndUser();
System.out.println("查询所有用户基本信息和订单信息(selectSkillAndUser),完成~");
for (Skill skill : skills) {
System.out.println("=====================");
System.out.println("技能:"+skill.getName());
for (User user : skill.getUsers()) {
System.out.println("用户"+user.getId()+":"+user.getUsername());
}
}
}
4. 总结
针对数据库中多对多的表结构,SQL查询结果集的映射处理可以根据业务需求简化为不同的一对多映射处理。