• Mybatis中的多表查询之用户与账户(一对多和一对一/多对一)结果集封装到对象立即加载与延迟加载


    Mybatis表之间关系有三种:

    1、 一对一:人和身份证号是一对一

    2、 一对多:一个用户和多个订单是一对多

    3、 多对一:多个订单和一个用户是多对一(用户角度)

    4、 多对多:老师和学生之间是多对多

    特例:如果拿出每一个订单,它都只能属于一个用户(订单角度),所以mybatis中就把多对一看成了一对一

    用户和账户:(一对多+一对一)

    1、(用户和账户一对多)一个用户可以有多个账户

    2、(账户和用户多对一)一个账户只能属于一个用户(多个账户也可以属于同一个用户)mybatis把多对一看成是一对一

    目标:

    当我们查询用户时,可以同时得到用户下所包含的账户信息。

    当我们查询账户时,可以同时得到账户的所属用户信息。

    在对应的四种表关系中,

    一对多、多对多:如果关联的对象是多,通常情况下都是采用延迟加载

    多对一、一对一:如果关联的对象是一,通常情况下都是采用立即加载

    其实Mybatis没有多对一的概念。

    当我们查询账户时,可以同时得到账户的所属用户信息,即(多对一)一对一立即加载步骤:

    在查询账户的时候,要不要把关联的用户查出来?

    在实际开发中,如果只有账户的信息(id、uid、money),对于使用者来说是不清楚的,因为你只显示uid,使用者不知道uid是谁,通常情况下除了显示账户的信息,还要显示username,从这来看,如果你不显示用户信息,你就不能查出用户名,

    立即加载:不管用不用,只要一调用方法,马上发起查询。

    在实际开发中,查询账户时,账户的所属用户信息应该是随着账户查询时一起查询出来

    Mybatis一对一实现立即加载

    实际上一次查询了两张表,一次把两张表查完了,自然而然没有延迟的概念了,因为是直接把所有数据都查出来。换言之,在实际开发中,在多对一或一对一的情况下,就可以采用此种方式实现功能

    1、 建立两张表:用户表和账户表

    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 `user` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; insert into `account`(`ID`,`UID`,`MONEY`) values (1,41,1000),(2,45,1000),(3,41,2000);

     

    让用户表和账户表之间具备一对多的关系,需要在账户表中添加外键

    2、创建maven工程,引入依赖

    <dependencies>
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.4.6</version>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.26</version>
                <scope>runtime</scope>
            </dependency>
            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <version>1.2.17</version>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.16</version>
            </dependency>
        </dependencies>

    3、 建立两个实体类:

    用户实体类

    @Data
    public class User implements Serializable {
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;
    }

    账户实体类

    @Data
    public class Account implements Serializable{
    private Integer id;
    private Integer uid;
    private Double money;
    private User user;
    }

    让用户和账户的实体类能体现出来一对一的关系,

    建立实体类关系的方式,这样我们在封装Account的时候,同时能把User的信息给封装出来。

    4、 编写dao接口

    public interface IAccountDao {
        List<Account> findAll();
    }

    5、编写映射配置文件

    账户的配置文件

    <?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">
    <!--创建映射配置文件,必须与dao接口的包结构相同-->
    <mapper namespace="com.itheima.dao.IAccountDao"><!--namespace的值是dao接口的全限定类名-->
        <!-- 定义封装account和user的resultMap -->
        <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" javaType="User">
                <id property="id" column="id"></id>
                <result column="username" property="username"></result>
                <result column="address" property="address"></result>
                <result column="sex" property="sex"></result>
                <result column="birthday" property="birthday"></result>
            </association>
        </resultMap>
        <select id="findAll" resultMap="accountUserMap">
            select u.*,a.id as aid,a.uid,a.money from account a , user u where u.id = a.uid;
        </select>
    </mapper>

    Mybatis把返回结果封装成对象。

    定义封装account和user的resultMap,由于给account表中的id起了别名aid,account表中id的列名应该改为aid.  

    resultMap中要想封装user的信息,一对一关系的映射用association标签,association标签的property属性值为类中的属性user此处column=“uid”可以不写,我们写上也没有问题,column属性值表示用户根据id查询时,所需要的参数的值,也就是说你findById,你by的这个id是谁呢?即uidselect * from user where id = #{uid},是通过uid获取user对象的,故column的属性值为uid注意uid是account表中的外键字段名称,不是你任意取的名字JavaType属性表示你封装的哪个类的权限定类名com.itheima.domain.User,由于用了别名,故可以直接用user

    注意:当SQL查询出数据后要对结果进行映射,即要定义封装account和user的resultMap

    select * from user u,account a where u.id=a.uid

    由于user表和account的表的id名称相同,所以第二个出现的会自动起别名。

    我们如果不想让它自动起别名,我们可以自己给account表的id起别名。

    select u.*,a.id as aid,a.uid,a.money from user u,account a where u.id=a.uid

    6、主配置文件

    <?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">
    <!--创建mybatis的主配置文件-->
    <configuration>
        <properties resource="jdbcConfig.properties"></properties>
        <typeAliases>
            <package name="com.itheima.domain"></package>
        </typeAliases>
        <environments default="mysql">
            <environment id="mysql"><!--id的值与上面default的值必须相同-->
                <transactionManager type="JDBC">
                </transactionManager>
                <dataSource type="POOLED"><!--配置文件写再dataSource标签下-->
                    <property name="driver" value="${driver}"></property>
                    <property name="url" value="${url}"></property>
                    <property name="username" value="${username}"></property>
                    <property name="password" value="${password}"></property>
                </dataSource>
            </environment>
        </environments>
        <mappers>
            <!--指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件-->
            <package name="com.itheima.dao"></package>
        </mappers>
    </configuration>

    jdbcConfig.properties

    driver=com.mysql.jdbc.Driver
    url=jdbc:mysql://localhost:3306/eesy
    username=root
    password=123456

    7、 写测试类实现配置:

    public class accountTest {
        private InputStream in ;
        private SqlSession session;
        private IAccountDao accountDao;
        @Before
        public void init() throws Exception{
            //1.读取配置文件
            in = Resources.getResourceAsStream("SqlMapConfig.xml");
            //2.创建SqlSessionFactory工厂
            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
            SqlSessionFactory factory = builder.build(in);
            //3.使用工厂生产SqlSession对象
            session = factory.openSession(true);
            //4.使用SqlSession创建Dao接口的代理对象
            accountDao = session.getMapper(IAccountDao.class);
        }
        @After
        public void destroy() throws IOException {
            //session.commit();
            //6.释放资源
            session.close();
            in.close();
        }
    
        @Test
        public void testFindAll(){
            List<Account> accounts = accountDao.findAll();
            for(Account account : accounts){
                System.out.println(account);
            }
        }
    }

    结果:

     当我们查询用户时,可以同时得到用户下所包含的账户信息,即一对多立即加载步骤:

    1、 建立两张表:用户表和账户表

    2、创建maven工程,引入依赖

    3、 建立两个实体类:

    账户实体类

    @Data
    public class Account implements Serializable{
        private Integer id;
        private Integer uid;
        private Double money;
    }

    用户实体类

    @Data
    public class User implements Serializable {
        private Integer id;
        private String username;
        private Date birthday;
        private String sex;
        private String address;
        private List<Account> accounts;
    }

    让用户和账户的实体类能体现出来一对多的关系,

    4、 编写dao接口

    public interface IUserDao {
    List<User> findAll();
    }

    5、编写映射配置文件

    用户映射配置文件

    <?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">
    <!--创建映射配置文件,必须与dao接口的包结构相同-->
    <mapper namespace="com.itheima.dao.IUserDao"><!--namespace的值是dao接口的全限定类名-->
        <!-- 定义User的resultMap-->
        <resultMap id="userAccountMap" type="User">
            <id property="id" column="id"></id>
            <result property="username" column="username"></result>
            <result property="address" column="address"></result>
            <result property="sex" column="sex"></result>
            <result property="birthday" column="birthday"></result>
            <!-- 配置user对象中accounts集合的映射 -->
            <collection property="accounts" ofType="Account">
                <id column="aid" property="id"></id>
                <result column="uid" property="uid"></result>
                <result column="money" property="money"></result>
            </collection>
        </resultMap>
    
        <!-- 查询所有 -->
        <select id="findAll" resultMap="userAccountMap">
            select u.*,a.id as aid,a.uid,a.money from user u left outer join account a on u.id = a.uid
        </select>
    </mapper>

    Mybatis把返回结果封装成对象。

    配置user对象中accounts集合的映射,用collection标签,property属性的值为User类中的属性accounts,ofType属性表示集合中元素的全限定类名com.itheima.domain.Account,由于用了别名,故可以直接用account。由于两个表中都有id,故将account表中的id起别名。

    6、主配置文件

    <?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">
    <!--创建mybatis的主配置文件-->
    <configuration>
        <properties resource="jdbcConfig.properties"></properties>
        <typeAliases>
            <package name="com.itheima.domain"></package>
        </typeAliases>
        <environments default="mysql">
            <environment id="mysql"><!--id的值与上面default的值必须相同-->
                <transactionManager type="JDBC">
                </transactionManager>
                <dataSource type="POOLED"><!--配置文件写再dataSource标签下-->
                    <property name="driver" value="${driver}"></property>
                    <property name="url" value="${url}"></property>
                    <property name="username" value="${username}"></property>
                    <property name="password" value="${password}"></property>
                </dataSource>
            </environment>
        </environments>
        <mappers>
            <!--指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件-->
            <package name="com.itheima.dao"></package>
        </mappers>
    </configuration>

    jdbcConfig.properties

    driver=com.mysql.jdbc.Driver
    url=jdbc:mysql://localhost:3306/eesy
    username=root
    password=123456

    7、 写测试类实现配置:

    public class MybatisTest {
        private InputStream in ;
        private SqlSession session;
        private IUserDao userDao;
        @Before
        public void init() throws Exception{
            //1.读取配置文件
            in = Resources.getResourceAsStream("SqlMapConfig.xml");
            //2.创建SqlSessionFactory工厂
            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
            SqlSessionFactory factory = builder.build(in);
            //3.使用工厂生产SqlSession对象
            session = factory.openSession(true);
            //4.使用SqlSession创建Dao接口的代理对象
            userDao = session.getMapper(IUserDao.class);
        }
        @After
        public void destroy() throws IOException {
            //session.commit();
            //6.释放资源
            session.close();
            in.close();
        }
    
        @Test
        public  void testFindAll() {
            //5.使用代理对象执行方法
            List<User> users = userDao.findAll();
            for(User user : users){
                System.out.println(user);
            }
        }
    }

    结果

     当我们查询用户时,可以同时得到用户下所包含的账户信息,即一对多延迟加载步骤:

    在查询用户的时候,要不要把关联的账户查出来?

    我们要做的功能就是查询用户,但是用户中关联了一个账户集合,并且这个用户有100个账户,我们一查用户就会把100个账户同时查出来,这无疑内存是非常大的开销而如果我们不用的时候,是完全不应该把这100个账户查出来的,但是这时候有一个新的问题,如果我们要用的话,你没查出来,那我们岂不是用不了了吗?所以这时候我们应该想到一个概念,在查询用户时,用户下的账户信息应该是什么时候使用什么时候查询的,而不应该是只要一查用户就把账户查出来(按需加载)

    延迟加载在真正使用数据时才发起查询,不用的时候不查询,也叫按需加载懒加载

    延迟加载的思想符合查询用户,当有用户要查询用户的账户,或者有用户要查询用户的订单,或者有用户要查询用户的角色,这时候,因为是一个集合,查出来会浪费内存空间,所以应该是延迟加载

    修改如下:

    1、在主配置文件中配置参数

    <?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">
    <!--创建mybatis的主配置文件-->
    <configuration>
        <properties resource="jdbcConfig.properties"></properties>
        <!--配置延迟加载策略-->
        <settings>
            <!--打开延迟加载开关-->
            <setting name="lazyLoadingEnabled" value="true"/>
            <!--将积极加载改为消极加载即按需加载-->
            <setting name="aggressiveLazyLoading" value="false"/>
        </settings>
        <typeAliases>
            <!--<typeAlias type="com.itheima.domain.User" alias="user"></typeAlias>-->
            <package name="com.itheima.domain"></package>
        </typeAliases>
    
        <environments default="mysql">
            <environment id="mysql"><!--id的值与上面default的值必须相同-->
                <transactionManager type="JDBC">
                </transactionManager>
                <dataSource type="POOLED"><!--配置文件写再dataSource标签下-->
                    <property name="driver" value="${driver}"></property>
                    <property name="url" value="${url}"></property>
                    <property name="username" value="${username}"></property>
                    <property name="password" value="${password}"></property>
                </dataSource>
            </environment>
        </environments>
        <mappers>
            <!--<mapper resource="com/itheima/dao/IUserDao.xml"></mapper>--><!--指定映射配置文件的位置,映射配置文件指的是每个dao独立的配置文件-->
            <package name="com.itheima.dao"></package>
        </mappers>
    </configuration>

    2、改造IUserDao.xml中的findAll方法的sql语句,因为如果用left outer join的话根本不可能实现延迟,因为一次查了两张表。改为一次查一张表

    <?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">
    <!--创建映射配置文件,必须与dao接口的包结构相同-->
    <mapper namespace="com.itheima.dao.IUserDao"><!--namespace的值是dao接口的全限定类名-->
    
        <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>
            <collection property="accounts" column="id" ofType="Account" select="com.itheima.dao.IAccountDao.findAccountByuid">
            </collection>
        </resultMap>
        <select id="findAll" resultMap="userAccountMap"><!--id的值是dao接口方法的名称,id的值与dao接口的全限定类名一一映射-->
            SELECT * from user
        </select>
    </mapper>

    修改resultMap配置;在映射配置文件的association标签内将实体类的属性名与数据库的表字段进行映射已经没有意义,因为没有查数据是不可能封装的使用collection标签的select属性,select属性指定的内容:查询账户的唯一标识,也就是在IAccountDao.xml中能根据用户id查询账户的配置。调用对方映射配置文件中的一个配置来实现查询的功能

    注意:collection标签中的column属性值id为User表的主键id,该id作为子查询的参数

    3、在IAccountDao接口中增加findAccountByuid方法

    public interface IAccountDao {
        List<Account> findAccountByuid(Integer uid);
    }

    4、在对方映射配置文件中根据用户id查询账户列表:

    <?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">
    <!--创建映射配置文件,必须与dao接口的包结构相同-->
    <mapper namespace="com.itheima.dao.IAccountDao"><!--namespace的值是dao接口的全限定类名-->
        <select id="findAccountByuid" resultType="Account">
            select * from account where uid=#{uid}
        </select>
    </mapper>

    5、测试

    public class MybatisTest {
        private InputStream in ;
        private SqlSession session;
        private IUserDao userDao;
        @Before
        public void init() throws Exception{
            //1.读取配置文件
            in = Resources.getResourceAsStream("SqlMapConfig.xml");
            //2.创建SqlSessionFactory工厂
            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
            SqlSessionFactory factory = builder.build(in);
            //3.使用工厂生产SqlSession对象
            session = factory.openSession(true);
            //4.使用SqlSession创建Dao接口的代理对象
            userDao = session.getMapper(IUserDao.class);
        }
        @After
        public void destroy() throws IOException {
            //session.commit();
            //6.释放资源
            session.close();
            in.close();
        }
    
        @Test
        public  void testFindAll() {
            //5.使用代理对象执行方法
            List<User> users = userDao.findAll();
        }
    }

    结果发现只执行了一个sql语句,实现了延迟的效果

    而当要使用用户的账户信息的时候才会查询账户信息,

    public class MybatisTest {
        private InputStream in ;
        private SqlSession session;
        private IUserDao userDao;
        @Before
        public void init() throws Exception{
            //1.读取配置文件
            in = Resources.getResourceAsStream("SqlMapConfig.xml");
            //2.创建SqlSessionFactory工厂
            SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
            SqlSessionFactory factory = builder.build(in);
            //3.使用工厂生产SqlSession对象
            session = factory.openSession(true);
            //4.使用SqlSession创建Dao接口的代理对象
            userDao = session.getMapper(IUserDao.class);
        }
        @After
        public void destroy() throws IOException {
            //session.commit();
            //6.释放资源
            session.close();
            in.close();
        }
    
        @Test
        public  void testFindAll() {
            //5.使用代理对象执行方法
            List<User> users = userDao.findAll();
            for(User user : users){
                System.out.println(user);
                List<Account> accounts = user.getAccounts();
                System.out.println(accounts);
            }
        }
    }

    一对多延迟加载的思想是在用的时候调用对方映射配置文件中的一个配置来实现查询的功能。

  • 相关阅读:
    什么是JAVA?JAVA能做什么?
    vagrant 虚拟机搭建 以及virtualbox 安装 配置
    sourcetree跳过注册的方法
    layuiAdmin安装
    Navicat获取注册码
    laravel artisan命令大全
    vagrant命令
    安装python 3.7
    搭建golang webcron 定时任务管理平台
    mysql创建用户
  • 原文地址:https://www.cnblogs.com/zwh0910/p/14661802.html
Copyright © 2020-2023  润新知