如果我的博客对你有帮助,欢迎进行评论✏️✏️、点赞👍👍、收藏⭐️⭐️,满足一下我的虚荣心💖🙏🙏🙏 。
mybatis感觉很久没使用了,前两天在看缓存的时候使用了一下,顺便把mybatis一对一,一对多等关联查询常用的写法记录一下,万一哪天再忘记了怎么用了,查看一下,毕竟官方的文档虽然全,但是例子不全,一不小心就掉坑里。
关联association元素用于处理一对一的类型关系,MyBatis 有两种不同的方式加载关联:
一、嵌套 Select 查询:通过执行另外一个 SQL 映射语句来加载期望的复杂类型。
二、嵌套结果映射:使用嵌套的结果映射来处理连接结果的重复子集。
目录
关联的嵌套 Select 查询
使用
使用前先介绍下它的属性:
column:数据库中的列明,或者列的别名,这和传递给 resultSet.getString(columnName) 方法的参数一样。 注意:在使用复合主键的时候,你可以使用 column=”{prop1=col1,prop2=col2}” 这样的语法来指定多个传递给嵌套 Select 查询语句的列名。这会使得 prop1 和 prop2 作为参数对象,被设置为对应嵌套 Select 语句的参数,简单说column为指定select查询需要的条件,即需要将本对象的哪一列的值传给select方法。
select:用于加载复杂类型属性的映射语句的 ID,它会从 column 属性指定的列中检索数据,作为参数传递给目标 select 语句。 具体请参考下面的例子。注意:在使用复合主键的时候,你可以使用 column=”{prop1=col1,prop2=col2}” 这样的语法来指定多个传递给嵌套 Select 查询语句的列名。这会使得 prop1 和 prop2 作为参数对象,被设置为对应嵌套 Select 语句的参数。
首先我先准备一下测试环境,还是基于前两篇使用缓存基础之上的,只是新添加了几张表,建表语句就不贴了,另外会在xml文件中写sql,所以需要在配置文件中添加配置指定xml的路径,还有看着之前的配置文件就不爽,改用yml文件,看着有层次清晰:
贴一下完整配置,方便小伙伴复制
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/mydatabase?serverTimezone=UTC
username: root
password: root
redis:
host: 127.0.0.1
mybatis:
mapper-locations: classpath:mybatis/*.xml
type-aliases-package: com.example.mybatis
configuration:
map-underscore-to-camel-case: true
logging:
level:
com.example.springbootmybatis.mapper: debug
debug: true
先试一下一对一查询,准备测试类:
@Data
public class User implements Serializable {
private Integer id;
private String name;
private Integer age;
private String sex;
private String province;
private String city;
private Date createdTime;
private String createdBy;
private Date updatedTime;
private String updatedBy;
private Wife wife;
}
Wife类:
@Data
public class Wife {
private int wifeId;
private int userId;
private String wifeName;
}
UserMapper.jave:
增加测试方法:
User selectUserWifeBySelect(Integer userId);
UserMapper.xml:
<resultMap id="userResultMap" type="User">
<association property="wife" column="id" javaType="Wife" select="selectWifeByUserId" />
</resultMap>
<select id="selectWifeByUserId" parameterType="java.lang.Integer" resultType="Wife">
select * from mybatis_wife where user_id = #{userId}
</select>
<select id="selectUserWifeBySelect" resultMap="userResultMap">
select * from mybatis_user where user_id = #{userId}
</select>
测试类:
@SpringBootTest
@RunWith(SpringRunner.class)
public class SpringbootmybatisApplicationTests {
@Autowired
private UserService userService;
@Test
public void associationTest1(){
User user = userMapper.selectUserWifeBySelect(1);
System.out.println("--- user ---" + user);
}
}
打印结果:
就是这么简单,我们有两个select查询语句:一个用来查询User,另外一个用查询Wife,而且User的结果映射描述了应该使用selectWifeByUserId语句加载它的wife属性。其它所有的属性将会被自动加载,只要它们的列名和属性名相匹配。
association标签中的column属性填的是当前查询对象User中的属性,而不是Wife中的用户属性user_id,比如这里改为传user_id,如下:
<resultMap id="userResultMapBySelect" type="User">
<association property="wife" column="user_id" javaType="Wife"
select="selectWifeByUserId"/>
其它都不变的情况下,查询结果如下:
如果觉得 selectWifeByUserId 写在UserMapper.xml中不是很合适,也可以写到WifeMapper.xml中,在association中调用的时候通过WifeMapper类全限定名调用selectWifeByUserId方法即可,如下:
<resultMap id="userResultMapBySelect" type="User">
<association property="wife" column="user_id" javaType="Wife"
select="com.yjh.learn.mybatislearn.mapper.WifeMapper.selectWifeByUserId"/>
常见错误
Error querying database. Cause: java.lang.IndexOutOfBoundsException: Index: 10, Size: 10
上面查询结果User类中我们只使用了@Data,亲测如果同时加上@Builder查询时会报错,这时候要加上有参和无参构造@NoArgsConstructor、@AllArgsConstructor。
N+1问题
这种方式虽然很简单,但在大型数据集或大型数据表上表现不佳。这个问题被称为“N+1 查询问题”。 概括地讲,N+1 查询问题是这样子的:
- 你执行了一个单独的 SQL 语句来获取结果的一个列表(就是“+1”)。
- 对列表返回的每条记录,你执行一个 select 查询语句来为每条记录加载详细信息(就是“N”)。
这个问题会导致成百上千的 SQL 语句被执行。有时候,我们不希望产生这样的后果。
好消息是,MyBatis 能够对这样的查询进行延迟加载,因此可以将大量语句同时运行的开销分散开来。 然而,如果你加载记录列表之后立刻就遍历列表以获取嵌套的数据,就会触发所有的延迟加载查询,性能可能会变得很糟糕。
所以还有另外一种方法。那就是关联的嵌套结果映射
关联的嵌套结果映射
使用关联的嵌套结果映射除了UserMapper.xml中不一样,其它的不用变,
UserMapper.xml:
<resultMap id="userResultMap" type="User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="sex" column="sex"/>
<result property="age" column="age"/>
<result property="province" column="province"/>
<result property="city" column="city"/>
<result property="createdTime" column="created_time"/>
<result property="createdBy" column="created_by"/>
<result property="updatedTime" column="updated_time"/>
<result property="updatedBy" column="updated_by"/>
<association property="wife" column="id" javaType="Wife" resultMap="wifeResultMap"/>
</resultMap>
<resultMap id="wifeResultMap" type="Wife">
<id property="wifeId" column="wife_id"/>
<result property="userId" column="user_id"/>
<result property="wifeName" column="wife_name"/>
</resultMap>
<select id="selectUserWife" resultMap="userResultMap">
select u.*, w.*
from mybatis_user u
left join mybatis_wife w on u.id = w.user_id
where u.id = #{userId}
</select>
如上:你可以看到,User和Wife的关联元素委托名为 “wifeResultMap” 的结果映射来加载Wife对象的实例。
非常重要: id 元素在嵌套结果映射中扮演着非常重要的角色。你应该总是指定一个或多个可以唯一标识结果的属性。 虽然,即使不指定这个属性,MyBatis 仍然可以工作,但是会产生严重的性能问题。 只需要指定可以唯一标识结果的最少属性。显然,你可以选择主键(复合主键也可以)。
现在,上面的示例使用了外部的结果映射元素来映射关联。这使得Wife的结果映射可以被重用。 然而,如果你不打算重用它,或者你更喜欢将你所有的结果映射放在一个具有描述性的结果映射元素中。 你可以直接将结果映射作为子元素嵌套在内。这里给出使用这种方式的等效例子:
<resultMap id="userResultMapSimple" type="User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="sex" column="sex"/>
<result property="age" column="age"/>
<result property="province" column="province"/>
<result property="city" column="city"/>
<result property="createdTime" column="created_time"/>
<result property="createdBy" column="created_by"/>
<result property="updatedTime" column="updated_time"/>
<result property="updatedBy" column="updated_by"/>
<association property="wife" javaType="Wife">
<id property="wifeId" column="wife_id"/>
<result property="userId" column="user_id"/>
<result property="wifeName" column="wife_name"/>
</association>
</resultMap>
<select id="selectUserWifeSimple" resultMap="userResultMapSimple">
select u.*, w.*
from mybatis_user u
left join mybatis_wife w on u.id = w.user_id
where u.id = #{userId}
</select>
关于mybatis的更多使用,看mybatis中文文档:mybatis – MyBatis 3 | XML 映射器
如果我的博客对你有帮助,欢迎进行评论✏️✏️、点赞👍👍、收藏⭐️⭐️,满足一下我的虚荣心💖🙏🙏🙏 。