zoukankan      html  css  js  c++  java
  • 第9章 MyBatis的关系映射

    在实际开发中,对数据库的操作通常涉及多张表,涉及了对象和对象之间的关联关系。针对多表之间的操作,MyBatis提供了关联映射,通过关联映射就可以很好的处理对象与对象之间的关联关系

    9.1 关联关系概述

    一对一:在任意一方引入对方主键作为外键。

    一对多:在“多”的一方,添加“一“的一方的主键作为外键。(连着多条线的一方是“多”)

    多对多:产生中间关系表,引入两张表的主键作为外键,两个主键成为联合主键或使用新的字段作为主键。

    用Java对象描述

    class A{
        B b;
    }
    class B{
        A a;
    }
    //一对一:在本类中定义对方类型的对象,如A类中定义B类类型的属性b,B类中定义A类类型的属性a
    class A{
        List<B>b;
    }
    class B{
        A a;
    }
    //一对多:就是一个A类类型中对应多个B类类型的情况,需要在A类中以集合的方式引入B类类型的对象,在B类中定义A类类型的属性a
    class A{
        List<B> b;
    }
    class B{
        List<A> a;
    }
    //多对多:在A类中定义B类类型的集合,在B类中定义A类类型的集合

    一对一:用<association>元素处理

    a)、property:指定映射到的实体类对象中的属性,与表字段一一对应。

    b)、column:指定数据库表中对应的字段。

    c)、javaType:指定映射到实体对象属性的类型。

    d)、select:指定引入嵌套查询的子SQL语句,该属性用于关联映射中的嵌套查询。

    e)、fetchType:指定在关联查询时是否启用延迟加载。该属性有lazy和eager两个属性值,默认值为lazy(即默认关联映射延迟加载)。

    MyBatis加载关联关系对象主要通过两种方式:嵌套查询和嵌套结果。

    问题:虽然使用嵌套查询的方式比较简单,但是嵌套查询的方式要执行多条SQL语句,这对于大型数据集合和列表展示不是很好,因为这样可能会导致成百上千条关联的SQL语句被执行,从而极大的消耗数据库性能并且会降低查询效率。类似暴力for循环吧。

    解决:MyBatis延迟加载的配置。使用MyBatis的延迟加载在一定程度上可以降低运行消耗并提高查询效率。MyBatis默认没有开启延迟加载,需要在核心配置文件中的<settings>元素内进行配置,具体配置方式如下:

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

    在映射文件中,<association>元素和<collection>元素中都已默认配置了延迟加载属性,即默认属性fetchType="lazy"(属性fetchType="eager"表示立即加载),所以在配置文件中开启延迟加载后,无需在映射文件中再做配置。

    举例:以个人和身份证之间的一对一关联关系

    创建两个表tb_idcard和tb_person

    USE mybatis;
    CREATE TABLE tb_idcard(
        id INT PRIMARY KEY AUTO_INCREMENT,
        CODE VARCHAR(18)
    );
    INSERT INTO tb_idcard(CODE) VALUE('152221198711020624');
    INSERT INTO tb_idcard(CODE) VALUE('152201199008150317');
    
    CREATE TABLE tb_person(
        id INT PRIMARY KEY AUTO_INCREMENT,
        NAME VARCHAR(32),
        age INT,
        sex VARCHAR(8),
        card_id INT UNIQUE,
        FOREIGN KEY(card_id) REFERENCES tb_idcard(id)
    );
    INSERT INTO tb_person(NAME,age,sex,card_id) VALUE('Rose',29,'',1);
    INSERT INTO tb_person(NAME,age,sex,card_id) VALUE('tom',27,'',2);

    此时表内数据:

    创建持久化类IdCard和Person

    package com.itheima.po;
    /**
     * 证件持久化类
     */
    public class IdCard {
        private Integer id;
        private String code;
        public Integer getId() {
            return id;
        }
        public void setId(Integer id) {
            this.id = id;
        }
        public String getCode() {
            return code;
        }
        public void setCode(String code) {
            this.code = code;
        }
        @Override
        public String toString() {
            return "IdCard [id=" + id + ", code=" + code + "]";
        }
    }
    IdCard
    package com.itheima.po;
    /**
     * 个人持久化类
     */
    public class Person {
        private Integer id;
        private String name;
        private Integer age;
        private String sex;
        private IdCard card;  //个人关联的证件
        public Integer getId() {
            return id;
        }
        public void setId(Integer id) {
            this.id = id;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public Integer getAge() {
            return age;
        }
        public void setAge(Integer age) {
            this.age = age;
        }
        public String getSex() {
            return sex;
        }
        public void setSex(String sex) {
            this.sex = sex;
        }
        public IdCard getCard() {
            return card;
        }
        public void setCard(IdCard card) {
            this.card = card;
        }
        @Override
        public String toString() {
            return "Person [id=" + id + ", name=" + name + ", "
                    + "age=" + age + ", sex=" + sex + ", card=" + card + "]";
        }
    }
    Person

    嵌套查询:

    IdCardMapper.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.itheima.mapper.IdCardMapper">
    
      <!-- 根据id查询证件信息,最普通的配置信息 -->
      <select id="findCodeById" parameterType="Integer" resultType="IdCard">
          SELECT * from tb_idcard where id=#{id}
      </select>
    
    </mapper>

    PersonMapper.xml映射文件:

    <!-- 嵌套查询:通过执行另外一条SQL映射语句来返回预期的特殊类型 -->
        <select id="findPersonById" parameterType="Integer" 
                                          resultMap="IdCardWithPersonResult123">
            SELECT * from tb_person where id=#{id}
        </select>
         
         <!-- resultMap最终还是要将结果映射到pojo上,type就是指定映射到哪一个pojo -->
        <resultMap type="Person" id="IdCardWithPersonResult123">
            <id property="id" column="id" />
            <result property="name" column="name" />
            <result property="age" column="age" />
            <result property="sex" column="sex" />
            
            <!-- 一对一:association使用select属性引入另外一条SQL语句,是另一个映射文件的select元素id -->
            <association property="card" column="card_id" javaType="IdCard"
                select="com.itheima.mapper.IdCardMapper.findCodeById" />
        </resultMap>

    测试方法:

        /**
         * 嵌套查询
         */
        @Test
        public void findPersonByIdTest() {
            // 1、通过工具类生成SqlSession对象
            SqlSession session = MybatisUtils.getSession();
            // 2.使用MyBatis嵌套查询的方式查询id为1的人的信息
            Person person = session.selectOne("com.itheima.mapper." 
                                       + "PersonMapper.findPersonById", 1);
            // 3、输出查询结果信息
            System.out.println(person);
            // 4、关闭SqlSession
            session.close();
        }
    findPersonByIdTest()

    运行结果:执行了多条简单的SQL语句

    嵌套结果:

    <!-- 嵌套结果:使用嵌套结果映射来处理重复的联合结果的子集 -->
        <select id="findPersonById2" parameterType="Integer" 
                                           resultMap="IdCardWithPersonResult2">
            SELECT p.*,idcard.code
            from tb_person p,tb_idcard idcard
            where p.card_id=idcard.id 
            and p.id= #{id}
        </select>
        
        <resultMap type="Person" id="IdCardWithPersonResult2">
            <id property="id" column="id" /><!-- 声明主键,id是关联查询对象的唯一标识符 -->
            <result property="name" column="name" />
            <result property="age" column="age" />
            <result property="sex" column="sex" />
            <association property="card" javaType="IdCard">
                <id property="id" column="card_id" />
                <result property="code" column="code" />
            </association>
        </resultMap>
    PersonMapper.xml映射文件
        /**
         * 嵌套结果
         */
        @Test
        public void findPersonByIdTest2() {
            // 1、通过工具类生成SqlSession对象
            SqlSession session = MybatisUtils.getSession();
            // 2.使用MyBatis嵌套结果的方法查询id为1的人的信息
            Person person = session.selectOne("com.itheima.mapper." 
                                       + "PersonMapper.findPersonById2", 1);
            // 3、输出查询结果信息
            System.out.println(person);
            // 4、关闭SqlSession
            session.close();
        }
    测试方法

    测试结果:只执行了一条复杂的SQL语句。

    DEBUG [main] - ==> Preparing: SELECT p.*,idcard.code from tb_person p,tb_idcard idcard where p.card_id=idcard.id and p.id= ?
    DEBUG [main] - ==> Parameters: 1(Integer)
    DEBUG [main] - <== Total: 1
    Person [id=1, name=Rose, age=29, sex=女, card=IdCard [id=1, code=152221198711020624]]

    select p.*,idcard.code from tb_person p,tb_idcard idcard where p.card_id=idcard.id and p.id = #{id}

    这里的p是tb_person的别名,因为别名容易写,第二个tb_person后接空格再加p是格式,如果不用别名就是:

    select tb_person.*,tb_idcard.code from tb_person,tb_idcard where tb_person.card_id=tb_idcard.id and tb_person.id = #{id}

    一对一关系配置模板:

    9.3 一对多

    <resultMap>元素中,包含一个子元素<collection>元素,属性大部分和<association>元素相同,但有一个特殊属性ofType,这个属性和javaType属性对应,用于指定实体对象中集合类属性所包含的元素类型。

    <collection>元素的使用模板:

    1.在MySQL中建表tb_user和tb_orders,并插入几条数据

    USE mybatis;
    
    CREATE TABLE tb_user(
        id INT(32) PRIMARY KEY AUTO_INCREMENT,
        username VARCHAR(32),
        address VARCHAR(256)
    );
    INSERT INTO tb_user VALUES('1','詹姆斯','克利夫兰');
    INSERT INTO tb_user VALUES('2','科比','洛杉矶');
    INSERT INTO tb_user VALUES('3','保罗','洛杉矶');
    
    USER mybatis;
    CREATE TABLE tb_orders(
        id INT(32) PRIMARY KEY AUTO_INCREMENT,
        number VARCHAR(32) NOT NULL,
        user_id INT(32) NOT NULL,
        FOREIGN KEY(user_id) REFERENCES tb_user(id)
    );
    INSERT INTO tb_orders VALUES('1','1000011','1');
    INSERT INTO tb_orders VALUES('2','1000012','1');
    INSERT INTO tb_orders VALUES('3','1000013','2');

    结果:

    2.创建持久化类Orders和User

    package com.itheima.po;
    import java.util.List;
    /**
     * 用户持久化类
     */
    public class User {
        private Integer id;                 // 用户编号
        private String username;           // 用户姓名
        private String address;            // 用户地址
        private List<Orders> ordersList; //用户关联的订单...................................
        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 String getAddress() {
            return address;
        }
        public void setAddress(String address) {
            this.address = address;
        }
        public List<Orders> getOrdersList() {
            return ordersList;
        }
        public void setOrdersList(List<Orders> ordersList) {
            this.ordersList = ordersList;
        }
        @Override
        public String toString() {
            return "User [id=" + id + ", username=" + username + ", address="
                    + address + ", ordersList=" + ordersList + "]";
        }
    }
    User.java
    package com.itheima.po;
    
    import java.util.List;
    
    /**
     * 订单持久化类
     */
    public class Orders {
        private Integer id;    //订单id
        private String number;//订单编号
        //关联商品集合信息
        private List<Product> productList;
    
        
        public Integer getId() {
            return id;
        }
        public void setId(Integer id) {
            this.id = id;
        }
        public String getNumber() {
            return number;
        }
        public void setNumber(String number) {
            this.number = number;
        }
    //    @Override
    //    public String toString() {
    //        return "Orders [id=" + id + ", number=" + number + "]";
    //    }
        public List<Product> getProductList() {
            return productList;
        }
        public void setProductList(List<Product> productList) {
            this.productList = productList;
        }
        @Override
        public String toString() {
            return "Orders [id=" + id + ", number=" + number + ", productList=" + productList + "]";
        }
        
    }
    Orders.java

     3.创建映射文件UserMapper.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.itheima.mapper.UserMapper">
    
        <!-- 一对多:查看某一用户及其关联的订单信息 
              注意:当关联查询出的列名相同,则需要使用别名区分 -->   
        <select id="findUserWithOrders123" parameterType="Integer" 
                               resultMap="UserWithOrdersResult123">
            SELECT u.*,o.id as orders_id,o.number 
            from tb_user u,tb_orders o 
            WHERE u.id=o.user_id 
             and u.id=#{id}
        </select>
        
        <resultMap type="User" id="UserWithOrdersResult123">
            <id property="id" column="id"/>
            <result property="username" column="username"/>
            <result property="address" column="address"/>
            
            <!-- 一对多关联映射:collection 
                ofType表示属性集合中元素的类型,List<Orders>属性即Orders类 -->
            <collection property="ordersList" ofType="Orders">
                <id property="id" column="orders_id"/>
                <result property="number" column="number"/>
            </collection>
            
        </resultMap>
    </mapper>

    4.测试方法:

    /**
         * 一对多    
         */
        @Test
        public void findUserTest() {
            // 1、通过工具类生成SqlSession对象
            SqlSession session = MybatisUtils.getSession();
            // 2、查询id为1的用户信息
            User user = session.selectOne("com.itheima.mapper."
                                    + "UserMapper.findUserWithOrders123", 1);
            // 3、输出查询结果信息
            System.out.println(user);
            // 4、关闭SqlSession
            session.close();
        }
    findUserTest

    5.查询结果:

    DEBUG [main] - ==> Preparing: SELECT u.*,o.id as orders_id,o.number from tb_user u,tb_orders o WHERE u.id=o.user_id and u.id=?
    DEBUG [main] - ==> Parameters: 1(Integer)
    DEBUG [main] - <== Total: 2
    User [id=1, username=詹姆斯, address=克利夫兰, ordersList=[Orders [id=1, number=1000011, productList=null], Orders [id=2, number=1000012, productList=null]]]

     

    9.4 多对多

    以商品和订单为例,一个订单可以包含多个商品,一个商品可以属于多个订单,数据库中的多对多联系通常使用一个中间表来维护,中间表中的订单id作为外键参照订单表的id,商品id作为外键参照商品表的id。

    (外键:这个表的某一属性是别的表的主键,可以有重复,可以有多个,可以为空)

    1.建表:tb_product和tb_ordersitem

    USE mybatis;
    
    CREATE TABLE tb_product(
        id INT(32) PRIMARY KEY AUTO_INCREMENT,
        NAME VARCHAR(32),
        price DOUBLE
    );
    INSERT INTO tb_product VALUES('1','Java基础入门','44.5');
    INSERT INTO tb_product VALUES('2','Java Web程序开发入门','38.5');
    INSERT INTO tb_product VALUES('3','SSM框架整合实战','50');
    
    CREATE TABLE tb_ordersitem(
        id INT(32) PRIMARY KEY AUTO_INCREMENT,
        orders_id INT(32),
        product_id INT(32),
        FOREIGN KEY(orders_id) REFERENCES tb_orders(id),
        FOREIGN KEY(product_id) REFERENCES tb_product(id)
    );
    INSERT INTO tb_ordersitem VALUES('1','1','1');
    INSERT INTO tb_ordersitem VALUES('2','1','3');
    INSERT INTO tb_ordersitem VALUES('3','3','3');
    View Code

    建表后:

    2.创建持久化类Product表示商品,订单用以前的Order

    package com.itheima.po;
    import java.util.List;
    /**
     * 商品持久化类
     */
    public class Product {
        private Integer id;  //商品id
        private String name; //商品名称
        private Double price;//商品单价
        private List<Orders> orders; //与订单的关联属性...........................
        public Integer getId() {
            return id;
        }
        public void setId(Integer id) {
            this.id = id;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public Double getPrice() {
            return price;
        }
        public void setPrice(Double price) {
            this.price = price;
        }
        public List<Orders> getOrders() {
            return orders;
        }
        public void setOrders(List<Orders> orders) {
            this.orders = orders;
        }
        @Override
        public String toString() {
            return "Product [id=" + id + ", name=" + name 
                               + ", price=" + price + "]";
        }
    }
    Product.java

    3.创建订单实体映射文件OrdersMapper.xml和ProductMapper.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.itheima.mapper.OrdersMapper">
    
        <!-- 多对多嵌套查询:通过执行另外一条SQL映射语句来返回预期的特殊类型
        定义了一个id为findOrdersWithPorduct的select语句来查询订单及其关联的商品信息-->
        <select id="findOrdersWithPorduct" parameterType="Integer" 
                  resultMap="OrdersWithProductResult">
            select * from tb_orders WHERE id=#{id}    
        </select>
        
        <resultMap type="Orders" id="OrdersWithProductResult">
            <id property="id" column="id" />
            <result property="number" column="number" />
            
            <!-- property属性表示订单持久化类中的商品属性,ofType属性表示集合中的数据为Product类型,而column的
            属性值会作为参数执行ProductMapper中定义的id为findProductById的执行语句来查询订单中的商品信息-->
            <collection property="productList" column="id" ofType="Product" 
                 select="com.itheima.mapper.ProductMapper.findProductById">
            </collection>
            
        </resultMap>
        
        
        
        <!-- 多对多嵌套结果查询:查询某订单及其关联的商品详情 -->
        <select id="findOrdersWithPorduct2" parameterType="Integer" 
                 resultMap="OrdersWithPorductResult2">
            select o.*,p.id as pid,p.name,p.price
            from tb_orders o,tb_product p,tb_ordersitem  oi
            WHERE oi.orders_id=o.id 
            and oi.product_id=p.id 
            and o.id=#{id}
        </select>
        
        <!-- 自定义手动映射类型 -->
        <resultMap type="Orders" id="OrdersWithPorductResult2">
            <id property="id" column="id" />
            <result property="number" column="number" />
            <!-- 多对多关联映射:collection -->
            <collection property="productList" ofType="Product">
                <id property="id" column="pid" />
                <result property="name" column="name" />
                <result property="price" column="price" />
            </collection>
        </resultMap>
        
    </mapper>
    OrdersMapper.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.itheima.mapper.ProductMapper">
        <select id="findProductById" parameterType="Integer" 
                                           resultType="Product">
            SELECT * from tb_product where id IN(
               SELECT product_id FROM tb_ordersitem  WHERE orders_id = #{id}
            )
        </select>
    </mapper>
    ProductMapper.xml

    4.测试方法:

        /**
         * 多对多
         */
        @Test
        public void findOrdersTest(){
            // 1、通过工具类生成SqlSession对象
            SqlSession session = MybatisUtils.getSession();
            // 2、查询id为1的订单中的商品信息
            Orders orders = session.selectOne("com.itheima.mapper."
                                   + "OrdersMapper.findOrdersWithPorduct", 1);
            // 3、输出查询结果信息
            System.out.println(orders);
            // 4、关闭SqlSession
            session.close();
        }
    findOrdersTest()

    5.测试结果:

    DEBUG [main] - ==> Preparing: select * from tb_orders WHERE id=?
    DEBUG [main] - ==> Parameters: 1(Integer)
    DEBUG [main] - <== Total: 1
    DEBUG [main] - ==> Preparing: SELECT * from tb_product where id IN( SELECT product_id FROM tb_ordersitem WHERE orders_id = ? )
    DEBUG [main] - ==> Parameters: 1(Integer)
    DEBUG [main] - <== Total: 2
    Orders [id=1, number=1000011, productList=[Product [id=1, name=Java基础入门, price=44.5], Product [id=3, name=SSM框架整合实战, price=50.0]]]

  • 相关阅读:
    Luogu 1080 【NOIP2012】国王游戏 (贪心,高精度)
    Luogu 1314 【NOIP2011】聪明的质检员 (二分)
    Luogu 1315 【NOIP2011】观光公交 (贪心)
    Luogu 1312 【NOIP2011】玛雅游戏 (搜索)
    Luogu 1525 【NOIP2010】关押罪犯 (贪心,并查集)
    Luogu 1514 引水入城 (搜索,动态规划)
    UVA 1394 And Then There Was One / Gym 101415A And Then There Was One / UVAlive 3882 And Then There Was One / POJ 3517 And Then There Was One / Aizu 1275 And Then There Was One (动态规划,思维题)
    Luogu 1437 [HNOI2004]敲砖块 (动态规划)
    Luogu 1941 【NOIP2014】飞扬的小鸟 (动态规划)
    HDU 1176 免费馅饼 (动态规划)
  • 原文地址:https://www.cnblogs.com/shoulinniao/p/10988828.html
Copyright © 2011-2022 走看看