• mybatis入门基础(六)----高级映射(一对一,一对多,多对多)


    一:订单商品数据模型

    1.数据库执行脚本

      创建数据库表代码:

     1 CREATE TABLE items (
     2   id INT NOT NULL  AUTO_INCREMENT,
     3   itemsname VARCHAR(32) NOT NULL COMMENT '商品名称',
     4   price FLOAT(10,1) NOT NULL COMMENT '商品定价',
     5   detail TEXT COMMENT '商品描述',
     6   pic VARCHAR(64) DEFAULT NULL COMMENT '商品图片',
     7   createtime DATETIME NOT NULL COMMENT '生产日期',
     8   PRIMARY KEY (id)
     9 )  DEFAULT CHARSET=utf8;
    10 
    11 /*Table structure for table `orderdetail` */
    12 
    13 CREATE TABLE orderdetail (
    14   id INT NOT NULL AUTO_INCREMENT,
    15  orders_id INT NOT NULL COMMENT '订单id',
    16   items_id INT NOT NULL COMMENT '商品id',
    17   items_num INT  DEFAULT NULL COMMENT '商品购买数量',
    18   PRIMARY KEY (id),
    19   KEY `FK_orderdetail_1` (`orders_id`),
    20   KEY `FK_orderdetail_2` (`items_id`),
    21   CONSTRAINT `FK_orderdetail_1` FOREIGN KEY (`orders_id`) REFERENCES `orders` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
    22   CONSTRAINT `FK_orderdetail_2` FOREIGN KEY (`items_id`) REFERENCES `items` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
    23 )  DEFAULT CHARSET=utf8;
    24 
    25 /*Table structure for table `orders` */
    26 
    27 CREATE TABLE orders (
    28   id INT NOT NULL AUTO_INCREMENT,
    29   user_id INT NOT NULL COMMENT '下单用户id',
    30   number VARCHAR(30) NOT NULL COMMENT '订单号',
    31   createtime DATETIME NOT NULL COMMENT '创建订单时间',
    32   note VARCHAR(100) DEFAULT NULL COMMENT '备注',
    33   PRIMARY KEY (`id`),
    34   KEY `FK_orders_1` (`user_id`),
    35   CONSTRAINT `FK_orders_id` FOREIGN KEY (`user_id`) REFERENCES `t_user` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
    36 )  DEFAULT CHARSET=utf8;
    37 
    38 /*Table structure for table `t_user` */
    39 
    40 CREATE TABLE t_user (
    41   id INT NOT NULL AUTO_INCREMENT,
    42   username VARCHAR(32) NOT NULL COMMENT '用户名称',
    43   birthday DATE DEFAULT NULL COMMENT '生日',
    44   sex CHAR(1) DEFAULT NULL COMMENT '性别',
    45   address  VARCHAR(256) DEFAULT NULL COMMENT '地址',
    46   PRIMARY KEY (`id`)
    47 ) DEFAULT CHARSET=utf8;
    View Code

      测试数据代码:

     1 /*Data for the table `items` */
     2 
     3 INSERT  INTO items(itemsname,price,detail,pic,createtime) VALUES 
     4 ('台式机',3000.0,'该电脑质量非常好!',NULL,'2015-07-07 13:28:53'),
     5 ('笔记本',6000.0,'笔记本性能好,质量好!',NULL,'2015-07-08 13:22:57'),
     6 ('背包',200.0,'名牌背包,容量大质量好!',NULL,'2015-07-010 13:25:02');
     7 
     8 /*Data for the table `orderdetail` */
     9 
    10 INSERT  INTO `orderdetail`(`orders_id`,`items_id`,`items_num`) VALUES
    11  (1,1,1),
    12  (1,2,3),
    13  (2,3,4),
    14  (3,2,3);
    15 
    16 /*Data for the table `orders` */
    17 
    18 INSERT  INTO `orders`(`user_id`,`number`,`createtime`,`note`) VALUES 
    19 (1,'1000010','2015-06-04 13:22:35',NULL),
    20 (1,'1000011','2015-07-08 13:22:41',NULL),
    21 (2,'1000012','2015-07-17 14:13:23',NULL),
    22 (3,'1000012','2015-07-16 18:13:23',NULL),
    23 (4,'1000012','2015-07-15 19:13:23',NULL),
    24 (5,'1000012','2015-07-14 17:13:23',NULL),
    25 (6,'1000012','2015-07-13 16:13:23',NULL);
    26 
    27 /*Data for the table `user` */
    28 
    29 INSERT  INTO `t_user`(`username`,`birthday`,`sex`,`address`) VALUES 
    30 ('王五',NULL,'2',NULL),
    31 ('张三','2014-07-10','1','北京市'),
    32 ('张小明',NULL,'1','河南郑州'),
    33 ('陈小明',NULL,'1','河南郑州'),
    34 ('张三丰',NULL,'1','河南郑州'),
    35 ('陈小明',NULL,'1','河南郑州'),
    36 ('王五',NULL,NULL,NULL),
    37  ('小A','2015-06-27','2','北京'),
    38 ('小B','2015-06-27','2','北京'),
    39 ('小C','2015-06-27','1','北京'),
    40 ('小D','2015-06-27','2','北京');
    View Code

    2.数据模型分析思路

    (1).每张表记录的数据内容:分模块对每张表记录的内容进行熟悉,相当 于你学习系统 需求(功能)的过程;

    (2).每张表重要的字段设置:非空字段、外键字段;

    (3).数据库级别表与表之间的关系:外键关系;

    (4).表与表之间的业务关系:在分析表与表之间的业务关系时一定要建立在某个业务意义基础上去分析

    3.针对订单商品模型的数据库思路分析:

      用户表:t_user-->记录了购买商品的用户信息

      订单表:orders-->记录了用户所创建的订单(购买商品的订单)

      订单明细表:orderdetail-->记录了订单的详细信息即购买商品的信息

      商品表:items-->记录了商品信息

    表与表之间的业务关系:

      在分析表与表之间的业务关系时需要建立 在某个业务意义基础上去分析。

      先分析数据级别之间有关系的表之间的业务关系:

    t_userorders

      t_user---->orders:一个用户可以创建多个订单,一对多

      orders--->t_user:一个订单只由一个用户创建,一对一

    orders和orderdetail

      orders--->orderdetail:一个订单可以包括多个订单明细,因为一个订单可以购买多个商品,每个商品的购买信息在orderdetail记录,一对多关系

      orderdetail--> orders:一个订单明细只能包括在一个订单中,一对一

    orderdetail和itesm

      orderdetail--->itesms:一个订单明细只对应一个商品信息,一对一

      items--> orderdetail:一个商品可以包括在多个订单明细 ,一对多

    再分析数据库级别没有关系的表之间是否有业务关系:

    orders和items

      ordersitems之间可以通过orderdetail表建立 关系。

    4.分析之后画出对应的图,方便直观的了解业务关系

    二、一对一查询

    2.1.需求:查询订单信息,关联查询用户信息

    2.2.resultType实现

    2.2.1.sql语句   

        确定查询的主表:订单表,确定查询的关联表:用户表。

    1 SELECT    t1.*,
    2         t2.username,
    3         t2.sex,
    4         t2.address
    5     FROM 
    6         orders t1,
    7         t_user t2
    8 WHERE t1.user_id=t2.id

    2.2.2.创建entity实体

      用户实体:User.java

    package com.mybatis.entity;
    import java.util.Date;
    import java.util.List;
    /** 
     * @ClassName: User
     * @Description: TODO(用户实体)
     * @author 阿赫瓦里
     */
    public class User {
        private Integer id;
        // 姓名
        private String username;
        // 性别
        private String sex;
        // 地址
        private String address;
        // 生日
        private Date birthday;
        // 用户创建的订单列表
        private List<Orders> ordersList;
           // getter and setter ......
    }

    订单实体:orders.java

    package com.mybatis.entity;
    import java.util.Date;
    import java.util.List;
    /**
     * @ClassName: Orders
     * @Description: TODO(订单实体)
     * @author 阿赫瓦里
     */
    public class Orders {
        /** 主键订单Id */
        private Integer id;
        /** 下单用户id */
        private Integer userid;
        /** 订单号 */
        private String number;
        /** 创建订单时间 */
        private Date createTime;
        /** 备注 */
        private String note;
        // 用户信息
        private User user;
        // 订单明细
        private List<OrderDetail> orderdetails;
           //  getter and setter ......
    }

    商品实体:Items.java

    package com.mybatis.entity;
    import java.util.Date;
    /**
     * @ClassName: Items
     * @Description: TODO(商品实体类)
     * @author 阿赫瓦里
     */
    public class Items {
        /** 商品表主键Id */
        private Integer id;
        /** 商品名称 */
        private String itemsName;
        /** 商品定价 */
        private float price;
        /** 商品描述 */
        private String detail;
        /** 商品图片 */
        private String picture;
        /** 生产日期 */
        private Date createTime;
    // getter and setter ......
    }

    订单明细实体:OrderDetail.java

    package com.mybatis.entity;
    /**
     * @ClassName: OrderDetail
     * @Description: TODO(订单明细实体)
     * @author 阿赫瓦里
     */
    public class OrderDetail {
        /** 主鍵,訂單明细表Id */
        private Integer id;
        /** 訂單Id */
        private Integer ordersId;
        /** 商品id */
        private Integer itemsId;
        /** 商品购买数量 */
        private Integer itemsNum;
        // 明细对应的商品信息
        private Items items;
            //  getter and setter ......
    }

     创建一个包装类,将查询到的信息可以全部映射到此类:OrdersCustom.java

    /**
     * @ClassName: OrdersCustom
     * @Description: TODO(订单的扩展类,通过此类映射订单和用户的查询结果,让此类继承字段较多的实体类)
     * @author: 阿赫瓦里
     */
    public class OrdersCustom extends Orders {
        // 添加用户的属性
        private String username;
        private String sex;
        private String address;
            // getter and setter......
    }

    2.2.3.创建OrdersCustomMapper.java

    package com.mybatis.Mapper;
    import java.util.List;
    import com.mybatis.entity.OrdersCustom;
    
    /**
     * @ClassName: OrdersMapperCustom
     * @Description: TODO(OrdersMapperCustom的mapper)
     * @author 阿赫瓦里
     */
    public interface OrdersCustomMapper {
        /** 查询订单,关联查询用户信息 */
        public List<OrdersCustom> findOrdersUser();
    }

    2.2.4.创建OrdersCustomMapper.xml和上面对应的接口名称一致,以便通过mapper接口加载配置文件

    <?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代理开发时,namespace有特殊作用,namespace等于mapper接口地址
     -->
    <mapper namespace="com.mybatis.mapper.OrdersCustomMapper">
            <!-- 查询订单,关联查询用户信息 -->
        <select id="findOrdersUser" resultType="com.mybatis.entity.OrdersCustom">
        SELECT t1.*,
            t2.username,
            t2.sex,
            t2.address
        FROM
            orders t1,
            t_user t2
        WHERE t1.user_id=t2.id
        </select>
    </mapper>

    2.3.resultMap实现

    2.3.1. sql语句同上

    2.3.2. resultMap映射思路:

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

    2.3.3 ordersCustomMapper.xml

       1. 定义resultMap

     1 <!-- 定义查询订单关联用户的 resultMap,将整个的查询结果映射到com.mybatis.entity.Orders中 -->
     2     <resultMap type="com.mybatis.entity.Orders" id="OrdersUserResultMap">
     3         <!-- 配置映射的订单信息 -->
     4         
     5         <!-- id:查询列中的唯一标识,订单信息中的唯一标识,如果多列组成唯一标识(如:一般数据库设计中的字典表 使用联合主键),就需要配置多个id 
     6             column:订单信息的唯一标识 列
     7             property:订单信息的唯一标识列所映射到orders中的那个属性(假如:数据库中orders表中的主键为orders_id,而实体属性名称为ordersId,
     8                 则这个配置应为<id column="orders_id" property="ordersId"/>,类似hibernate实体映射文件配置)。
     9         -->
    10         <id column="id" property="id"/>
    11         <result column="user_id" property="userid"/>
    12         <result column="number" property="number"/>
    13         <result column="createtime" property="createTime"/>
    14         <result column="note" property="note"/>
    15         
    16         <!-- 配置映射的关联用户信息 -->
    17         
    18         <!--association:用于映射关联查询单个对象的信息
    19             property:要将关联查询的用户信息映射到Orders中那个属性
    20           -->
    21         <association property="user" javaType="com.mybatis.entity.User">
    22             <!-- id:关联查询用户的唯一标识 
    23                 column:指定唯一标识用户信息的列
    24                 property:映射到user的那个属性
    25             -->
    26             <id column="user_id" property="id"/>
    27             <result column="username" property="username"/>
    28             <result column="sex" property="sex"/>
    29             <result column="address" property="address"/>
    30         </association>
    31         
    32     </resultMap>

       2. statement定义

     1     <!-- 查询订单,关联查询用户信息,使用resultMap实现 -->
     2     <select id="findOrdersUserResultMap" resultMap="OrdersUserResultMap">
     3             SELECT t1.*,
     4                 t2.username,
     5                 t2.sex,
     6                 t2.address
     7             FROM
     8                 orders t1,
     9                 t_user t2
    10             WHERE t1.user_id=t2.id
    11     </select>

     3.OrderCustomMapper.java接口中添加下面的方法

    /** 查询订单关联查询用户信息,使用reslutMap实现*/
    public List<Orders>findOrdersUserResultMap();

    4.对是resultType和resultMap实现的Junit测试

     1 package com.mybatis.test;
     2 
     3 import java.io.InputStream;
     4 import java.util.List;
     5 
     6 import org.apache.ibatis.io.Resources;
     7 import org.apache.ibatis.session.SqlSession;
     8 import org.apache.ibatis.session.SqlSessionFactory;
     9 import org.apache.ibatis.session.SqlSessionFactoryBuilder;
    10 import org.junit.Before;
    11 import org.junit.Test;
    12 
    13 import com.mybatis.entity.Orders;
    14 import com.mybatis.entity.OrdersCustom;
    15 import com.mybatis.mapper.OrdersCustomMapper;
    16 
    17 public class OrdersCustomMapperTest {
    18 
    19     private SqlSessionFactory sqlSessionFactory;
    20 
    21     // 此方法是在执行findUserByIdTest之前执行
    22     @Before
    23     public void setUp() throws Exception {
    24         String resource = "SqlMapConfig.xml";
    25         InputStream inputStream = Resources.getResourceAsStream(resource);
    26         // 创建SqlSessionFcatory
    27         sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    28     }
    29 
    30     // 查询订单,关联查询用户信息,使用resultType实现的测试
    31     @Test
    32     public void TestFindOrdersUser() {
    33         SqlSession sqlSession = sqlSessionFactory.openSession();
    34         // 创建代理对象
    35         OrdersCustomMapper oc = sqlSession.getMapper(OrdersCustomMapper.class);
    36         // 调用mapper的方法
    37         List<OrdersCustom> list = oc.findOrdersUser();
    38         System.out.println(list);
    39         sqlSession.close();
    40     }
    41 
    42     // 查询订单,关联查询用户信息,使用resultMap实现的测试
    43     @Test
    44     public void TestFindOrdersUserResultMap() {
    45         SqlSession sqlSession = sqlSessionFactory.openSession();
    46         // 创建代理对象
    47         OrdersCustomMapper oc = sqlSession.getMapper(OrdersCustomMapper.class);
    48         // 调用mapper的方法
    49         List<Orders> list = oc.findOrdersUserResultMap();
    50         System.out.println(list);
    51         sqlSession.close();
    52 
    53     }
    54 
    55 }
    View Code

    5.resultTyperesultMap实现一对一查询小结

     实现一对一查询:

      a.resultType:使用resultType实现较为简单,如果pojo中没有包括查询出来的列名,需要增加列名对应的属性,即可完成映射。

      b.如果没有查询结果的特殊要求建议使用resultType

      c.resultMap:需要单独定义resultMap,实现有点麻烦,如果对查询结果有特殊的要求,使用resultMap可以完成将关联查询映射pojo的属性中。

      d.resultMap可以实现延迟加载,resultType无法实现延迟加载。

    三、一对多查询

     3.1. 需求:查询订单(关联用户)及订单明细

     3.2. 在orders.java类中添加List<orderDetail> orderDetails属性(上面实体已添加)

        最终会将订单信息映射到orders中,订单所对应的订单明细映射到orders中的orderDetails属性中.

    3.3.在ordersCustomMapper.xml中添加如下代码

        <!-- 查询订单关联查询用户及订单明细 -->
        <select id="findOrdersAndOrderDetailResultMap" resultMap="ordersAndOrderDetailResultMap">
                SELECT 
                      t1.*,
                      t2.username,
                      t2.sex,
                      t2.address,
                      t3.id orderdetail_id,
                      t3.items_id,
                      t3.items_num,
                      t3.orders_id
                FROM
                      orders t1,
                      t_user t2,
                      orderdetail t3
                WHERE t1.user_id = t2.id AND t3.orders_id=t1.id
        </select>

    resultMap的定义同样添加到ordersCustomMapper.xml

    <!-- 查询订单(关联用户)及订单明细的resultMap -->
        <resultMap type="com.mybatis.entity.Orders" id="ordersAndOrderDetailResultMap" extends="OrdersUserResultMap">
            <!-- 订单信息 -->
            <!-- 关联用户信息 -->
            <!-- 使用extends继承,不用在中配置订单信息和用户信息的映射-->
            
            <!-- 关联订单明细信息 
                一个订单关联查询出了多条订单明细,要使用collection映射
                collection:对关联查询到的多条记录映射到集合中
                property:将关联查询到的多条记录映射到orders类的那个属性
                ofType:指定映射的集合属性中pojo的类型
            -->
            <collection property="orderdetails" ofType="com.mybatis.entity.OrderDetail">
                <!-- id:唯一标识
                     property:要将订单明细的唯一标识映射到com.mybatis.entity.OrderDetail的那个属性
                 -->
                <id column="orderdetail_id" property="id"/>
                <result column="items_id" property="itemsId"/>
                <result column="items_num" property="itemsNum"/>
                <result column="orders_id" property="ordersId"/>
            </collection>
        </resultMap>

    3.4. 在OrderCustomMapper.java接口类中添加一个方法

    /**查询订单(关联用户)以及订单明细*/
        public List<OrderDetail>findOrdersAndOrderDetailResultMap();

    3.5.在Junit测试类中添加测试方法

    // 查询订单(关联用户)以及订单明细的测试
        @Test
        public void TestFindOrdersAndOrderDetailResultMap() {
            SqlSession sqlSession = sqlSessionFactory.openSession();
            // 创建代理对象
            OrdersCustomMapper oc = sqlSession.getMapper(OrdersCustomMapper.class);
            // 调用mapper的方法
            List<OrderDetail> list = oc.findOrdersAndOrderDetailResultMap();
            System.out.println(list);
            sqlSession.close();  
        }

    3.6. 小结 

     mybatis使用resultMapcollection对关联查询的多条记录映射到一个list集合属性中。

     使用resultType实现:将订单明细映射到orders中的orderdetails中,需要自己处理,使用双重循环遍历,去掉重复记录,将订单明细放在orderdetails中。

    四、多对多查询

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

    4.2.映射思路

      将用户信息映射到user中。

      在user类中添加订单列表属性List<Orders> orderslist,将用户创建的订单映射到orderslist;

      在Orders中添加订单明细列表属性List<OrderDetail>orderdetials,将订单的明细映射到orderdetials;

      在OrderDetail中添加Items属性,将订单明细所对应的商品映射到Item;

    4.3.OrdersCustomMapper.xml添加如下代码

    <!-- 查询用户即购买的商品信息的ResultMap -->
            <resultMap type="com.mybatis.entity.User" id="userAndItemsResultMap">
                <!-- 用户信息 -->
                <id column="user_id" property="id"/>
                <result column="username" property="username"/>
                <result column="sex" property="sex"/>
                <result column="address" property="address"/>
            <!-- 订单信息
                一个用户对应多个订单,使用collection映射 -->
                <collection property="ordersList" ofType="com.mybatis.entity.Orders">
                     <id column="id" property="id"/>
                     <result column="user_id" property="userid"/>
                    <result column="number" property="number"/>
                    <result column="createtime" property="createTime"/>
                    <result column="note" property="note"/>
                
                 <!-- 订单明细
                         一个订单包括 多个明细
                      -->
                      <collection property="orderdetails" ofType="com.mybatis.entity.OrderDetail">
                              <id column="orderdetail_id" property="id"/>
                             <result column="items_id"   property="itemsId"/>
                             <result column="items_num"  property="itemsNum"/>
                             <result column="orders_id"  property="ordersId"/>
                             <!-- 商品信息
                                  一个订单明细对应一个商品
                               -->
                           <association property="items" javaType="com.mybatis.entity.Items">
                               <id column="items_id" property="id"/>
                               <result column="items_name" property="itemsName"/>
                               <result column="items_detail" property="detail"/>
                               <result column="items_price" property="price"/>
                           </association>
                      </collection>
                  </collection>
                
            </resultMap>
        <!-- 查询用户及用户购买的商品信息,使用resulaMap-->
        <select id="findUserAndItemsResultMap" resultMap="userAndItemsResultMap">
                SELECT 
                       t1.*,
                       t2.username,
                       t2.sex,
                       t2.address,
                       t3.id orderdetail_id,
                       t3.items_id,
                       t3.items_num,
                       t3.orders_id,
                       t4.itemsname items_name,
                       t4.detail items_detail,
                       t4.price items_price
                FROM
                      orders t1,
                      t_user t2,
                      orderdetail t3,
                      items t4
                WHERE t1.user_id =  t2.id AND  t3.orders_id=t1.id AND t3.items_id = t4.id
        </select>

    4.4. 在OrderCustomMapper.java添加如下方法

        /** 查询用户及用户所购买的商品信息 */
        public List<User> findUserAndItemsResultMap();

    4.5.在Junit测试类中添加测试方法 

        // 查询用户及用户购买的商品的信息
        @Test
        public void TestFindUserAndItemsResultMap() {
            SqlSession sqlSession = sqlSessionFactory.openSession();
            // 创建代理对象
            OrdersCustomMapper oc = sqlSession.getMapper(OrdersCustomMapper.class);
            // 调用mapper的方法
            List<User> list = oc.findUserAndItemsResultMap();
            System.out.println(list);
            sqlSession.close();
        }

    4.6.resultMap总结

    resultType

      作用:将查询结果按照sql列名pojo属性名一致性映射到pojo中。

      场合

        常见一些明细记录的展示,比如用户购买商品明细,将关联查询信息全部展示在页面时,此时可直接使用resultType将每一条记录映射到pojo中,在前端页面遍历listlist中是pojo)即可。

    resultMap

      使用associationcollection完成一对一和一对多高级映射(对结果有特殊的映射要求)。

    association

      作用:将关联查询信息映射到一个pojo对象中。

      场合

        为了方便查询关联信息可以使用association将关联订单信息映射为用户对象的pojo属性中,比如:查询订单及关联用户信息。

        使用resultType无法将查询结果映射到pojo对象的pojo属性中,根据对结果集查询遍历的需要选择使用resultType还是resultMap

    collection

      作用:将关联查询信息映射到一个list集合中。

      场合

        为了方便查询遍历关联信息可以使用collection将关联信息映射到list集合中,比如:查询用户权限范围模块及模块下的菜单,可使用collection将模块映射到模块list中,将菜单列表映射到模块对象的菜单list属性中,这样的作的目的也是方便对查询结果集进行遍历查询。

      如果使用resultType无法将查询结果映射到list集合中。

  • 相关阅读:
    机器学习项目流程
    机器学习之数据归一化问题
    python三数之和
    从不订购的客户
    case when then的用法-leetcode交换工资
    .NET Core Api 集成 swagger
    .NET CORE 2.1 导出excel文件的两种方法
    Dapper的基本使用
    (转)深入研究MiniMVC之后续篇
    (转)深入研究 蒋金楠(Artech)老师的 MiniMvc(迷你 MVC),看看 MVC 内部到底是如何运行的
  • 原文地址:https://www.cnblogs.com/selene/p/4627446.html
Copyright © 2020-2023  润新知