1. MyBatis 高级映射与延迟加载(分步查询)的详细内容
@[toc]
2. 准备工作
准备数据库表:一个班级对应多个学生。班级表:t_clazz;学生表:t_student
在pom.xml
文件当中配置相关的依赖的 jar 包如下:
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.rainbowsea</groupId>
<artifactId>mybatis-005-crud-blog</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<!-- mybatis 的依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.10</version>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<!-- 引入 logback的依赖,这个日志框架实现了slf4j 规范-->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.11</version>
</dependency>
</dependencies>
</project>
配置 logback 的配置文件,用于打印显示,我们的日志信息,方便我们查看我们的运行过程,效果。
xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!-- 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
<!--mybatis log configure-->
<logger name="com.apache.ibatis" level="TRACE"/>
<logger name="java.sql.Connection" level="DEBUG"/>
<logger name="java.sql.Statement" level="DEBUG"/>
<logger name="java.sql.PreparedStatement" level="DEBUG"/>
<!-- 日志输出级别,logback日志级别包括五个:TRACE < DEBUG < INFO < WARN < ERROR -->
<root level="DEBUG">
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE"/>
</root>
</configuration>
配置 MyBatis 的核心配置文件,
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>
<!-- 使用 <package> 还可以将这个包下的所有的类的全部自动起别名,别名就是简名,不区分大小写 -->
<package name="com.rainbowsea.mybatis.pojo"/>
</typeAliases>
<environments default="mybatis">
<environment id="mybatis">
<!-- MANAGED 没有用第三框架管理的话,都是会被提交的,没有事务上的管理了 -->
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="MySQL123"/>
</dataSource>
</environment>
</environments>
<mappers>
<!-- 这里也是可以使用 package 包名扫描,但是同样的:对应接口路径要一致,接口名一致-->
<package name="com.rainbowsea.mybatis.mapper"></package>
</mappers>
</configuration>
3. 多对一 高级映射
多对一的高级映射 多种方式,常见的包括三种:
- 第一种方式:一条SQL语句,级联属性映射。
- 第二种方式:一条SQL语句,association。
- 第三种方式:两条SQL语句,分步查询。(这种方式常用:优点一是可复用。优点二是支持懒加载。)
多的的一方是:Student 一的一方是: Clazz
怎么分主表和副表 原则:谁在前看,谁就是主表
多对一:多在前,那么多就是主表, 一对多:一在前,那么一就是主表
对照 t_clazz,t_stu 创建的ORM 映射的 Clazz,Student 类
注意:在MyBatis 当中对应的ORM ,一般在框架里对应的 Bean实体类,一定要实现该 set 和 get 方法以及无参数构造方法,无法框架无法使用反射机制,进行操作 。
建议用包装类,这样可以防止 Null的问题,因为(简单类型 int num = null ,是不可以赋值为 null)的编译无法通过
pojo类Student中添加一个属性:Clazz clazz; 表示学生关联的班级对象。
java
package com.rainbowsea.mybatis.pojo;
/**
* 学生信息
*/
public class Student { // Student 是多的一方
private Integer sid;
private String sname;
private Clazz clazz; // Clazz 是一的一方
public Student() {
}
public Student(Integer sid, String sname, Clazz clazz) {
this.sid = sid;
this.sname = sname;
this.clazz = clazz;
}
@Override
public String toString() {
return "Student{" +
"sid=" + sid +
", sname='" + sname + '\'' +
", clazz=" + clazz +
'}';
}
public Integer getSid() {
return sid;
}
public void setSid(Integer sid) {
this.sid = sid;
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
public Clazz getClazz() {
return clazz;
}
public void setClazz(Clazz clazz) {
this.clazz = clazz;
}
}
java
package com.rainbowsea.mybatis.pojo;
/**
* 多对一
*/
public class Clazz {
private Integer cid;
private String cname;
public Clazz() {
}
public Clazz(Integer cid, String cname) {
this.cid = cid;
this.cname = cname;
}
@Override
public String toString() {
return "Clazz{" +
"cid=" + cid +
", cname='" + cname + '\'' +
'}';
}
public Integer getCid() {
return cid;
}
public void setCid(Integer cid) {
this.cid = cid;
}
public String getCname() {
return cname;
}
public void setCname(String cname) {
this.cname = cname;
}
}
3.1 第一种方式:级联属性映射
java
package com.rainbowsea.mybatis.mapper;
import com.rainbowsea.mybatis.pojo.Student;
import java.util.List;
public interface StudentMapper {
/**
* 根据id获取学生信息,同时获取学生关联的班级信息
* @param id 学生的id
* @return 学生对象,但是学生对象当中含有班级对象
*/
Student selectById(Integer id);
}
resultMap 标签当中定义POJO类属性 与 数据表字段名映射关系:
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">
<!--namespace 一定要是:对应的接口的全限定类名-->
<mapper namespace="com.rainbowsea.mybatis.mapper.StudentMapper">
<resultMap id="studentResultMap" type="Student">
<id property="sid" column="sid"></id>
<result property="sname" column="sname"></result>
<result property="clazz.cid" column="cid"></result>
<result property="clazz.cname" column="cname"></result>
</resultMap>
<!-- id 要是 namespace 对应接口上的方法名: -->
<select id="selectById" resultMap="studentResultMap">
select s.sid,
s.sname,
c.cid,
c.cname
from t_stu s
left join t_clazz c on s.cid = c.cid
where s.sid = #{sid}
</select>
</mapper>
运行测试:
java
package com.rainbowsea.mybatis.test;
import com.rainbowsea.mybatis.mapper.StudentMapper;
import com.rainbowsea.mybatis.pojo.Student;
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;
public class StudentMapperTest {
@Test
public void testSelectById() throws IOException {
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"), "mybatis");
SqlSession sqlSession = sqlSessionFactory.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student student = mapper.selectById(5);
System.out.println(student);
sqlSession.close();
}
}
3.2 第二种方式:association
与第一种方式类似,在第一种方式的基础上只需要修改 resultMap 中的配置:association 即可。其他位置都不需要修改。
association翻译为:关联。
学生对象关联一个对象(这里关联班级对象)。
java
import com.rainbowsea.mybatis.pojo.Student;
import java.util.List;
public interface StudentMapper {
/**
* 一条SQL语句,association
* @param id
* @return
*/
Student selectByIdAssociation(Integer id);
}
xmlassociation 翻译为关联,一个student 对象关联一个Clazz对象 property,提供要映射的POJO类的属性名 javaType: 用来指定要映射的Java类(全限定类名,启用了别名可以用别名)
java
<?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.rainbowsea.mybatis.mapper.StudentMapper">
<resultMap id="studentResultMapAssociation" type="Student">
<id property="sid" column="sid"></id>
<result property="sname" column="sname"></result>
<!--
association 翻译为关联,一个student 对象关联一个Clazz对象
property,提供要映射的POJO类的属性名
javaType: 用来指定要映射的Java类(全限定类名,启用了别名可以用别名)
-->
<!-- <association property="clazz" javaType="com.rainbowsea.mybatis.pojo.Clazz"></association>
我们开启的别名机制-->
<association property="clazz" javaType="Clazz">
<id property="cid" column="cid"></id>
<result property="cname" column="cname"></result>
</association>
</resultMap>
<select id="selectByIdAssociation" resultMap="studentResultMapAssociation">
select s.sid,
s.sname,
c.cid,
c.cname
from t_stu s
left join t_clazz c on s.cid = c.cid
where s.sid = #{sid}
</select>
</mapper>
运行测试:
java
import com.rainbowsea.mybatis.mapper.StudentMapper;
import com.rainbowsea.mybatis.pojo.Student;
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;
public class StudentMapperTest {
@Test
public void testStudentResultMapAssociation() throws IOException {
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"), "mybatis");
SqlSession sqlSession = sqlSessionFactory.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student student = mapper.selectByIdAssociation(5);
System.out.println(student);
sqlSession.close();
}
}
3.3 第三种方式:分步查询
分布查询的优点:
- 第一:复用性增强,可以重复证明。(大步拆成N多个小碎布,每一个小碎步更加可以重复利用)
- 第二:采用这种分步查询,可以充分利用他们的延迟加载/懒加载机制
什么是延迟加载(懒加载),有什么用?
延迟加载的核心原理是:用的时候再执行查询语句,不用的时候不查询 作用:提高性能,尽可能的不查,或者尽可能的少查,来提高效率
在 mybatis 当中怎么开启延迟加载呢?
association标签之哦给你添加 fetchType = "lazy"
注意,默认情况下是没有开启延迟加载的,需要设置,fetchType="lazy"
这种在association标签中配置fetchType="lazy" 是局部的设置,只对当前association关联的sql语句起作用,fetchType="eager" 表示关闭局部的延迟加载
实际的开发中的模式: 把全局的延迟加载打开。 如果某一步不需要使用延迟加载,请设置fetchType="eager" 即可
java
import com.rainbowsea.mybatis.pojo.Student;
import java.util.List;
public interface StudentMapper {
/**
*分部查询第一步,先根据学生的sid 查询学生的信息
* @param id
* @return
*/
Student selectByIdStep1(Integer id);
}
其他位置不需要修改,只需要修改以及添加以下三处:
第一处:association中select位置填写sqlId。sqlId= namespace+id。其中column属性作为这条子sql语句的条件。
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">
<!--namespace 一定要是:对应的接口的全限定类名-->
<mapper namespace="com.rainbowsea.mybatis.mapper.StudentMapper">
<!--
分布查询的优点:
第一:复用性增强,可以重复证明。(大步拆成N多个小碎布,每一个小碎步更加可以重复利用)
第二:采用这种分步查询,可以充分利用他们的延迟加载/懒加载机制
什么是延迟加载(懒加载),有什么用?
延迟加载的核心原理是:用的时候再执行查询语句,不用的时候不查询
作用:提高性能,尽可能的不查,或者尽可能的少查,来提高效率
在mybatis当中怎么开启延迟加载呢?
association标签之哦给你添加fetchType="lazy"
注意,默认情况下是没有开启延迟加载的,需要设置,fetchType="lazy"
这种在association标签中配置fetchType="lazy" 是局部的设置,只对当前association关联的sql语句起作用
fetchType="eager" 表示关闭局部的延迟加载
实际的开发中的模式:
把全局的延迟加载打开。
如果某一步不需要使用延迟加载,请设置fetchType="eager" 即可
-->
<!-- 两条SQL语句,完成多对一的分布查询-->
<!-- 这里是第一步,根据学生的id查询学生的所有信息,这些信息当中含有班级id(cid)-->
<!-- type 是Java中的类集合/数组除了Map,存储的元素类型《用全限定类名,启用了别名机制,用别名
column 数据库查询的字段名-->
<resultMap id="studentResulMapByStep" type="Student">
<id property="sid" column="sid"></id>
<result property="sname" column="sname"></result>
<!-- <association property="clazz(第一步查询的字段名(与第二步关联的字段))" select="这里需要指定另外第二步SQL语句的ID(com.rainbowsea.mybatis.mapper
.ClazzMapper
.selectByIdStep2)"
column="cid 第二步SQL语句传的字段信息,查询"></association>-->
<!-- <association property="clazz" select="com.rainbowsea.mybatis.mapper.ClazzMapper.selectByIdStep2"-->
<!-- column="cid" fetchType="lazy"></association> <-->
<association property="clazz" select="com.rainbowsea.mybatis.mapper.ClazzMapper.selectByIdStep2"
column="cid" fetchType="lazy"></association>
<!--一条SQL语句一条 association-->
</resultMap>
<select id="selectByIdStep1" resultMap="studentResulMapByStep">
select sid, sname, cid
from t_stu
where sid = #{sid}
</select>
</mapper>
第二处:在ClazzMapper接口中添加方法
java
package com.rainbowsea.mybatis.mapper;
import com.rainbowsea.mybatis.pojo.Clazz;
public interface ClazzMapper {
/**
* 多对一:分布查询第二步:根据cid获取班级信息
*
* @param cid
* @return
*/
Clazz selectByIdStep2(Integer cid);
}
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">
<!--namespace 一定要是:对应的接口的全限定类名-->
<mapper namespace="com.rainbowsea.mybatis.mapper.ClazzMapper">
<!-- id 要是 namespace 对应接口上的方法名: -->
<!-- id 要是 namespace 对应接口上的方法名: -->
<select id="selectByIdStep2" resultType="Clazz">
select cid, cname
from t_clazz
where cid = #{cid}
</select>
</mapper>
运行测试:
java
import com.rainbowsea.mybatis.mapper.StudentMapper;
import com.rainbowsea.mybatis.pojo.Student;
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;
public class StudentMapperTest {
@Test
public void testselectByIdStep1() throws IOException {
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"), "mybatis");
SqlSession sqlSession = sqlSessionFactory.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student student = mapper.selectByIdStep1(5);
System.out.println(student);
sqlSession.close();
}
}
4. 多对一延迟加载
延迟加载的核心原理是:用的时候再执行查询语句,不用的时候不查询 作用:提高性能,尽可能的不查,或者尽可能的少查,来提高效率
要想支持延迟加载,非常简单,只需要在association标签中添加 fetchType="lazy" 即可。
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">
<!--namespace 一定要是:对应的接口的全限定类名-->
<mapper namespace="com.rainbowsea.mybatis.mapper.StudentMapper">
<!--
分布查询的优点:
第一:复用性增强,可以重复证明。(大步拆成N多个小碎布,每一个小碎步更加可以重复利用)
第二:采用这种分步查询,可以充分利用他们的延迟加载/懒加载机制
什么是延迟加载(懒加载),有什么用?
延迟加载的核心原理是:用的时候再执行查询语句,不用的时候不查询
作用:提高性能,尽可能的不查,或者尽可能的少查,来提高效率
在mybatis当中怎么开启延迟加载呢?
association标签之哦给你添加fetchType="lazy"
注意,默认情况下是没有开启延迟加载的,需要设置,fetchType="lazy"
这种在association标签中配置fetchType="lazy" 是局部的设置,只对当前association关联的sql语句起作用
fetchType="eager" 表示关闭局部的延迟加载
实际的开发中的模式:
把全局的延迟加载打开。
如果某一步不需要使用延迟加载,请设置fetchType="eager" 即可
-->
<!-- 两条SQL语句,完成多对一的分布查询-->
<!-- 这里是第一步,根据学生的id查询学生的所有信息,这些信息当中含有班级id(cid)-->
<!-- type 是Java中的类集合/数组除了Map,存储的元素类型《用全限定类名,启用了别名机制,用别名
column 数据库查询的字段名-->
<resultMap id="studentResulMapByStep" type="Student">
<id property="sid" column="sid"></id>
<result property="sname" column="sname"></result>
<!-- <association property="clazz(第一步查询的字段名(与第二步关联的字段))" select="这里需要指定另外第二步SQL语句的ID(com.rainbowsea.mybatis.mapper
.ClazzMapper
.selectByIdStep2)"
column="cid 第二步SQL语句传的字段信息,查询"></association>-->
<!-- <association property="clazz" select="com.rainbowsea.mybatis.mapper.ClazzMapper.selectByIdStep2"-->
<!-- column="cid" fetchType="lazy"></association> <-->
<association property="clazz" select="com.rainbowsea.mybatis.mapper.ClazzMapper.selectByIdStep2"
column="cid" fetchType="lazy"></association>
<!--一条SQL语句一条 association-->
</resultMap>
<select id="selectByIdStep1" resultMap="studentResulMapByStep">
select sid, sname, cid
from t_stu
where sid = #{sid}
</select>
</mapper>
我们现在只查询学生名字,修改测试程序:
java
import com.rainbowsea.mybatis.mapper.StudentMapper;
import com.rainbowsea.mybatis.pojo.Student;
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;
public class StudentMapperTest {
@Test
public void testSelectByIdStep1() throws IOException {
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"), "mybatis");
SqlSession sqlSession = sqlSessionFactory.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student student = mapper.selectByIdStep1(5);
System.out.println("学生的姓名: " + student.getSname());
sqlSession.close();
}
}
如果后续需要使用到学生所在班级的名称,这个时候才会执行关联的sql语句,修改测试程序:
java
import com.rainbowsea.mybatis.mapper.StudentMapper;
import com.rainbowsea.mybatis.pojo.Student;
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;
public class StudentMapperTest {
@Test
public void testSelectByIdStep1() throws IOException {
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"), "mybatis");
SqlSession sqlSession = sqlSessionFactory.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student student = mapper.selectByIdStep1(5);
System.out.println("学生的姓名: " + student.getSname());
// 到这里之后,想获取班级名字了
String cname = student.getClazz().getCname();
System.out.println("学生的班级名称:" + cname);
sqlSession.close();
}
}
通过以上的执行结果可以看到,只有当使用到班级名称之后,才会执行关联的sql语句,这就是延迟加载。
当然上述方式,仅仅只是局部设置的延迟加载(这对当前 association 关联的 SQL 语句起作用,对其他的位置时不起作用的)。association标签添加fetchType="lazy" 注意,默认情况下是没有开启延迟加载的,需要设置,fetchType="lazy" 这种在association标签中 配置fetchType="lazy" 是局部的设置,只对当前association关联的 sql语句起作用 fetchType="eager" 表示关闭局部的延迟加载
在mybatis中如何开启全局的延迟加载呢 ?需要setting配置,参考 MyBatis 中文开发手册 mybatis.net.cn/ 如下:
xml
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
注意:settings 标签的正确位置顺序,可以根据报错信息进行纠正。
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>
<settings>
<!-- 启用驼峰命名映射-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!-- 延迟加载的全局开关,默认值是 false 不开启 (简单的说就是:所有只要但凡带有分布的,都采用延迟加载)-->
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
<!-- 起别名-->
<typeAliases>
<!-- 使用 <package> 还可以将这个包下的所有的类的全部自动起别名,别名就是简名,不区分大小写 -->
<package name="com.rainbowsea.mybatis.pojo"/>
</typeAliases>
<environments default="mybatis">
<environment id="mybatis">
<!-- MANAGED 没有用第三框架管理的话,都是会被提交的,没有事务上的管理了 -->
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="MySQL123"/>
</dataSource>
</environment>
</environments>
<mappers>
<!-- 这里也是可以使用 package 包名扫描,但是同样的:对应接口路径要一致,接口名一致-->
<package name="com.rainbowsea.mybatis.mapper"></package>
</mappers>
</configuration>
把fetchType="lazy"去掉。 测试 全局设置是否有效。
执行以下程序:
通过以上的测试可以看出,我们已经开启了全局延迟加载策略。开启全局延迟加载之后,所有的sql都会支持延迟加载。如果我们想其中的某个SQL 语句你不希望它支持延迟加载怎么办呢?
可以将该SQL 语句的 fetchType 设置为 eager,就不会启用延迟加载机制的。这样的话,针对某个特定的SQL 语句,你就关闭了延迟加载机制。后期我们要不要开启延迟加载机制,主要看实际的业务需求是怎样的。
运行测试:
实际的开发中的模式:把全局的延迟加载打开。如果某一步不需要使用延迟加载,请设置fetchType="eager" 即可
5. 一对多 高级映射
一对多的实现,通常是在一的一方中有List集合属性。
在 Clazz 类中添加List<Student> stus ; 属性。
java
这里的一对多:关系
public class Student{}
public class Clazz { // 一个班级对象
// 一个班级对应多个学生
// 怎么去表示这个班级对应了多个学生对象呢
// 集合或者数组都可以容纳多个元素
List<Student> studentList
一对多,一在前,一是主表,多是副表
主表: t_clazz
副表: t_stu
}
java
package com.rainbowsea.mybatis.pojo;
import java.util.List;
/**
* 多对一
*/
public class Clazz {
private Integer cid;
private String cname;
private List<Student> stus;
public Clazz() {
}
public Clazz(Integer cid, String cname) {
this.cid = cid;
this.cname = cname;
}
public Clazz(Integer cid, String cname, List<Student> stus) {
this.cid = cid;
this.cname = cname;
this.stus = stus;
}
public List<Student> getStus() {
return stus;
}
public void setStus(List<Student> stus) {
this.stus = stus;
}
@Override
public String toString() {
return "Clazz{" +
"cid=" + cid +
", cname='" + cname + '\'' +
", stus=" + stus +
'}';
}
public Integer getCid() {
return cid;
}
public void setCid(Integer cid) {
this.cid = cid;
}
public String getCname() {
return cname;
}
public void setCname(String cname) {
this.cname = cname;
}
}
java
package com.rainbowsea.mybatis.pojo;
/**
* 学生信息
*/
public class Student {
private Integer sid;
private String sname;
public Student() {
}
public Student(Integer sid, String sname) {
this.sid = sid;
this.sname = sname;
}
@Override
public String toString() {
return "Student{" +
"sid=" + sid +
", sname='" + sname + '\'' +
'}';
}
public Integer getSid() {
return sid;
}
public void setSid(Integer sid) {
this.sid = sid;
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
}
5.1 第一种方式:collection
**注意:是 collection 不是 association 。 **
如果返回的映射 POJO 类当中有一个集合属性,则用 collection 标签负责处理。
xml
-对多,这里是 collection ,collection 是集合的意思,定义集合/数组当中元素的映射信息(这里是Student 类型的映射)
ofType 属性用来指定集合当中的元素类型,全限定类名(启用别名用别名)注意是ofType,表示"集合中的类型"。
<collection property="stus(表示POJo类中的属性)" ofType="Student"></collection>-->
java
package com.rainbowsea.mybatis.mapper;
import com.rainbowsea.mybatis.pojo.Clazz;
public interface ClazzMapper {
/**
* 根据班级编号查询班级信息
* @param cid
* @return
*/
Clazz selectByCollection(Integer cid);
}
sql
<?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.rainbowsea.mybatis.mapper.ClazzMapper">
<!-- id 要是 namespace 对应接口上的方法名: -->
<resultMap id="clazzResultMap" type="Clazz">
<id property="cid" column="cid"></id>
<result property="cname" column="cname"></result>
<!-- -对多,这里是 collection ,collection 是集合的意思,定义集合/数组当中元素的映射信息(这里是Student 类型的映射)-->
<!-- ofType 属性用来指定集合当中的元素类型,全限定类名(启用别名用别名)-->
<!-- <collection property="stus(表示POJo类中的属性)" ofType="Student"></collection>-->
<collection property="stus" ofType="Student">
<id property="sid" column="sid"></id>
<result property="sname" column="sname"></result>
</collection>
</resultMap>
<select id="selectByCollection" resultMap="clazzResultMap">
select c.cid, c.cname, s.sid, s.sname
from t_clazz c
left join t_stu s on c.cid = s.cid
where c.cid = #{cid}
</select>
</mapper>
运行测试:
java
package com.rainbowsea.mybatis.test;
import com.rainbowsea.mybatis.mapper.ClazzMapper;
import com.rainbowsea.mybatis.pojo.Clazz;
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;
public class ClazzMapperTest {
@Test
public void testSelectByCollection() throws IOException {
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"), "mybatis");
SqlSession sqlSession = sqlSessionFactory.openSession();
ClazzMapper mapper = sqlSession.getMapper(ClazzMapper.class);
Clazz clazz = mapper.selectByCollection(1000);
System.out.println(clazz);
sqlSession.close();
}
}
5.2 第二种方式:分步查询
java
package com.rainbowsea.mybatis.mapper;
import com.rainbowsea.mybatis.pojo.Clazz;
public interface ClazzMapper {
/**
* 分布查询,第一步,根据班级编号获取班级信息
* @param cid
* @return
*/
Clazz selectByStep1(Integer cid);
}
java
package com.rainbowsea.mybatis.mapper;
import com.rainbowsea.mybatis.pojo.Student;
import java.util.List;
public interface StudentMapper {
/**
* 一对多:根据班级编号查询学生信息
*/
List<Student> selectByCidsStep2(Integer cid);
}
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">
<!--namespace 一定要是:对应的接口的全限定类名-->
<mapper namespace="com.rainbowsea.mybatis.mapper.ClazzMapper">
<!-- 分布查询第一步:根据班级的cid获取班级信息-->
<resultMap id="clazzResultMapStep" type="Clazz">
<id property="cid" column="cid"></id>
<result property="cname" column="cname"></result>
<collection property="stus"
select="com.rainbowsea.mybatis.mapper.StudentMapper.selectByCidsStep2"
column="cid">
</collection>
</resultMap>
<select id="selectByStep1" resultMap="clazzResultMapStep">
select cid, cname
from t_clazz
where cid = #{cid}
</select>
</mapper>
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">
<!--namespace 一定要是:对应的接口的全限定类名-->
<mapper namespace="com.rainbowsea.mybatis.mapper.StudentMapper">
<!-- 一对多 ,分布查询第二步-->
<select id="selectByCidsStep2" resultType="Student">
select sid, sname, cid
from t_stu
where cid = #{cid}
</select>
</mapper>
运行测试:
java
package com.rainbowsea.mybatis.test;
import com.rainbowsea.mybatis.mapper.ClazzMapper;
import com.rainbowsea.mybatis.pojo.Clazz;
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;
public class ClazzMapperTest {
@Test
public void testSelectByStep1() throws IOException {
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"), "mybatis");
SqlSession sqlSession = sqlSessionFactory.openSession();
ClazzMapper mapper = sqlSession.getMapper(ClazzMapper.class);
Clazz clazz = mapper.selectByStep1(1000);
//System.out.println(clazz);
// 只访问班级名字
System.out.println(clazz.getCname());
// 只有用到的时候才会去执行第二步SQL
System.out.println(clazz.getStus());
sqlSession.close();
}
}
6. 一对多延迟加载
一对多延迟加载机制和多对一是一样的。同样是通过两种方式:
- 第一种:fetchType="lazy"
- 第二种:修改全局的配置setting,**lazyLoadingEnabled=true,**如果开启全局延迟加载,想让某个sql不使用延迟加载:fetchType="eager"
7. 多对多 高级映射
多对多:分解成两个一对多,然后其中一个是:一对一的关系
8. 总结:
- 多对一的高级映射 多种方式,常见的包括三种:
- 第一种方式:一条SQL语句,级联属性映射。
- 第二种方式:一条SQL语句,association。
- 第三种方式:两条SQL语句,分步查询。(这种方式常用:优点一是可复用。优点二是支持懒加载。)
- association翻译为:关联。学生对象关联一个对象(这里关联班级对象)。
- 分布查询就是利用延时加载的机制。
- 延迟加载的核心原理是:用的时候再执行查询语句,不用的时候不查询,作用:提高性能,尽可能的不查,或者尽可能的少查,来提高效率。
- 在association标签中配置fetchType="lazy" 是局部的设置,只对当前association关联的sql语句起作用,fetchType="eager" 表示关闭局部的延迟加载。
- 如果返回的映射 POJO 类当中有一个集合属性,则用 collection 标签负责处理。
- 实际的开发中的模式:把全局的延迟加载打开。如果某一步不需要使用延迟加载,请设置fetchType="eager" 即可
- 多对多:分解成两个一对多,然后其中一个是:一对一的关系
9. 最后:
"在这个最后的篇章中,我要表达我对每一位读者的感激之情。你们的关注和回复是我创作的动力源泉,我从你们身上吸取了无尽的灵感与勇气。我会将你们的鼓励留在心底,继续在其他的领域奋斗。感谢你们,我们总会在某个时刻再次相遇。"