文章目录
前言
写这篇博客之前呢,查看了下已经写过的博客,感觉针对于Mybatis
的使用都太过简单。总觉得少了点什么,于是决定补充一下Mybatis中关联查询时,ResultMap
配置写一对一
、一对多
的使用案例。
环境
本次测试采取Springboot 2.1.4.RELEASE
结合mybatis 1.3.0
进行测试。
项目搭建
依赖引入
主要引入Springboot 2.1.4.RELEASE
和mybatis 1.3.0
。具体如下所示:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 1.配置druid数据库连接池 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<!-- <version>5.0.4</version> -->
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.16</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- 2.导入mybatis库文件 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
<!-- 使用aop拦截器 需要的aop库文件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</dependency>
<!-- 阿里巴巴json -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.44</version>
</dependency>
<!-- lombok 引入 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.20</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<!-- 打JAR包,不包含依赖文件;显式剔除配置文件 -->
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<!-- 剔除配置文件 -->
<!-- <excludes> <exclude>*.properties</exclude> <exclude>*.yml</exclude>
<exclude>*.xml</exclude> <exclude>*.txt</exclude> </excludes> -->
<archive>
<manifest>
<addClasspath>true</addClasspath>
<!-- MANIFEST.MF 中 Class-Path 各个依赖加入前缀 -->
<!--lib文件夹内容,需要 maven-dependency-plugin插件补充 -->
<classpathPrefix>lib/</classpathPrefix>
<!-- jar包不包含唯一版本标识 -->
<useUniqueVersions>false</useUniqueVersions>
<!--指定入口类 -->
<mainClass>cn.linkpower.dtuAuthPlatform.DtuAuthPlatformApplication</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<!-- 复制依赖的jar包到指定的文件夹里 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<!-- 跳过单元测试 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
<!--<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>-->
</plugins>
</build>
配置文件编写
配置application.yml
,指定数据库连接配置,以及配置mybatis。
#mybatis 配置
mybatis:
config-location: classpath:mybatis/mybatis-config.xml
mapper-locations: classpath:mybatis/test/*.xml
type-aliases-package: cn.linkpower.dtuAuthPlatform.vo
spring:
datasource:
url: jdbc:mysql://192.168.99.100:3306/mybatis_test?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
type: com.alibaba.druid.pool.DruidDataSource #当前数据源的操作类型
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
filters: stat,wall
#配置初始化大小/最小/最大
initial-size: 5
min-idle: 5
max-active: 20
#获取连接等待超时时间
max-wait: 60000
#间隔多久进行一次检测,检测需要关闭的空闲连接
time-between-eviction-runs-millis: 60000
#一个连接在池中最小生存的时间
min-evictable-idle-time-millis: 300000
#validation-query: SELECT 'x'
validation-query: SELECT 1 FROM DUAL
test-while-idle: true
test-on-borrow: false
test-on-return: false
#打开PSCache,并指定每个连接上PSCache的大小。oracle设为true,mysql设为false。分库分表较多推荐设置为false
pool-prepared-statements: false
max-pool-prepared-statement-per-connection-size: 20
一对一测试(association )
测试1
测试一对一
的实现,先需要进行数据库表的创建和数据的导入,具体SQL如下所示:
CREATE TABLE tb_person (
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR ( 32 ),
age INT,
sex VARCHAR ( 8 ),
card_id INT UNIQUE
);
INSERT INTO tb_person(name,age,sex,card_id) VALUES('zhangsan',29,'女',1);
INSERT INTO tb_person(name,age,sex,card_id) VALUES('lisi',29,'男',2);
CREATE TABLE tb_idcard (
id INT PRIMARY KEY AUTO_INCREMENT,
sfzh VARCHAR ( 18 ),
bfjg VARCHAR ( 50 ));
INSERT INTO tb_idcard(sfzh,bfjg) VALUES('1234567894561333','东昌府公安局');
INSERT INTO tb_idcard(sfzh,bfjg) VALUES('5548465132151845','东昌府公安局');
由于是参考Mybatis的关联映射(一对一 一对多 多对多)进行设定,
对其中的部分注重点进行额外说明,此处数据引入以逆风微笑的李同学
设定为主。
数据库中数据表创建并填充数据后,就需要编写pojo
类。
import lombok.Data;
@Data
public class Tb_person {
private Integer id;
private String name;
private Integer age;
private String sex;
private Tb_idcard tb_idcard; //主键所在类的对象
@Override
public String toString() {
return "Tb_person:{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
", tb_idcard=" + tb_idcard +
'}';
}
}
import lombok.Data;
@Data
public class Tb_idcard {
private Integer id;
private String sfzh;
private String bfjg;
}
编写Mapper接口
:
import cn.mybatis.vo.Tb_person;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface UserMapper {
List<Tb_person> findAll();
}
编写与UserMapper.java
类对应的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">
<!-- parameterType 输入映射
resultType 和 resultMap 完成输出映射-->
<!-- 与接口相关联-->
<mapper namespace="cn.mybatis.mapper.UserMapper">
<resultMap id="findP_C" type="cn.mybatis.vo.Tb_person">
<!-- id:你返回的主键-->
<!-- column: 指定表中对应的字段-->
<!-- property: 指定映射到的实体类对象属性-->
<id column="id" property="id"></id>
<result column="name" property="name"></result>
<result column="age" property="age"></result>
<result column="sex" property="sex"></result>
<!--注意 :javaType 指定映射到实体对象属性的类型 -->
<association property="tb_idcard" javaType="cn.mybatis.vo.Tb_idcard">
<id column="card_id" property="id"></id>
<result column="sfzh" property="sfzh"></result>
<result column="bfjg" property="bfjg"></result>
</association>
</resultMap>
<select id="findAll" resultMap="findP_C">
SELECT
tp.`name`,
tp.age,
tp.sex,
ti.id,
ti.sfzh,
ti.bfjg
FROM
tb_person tp,
tb_idcard ti
WHERE
tp.card_id = ti.id
</select>
</mapper>
针对UserMapper.java
类,创建测试类进行测试运行:
import java.util.List;
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class UserMapperTest {
@Autowired
private UserMapper userMapper;
@Test
public void getUserInfo(){
List<Tb_person> all = userMapper.findAll();
log.info(all.toString());
log.info("==============");
all.forEach(e->{
String name = e.getName();
Tb_idcard tb_idcard = e.getTb_idcard();
String bfjg = tb_idcard.getBfjg();
log.info("{},{}",name,bfjg);
});
}
}
执行后,控制台打印日志信息如下所示:
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2f66f0d0] was not registered for synchronization because synchronization is not active
2022-02-20 17:38:40.445 INFO 46536 --- [ main] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} inited
JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@435e60ff] will not be managed by Spring
==> Preparing: SELECT tp.`name`, tp.age, tp.sex, ti.id, ti.sfzh, ti.bfjg FROM tb_person tp, tb_idcard ti WHERE tp.card_id = ti.id
==> Parameters:
<== Columns: name, age, sex, id, sfzh, bfjg
<== Row: zhangsan, 29, 女, 1, 1234567894561333, 东昌府公安局
<== Row: lisi, 29, 男, 2, 5548465132151845, 东昌府公安局
<== Total: 2
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2f66f0d0]
2022-02-20 17:38:40.581 INFO 46536 --- [ main] cn.mybatis.mapper.UserMapperTest : [Tb_person:{id=1, name='zhangsan', age=29, sex='女', tb_idcard=Tb_idcard(id=null, sfzh=1234567894561333, bfjg=东昌府公安局)}, Tb_person:{id=2, name='lisi', age=29, sex='男', tb_idcard=Tb_idcard(id=null, sfzh=5548465132151845, bfjg=东昌府公安局)}]
2022-02-20 17:38:40.581 INFO 46536 --- [ main] cn.mybatis.mapper.UserMapperTest : ==============
2022-02-20 17:38:40.581 INFO 46536 --- [ main] cn.mybatis.mapper.UserMapperTest : zhangsan,东昌府公安局
2022-02-20 17:38:40.582 INFO 46536 --- [ main] cn.mybatis.mapper.UserMapperTest : lisi,东昌府公安局
细心的人可以发现,关于数据的查询结果,其中Tb_person.java
类中,存在private Tb_idcard tb_idcard
属性。
此处是
一对一关系
,即:每个人只会有一串身份证号。
所以使用到<association>
标签配置关联。
但是根据控制台中数据的打印结果,难免会发现与之对应的数据存在null
的问题。
null问题解决
SQL执行是查询到了数据信息,出现上述null
的问题,根源在于映射关联的问题
。
<id>
标签,表明每张表的主键
。
column
参数,通常为数据库的对应的列名
,但可以是别名!
property
参数,表示映射的实体类的类型。
<result>
标签,用于配置非主键
的其他列对应信息。
参考:mybatis中collection标签中各属性的说明
根据上面的分析,可以将<resultMap>
进行修改。修改后如下所示:
<resultMap id="findP_C" type="cn.mybatis.vo.Tb_person">
<!-- id:你返回的主键-->
<!-- column: 指定表中对应的字段-->
<!-- property: 指定映射到的实体类对象属性-->
<id column="id" property="id"></id>
<result column="name" property="name"></result>
<result column="age" property="age"></result>
<result column="sex" property="sex"></result>
<!--注意 :javaType 指定映射到实体对象属性的类型 -->
<association property="tb_idcard" javaType="cn.mybatis.vo.Tb_idcard">
<!--column 标签可以配置数据库表的列名,也可以配置别名-->
<id column="card_id" property="id"></id>
<result column="sfzh" property="sfzh"></result>
<result column="bfjg" property="bfjg"></result>
</association>
</resultMap>
<select id="findAll" resultMap="findP_C">
SELECT
tp.id id,
tp.`name`,
tp.age,
tp.sex,
ti.id as card_id, /*注意这里的别名,与association标签中的id标签中column对应*/
ti.sfzh,
ti.bfjg
FROM
tb_person tp,
tb_idcard ti
WHERE
tp.card_id = ti.id
</select>
column
可以是数据库表的列名,也可以是sql 查询后的 字段别名
!
再次执行上面的测试代码,运行后控制台数据如下所示:
能达到映射关系
的对应!
一对多测试(collection)
每个人都可能会有多个订单信息。
首先还是先创建数据库的表,同时向其中导入相关的测试数据:
CREATE TABLE tb_user(
id INT(32) PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(32),
address VARCHAR(256)
);
INSERT INTO tb_user VALUES( 1, '张三', '山东聊城' );
INSERT INTO tb_user VALUES( 2, '李四', '山东济南' );
INSERT INTO tb_user VALUES( 3, '王五', '山东青岛' );
CREATE TABLE tb_orders (
id INT ( 32 ) PRIMARY KEY AUTO_INCREMENT,
number VARCHAR ( 32 ) NOT NULL,
user_id INT ( 32 ) NOT NULL,
FOREIGN KEY ( user_id ) REFERENCES tb_user ( id ));
INSERT INTO tb_orders VALUES(1,'20190402001',1);
INSERT INTO tb_orders VALUES(2,'20190402002',2);
INSERT INTO tb_orders VALUES(3,'20190402003',1);
编写pojo
实体类,与对应的数据库表进行对应。
import lombok.Data;
import java.util.List;
@Data
public class Tb_user {
private Integer userId;
private String username;
private String address;
private List<Tb_orders> tb_ordersList;
@Override
public String toString() {
return "Tb_user:{" +
"userId=" + userId +
", username='" + username + '\'' +
", address='" + address + '\'' +
", tb_ordersList:" + tb_ordersList +
'}';
}
}
import lombok.Data;
@Data
public class Tb_orders {
private Integer orderId;
private Integer userId;
private String number;
@Override
public String toString() {
return "Tb_orders{" +
"orderId=" + orderId +
", userId=" + userId +
", number='" + number + '\'' +
'}';
}
}
编写对应的OrdersMapper.java
接口。
import cn.mybatis.vo.two.Tb_user;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface OrdersMapper {
List<Tb_user> findAll();
}
针对OrdersMapper.java
接口,编写对应的OrdersMapper.xml
配置文件:
首先需要保证
pojo
类,与数据库中各个表的列名称进行对应。
其次,编写指定的resultMap
,封装映射结果集。
<?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">
<!-- parameterType 输入映射
resultType 和 resultMap 完成输出映射-->
<!-- 与接口相关联-->
<mapper namespace="cn.mybatis.mapper.two.OrdersMapper">
<resultMap id="findU_O" type="cn.mybatis.vo.two.Tb_user">
<!--column 标签可以配置数据库表的列名,也可以配置别名-->
<id column="id" property="userId"></id>
<result column="username" property="username"></result>
<result column="address" property="address"></result>
<collection property="tb_ordersList" ofType="cn.mybatis.vo.two.Tb_orders">
<!--与具体的sql相关联,根据编写的sql返回结果集的key,进行解析;
如果column 设定的是数据库的列名,但是查询结果使用了as进行了指向,则不能获取到对应的数据;
即:设定了as别名,此处column则需要设定为别名称-->
<id column="orderId" property="orderId"></id>
<result column="userId" property="userId"></result>
<result column="number" property="number"></result>
</collection>
</resultMap>
<!--<resultMap id="findU_O" type="cn.mybatis.vo.two.Tb_user">
<id column="id" property="userId"></id>
<result column="username" property="username"></result>
<result column="address" property="address"></result>
<collection property="tb_ordersList" ofType="cn.mybatis.vo.two.Tb_orders">
<id column="id" property="orderId"></id>
<result column="number" property="number"></result>
<result column="user_id" property="userId"></result>
</collection>
</resultMap>-->
<select id="findAll" resultMap="findU_O">
SELECT
tu.id,
tu.username,
tu.address,
tbo.user_id userId,
tbo.id orderId,
tbo.number
FROM
tb_user tu,
tb_orders tbo
WHERE
tu.id = tbo.user_id
</select>
</mapper>
注意点:
column 标签
可以配置数据库表的列名
,也可以配置别名
。
所以,在上述的<resultMap>
中,配置的<collection>
标签中的各个字段类型的column
字段,是根据SQL查询返回名称
进行配置!
编写OrdersMapper.java
的测试类,如下所示:
package cn.mybatis.mapper.two;
import cn.mybatis.vo.Tb_idcard;
import cn.mybatis.vo.Tb_person;
import cn.mybatis.vo.two.Tb_user;
import junit.framework.TestCase;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class OrdersMapperTest {
@Autowired
private OrdersMapper ordersMapper;
@Test
public void getOrderInfo(){
List<Tb_user> all = ordersMapper.findAll();
log.info(all.toString());
log.info("=====================");
all.forEach(e->{
Tb_user e1 = e;
log.info(e1.toString());
});
}
}
运行测试代码,查看控制台的测试结果:
2022-02-20 18:03:52.117 INFO 46932 --- [ main] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} inited
JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@5ba26eb0] will not be managed by Spring
==> Preparing: SELECT tu.id, tu.username, tu.address, tbo.user_id userId, tbo.id orderId, tbo.number FROM tb_user tu, tb_orders tbo WHERE tu.id = tbo.user_id
==> Parameters:
<== Columns: id, username, address, userId, orderId, number
<== Row: 1, 张三, 山东聊城, 1, 1, 20190402001
<== Row: 1, 张三, 山东聊城, 1, 3, 20190402003
<== Row: 2, 李四, 山东济南, 2, 2, 20190402002
<== Total: 3
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6fc29daa]
2022-02-20 18:03:52.262 INFO 46932 --- [ main] cn.mybatis.mapper.two.OrdersMapperTest : [Tb_user:{userId=1, username='张三', address='山东聊城', tb_ordersList:[Tb_orders{orderId=1, userId=1, number='20190402001'}, Tb_orders{orderId=3, userId=1, number='20190402003'}]}, Tb_user:{userId=2, username='李四', address='山东济南', tb_ordersList:[Tb_orders{orderId=2, userId=2, number='20190402002'}]}]
2022-02-20 18:03:52.262 INFO 46932 --- [ main] cn.mybatis.mapper.two.OrdersMapperTest : =====================
2022-02-20 18:03:52.263 INFO 46932 --- [ main] cn.mybatis.mapper.two.OrdersMapperTest : Tb_user:{userId=1, username='张三', address='山东聊城', tb_ordersList:[Tb_orders{orderId=1, userId=1, number='20190402001'}, Tb_orders{orderId=3, userId=1, number='20190402003'}]}
2022-02-20 18:03:52.263 INFO 46932 --- [ main] cn.mybatis.mapper.two.OrdersMapperTest : Tb_user:{userId=2, username='李四', address='山东济南', tb_ordersList:[Tb_orders{orderId=2, userId=2, number='20190402002'}]}
数据能够一一对应!
总结
针对本次测试和观察问题现象,对其做出以下几点总结
- 1、column 标签可以配置数据库表的列名,也可以配置别名。
根据指定的SQL返回结果集映射类型定义。
- 2、如果需要返回类似
JSON
的数据格式,需要重写 toString()
,让其返回样式和JSON一致即可。
资料参考
代码参考
2022.06.09 关于一对多查询效率问题
类似的,查询出的数据取消映射,可以借鉴如下方式进行数据转换。
public class Test2 {
public static void main(String[] args) {
List<TestVo> userList = new ArrayList<>();
TestVo obj1 = new TestVo();
obj1.setMonth("一月");
obj1.setName("xj1");
obj1.setAge(BigDecimal.TEN);
userList.add(obj1);
TestVo obj2 = new TestVo();
obj2.setMonth("一月");
obj2.setName("xj11");
obj2.setAge(BigDecimal.TEN);
userList.add(obj2);
TestVo obj3 = new TestVo();
obj3.setMonth("一月");
obj3.setName("xj111");
obj3.setAge(BigDecimal.TEN);
userList.add(obj3);
TestVo obj4 = new TestVo();
obj4.setMonth("二月");
obj4.setName("xj2");
obj4.setAge(BigDecimal.TEN);
userList.add(obj4);
//System.out.println( userList.toString());
Map<String, List<TestVo>> collect = userList.stream().collect(Collectors.groupingBy(TestVo::getMonth));
for (Map.Entry<String, List<TestVo>> stringListEntry : collect.entrySet()) {
System.out.println(stringListEntry.getKey() + " === "+stringListEntry.getValue());
List<TestVo> value = stringListEntry.getValue();
value.forEach(e->{
System.out.println(" ----- >"+e);
});
}
//System.out.println(collect);
}
}
主要方式为:
Map<String, List<TestVo>> collect = userList.stream()
.collect(Collectors.groupingBy(TestVo::getMonth));