首页 IT技术

MySQL关联关系association:1对1关联的三种方法

时间:2019-06-20 08:45:14 分类:IT技术

association

原文出自:http://blog.csdn.net/soonfly/article/details/63688288 

本篇主要讲关联关系:一对一关系与一对多关系。 

先建5个表: 

这里写图片描述

DROP TABLE IF EXISTS `category`;
CREATE TABLE `category` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `catename` varchar(50) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;


DROP TABLE IF EXISTS `order`;
CREATE TABLE `order` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `orderno` varchar(20) NOT NULL COMMENT '系统订单号',
  `totalprice` decimal(10,2) DEFAULT NULL COMMENT '订单总价',
  `create_time` int(11) NOT NULL COMMENT '创建时间',
  `create_userid` int(10) unsigned NOT NULL COMMENT '创建用户',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8


DROP TABLE IF EXISTS `order_detail`;
CREATE TABLE `order_detail` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `order_id` int(10) unsigned NOT NULL,
  `product_id` int(10) unsigned NOT NULL,
  `productname` varchar(50) NOT NULL COMMENT '商品快照:名称',
  `price` decimal(10,2) NOT NULL COMMENT '商品快照:单价',
  `num` int(10) unsigned NOT NULL COMMENT '购买数量',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8


DROP TABLE IF EXISTS `product`;
CREATE TABLE `product` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `productname` varchar(50) NOT NULL,
  `price` decimal(10,2) DEFAULT NULL,
  `cateid` int(11) NOT NULL DEFAULT '1',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `username` varchar(50) DEFAULT NULL,
  `password` varchar(50) DEFAULT NULL,
  `salt` varchar(50) DEFAULT NULL,
  `sex` smallint(1) DEFAULT NULL COMMENT '0-未知 1-男 2-女',
  `address` varchar(50) DEFAULT NULL,
  `cellphone` varchar(30) DEFAULT NULL,
  `email` varchar(30) DEFAULT NULL,
  `islock` smallint(1) unsigned NOT NULL DEFAULT '0',
  `isvalidate` smallint(1) unsigned NOT NULL DEFAULT '1',
  `isdel` smallint(1) unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=111 DEFAULT CHARSET=utf8;

五个表关系如下: 

这里写图片描述 

梳理图上的4根关系线: 

1、1个用户可以有0个或多个订单:[user表]–>[order表]是1对多(0.n)关系 

2、1个有效订单会购买1条或多条商品分录:[order表]–>[order_detail表]是1对多(1.n)关系 

3、1条商品分录必定对应一个产品详情:[order_detail表]–>[product表]是1对1关系 

4、1个商品分类下可能会有0个或多个商品:[category表]–>[product表]是1对多(0.n)关系 

5、所有1对多关系,反过来一定是1对1关系。比如[user表]–>[order表]是1对多(0.n)关系,那么反过来[order表]–>[user表]是1对1关系。每个订单都会有与之对应的唯一用户。 

要注意,所有1对1关系,反过来却不一定是1对1关系。比如[order_detail表]–>[product表]是1对1关系,反过来[product表]–>[order_detail表]就不是1对1关系。因为同一个产品可能会在多个不同的订单分录中出现(热门商品大家都愿意买嘛)

这篇讲一对一关系 

[order表]–>[user表]是1对1关系,那么就拿这个来练手。1对1关联关系用于对一个表外键的扩展。 

现在要在后台系统中按以下字段显示订单列表。 

这里写图片描述 

[order表]中没有用户名称,用户地址,联系电话这三个字段,只能靠creat_userid去关联查询对应的那个用户信息。 

查询语句如下:

SELECT `order`.*,`user`.username,`user`.address,`user`.cellphone 
    FROM `order`,`user` 
    WHERE `order`.create_userid=`user`.id

有三种方式可以实现:

一、扩展新建POJO对象,不使用association标签

step1.我们已经有一个与表对应的Order类,但是没有用户名称,用户地址,联系电话这三个字段。现在新建一个类OrderExtend,扩充字段:

public class OrderExtend extends Order{
    public OrderExtend() {
        super();
    }
    /*添加用于展示的用户名称,用户地址,联系电话这三个字段*/
    String username;
    String address;
    String cellphone;

    /*下面get和set方法*/
    getter and setter....
}

step2.创建接口及Xml

public interface OrderExtendMapper {
    //查询单个订单详情,关联查询用户信息
    public OrderExtend getByOrderno(String orderno) throws Exception;
    //查询订单列表,关联查询用户信息
    public List<OrderExtend> getList() throws Exception;
}
<mapper namespace="twm.mybatisdemo.mapper.OrderExtendMapper">
    <select id="getByOrderno" parameterType="String"
        resultType="twm.mybatisdemo.pojo.OrderExtend">
        SELECT
        `order`.*,`user`.username,`user`.address,`user`.cellphone
        FROM `order` ,`user`
        WHERE `order`.create_userid=`user`.id AND `order`.create_userid=#{id}
    </select>

    <select id="getList" resultType="twm.mybatisdemo.pojo.OrderExtend">
        SELECT
        `order`.*,`user`.username,`user`.address,`user`.cellphone
        FROM `order`
        ,`user`
        WHERE `order`.create_userid=`user`.id
    </select>
</mapper>

step3.调用

public static void main(String[] args) throws Exception {
        SqlSession session = SqlSessionAssist.getSession();

        OrderExtendMapper ordermapper = session
                .getMapper(OrderExtendMapper.class);
        OrderExtend order = ordermapper.getByOrderno("M201209012578917");
        System.out.println(order.getOrderno() + "," + order.getUsername() + ","
                + order.getAddress() + "," + order.getCellphone());
    }

二、(推荐)用sql联合查询,使用association标签

这个不需要新建扩展类了。在Order类中,新增一个User类型的属性,将查询出来的用户相关数据通过association标签映射到user。 

association专门用来建立1对1关联关系。其中 

property:指定对象的属性名 

javaType:指定要映射的对象的类型。

step1.在Order类中,新增一个User类型的属性

public class OrderExtend extends Order{
    /*添加用于展示的用户名称,用户地址,联系电话这三个字段*/
    String username;
    String address;
    String cellphone;
    User user;

    public User getUser() {
        return user;
    }
    public void setUser(User user) {
        this.user = user;
    }
    /*下面get和set方法*/
    getter and setter....
}

step2.创建映射器接口及配置Xml 

twm.mybatisdemo.mapper包下创建 

OrderMapper.java:

public interface OrderMapper {
    //查询单个订单详情,关联查询用户信息
    public Order getByOrderno(String orderno) throws Exception;
    //查询订单列表,关联查询用户信息
    public List<Order> getList() throws Exception;
}

OrderMapper.xml:

<mapper namespace="twm.mybatisdemo.mapper.OrderMapper">
    <!-- 定义类型映射 -->
    <resultMap type="Order" id="OrderMap">
        <!-- 订单表属性 -->
        <id column="id" property="id" />
        <result column="orderno" property="orderno" />
        <result column="create_time" property="create_time" />
        <result column="create_userid" property="create_userid" />
        <!-- 关联的用户信息 -->
        <!-- association用于关联查询:
        property指属性,javaType是要映射的对象的类型。 -->
        <association property="user" javaType="User">
            <result column="username" property="username" />
            <result column="address" property="address" />
            <result column="cellphone" property="cellphone" />
        </association>
    </resultMap>

    <select id="getByOrderno" parameterType="String"
        resultMap="OrderMap">
        SELECT
        `order`.*,`user`.username,`user`.address,`user`.cellphone
        FROM `order`
        ,`user`
        WHERE `order`.create_userid=`user`.id AND
        `order`.orderno=#{orderno}
    </select>

    <select id="getList" resultMap="OrderMap">
        SELECT
        `order`.*,`user`.username,`user`.address,`user`.cellphone
        FROM `order`
        ,`user`
        WHERE `order`.create_userid=`user`.id
    </select>
</mapper>

step3.调用

public static void main(String[] args) throws Exception {
    SqlSession session = SqlSessionAssist.getSession();

    OrderMapper ordermapper = session.getMapper(OrderMapper.class);
    Order order = ordermapper.getByOrderno("M201209012578917");
    System.out.println(order.getOrderno() + ","
            + order.getUser().getUsername() + ","
            + order.getUser().getAddress() + ","
            + order.getUser().getCellphone());

}

三、不用sql联合查询,通过association的延迟加载来实现

什么是延迟加载?如果先查询订单信息即可满足业务要求就不会去查询用户,只有当用到用户信息时再查询用户信息。 

对用户信息按需去查询就是延迟加载。 

比如上面,只有当调用Order中的getUser方法获取关联的user数据时,才会触发数据库查询user表。

mybatis默认没有开启延迟加载,需要在SqlMapConfig.xml中setting配置。 

lazyLoadingEnabled:全局性设置懒加载。如果设为‘false’,则所有相关联的都会被初始化加载。允许值有:true | false。默认值:false 

aggressiveLazyLoading:当设置为‘true’的时候,懒加载的对象可能被任何懒属性全部加载。否则,每个属性都按需加载。允许值有:true | false。默认值:true

和第二种方式比,其它都不变。只是DAOImplement层有一些变化,XML文件要调整三处:

第一处:新增一个用户查询语句:

<!-- 新增一个用户查询语句:getUser -->
<select id="getUser" parameterType="int" resultType="User">
    SELECT
    `username`,`address`,`cellphone`
    FROM `user`
    WHERE `id` =#{_parameter}
</select>

第二处:把原来resultMap的association标签改为

<association property="user" javaType="User" column="create_userid" select="getUser" />

第三处:把getByOrderno和getList查询语句改为普通的select单表查询。

<?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="twm.mybatisdemo.mapper.OrderMapper">
    <!-- 定义类型映射 -->
    <resultMap type="Order" id="OrderMap">
        <!-- 订单表属性 -->
        <id column="id" property="id" />
        <result column="orderno" property="orderno" />
        <result column="create_time" property="create_time" />
        <result column="create_userid" property="create_userid" />
        <!-- 关联的用户信息 -->
        <!-- association用于关联查询: property指属性,javaType是要映射的对象的类型。 -->
        <association property="user" javaType="User" column="create_userid"
            select="getUser" />
    </resultMap>

<!-- 新增一个用户查询:getUser。getUser这一段可以删掉,用user对象的查询方法 -->
<select id="getUser" parameterType="int" resultType="User">
    SELECT
    `username`,`address`,`cellphone`
    FROM `user`
    WHERE `id` =#{_parameter}
</select>

    <select id="getByOrderno" parameterType="String" resultMap="OrderMap">
        SELECT * FROM `order`  WHERE `order`.orderno=#{orderno}
    </select>

    <select id="getList" resultMap="OrderMap">
        SELECT * FROM `order` 
    </select>
</mapper>

一切OK了。 

association的几个属性: 

property:指定内部对象属性名 

javaType:内部映射的对象的类型。 

column:要传给select语句的参数,相当于指定外键字段。 

select:指定用户查询语句的ID

getUser用户查询这一段语句也可以省略,因为之前在twm.mybatisdemo.mapper.UserMapper(UserMapper.xml)中创建过一个selectById查询。所以这里可以删掉getUser那一段查询,把association改一下:

<association property="user" javaType="User" column="create_userid" select="twm.mybatisdemo.mapper.UserMapper.selectById" />

事实上,大多数业务场景显示的表格,都会用到多个表字段。 

如果采用延迟加载,会存在N+1问题。 

什么是N+1问题呢? 

每一个获取Order内部的User对象,都会进行一次select查询 

那么当运行过程中执行Order的getList方法时,SQL首先进行1次查询,查询结果如果有N条订单记录,那么实际在每条订单中显示过程中还要运行一次select用户的查询,共n次。 

SQL总共执行了n+1次。相比第二种方法的只进行一次联合查询,这种方式无疑是低效的。 

如果业务场景的表格显示字段,并没有跨表,那么可以采用延迟加载方式

推荐文章

重点栏目推荐