Mybatis学习03 - 多表查询和延迟加载以及使用缓存

环境准备

  1. 创建数据库表user、account、role和user_role

    DROP TABLE IF EXISTS `user`;
    
    CREATE TABLE `user` (
      `id` int(11) NOT NULL auto_increment,
      `username` varchar(32) NOT NULL COMMENT '用户名称',
      `birthday` datetime default NULL COMMENT '生日',
      `sex` char(1) default NULL COMMENT '性别',
      `address` varchar(256) default NULL COMMENT '地址',
      PRIMARY KEY  (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    insert  into `user`(`id`,`username`,`birthday`,`sex`,`address`) 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','女','北京修正');
    
    DROP TABLE IF EXISTS `account`;
    
    CREATE TABLE `account` (
      `ID` int(11) NOT NULL COMMENT '编号',
      `UID` int(11) default NULL COMMENT '用户编号',
      `MONEY` double default NULL COMMENT '金额',
      PRIMARY KEY  (`ID`),
      KEY `FK_Reference_8` (`UID`),
      CONSTRAINT `FK_Reference_8` FOREIGN KEY (`UID`) REFERENCES `users` (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    insert  into `account`(`ID`,`UID`,`MONEY`) values (1,57,1000),(2,56,1000),(3,57,2000);
    
    DROP TABLE IF EXISTS `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,'校长','管理整个学校');
    
    DROP TABLE IF EXISTS `user_role`;
    
    CREATE TABLE `user_role` (
      `UID` int(11) NOT NULL COMMENT '用户编号',
      `RID` int(11) NOT NULL COMMENT '角色编号',
      PRIMARY KEY  (`UID`,`RID`),
      KEY `FK_Reference_10` (`RID`),
      CONSTRAINT `FK_Reference_10` FOREIGN KEY (`RID`) REFERENCES `role` (`ID`),
      CONSTRAINT `FK_Reference_9` FOREIGN KEY (`UID`) REFERENCES `user` (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    insert  into `user_role`(`UID`,`RID`) values (41,1),(45,1),(41,2);
    
  2. 在domain层下创建实体类User、Account和Role

    package com.chenpeng.domain;
    
    import java.io.Serializable;
    import java.util.Date;
    import java.util.List;
    
    public class User implements Serializable {
        private Integer id;
        private String username;
        private Date birthday;
        private String sex;
        private String address;
    
        //一对多关系映射,主表实体应该包含从表实体的集合应用
        private List<Account> accounts;
    
        public List<Account> getAccounts() {
            return accounts;
        }
        
        public void setAccounts(List<Account> accounts) {
            this.accounts = accounts;
        }
        
        //多对多关系映射
        private List<Role> roles;
    
        public List<Role> getRoles() {
            return roles;
        }
    
        public void setRoles(List<Role> roles) {
            this.roles = roles;
        }
        
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getUsername() {
            return username;
        }
    
        public void setUsername(String username) {
            this.username = username;
        }
    
        public Date getBirthday() {
            return birthday;
        }
    
        public void setBirthday(Date birthday) {
            this.birthday = birthday;
        }
    
        public String getSex() {
            return sex;
        }
    
        public void setSex(String sex) {
            this.sex = sex;
        }
    
        public String getAddress() {
            return address;
        }
    
        public void setAddress(String address) {
            this.address = address;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", username='" + username + '\'' +
                    ", birthday=" + birthday +
                    ", sex='" + sex + '\'' +
                    ", address='" + address + '\'' +
                    '}';
        }
    }
    
    package com.chenpeng.domain;
    
    import java.io.Serializable;
    
    public class Account implements Serializable {
        private Integer id;
        private Integer uid;
        private Double money;
        
        //多对一关系映射,从表实体应该包含主表实体的引用
        private User user;
    
        public User getUser() {
            return user;
        }
    
        public void setUser(User user) {
            this.user = user;
        }
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public Integer getUid() {
            return uid;
        }
    
        public void setUid(Integer uid) {
            this.uid = uid;
        }
    
        public Double getMoney() {
            return money;
        }
    
        public void setMoney(Double money) {
            this.money = money;
        }
    
        @Override
        public String toString() {
            return "Account{" +
                    "id=" + id +
                    ", uid=" + uid +
                    ", money=" + money +
                    '}';
        }
    }
    
    package com.chenpeng.domain;
    
    import java.io.Serializable;
    import java.util.List;
    
    public class Role implements Serializable {
        private Integer id;
        private String roleName;
        private String roleDesc;
        
        //多对多关系映射
        private List<User> users;
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getRoleName() {
            return roleName;
        }
    
        public void setRoleName(String roleName) {
            this.roleName = roleName;
        }
    
        public String getRoleDesc() {
            return roleDesc;
        }
    
        public void setRoleDesc(String roleDesc) {
            this.roleDesc = roleDesc;
        }
    
        public List<User> getUsers() {
            return users;
        }
    
        public void setUsers(List<User> users) {
            this.users = users;
        }
    
        @Override
        public String toString() {
            return "Role{" +
                    "id=" + id +
                    ", roleName='" + roleName + '\'' +
                    ", roleDesc='" + roleDesc + '\'' +
                    '}';
        }
    }
    
  3. 在resources目录下创建主配置文件SqlMapConfig.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <!-- 引入Mybatis的xml约束 -->
    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <!--Mybatis的主配置文件-->
    <configuration>
        <!--配置properties-->
        <properties url="file:///C:/Users/chenpeng/IdeaProjects/Mybatis/src/main/resources/jdbcConfig.properties">
        </properties>
        <!--使用typeAliases配置别名,它只能配置domain中类的别名-->
        <typeAliases>
            <package name="com.chenpeng.domain"/>
        </typeAliases>
        <!--配置环境-->
        <environments default="mysql">
            <!--配置mysql的环境-->
            <environment id="mysql">
                <!--配置事务的类型-->
                <transactionManager type="JDBC"></transactionManager>
                <!--配置数据源(连接池)-->
                <dataSource type="POOLED">
                    <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>
            <package name="com.chenpeng.dao"/>
        </mappers>
    </configuration>
    

多表查询

多对一查询

实现多对一查询

  1. 在dao层接口AccountDao中定义查询方法

    public interface AccountDao {
        /**
         * 查询所有账户同时包含对应的用户信息
         * @return
         */
        List<Account> findAll();
    }
    
  2. 在AccountDao.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.chenpeng.dao.AccountDao">
        <resultMap id="accountUserMap" type="account">
            <id property="id" column="aid"></id>
            <result property="uid" column="uid"></result>
            <result property="money" column="money"></result>
            <!--多对一的关系映射,配置封装user的内容-->
            <association property="user" column="uid" javaType="user">
                <id property="id" column="id"></id>
                <result property="username" column="username"></result>
                <result property="birthday" column="birthday"></result>
                <result property="sex" column="sex"></result>
                <result property="address" column="address"></result>
            </association>
        </resultMap>
        <!--查询所有账户同时包含对应的用户信息-->
        <select id="findAll" resultMap="accountUserMap">
            select u.*,a.id as aid,a.uid,a.money from users u,account a where u.id = a.uid
        </select>
    </mapper>
    

一对多查询

实现一对多查询

  1. 在dao层接口UserDao中定义查询方法

    public interface UserDao {
        /**
         * 查询所有用户同时包含对应的账户信息
         * @return
         */
        List<User> findAll();
    }
    
  2. 在UserDao.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.chenpeng.dao.UserDao">
        <resultMap id="userAccountMap" type="user">
            <id property="id" column="id"></id>
            <result property="username" column="username"></result>
            <result property="birthday" column="birthday"></result>
            <result property="sex" column="sex"></result>
            <result property="address" column="address"></result>
            <!--配置user对象中accounts集合的映射-->
            <collection property="accounts" ofType="account">
                <id property="id" column="aid"></id>
                <result property="uid" column="uid"></result>
                <result property="money" column="money"></result>
            </collection>
        </resultMap>
        <!--查询所有用户同时包含对应的账户信息-->
        <select id="findAll" resultMap="userAccountMap">
            select u.*,a.id as aid,a.uid,a.money from users u left outer join account a on u.id = a.uid
        </select>
    </mapper>
    

多对多查询

实现多对多查询

  1. 查询所有用户同时包含对应的角色信息

    1. 在dao层接口UserDao中定义查询方法

      public interface UserDao {
          /**
           * 查询所有用户同时包含对应的角色信息
           * @return
           */
          List<User> findAll();
      }
      
    2. 在UserDao.xml中添加配置

      <resultMap id="userMap" type="user">
              <id property="id" column="id"></id>
              <result property="username" column="username"></result>
              <result property="birthday" column="birthday"></result>
              <result property="sex" column="sex"></result>
              <result property="address" column="address"></result>
              <!--配置user对象中roles集合的映射-->
              <collection property="roles" ofType="role">
                  <id property="id" column="roleid"></id>
                  <result property="roleName" column="role_name"></result>
                  <result property="roleDesc" column="role_desc"></result>
              </collection>
          </resultMap>
          <!--查询所有用户同时包含对应的角色信息-->
          <select id="findAll" resultMap="userMap">
              select u.*,ur.*,r.id as roleid,r.role_name,role_desc from users u
              left outer join user_role ur on ur.uid = u.id
              left outer join role r on r.id = ur.rid
          </select>
      
  2. 查询所用角色同时包含对应的用户信息

    1. 在dao层接口RoleDao中定义查询方法

      public interface RoleDao {
      
          /**
           * 查询所有角色同时包含用户的信息
           * @return
           */
          List<Role> findAll();
      }
      
    2. 在RoleDao.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.chenpeng.dao.RoleDao">
      
          <resultMap id="roleMap" type="role">
              <id property="id" column="id"></id>
              <result property="roleName" column="role_name"></result>
              <result property="roleDesc" column="role_desc"></result>
              <collection property="users" ofType="user">
                  <id property="id" column="userid"></id>
                  <result property="username" column="username"></result>
                  <result property="birthday" column="birthday"></result>
                  <result property="sex" column="sex"></result>
                  <result property="address" column="address"></result>
              </collection>
          </resultMap>
          <select id="findAll" resultMap="roleMap">
              select r.*,ur.*,u.id as userid,u.username,u.birthday,u.sex,u.address from role r
              left outer join user_role ur on r.id = ur.rid
              left outer join users u on ur.uid = u.id
          </select>
      </mapper>
      

延迟加载

什么是延迟加载

  • 在真正使用数据的时候才发起查询,不用的是不查询,按需加载,也叫懒加载
  • 在多表查询中,一对多和多对多通常采用延迟加载

什么是立即加载

  • 不管用不用,只要一调用方法,就马上发起查询
  • 在多表查询中,多对一和一对一通常采用立即加载

实现延迟加载

开启Mybatis延迟加载

在Mybatis主配置文件SqlMapConfig.xml中添加配置

<!--配置mybatis延迟加载的全局开关-->
    <settings>
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="aggressiveLazyLoading" value="false"/>
    </settings>

多对一查询的延迟加载

  1. 定义主表查询和附表查询的dao层接口方法

    • 在AccountDao中定义查询方法findAll()

      public interface AccountDao {
          /**
           * 查询所有账户
           * @return
           */
          List<Account> findAll();
      }
      
    • 在UserDao中定义查询方法findById()

      public interface UserDao {
          /**
           * 根据id查询用户
           * @param id
           * @return
           */
          User findById(Integer id);
      }
      
  2. 在配置文件中配置上述两个查询方法

    • 在AccountDao中添加配置

      <?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.chenpeng.dao.AccountDao">
          <resultMap id="accountUserMap" type="account">
              <id property="id" column="id"></id>
              <result property="uid" column="uid"></result>
              <result property="money" column="money"></result>
              <!--多对一的关系映射,配置封装user的内容-->
              <association property="user" column="uid" javaType="user" select="com.chenpeng.dao.UserDao.findById">
              </association>
          </resultMap>
          <!--查询所有-->
          <select id="findAll" resultMap="accountUserMap">
              select * from account
          </select>
      </mapper>
      
    • 在UserDao.xml中添加配置

      <select id="findById" parameterType="int" resultType="user">
          select * from users where id = #{uid}
      </select>
      

一对多的延迟加载

  1. 定义主表查询和附表查询的dao层接口方法

    • 在UserDao中定义查询方法

      public interface UserDao {
          /**
           * 查询所有用户
           * @return
           */
          List<User> findAll();
      }
      
    • 在AccountDao中定义查询方法

      public interface AccountDao {
          /**
           * 根据用户id查询账户
           * @return
           */
          List<Account> findByUid(Integer uid);
      }
      
  2. 在配置文件中配置上述两个查询方法

    • 在UserDao.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.chenpeng.dao.UserDao">
          <resultMap id="userMap" type="user">
              <id property="id" column="id"></id>
              <result property="username" column="username"></result>
              <result property="birthday" column="birthday"></result>
              <result property="sex" column="sex"></result>
              <result property="address" column="address"></result>
              <!--配置user对象中accounts集合的映射-->
              <collection property="accounts" ofType="account" column="id" select="com.chenpeng.dao.AccountDao.findByUid">
              </collection>
          </resultMap>
          <!--查询所有-->
          <select id="findAll" resultMap="userMap">
              select * from users
          </select>
      </mapper>
      
    • 在AccountDao中添加配置

      <!--根据用户id查询账户-->
          <select id="findByUid" resultType="account" parameterType="int">
              select * from account where uid = #{uid}
          </select>
      

Mybatis中的缓存

什么是缓存

  • 指暂时存储在内存中的数据

什么样的数据能使用缓存

  • 经常查询,数据不经常改变,数据的准确与否对结果没有很大的影响

什么样的数据不能使用缓存

  • 数据经常改变,数据的准确与否对结果有很大的影响

Mybatis中的一级缓存

  • Mybatis中的一级缓存是Mybatis中SqlSession对象的缓存,当我们执行查询之后,查询的结果会同时存入到SqLSession为我们提供的一块区域中(该区域的结构是一个map集合)

  • 当我们再次查询相同的数据时,Mybatis会先去SqLSession中查询是否有,有的话直接拿出来

  • 当SqLSession对象消失时,Mybatis的一级缓存也会消失

  • 测试一级缓存

    • 在UserTest中定义测试方法

      @Test
          public void testFirstCache(){
              User user = userDao.findById(41);
              System.out.println(user);
              User user1 = userDao.findById(41);
              System.out.println(user1);
              System.out.println(user == user1);
          }
      
    • 输出结果为

      com.chenpeng.domain.User@4bb4de6a
      com.chenpeng.domain.User@4bb4de6a
      true
      

      两次查询的user对象一样

  • 触发清空一级缓存的情况:当调用SqlSession对象的修改、添加、删除、commit()和close()等方法时,就会清空一级缓存

Mybatis中的二级缓存

  • Mybatis中的二级缓存是SqlSessionFactory对象的缓存,由同一个SqlSessionFactory对象创建的SqlSession对象共享其缓存

二级缓存使用步骤

  1. 让Mybatis框架支持二级缓存

    在主配置文件SqlMapConfig.xml中添加配置

    <settings>
          <setting name="cacheEnabled" value="true"/>
    </settings>
    
  2. 让当前的映射文件支持二级缓存

    在UserDao.xml中添加配置

    <!--开启user支持二级缓存-->
        <cache></cache>
    
  3. 让当前的查询方法支持二级缓存

    <select>标签中添加useCache属性

    <select id="findById" parameterType="int" resultType="user" useCache="true">
         select * from users where id = #{uid}
    </select>
    
  4. 测试二级缓存

    定义测试方法

    @Test
        public void testSecondCache(){
            SqlSession sqlSession1 = factory.openSession();
            UserDao userDao1 = sqlSession1.getMapper(UserDao.class);
            User user1 = userDao1.findById(58);
            System.out.println(user1);
    
            SqlSession sqlSession2 = factory.openSession();
            UserDao userDao2 = sqlSession2.getMapper(UserDao.class);
            User user2 = userDao2.findById(58);
            System.out.println(user2);
            System.out.println(user1);
            System.out.println(user1 == user2);
        }
    

    输出结果为

    com.chenpeng.domain.User@aecb35a
    com.chenpeng.domain.User@4bb4de6a
    false
    

    两次查询的user对象并不一样,这是因为SqlSessionFactory对象的缓存存的是数据而不是对象,第二次查询时SqlSessionFactory只是把缓存的数据封装到User中,所以两者不一样

posted @ 2020-04-14 16:50  codeDD  阅读(155)  评论(0编辑  收藏  举报