美文网首页
MyBatis 由浅入深(实践篇)-3

MyBatis 由浅入深(实践篇)-3

作者: HansenGuan | 来源:发表于2017-09-18 22:14 被阅读0次

    MyBatis 高级知识

    1. 开发环境

    [Java环境] :jdk1.8.0_91
    [开发工具] : IDEA 2016.2.5
    [数据库] : MySQL 5.7.13
    [项目管理工具]:Maven 3.1.1

    1. 数据库
    --Table structure for tb_items
    DROP TABLE IF EXISTS `tb_items`;
    CREATE TABLE `tb_items` (
      `id` int(11) NOT NULL,
      `name` varchar(255) DEFAULT NULL COMMENT '商品名',
      `price` float(10,1) DEFAULT NULL COMMENT '价格',
      `detail` varchar(255) DEFAULT NULL COMMENT '商品介绍',
      `stock` int(255) DEFAULT NULL COMMENT '库存',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    
    -- Table structure for tb_orderdetail
    DROP TABLE IF EXISTS `tb_orderdetail`;
    CREATE TABLE `tb_orderdetail` (
      `id` int(11) NOT NULL,
      `order_id` int(11) NOT NULL COMMENT '订单id',
      `item_id` int(11) NOT NULL COMMENT '商品id',
      `count` int(11) DEFAULT NULL COMMENT '订单量',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    -- Table structure for tb_orders
    DROP TABLE IF EXISTS `tb_orders`;
    CREATE TABLE `tb_orders` (
      `id` int(11) NOT NULL,
      `userId` int(11) NOT NULL,
      `number` varchar(255) DEFAULT NULL COMMENT '订单号',
      `createtime` date DEFAULT NULL,
      `note` varchar(255) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    -- Table structure for tb_user
    DROP TABLE IF EXISTS `tb_user`;
    CREATE TABLE `tb_user` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `username` varchar(255) DEFAULT NULL,
      `sex` varchar(10) DEFAULT NULL,
      `birthday` date DEFAULT NULL,
      `address` varchar(500) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
    

    用户表 tb_user:记录用户信息
    订单表 tb_orders:记录用户所创建的订单(购买商品的订单)
    订单明细表 tb_orderdetail:记录了订单的详细信息即购买商品的信息
    商品表 tb_items:记录了商品信息

    表之间的关联关系

    实际业务中,实体与实体之间存在一定的业务关系(一对一,一对多,多对多),而关系性数据库通常是根据业务实体建表,所以上述几个关系也可以用来形容表之间的关联关系。

    那么根据上述所建的 订单类业务表分析:

    1. 用户(tb_user) 和 订单 (tb_orders)
      一个用户可以有多个订单(一对多)
      一个订单只能由一个用户创建(一对一)
    2. 订单(tb_orders) 和 订单详情(tb_orderdetail)
      一个订单可以有多个订单明细(一对多)
      一个订单明细只能属于一个订单(一对一)
    3. 订单详情(tb_orderdetail) 和 商品(tb_items)
      一个订单明细只能对应一个商品(一对一)
      一个商品可以包含在多个订单明细中(一对多)
    4. 订单(tb_orders) 和 商品(tb_items)
      一个订单可以包含多个商品,同理一个商品可以被多个订单包含(多对多)

    订单(tb_orders) 和 商品(tb_items)之间的这种多对多关系在很多实际业务中不利于DTO数据建模及开发维护,此时,使用订单详情(tb_orderdetail) 可以将 一个多对多关系拆分成两个一对多关系。
    多对多关系拆分成一对多关系,是简化数据库实体建模常用的一种方式

    复杂数据模型

    在实际开发过程中,经常从多个有关联关系的表中提取出符合业务需求的数据,此时需要将返回的数据封装成一个对象,此时这个对象就需要针对表关联关系(一对一,一对多,多对多)进行设计。
    如何使用 MyBatis 将这个包含关联关系的复杂对象与 sql 返回数据进行绑定,使程序运行时自动映射成该对象实例呢?

    一对一查询

    业务需求:查询订单列表用户及下订单的用户信息

    sql 实现业务:

    SELECT orders.*, USER.username, USER.sex, USER.address FROM tb_orders orders, tb_user user WHERE orders.userId = user.id
    
    

    使用 resultType

    1.创建实体对象(pojo)

    Order.java

    import cn.guan.mybatis.example2.User;
    
    import java.util.Date;
    import java.util.List;
    
    /**
     *  订单实体类
     **/
    public class Order {
        private Integer id;
        private Integer userId;
        private String number;
        private Date createtime;
        private String note;
        //用户信息
        private User user;
        //订单明细
        private List<Orderdetail> orderdetails;
    
        //setter and getter and toString
        ···
    }
    
    

    Orderdetail.java

    **
     * 订单详情实体类
     **/
    public class Orderdetail {
        private Integer id;
        private Integer order_id;
        private Integer itemsId;
        private Integer count;
        //明细对应的商品信息
        private Item items;
    
        //setter and getter and toString
        ···
    }
    
    

    Item.java

    /**
     * 商品实体类
     **/
    public class Item {
        private Integer id;
        private String name;
        private Float price;
        private String detail;
        private Integer stock;
        
        //setter and getter and toString
        ···
    }
    
    

    OrderCustom.java

    /**
     * @Description : 通过此类映射订单和用户查询的结果,让此类继承包括 字段较多的pojo类
     */
    public class OrderCustom extends Order{
    
        //用户属性
        private String username;
        private String sex;
        private String address;
    
        //setter and getter and toString
        ···
    }
    

    2.创建 Mapper 接口
    OrderCustomMapper.java

    /**
     *@Description : mapper 接口
     */
    public interface OrderCustomMapper {
        OrderCustom findOrdersUserByResultType() throws Exception;
    }
    
    

    3.创建 Mapper 接口对应 *-mapper.xml 并配置 路径
    ordercustom-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 :命名空间,对 sql 进行分类化管理,用于隔离 sql 语句-->
    <!--命名空间设置为 mapper 接口地址  -->
    <mapper namespace="cn.guan.mybatis.example4.OrderCustomMapper">
        <select id="findOrdersUserByResultType" resultType="cn.guan.mybatis.example4.OrderCustom">
            SELECT orders.*, USER.username, USER.sex, USER.address FROM tb_orders orders, tb_user user WHERE orders.userId = user.id
        </select>
    
    </mapper>
    

    mybatis-config.xml 中配置加载路径

      <!--加载 mapper 文件 路径-->
        <mappers>
            <mapper resource="mapperDir2/ordercustom-mapper.xml" />
        </mappers>
    

    4.测试

    MyBatisMappingTest.java

    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.Before;
    import org.junit.Test;
    
    import java.io.InputStream;
    
    /**
     * @Description : MyBatis 多种映射关系测试
     */
    public class MyBatisMappingTest {
    
        private SqlSessionFactory sqlSessionFactory;
    
        @Before
        public void init() throws Exception
        {
            //创建sqlSessionFactory
            //Mybatis 配置文件
            String resource = "mybatis-config.xml";
            //得到配置文件流
            InputStream inputStream = Resources.getResourceAsStream(resource);
            //创建会话工厂,传入Mybatis的配置文件信息
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        }
    
        @Test
        public void testOne2One(){
            SqlSession sqlSession = sqlSessionFactory.openSession();
            //创建usermapper对象,mybatis自动生成代理对象
            OrderCustomMapper orderCustomMapper = sqlSession.getMapper(OrderCustomMapper.class);
            //调用UserMapper的方法
            OrderCustom orderCustom = null;
            try {
                orderCustom = orderCustomMapper.findOrdersUserByResultType();
                System.out.println(orderCustom);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    

    使用 resultMap

    使用 resultMap 将查询结果中的订单信息映射到 Orders 对象中,在 orders 类中添加 User 属性,将关联查询出来的用户信息映射到 orders 对象中的 user 属性中。

    1.修改 ordercustom-mapper.xml
    定义 resultMap

    <?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 :命名空间,对 sql 进行分类化管理,用于隔离 sql 语句-->
    <!--命名空间设置为 mapper 接口地址  -->
    <mapper namespace="cn.guan.mybatis.example4.OrderCustomMapper">
    
        <!--定义查询订单关联查询用户信息的resultMap
              将整个查询结果映射到cn.guan.mybatis.example4.Order
          -->
        <resultMap id="OrderUserResultMap" type="cn.guan.mybatis.example4.Order">
            <!--配置映射的订单信息-->
            <!--id表示查询结果中的唯一标识  在这里是订单的唯一标识  如果是由多列组成的唯一标识,那么就需要配置多个id
            column:id 是订单信息中的唯一标识列
            property:id 是订单信息唯一标识列所映射到orders中的id属性
            最终resultMap对column和property做一个映射关系(对应关系)
            -->
            <id column="id" property="id"/>
            <result column="userId" property="userId"/>
            <result column="number" property="number"/>
            <result column="createtime" property="createtime"/>
            <result column="note" property="note"/>
            <!--配置映射的关联用户信息
                      association 用于映射关联查询单个对象的信息
                      property  将要关联查询的用户信息映射到 orders中的属性中去
                  -->
            <association property="user" javaType="cn.guan.mybatis.example2.User">
                <!--id 关联用户信息的唯一标识
                    column: 指定唯一标识用户的信息
                    property:映射到user的那个属性
                -->
                <id column="id" property="id"/>
                <result column="username" property="username"/>
                <result column="sex" property="sex"/>
                <result column="address" property="address"/>
                <result column="birthday" property="birthday"/>
            </association>
        </resultMap>
        
        <select id="findOrdersUserByResultMap" parameterType="int" resultMap="OrderUserResultMap">
            SELECT orders.*, USER.username, USER.sex, USER.address FROM tb_orders orders, tb_user user WHERE orders.userId = user.id
        </select>
    </mapper>
    
    

    2.新增一个接口
    OrderCustomMapper.java

    /**
     *@Description : mapper 接口
     */
    public interface OrderCustomMapper {
        OrderCustom findOrdersUserByResultType() throws Exception;
        List<Order> findOrdersUserByResultMap() throws Exception;
    }
    
    

    3.测试

        
        ···
    
        @Test
        public void testOne2OneByResultMap(){
            SqlSession sqlSession = sqlSessionFactory.openSession();
            //创建usermapper对象,mybatis自动生成代理对象
            OrderCustomMapper orderCustomMapper = sqlSession.getMapper(OrderCustomMapper.class);
            //调用UserMapper的方法
            List<Order> orderList = null;
            try {
                orderList = orderCustomMapper.findOrdersUserByResultMap();
                System.out.println(orderList);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
    

    一对多查询

    需求:查询订单及订单明细

    使用sql 实现业务:

    SELECT c.username,c.sex,c.address,a.*,b.id as orderdetailId,b.item_id as itemId,b.count 
    FROM tb_orders a 
    LEFT JOIN tb_orderdetail b on a.id = b.order_id
    LEFT JOIN tb_user c on a.userId = c.id;
    
    

    使用 resultType

    分析:
    如果使用 resultType 将查询结果映射到 pojo 中,订单信息就会重复。

    使用 resultMap

    预期:
    对 order 的映射不能出现重复记录。
    在 Order 中添加 一个 Orderdetail 的 List 属性,将查询结果中的订单信息映射到 Order 中,订单明细信息映射到 List<Orderdetail> 中,这样订单信息就不会重复。

    1.修改 ordercustom-mapper.xml
    定义 resultMap

    <?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 :命名空间,对 sql 进行分类化管理,用于隔离 sql 语句-->
    <!--命名空间设置为 mapper 接口地址  -->
    <mapper namespace="cn.guan.mybatis.example4.OrderCustomMapper">
    
        <!--定义查询订单关联查询用户信息的resultMap
              将整个查询结果映射到cn.guan.mybatis.example4.Order
          -->
        <resultMap id="OrderUserResultMap" type="cn.guan.mybatis.example4.Order">
            <!--配置映射的订单信息-->
            <!--id表示查询结果中的唯一标识  在这里是订单的唯一标识  如果是由多列组成的唯一标识,那么就需要配置多个id
            column:id 是订单信息中的唯一标识列
            property:id 是订单信息唯一标识列所映射到orders中的id属性
            最终resultMap对column和property做一个映射关系(对应关系)
            -->
            <id column="id" property="id"/>
            <result column="userId" property="userId"/>
            <result column="number" property="number"/>
            <result column="createtime" property="createtime"/>
            <result column="note" property="note"/>
            <!--配置映射的关联用户信息
                      association 用于映射关联查询单个对象的信息
                      property  将要关联查询的用户信息映射到 orders中的属性中去
                  -->
            <association property="user" javaType="cn.guan.mybatis.example2.User">
                <!--id 关联用户信息的唯一标识
                    column: 指定唯一标识用户的信息
                    property:映射到user的那个属性
                -->
                <id column="id" property="id"/>
                <result column="username" property="username"/>
                <result column="sex" property="sex"/>
                <result column="address" property="address"/>
                <result column="birthday" property="birthday"/>
            </association>
        </resultMap>
    
        <!--定义查询订单关联查询用户信息的resultMap
              将整个查询结果映射到cn.guan.mybatis.example4.Order
          -->
        <resultMap id="OrderAndOrderdetailsResultMap" type="cn.guan.mybatis.example4.Order" extends="OrderUserResultMap">
            <!-- 订单信息 -->
            <!-- 用户信息 -->
            <!-- 使用extends继承,不用在中配置订单信息和用户信息的映射 -->
            <!-- 订单明细信息
            一个订单关联查询出了多条明细,要使用collection进行映射
            collection:对关联查询到多条记录映射到集合对象中
            property:将关联查询到多条记录映射到cn.zhisheng.mybatis.po.Orders哪个属性
            ofType:指定映射到list集合属性中pojo的类型
             -->
            <collection property="orderdetails" ofType="cn.guan.mybatis.example4.Orderdetail">
                <!-- id:订单明细唯 一标识
           property:要将订单明细的唯 一标识 映射到cn.zhisheng.mybatis.po.Orderdetail的哪个属性-->
                <id column="orderdetailId" property="id"/>
                <result column="itemId" property="itemsId"/>
                <result column="count" property="count"/>
                <result column="orderId" property="order_id"/>
            </collection>
        </resultMap>
        
        <select id="findAllOrdersAndOrderdetails" parameterType="int" resultMap="OrderAndOrderdetailsResultMap">
           SELECT c.username,c.sex,c.address,a.*,b.id as orderdetailId,b.order_id as orderId ,b.item_id as itemId,b.count as count
            FROM tb_orders a
            LEFT JOIN tb_orderdetail b on a.id = b.order_id
            LEFT JOIN tb_user c on a.userId = c.id;
        </select>
    
    </mapper>
    

    2.新增一个接口

    OrderCustomMapper.java

    /**
     *@Description : mapper 接口
     */
    public interface OrderCustomMapper {
        OrderCustom findOrdersUserByResultType() throws Exception;
        List<Order> findOrdersUserByResultMap() throws Exception;
        List<Order> findAllOrdersAndOrderdetails() throws Exception;
    }
    
    

    3.测试

        
        ···
    
        @Test
        public void testOne2MoreByResultMap(){
            SqlSession sqlSession = sqlSessionFactory.openSession();
            //创建usermapper对象,mybatis自动生成代理对象
            OrderCustomMapper orderCustomMapper = sqlSession.getMapper(OrderCustomMapper.class);
            //调用UserMapper的方法
            List<Order> orderList = null;
            try {
                orderList = orderCustomMapper.findOrdersUserByResultMap();
                System.out.println(orderList);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
    

    多对多查询

    需求:查询用户及用户购买的商品信息

    sql 实现业务:

    SELECT c.username,c.sex,c.address,a.*,b.id as orderdetailId,b.order_id as orderId ,b.item_id as itemId,b.count as count,d.`name` as itemName,d.price as itemPrice,d.detail as itemDetail
    FROM tb_orders a 
    LEFT JOIN tb_orderdetail b on a.id = b.order_id
    LEFT JOIN tb_user c on a.userId = c.id
    LEFT JOIN tb_items d on b.item_id = d.id;
    

    映射思路:
    用户信息映射到 User 中,
    在 User 中添加 List<Order> 属性,用于映射用户下的订单
    Order 中的 List<Orderdetail> 用于映射 Order 对应的订单明细
    Orderdetail 中添加 Item 属性用于映射明细对应商品的信息

    1.pojo 类

    User.java

    
    public class User {
        //user 基本属性
        ···
        private List<Order> orders;
        
        //setter and getter and toString
        ···
    }
    
    

    Order.java

    public class Order {
        //Order 基本属性
        ···
        //订单明细
            private List<Orderdetail> orderdetails;
        
            //setter and getter and toString
            ···
    }
    
    

    Orderdetail.java

    public class Orderdetail {
        //Orderdetail 基本属性
        ···
        //明细对应的商品信息
            private Item items;
        
            //setter and getter and toString
            ···
    }
    
    

    2.映射文件

    userorders-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 :命名空间,对 sql 进行分类化管理,用于隔离 sql 语句-->
    <!--命名空间设置为 mapper 接口地址  -->
    <mapper namespace="cn.guan.mybatis.example4.UserOrdersMapper">
        <!--定义查询用户及用户购买商品信息的 resultMap-->
        <resultMap id="UserAndOrdersResultMap" type="cn.guan.mybatis.example4.User">
            <!--用户信息-->
            <id column="id" property="id"/>
            <result column="username" property="username"/>
            <result column="sex" property="sex"/>
            <result column="address" property="address"/>
            <result column="birthday" property="birthday"/>
    
            <!--订单信息
                一个用户对应多个订单,使用collection映射
            -->
            <collection property="orders" ofType="cn.guan.mybatis.example4.Order">
                <id column="id" property="id"/>
                <result column="userId" property="userId"/>
                <result column="number" property="number"/>
                <result column="createtime" property="createtime"/>
                <result column="note" property="note"/>
                <!-- 订单明细
                   一个订单包括 多个明细
               -->
                <collection property="orderdetails" ofType="cn.guan.mybatis.example4.Orderdetail">
                    <id column="orderdetailId" property="id"/>
                    <result column="itemId" property="itemsId"/>
                    <result column="count" property="count"/>
                    <result column="orderId" property="order_id"/>
    
                    <!-- 商品信息
                         一个订单明细对应一个商品
                    -->
                    <association property="items" javaType="cn.guan.mybatis.example4.Item">
                        <id column="itemId" property="id"/>
                        <result column="itemName" property="name"/>
                        <result column="itemPrice" property="price"/>
                        <result column="itemDetail" property="detail"/>
                    </association>
                </collection>
    
            </collection>
    
    
        </resultMap>
        
        <select id="findUserAndOrdersByUserId" parameterType="int" resultMap="UserAndOrdersResultMap">
           SELECT c.id,c.username,c.sex,c.address,a.*,b.id as orderdetailId,b.order_id as orderId ,b.item_id as itemId,b.count as count,d.`name` as itemName,d.price as itemPrice,d.detail as itemDetail
            FROM tb_orders a
            LEFT JOIN tb_orderdetail b on a.id = b.order_id
            LEFT JOIN tb_user c on a.userId = c.id
            LEFT JOIN tb_items d on b.item_id = d.id
            where c.id = #{value};
        </select>
    
    </mapper>
    
    

    加载映射文件:
    mybatis-config.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>
        <environments default="development">
            <environment id="development">
                <!-- 使用jdbc事务管理,事务由 Mybatis 控制-->
                <transactionManager type="JDBC" />
                <!-- 数据库连接池,由Mybatis管理,db_mybatis,Mysql用户名root,123456 -->
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.jdbc.Driver" />
                    <property name="url" value="jdbc:mysql://localhost:3306/db_mybatis?characterEncoding=utf-8" />
                    <property name="username" value="root" />
                    <property name="password" value="123456" />
                </dataSource>
            </environment>
        </environments>
        <!--加载 mapper 文件 路径-->
        <mappers>
            ···
            <mapper resource="mapperDir2/userorders-mapper.xml" />
        </mappers>
    </configuration>
    

    2.接口定义
    UserOrdersMapper.java

    import java.util.List;
    
    /用户及所下订单信息查询接口
    
    public interface UserOrdersMapper {
        List<User> findUserAndOrdersByUserId(int userid) throws  Exception;
    }
    

    3.测试

    
        ···
    
        @Test
        public void testMore2MoreByResultMap(){
            SqlSession sqlSession = sqlSessionFactory.openSession();
            //创建usermapper对象,mybatis自动生成代理对象
            UserOrdersMapper userOrdersMapper = sqlSession.getMapper(UserOrdersMapper.class);
            //调用UserMapper的方法
            List<User> users = null;
            try {
                users = userOrdersMapper.findUserAndOrdersByUserId(1);
                System.out.println(users);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    

    结果集映射总结

    1. resultType
      按照sql 查询结果的字段名(column)与 resultType 定义的 pojo 类的属性名一对一映射,完成映射。
      通常用于简单的不包含表连接关系的结果映射。

    2. resultMap
      需要完成一对一,一对多,多对多这些特殊的包含表连接关系的复杂结果映射。

    3. association
      将一对一的关联查询信息映射到 pojo 中,使用 resultType 无法完成成员属性为对象的自动映射。
      例:订单 pojo 中包含一个 User 对象,使用 resultType 无法将 sql 的查询结果中的 User 信息自动映射到 User 成员变量中。

    4. collection
      将一对多的关联查询信息映射到一个 List 集合中

    使用 resultMap 能对复杂的查询结果集进行面向对象设计,而且可以通过成员变量的形式,大大减少 pojo 类的创建,同时也可以减少冗余信息的存储,方便对结果集进行遍历查询。


    参考链接:
    http://blog.csdn.net/column/details/13905.html

    相关文章

      网友评论

          本文标题:MyBatis 由浅入深(实践篇)-3

          本文链接:https://www.haomeiwen.com/subject/akrisxtx.html