MyBatis多表查询和延迟加载

186 阅读10分钟

查询

一对一查询

案例

一个银行账户只能由一个用户使用,即在银行的角度来看,查询账户信息是一对一查询。

数据表person和baccount


CREATE TABLE `person`  (
  `id` int(11) NOT NULL,
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `birthday` date NOT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

insert  into `person`(`id`,`name`,`birthday`) values 
(41,'老王','2018-02-27 17:47:08'),
(42,'小二王','2018-03-02 15:09:37'),
(43,'小二王','2018-03-04 11:34:34'),
(45,'传智播客','2018-03-04 12:04:06'),
(46,'老王','2018-03-07 17:37:26'),
(48,'小马宝莉','2018-03-08 11:44:00');

CREATE TABLE `baccount`  (
  `ID` int(11) NOT NULL COMMENT '编号',
  `PID` int(11) NULL DEFAULT NULL COMMENT '用户编号',
  `MONEY` double NULL DEFAULT NULL COMMENT '金额',
  PRIMARY KEY (`ID`) USING BTREE,
  INDEX `FK_Reference_8`(`PID`) USING BTREE,
  CONSTRAINT `FK_Reference_8` FOREIGN KEY (`PID`) REFERENCES `person` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

insert  into `baccount`(`ID`,`PID`,`MONEY`) values (1,46,1000),(2,45,1000),(3,46,2000);

对应的javabean

public class Person {
    private Integer id;
    private Date birthday;
    private String name;
    
    //省略get/set,toString方法
}
public class BAccount {
    private Integer id;
    private Integer pid;
    private Double money;

    private Person person;
    //省略get/set,toString方法
}

dao接口:

public interface BAccountDao {
    List<BAccount> findAllBAccountAndPerson();
}

映射文件:

<?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.on1.dao.BAccountDao">
    
    <resultMap id="baccountMap" type="BAccount">
        <id property="id" column="bid" ></id>
        <result property="pid" column="pid"></result>
        <result property="money" column="money"></result>
<!--        一对一映射关系-->
        <association property="person" column="pid" javaType="Person">
            <id property="id" column="id"></id>
            <result column="name" property="name"></result>
            <result column="birthday" property="birthday"></result>
        </association>
    </resultMap>

<!--    查询账户的id,用户id用户的全部信息,-->
    <select id="findAllBAccountAndPerson" resultMap="baccountMap">
         select p.*, ba.id as bid, ba.pid, ba.money from baccount ba, person p where ba.pid = p.id;
    </select>
</mapper>

mybatis配置文件:

<?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="jdbcConfig.properties">
    </properties>

    <typeAliases>
        <package name="com.on1.bean"/>
    </typeAliases>
    
    <environments default="mysql">

        <environment id="mysql">
            <!--配置事务类型:使用了 JDBC 的提交和回滚设置-->
            <transactionManager type="JDBC"/>
            <!--配置数据池-->
            <dataSource type="UNPOOLED">
                <!--连接数据库的基本信息-->
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>

        <mapper resource="com/on1/dao/AccountDao.xml"/>
        <mapper resource="com/on1/dao/BAccountDao.xml"/>
        <mapper resource="com/on1/dao/PersonDao.xml"/>
    </mappers>
</configuration>

测试类:

public class day3 {
    InputStream in;
    SqlSession sqlSession;
    BAccountDao bAccountDao;

    @Before
    public void init() throws Exception {
        //        加载配置文件
        in = Resources.getResourceAsStream("mybatisConfig.xml");
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        sqlSession = factory.openSession();
        //        获取代理对象
        bAccountDao = sqlSession.getMapper(BAccountDao.class);
    }

    @After
    public void destroy() throws Exception {
        sqlSession.close();
        in.close();
    }

    @Test
    public void testFindAllBAccountAndPerson() {
        List<BAccount> bAccountList = bAccountDao.findAllBAccountAndPerson();
        for(BAccount bau : bAccountList) {
            System.out.println(bau);
            System.out.println("账户所属用户信息:");
            System.out.println(bau.getPerson());
            System.out.println("--------------------------");
        }
    }
}

控制台输出:

一对多查询

案例

站在用户的角度,一个用户可以持有多个银行账户信息。

代码

Person类

public class Person {
    private Integer id;
    private Date birthday;
    private String name;

    private List<BAccount> bAccountList;
    //省略get /set方法,toString方法
}

dao接口

public interface PersonDao {
    List<Person> findAllPersonAndBAccount();
}

PeroDao.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.on1.dao.PersonDao">
    <resultMap id="personBAccountMap" type="Person">
        <id property="id" column="id"></id>
        <result property="name" column="name"/>
        <result property="birthday" column="birthday"/>

        <!--指定一对多的对应关系,ofType属性表示集合的数据类型-->
        <collection property="bAccountList" ofType="BAccount">
         <!--  id别名bid  -->
            <id column="bid" property="id"></id>               
            <result property="pid" column="pid"></result>
            <result property="money" column="money"></result>
        </collection>
    </resultMap>
<!--    返回用户全部信息,该用户持有账户的id,pid及金额信息-->
    <select id="findAllPersonAndBAccount" resultMap="personBAccountMap">
         select p.*, ba.id as bid, ba.pid, ba.money from person p left outer join baccount ba on p.id = ba.pid;
    </select>
</mapper>

测试方法:

@Before
    public void init() throws Exception {
        //        加载配置文件
        in = Resources.getResourceAsStream("mybatisConfig.xml");
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        sqlSession = factory.openSession();
        //        获取代理对象
        personDao = sqlSession.getMapper(PersonDao.class);
    }

    @After
    public void destroy() throws Exception {
        sqlSession.close();
        in.close();
    }
@Test
    public void testFindAllPersonAndBAccount() {
        List<Person> personList = personDao.findAllPersonAndBAccount();
        for(Person person : personList) {
            System.out.println("--------用户信息----------");
            System.out.println(person);
            System.out.println("该用户持有账户信息:");
            System.out.println(person.getbAccountList());
            System.out.println("--------------------------");
        }
    }

控制台输出:

多对多查询

案例

新建一个表示人物所扮演角色(职位)的role表和记录两表id的person_role表。

站在Role的角度,一个Pole可以由多个Person扮演,这样的关系是一对多的; 站在Person的角度,一个Person可以扮演多个Role,这样的关系同样是一对多的,因此我们可以认为Role和Person的多对多关系可以拆分为两个一对多的关系。

现在我们需要查询角色表和它对应的人物信息,但查询角色对应的人物信息只能通过中间表person_role表提供的pid才能知道人物信息。

Role到Person的多对多

role表和person_role表

CREATE TABLE `role` (
  `ID` int(11) NOT NULL COMMENT '编号',
  `ROLE_NAME` varchar(30) default NULL COMMENT '角色名称',
  `ROLE_DESC` varchar(60) default NULL COMMENT '角色描述',
  PRIMARY KEY  (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

insert  into `role`(`ID`,`ROLE_NAME`,`ROLE_DESC`) values 
(1,'院长','管理整个学院'),
(2,'总裁','管理整个公司'),(3,'校长','管理整个学校');


CREATE TABLE `person_role` (
  `PID` int(11) NOT NULL COMMENT '任务编号',
  `RID` int(11) NOT NULL COMMENT '人物编号',
  PRIMARY KEY  (`PID`,`RID`),
  KEY `FK_Reference_10` (`RID`),
  CONSTRAINT `FK_Reference_10` FOREIGN KEY (`RID`) REFERENCES `role` (`ID`),
  CONSTRAINT `FK_Reference_9` FOREIGN KEY (`PID`) REFERENCES `person` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

insert  into `person_role`(`PID`,`RID`) values (41,1),(45,1),(41,2);

Role类:

public class Role {
    private Integer roleId;
    private String roleName;
    private String roleDesc;
    private List<Person> personList;
    //省略get/set toString方法
}

dao接口:

public interface RoleDao {
    List<Role> findAllRoles();
}

在mybatis配置文件中的mappers标签里添加:

<mapper resource="com/on1/dao/RoleDao.xml"/>

resources文件夹下新建映射文件RoleDap.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.on1.dao.RoleDao">
    <resultMap id="roleMap" type="Role">
        <id property="roleId" column="rid"/>
        <result property="roleName" column="role_name"/>
        <result property="roleDesc" column="role_desc"/>
        <collection property="personList" ofType="Person">
            <id column="id" property="id"/>
            <result column="name" property="name"/>
            <result column="birthday" property="birthday"/>
        </collection>
    </resultMap>

<!--    获取每个人物的全部信息,该人物扮演角色的id,角色名,角色名解释-->
    <select id="findAllRoles" resultMap="roleMap">
        select p.*, r.id as rid, r.role_name, r.role_desc from role r
            left outer join person_role pr on r.id = pr.rid
            left outer join person p on p.id = pr.pid
    </select>
</mapper>

测试方法:

//记得在测试类里添加
//RoleDao roleDao;并在init()方法添加对应的获取代理步骤
//roleDao = sqlSession.getMapper(RoleDao.class);
@Test
    public void testFindAllRoles() {
        List<Role> roleList = roleDao.findAllRoles();
        for(Role role : roleList) {
            System.out.println("--------角色信息----------");
            System.out.println(role);
            System.out.println("该角色的扮演人物");
            System.out.println(role.getPersonList());
        }
    }

控制台输出:

Person到Role的多对多

现在我们要返回每个Person的全部信息以及它扮演的角色信息。

Person类:

public class Person {
    private Integer id;
    private Date birthday;
    private String name;
    private List<Role> roleList;

   //省略get/set toString方法
 }

PersonDao接口添加方法:

 List<Person> findAllPersonAndRole();

映射文件PersonDao.xml中的mapper标签里添加:

<resultMap id="personMap" type="Person">
        <id property="id" column="id"></id>
        <result property="name" column="name"/>
        <result property="birthday" column="birthday"/>
        <collection property="roleList" ofType="Role">
            <id property="roleId" column="rid"></id>
            <result property="roleName" column="role_name"/>
            <result property="roleDesc" column="role_desc"/>
        </collection>
</resultMap>

<!--    返回该人物全部信息及其担任角色id-->
<select id="findAllPersonAndRole" resultMap="personMap">
    select p.*, r.id as rid, r.role_name, r.role_desc from Person p
        left outer join person_role pr on p.id = pr.pid
        left outer join role r on r.id = pr.rid
</select>

测试方法:

@Test
    public void testFindAllPersonAndRole() {
        List<Person> personList = personDao.findAllPersonAndRole();
        for(Person person : personList) {
            System.out.println("--------人物信息----------");
            System.out.println(person);
            System.out.println("该人物担任的角色:");
            System.out.println(person.getRoleList());
            System.out.println("--------------------------");
        }
    }

控制台输出:

延迟加载

延迟加载是指在需要用到数据时才加载,例如在实际开发中很多时候 我们并不需要总是在加载人物信息时就一定要加载它的账户信息。

这样就会先从单表查询,等需要时再从关联表去关联查询,速度会快很多(查询单表比查询关联多张表快);但也存在弊处:只有当需要时才进行查询,这样在大批量数据查询时,耗费时间增加。

使用association标签实现延迟加载

查询账户(BAccount)信息并且管理查询人物(Person)信息,在前面我们使用了 association、collection 实现一对一及一对多映射,而这两个标签也具备延迟加载的功能。

两个数据表:

CREATE TABLE `person`  (
  `id` int(11) NOT NULL,
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `birthday` date NOT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

INSERT INTO `person` VALUES (41, '老王', '2018-02-27');
INSERT INTO `person` VALUES (42, '小二王', '2018-03-02');
INSERT INTO `person` VALUES (43, '小二王', '2018-03-04');
INSERT INTO `person` VALUES (45, '传智播客', '2018-03-04');
INSERT INTO `person` VALUES (46, '老王', '2018-03-07');
INSERT INTO `person` VALUES (48, '小马宝莉', '2018-03-08');

CREATE TABLE `baccount`  (
  `ID` int(11) NOT NULL COMMENT '编号',
  `PID` int(11) NULL DEFAULT NULL COMMENT '用户编号',
  `MONEY` double NULL DEFAULT NULL COMMENT '金额',
  PRIMARY KEY (`ID`) USING BTREE,
  INDEX `FK_Reference_8`(`PID`) USING BTREE,
  CONSTRAINT `FK_Reference_8` FOREIGN KEY (`PID`) REFERENCES `person` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;


INSERT INTO `baccount` VALUES (1, 46, 1000);
INSERT INTO `baccount` VALUES (2, 45, 1000);
INSERT INTO `baccount` VALUES (3, 46, 2000);

Person类

public class Person {
    private Integer id;
    private Date birthday;
    private String name;

    private List<BAccount> bAccountList;
    //省略get /set方法,toString方法
}
public class BAccount {
    private Integer id;
    private Integer pid;
    private Double money;
     //省略get /set方法,toString方法
}

dao层接口

public interface BAccountDao {
    List<BAccount> findAllBAccountAndPerson();
}
public interface PersonDao {
  Person findById(Integer pid);
}

映射文件:

<mapper namespace="com.on1.dao.PersonDao">>
    <select id="findById" resultType="person" parameterType="int">
        select * from person where id = #{pid}
    </select>
</mapper>
<mapper namespace="com.on1.dao.BAccountDao">

    <resultMap id="baccountMap" type="BAccount">
        <id property="id" column="id" />
        <result property="pid" column="pid"/>
        <result property="money" column="money"/>
        <!--select属性指定 查询人物的唯一标识,即PersonDao.xml里的sql语句:findById -->
        <!--column属性指定 根据id查询人物所需要的参数值   -->
        <association property="person" javaType="Person"
            select="com.on1.dao.PersonDao.findById" column="pid">
        </association>

    <select id="findAllBAccountAndPerson" resultMap="baccountMap">
        select * from baccount;
    </select>

测试方法:

 @Test
    public void testFindAllBAccountAndPerson() {
        List<BAccount> bAccountList = bAccountDao.findAllBAccountAndPerson();
        for(BAccount bau : bAccountList) {
            System.out.println("------------账户信息-------");
            System.out.println(bau);
            System.out.println("账户所属用户信息:");
            System.out.println(bau.getPerson());
            System.out.println("--------------------------");
        }
    }

在配置文件中开启 Mybatis 的延迟加载策略

 <settings>
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>

在没开启延迟加载的支持时,控制台的输出如下,

开启后控制台的输出如下,可以发现,查询对应的人物信息是一段一段的执行,而非一股脑的在开始时就全部执行

如果测试方法中不遍历,即不去查询账户的人物信息

 @Test
    public void testFindAllBAccountAndPerson() {
        List<BAccount> bAccountList = bAccountDao.findAllBAccountAndPerson();
    }

则延迟加载后的控制台输出如下,仅执行了查询账户的全部信息

而没有开启延迟加载的控制台输出如下,它执行了查询账户及所属人物的全部信息

使用Collection实现延迟加载

同样的例子,不过本次主要查询人物信息,等需要时才查询持有的账户信息。

dao层接口:

public interface PersonDao {
    Person findById(Integer pid);
}
public interface BAccountDao {
    List<BAccount> findBAccountByPid(int pid);
}

映射文件:

<mapper namespace="com.on1.dao.BAccountDao">
    
    <resultMap id="baccountMap" type="BAccount">
        <id property="id" column="id" />
        <result property="pid" column="pid"/>
        <result property="money" column="money"/>
    </resultMap>

<!--    根据pid查询账户-->
    <select id="findBAccountByPid" resultType="BAccount" parameterType="int">
        select * from baccount where pid = #{pid}
    </select>
<mapper namespace="com.on1.dao.PersonDao">
    
    <resultMap id="personBAccountMap" type="Person">
        <id property="id" column="id"></id>
        <result property="name" column="name"/>
        <result property="birthday" column="birthday"/>

        <collection property="bAccountList" ofType="BAccount"
                    select="com.on1.dao.BAccountDao.findBAccountByPid" column="id">
        </collection>
    </resultMap>

    <select id="findAllPersonAndBAccount" resultMap="personBAccountMap">
         select * from person;
    </select>

测试方法:

 @Test
    public void testFindAllPersonAndBAccount() {
        List<Person> personList = personDao.findAllPersonAndBAccount();
        //        for(Person person : personList) {
//            System.out.println("--------用户信息----------");
//            System.out.println(person);
//            System.out.println("该用户持有账户信息:");
//            System.out.println(person.getbAccountList());
//            System.out.println("--------------------------");
//        }
    }

开启延迟加载后控制台输出:

取消掉测试方法中的全部注解后,运行后的控制台输出为: