zoukankan      html  css  js  c++  java
  • Java实战之02Hibernate04多表映射

    十、多表映射

    0、内容补充:数据完整性

    作用:防止用户的误操作。

    实体完整性:主键。用于确定表中唯一的一条记录。

    域完整性:表中的字段。

    数据类型约束:

    非空约束:

    唯一约束:

    参照完整性:

    多表设计:表之间的关系

    一对多(用的最多的)

    多对多(比较重要)

    一对一(实际开发中,根本不用)

    1、一对多关系映射(非常重要)

    1.1、单向多对一映射

     1 /**
     2  * 客户的数据模型
     3  * @author zhy
     4  * 
     5  * 一个客户可以有多个订单
     6  * 多个订单属于一个客户。
     7  * 
     8  * 客户和订单之间的关系是一对多
     9  */
    10 public class Customer implements Serializable {
    11 
    12     private Integer id;
    13     private String name;
    14     private Integer age;
    15     
    16     //一对多关系映射:一个客户可以有多个订单
    17     private Set<Order> orders = new HashSet<Order>(0);
    18     
    19     
    20     public Integer getId() {
    21         return id;
    22     }
    23     public void setId(Integer id) {
    24         this.id = id;
    25     }
    26     public String getName() {
    27         return name;
    28     }
    29     public void setName(String name) {
    30         this.name = name;
    31     }
    32     public Integer getAge() {
    33         return age;
    34     }
    35     public void setAge(Integer age) {
    36         this.age = age;
    37     }
    38     public Set<Order> getOrders() {
    39         return orders;
    40     }
    41     public void setOrders(Set<Order> orders) {
    42         this.orders = orders;
    43     }
    44     @Override
    45     public String toString() {
    46         return "Customer [id=" + id + ", name=" + name + ", age=" + age + "]";
    47     }    
    48 }
     1 /**
     2  * 订单的数据模型
     3  * @author zhy
     4  *
     5  * 一个客户可以有多个订单
     6  * 多个订单属于一个客户。
     7  * 
     8  * 客户和订单之间的关系是一对多
     9  */
    10 public class Order implements Serializable {
    11 
    12     private Integer id;
    13     private String ordernum;
    14     private Float money;
    15     
    16     //多对一关系映射:多个订单属于一个客户。
    17     private Customer customer;
    18     
    19     
    20     public Integer getId() {
    21         return id;
    22     }
    23     public void setId(Integer id) {
    24         this.id = id;
    25     }
    26     public String getOrdernum() {
    27         return ordernum;
    28     }
    29     public void setOrdernum(String ordernum) {
    30         this.ordernum = ordernum;
    31     }
    32     public Float getMoney() {
    33         return money;
    34     }
    35     public void setMoney(Float money) {
    36         this.money = money;
    37     }
    38     public Customer getCustomer() {
    39         return customer;
    40     }
    41     public void setCustomer(Customer customer) {
    42         this.customer = customer;
    43     }
    44     @Override
    45     public String toString() {
    46         return "Order [id=" + id + ", ordernum=" + ordernum + ", money=" + money + "]";
    47     }
    48 }
     1 <hibernate-mapping package="cn.itcast.domain">
     2     <class name="Customer" table="T_CUSTOMERS">
     3         <id name="id" column="id">
     4             <generator class="native"></generator>
     5         </id>
     6         <property name="name" column="NAME"></property>
     7         <property name="age" column="AGE"></property>
     8         <!-- 一对多关系映射: 
     9             set元素:
    10                 作用:映射集合元素
    11                 属性:
    12                     name:映射实体类中的集合属性
    13                     table:指定对应的表
    14             key元素:它是set的子元素
    15                 作用:就是用于指定外键的
    16                 属性:
    17                     column:指定外键字段的名称
    18             one-to-many元素:它是set的子元素
    19                 作用:描述当前实体映射文件和set中指定属性之间的关系。
    20                 属性:
    21                     class:指定是从表的实体类名称        
    22         -->
    23         <set name="orders" table="T_ORDERS" cascade="save-update,delete" inverse="true">
    24             <key column="CUSTOMER_ID"></key>
    25             <one-to-many class="Order"/>
    26         </set>
    27     </class>
    28 </hibernate-mapping>
     1 <hibernate-mapping package="cn.itcast.domain">
     2     <class name="Order" table="T_ORDERS">
     3         <id name="id" column="id">
     4             <generator class="native"></generator>
     5         </id>
     6         <property name="ordernum" column="ORDERNUM"></property>
     7         <property name="money" column="MONEY"></property>
     8         
     9         <!-- 多对一关系映射 
    10             使用的元素:many-to-one
    11             属性:
    12                 name:指定的是在实体类中要映射的属性
    13                 class:指定该属性所对应的类
    14                 column:指定外键字段。
    15         -->    
    16         <many-to-one name="customer" class="Customer" column="CUSTOMER_ID" cascade="save-update"></many-to-one>
    17     </class>
    18 </hibernate-mapping>

    a、保存操作

     1 /*
     2      * 保存操作
     3      * 需求:
     4      *     保存两个订单,同时保存一个客户
     5      *  一定是先保存订单,再保存客户
     6      * 问题:
     7      *     当我们先保存订单,再保存客户时,会执行5条SQL语句
     8      *     Hibernate: insert into T_ORDERS (ORDERNUM, MONEY, CUSTOMER_ID) values (?, ?, ?)
     9         Hibernate: insert into T_ORDERS (ORDERNUM, MONEY, CUSTOMER_ID) values (?, ?, ?)
    10         Hibernate: insert into T_CUSTOMERS (NAME, AGE) values (?, ?)
    11         Hibernate: update T_ORDERS set ORDERNUM=?, MONEY=?, CUSTOMER_ID=? where id=?
    12         Hibernate: update T_ORDERS set ORDERNUM=?, MONEY=?, CUSTOMER_ID=? where id=?
    13           解决办法:
    14            实际上我们只需要三条insert语句就够了
    15            在保存时,先保存主表数据,再保存从表数据
    16      */
    17     @Test
    18     public void test1(){
    19         //数据准备
    20         Customer c1 = new Customer();
    21         c1.setName("test");
    22         c1.setAge(18);
    23         
    24         Order o1 = new Order();
    25         o1.setOrdernum("A001");
    26         o1.setMoney(100f);
    27         
    28         Order o2 = new Order();
    29         o2.setOrdernum("A002");
    30         o2.setMoney(200f);
    31         //建立单向多对一关联关系
    32         o1.setCustomer(c1);
    33         o2.setCustomer(c1);
    34             
    35         Session s = HibernateUtil.getSession();
    36         Transaction tx = s.beginTransaction();
    37         //保存操作
    38         s.save(c1);
    39         s.save(o1);
    40         s.save(o2);
    41         
    42         tx.commit();
    43         s.close();
    44     }

    b、查询操作

     1 @Test
     2     public void test2(){
     3         //数据准备
     4         Customer c1 = new Customer();//临时态
     5         c1.setName("test2");
     6         c1.setAge(28);
     7         
     8         Session s = HibernateUtil.getSession();
     9         Transaction tx = s.beginTransaction();
    10         //查询id为1的订单
    11         Order o1 = s.get(Order.class, 1);//持久态        
    12         tx.commit();
    13         s.close();
    14     }

    c、持久态引用临时态报错

     1     /*
     2      * 更新操作
     3      * 需求:
     4      *     先创建一个订单,然后查询出来一个客户。
     5      *     建立客户和新订单的关联关系。
     6      *     更新客户
     7      * 问题:
     8      *     一个持久态对象,关联了一个临时态的对象。
     9      * 解决办法:
    10      *     配置级联保存更新
    11      *     <set name="orders" table="T_ORDERS" cascade="save-update">
    12      */
    13     @Test
    14     public void test2(){
    15         
    16         //创建一个新的订单
    17         Order o1 = new Order();//临时态
    18         o1.setOrdernum("A003");
    19         o1.setMoney(100f);
    20         
    21         Session s = HibernateUtil.getSession();
    22         Transaction tx = s.beginTransaction();
    23         //查询一个客户
    24         Customer c1 = s.get(Customer.class, 1);//持久态
    25         //建立双向关联关系
    26         c1.getOrders().add(o1);
    27         o1.setCustomer(c1);
    28         //更新操作
    29         s.update(c1);
    30         
    31         tx.commit();
    32         s.close();
    33     }

    d、级联保存和更新

     1 /*
     2      * 
     3      * 更新操作
     4      * 需求:
     5      *     创建一个新的客户,查询出来一个订单。把新客户和查询的订单建立关联关系。
     6      *     然后更新订单
     7      * 问题:
     8      *     一个持久态对象,关联了一个临时态对象。会报错。
     9      * 解决办法:
    10      *     思路:在更新之前,先把临时态对象,转成持久态。(先执行保存,再执行更新)
    11      *  执行级联保存更新。
    12      *  在配置文件中配置:要想级联谁,就在对应的映射属性上配置
    13      *      cascade属性:就是用于配置级联操作的
    14      *  <many-to-one name="customer" class="Customer" column="CUSTOMER_ID" cascade="save-update">
    15      */
    16     @Test
    17     public void test2(){
    18         //数据准备
    19         Customer c1 = new Customer();//临时态
    20         c1.setName("test2");
    21         c1.setAge(28);
    22         
    23         Session s = HibernateUtil.getSession();
    24         Transaction tx = s.beginTransaction();
    25         //查询id为1的订单
    26         Order o1 = s.get(Order.class, 1);//持久态
    27         //建立订单和客户的单向多对一关联关系
    28         o1.setCustomer(c1);
    29         //更新订单
    30         s.update(o1);        
    31         tx.commit();
    32         s.close();
    33     }

    1.2、双向关联映射

    注意事项:

    Hibernate要求在持久化类中定义集合属性时,必须把属性声明为接口类型,如Set、Map、List.声明接口类型可提高持久化类的透明性。(与延迟加载有关)

    通常在定义集合属性时,直接初始化为一个实现类的实例。可避免空指针异常。 

    a、双向关联关系保存操作

     1 /*
     2      * 保存操作
     3      * 需求:
     4      *     先保存客户,再保存订单
     5           问题:
     6              当我们建立了双向关联关系之后,就算是先保存主表,再保存从表,也是会产生5条SQL语句
     7            Hibernate: insert into T_CUSTOMERS (NAME, AGE) values (?, ?)
     8         Hibernate: insert into T_ORDERS (ORDERNUM, MONEY, CUSTOMER_ID) values (?, ?, ?)
     9         Hibernate: insert into T_ORDERS (ORDERNUM, MONEY, CUSTOMER_ID) values (?, ?, ?)
    10         Hibernate: update T_ORDERS set CUSTOMER_ID=? where id=?
    11         Hibernate: update T_ORDERS set CUSTOMER_ID=? where id=?
    12            解决办法:
    13               思路:让主表的集合放弃维护关联关系的权利。
    14               操作方式:注释上
    15                   c1.getOrders().add(o1);
    16                   c1.getOrders().add(o2);
    17      */
    18     @Test
    19     public void test1(){
    20         //数据准备
    21         Customer c1 = new Customer();
    22         c1.setName("testC");
    23         c1.setAge(18);
    24         
    25         Order o1 = new Order();
    26         o1.setOrdernum("C001");
    27         o1.setMoney(100f);
    28         
    29         Order o2 = new Order();
    30         o2.setOrdernum("C002");
    31         o2.setMoney(200f);
    32         
    33         //建立双向一对多关联关系
    34         o1.setCustomer(c1);
    35         o2.setCustomer(c1);
    36         
    37         //c1.getOrders().add(o1);
    38         //c1.getOrders().add(o2);
    39         
    40         
    41         
    42         
    43         Session s = HibernateUtil.getSession();
    44         Transaction tx = s.beginTransaction();
    45         //保存操作
    46         s.save(c1);
    47         s.save(o1);
    48         s.save(o2);
    49         
    50         tx.commit();
    51         s.close();
    52     }

    b、双向关联关系,持久态关联临时态的问题及解决

     1 /*
     2      * 更新操作
     3      * 需求:
     4      *     先创建一个订单,然后查询出来一个客户。
     5      *     建立客户和新订单的关联关系。
     6      *     更新客户
     7      * 问题:
     8      *     一个持久态对象,关联了一个临时态的对象。
     9      * 解决办法:
    10      *     配置级联保存更新
    11      *     <set name="orders" table="T_ORDERS" cascade="save-update">
    12      */
    13     @Test
    14     public void test2(){
    15         
    16         //创建一个新的订单
    17         Order o1 = new Order();//临时态
    18         o1.setOrdernum("A003");
    19         o1.setMoney(100f);
    20         
    21         Session s = HibernateUtil.getSession();
    22         Transaction tx = s.beginTransaction();
    23         //查询一个客户
    24         Customer c1 = s.get(Customer.class, 1);//持久态
    25         //建立双向关联关系
    26         c1.getOrders().add(o1);
    27         o1.setCustomer(c1);
    28         //更新操作
    29         s.update(c1);
    30         
    31         tx.commit();
    32         s.close();
    33     }

    c、变更关系(关于双向关联的处理办法)

     1 /*
     2      * 需求:变更关系
     3      *     把id为1的订单,从属于2号客户,改为属于1号客户
     4      * 解决办法:
     5      *     使用配置的方式,来实现让有集合的一方,放弃维护的权利
     6      *     inverse:是否放弃维护的权利
     7      *     取值:true放弃  和  false 不放弃(默认值)。
     8      *  inverse用于应该只出现在set元素上
     9      * <set name="orders" table="T_ORDERS" cascade="save-update" inverse="true" > 
    10      */
    11     @Test
    12     public void test3(){
    13         Session s = HibernateUtil.getSession();
    14         Transaction tx = s.beginTransaction();
    15         Customer c1 = s.get(Customer.class, 1);//查询出来1号客户
    16         Order o1 = s.get(Order.class, 1);//查询出啦1号订单
    17         //建立双向关联关系
    18         c1.getOrders().add(o1);
    19         o1.setCustomer(c1);
    20         //更新操作
    21         s.update(c1);
    22         
    23         tx.commit();
    24         s.close();
    25         
    26         //System.out.println(c1.getOrders());
    27     }

    为了保持程序的健壮性,建议是双向关联,就建立双向关联的关系。

    弊端:当我们使用了双向关联时,会有冗余的SQL语句执行,造成程序的效率下降。

    解决办法:不要双向维护关联关系,让少的一方放弃维护权利。(但不要在代码中修改,而是写在配置文件中)

    d、解除关系

     1 /*
     2      * 解除关系
     3      * 需求:
     4      *     把1订单和1号客户之间的关系解除
     5      */
     6     @Test
     7     public void test6(){
     8         Session s = HibernateUtil.getSession();
     9         Transaction tx = s.beginTransaction();
    10         Customer c1 = s.get(Customer.class,1);
    11         Order o1 = s.get(Order.class, 1);
    12         
    13         //解除1号订单和1号客户之间的关系
    14         c1.getOrders().remove(o1);
    15         o1.setCustomer(null);
    16         
    17         
    18         tx.commit();
    19         s.close();    
    20     }

    e、删除操作

    /*
         * 需求:
         *     删除一个客户
         * 
         * 在直接删除客户的时候,如果客户的集合属性(set元素)上没有配置inverse=true,
         * 会直接把客户删除掉,同时把订单中关联改该客户的id置为null
         * 
         * 在直接删除客户的时候,如果客户的集合属性(set元素)上配置了inverse=true,
         * 如果有订单引用该客户的话,则不能删除成功。
         * 
         * 原因:
         *     因为客户的集合属性已经放弃维护和订单之间的关联关系,也就是说,它将不能把
         *  订单的CUSTOMER_ID列置为null。
         */
        @Test
        public void test5(){
            Session s = HibernateUtil.getSession();
            Transaction tx = s.beginTransaction();
            Customer c1 = s.get(Customer.class,3);
            s.delete(c1);
            tx.commit();
            s.close();    
        }
        
        /*
         * 需求:
         *     删除一个订单
         * 
         * 可以删除成功
         */
        @Test
        public void test4(){
            Session s = HibernateUtil.getSession();
            Transaction tx = s.beginTransaction();
            Order o1 = s.get(Order.class, 1);
            s.delete(o1);
            tx.commit();
            s.close();    
        }
    /*
         * 级联删除:
         * 
         *     <set name="orders" table="T_ORDERS" cascade="save-update,delete" inverse="true">
         */
        @Test
        public void test8(){
            Session s = HibernateUtil.getSession();
            Transaction tx = s.beginTransaction();
            Customer c1 = s.get(Customer.class,2);
            s.delete(c1);
            tx.commit();
            s.close();    
        }
        
        /*
         * 孤儿数据
         *      在一对多对象关系映射中,数据具有父子关系,当子数据和父数据之间失去了关联关系。子数据被称之为孤儿数据
         * 孤儿删除
         *     在hibernate中认为孤儿数据,是没有存在的意义,理应删除。
         * 需要在配置文件中配置
         *     孤儿删除的配置:cascade="delete-orphan"
         *     <set name="orders" table="T_ORDERS" cascade="save-update,delete-orphan" inverse="true">
         */
        @Test
        public void test7(){
            Session s = HibernateUtil.getSession();
            Transaction tx = s.beginTransaction();
            Customer c1 = s.get(Customer.class,1);
            Order o1 = s.get(Order.class, 1);
            
            //解除1号订单和1号客户之间的关系
            c1.getOrders().remove(o1);
            o1.setCustomer(null);
            
            //s.update(c1);
            tx.commit();//有快照机制的存在
            s.close();    
        }

    2、映射多对多

    2.1、多对多单项映射

    a、保存操作

     1 /**
     2  * 学生的实体模型
     3  * @author zhy
     4  * 
     5  * 一个教师可以教授多个学生
     6  * 一个学生可以被多个教师教授
     7  * 
     8  * 教师和学生的关系是多对多
     9  */
    10 public class Student implements Serializable {
    11 
    12     private Integer id;
    13     private String name;
    14     private String gender;
    15     
    16     //多对多关系映射
    17     private Set<Teacher> teachers = new HashSet<Teacher>(0);
    18     
    19     
    20     public Integer getId() {
    21         return id;
    22     }
    23     public void setId(Integer id) {
    24         this.id = id;
    25     }
    26     public String getName() {
    27         return name;
    28     }
    29     public void setName(String name) {
    30         this.name = name;
    31     }
    32     public String getGender() {
    33         return gender;
    34     }
    35     public void setGender(String gender) {
    36         this.gender = gender;
    37     }
    38     public Set<Teacher> getTeachers() {
    39         return teachers;
    40     }
    41     public void setTeachers(Set<Teacher> teachers) {
    42         this.teachers = teachers;
    43     }
    44     @Override
    45     public String toString() {
    46         return "Student [id=" + id + ", name=" + name + ", gender=" + gender + "]";
    47     }
    48 }
     1 <hibernate-mapping package="cn.itcast.domain">
     2     <class name="Student" table="T_STUDENTS">
     3         <id name="id" column="id">
     4             <generator class="native"></generator>
     5         </id>
     6         <property name="name" column="NAME"></property>
     7         <property name="gender" column="GENDER"></property>
     8         <!-- 多对多关系映射 
     9             set元素:
    10                 作用:就是用于映射集合属性
    11                 属性:
    12                     name:指定集合属性名称
    13                     table:指定的是关联关系表
    14             key元素:
    15                 作用:就是用于指定外键的
    16                 属性:
    17                     column:指定当前实体类在关联关系表中的外键
    18             many-to-many元素:
    19                 作用:指定和对方之间的关系是多对多
    20                 属性:
    21                     class:指定对方的实体类名称
    22                     column:指定对方在关联关系表中的外键-->
    23         <set name="teachers" table="t_teacher_student_ref" cascade="save-update,delete" >
    24             <key column="STUDENT_ID"/>
    25             <many-to-many class="Teacher" column="TEACHER_ID"/>
    26         </set>
    27     </class>
    28 </hibernate-mapping>
     1 /**
     2  * 教师的实体模型
     3  * @author zhy
     4  *
     5  * 一个教师可以教授多个学生
     6  * 一个学生可以被多个教师教授
     7  * 
     8  * 教师和学生的关系是多对多
     9  *
    10  */
    11 public class Teacher implements Serializable {
    12     
    13     private Integer id;
    14     private String name;
    15     private Float salary;
    16     
    17     //多对多关系映射
    18     private Set<Student> students = new HashSet<Student>(0);
    19     
    20     public Integer getId() {
    21         return id;
    22     }
    23     public void setId(Integer id) {
    24         this.id = id;
    25     }
    26     public String getName() {
    27         return name;
    28     }
    29     public void setName(String name) {
    30         this.name = name;
    31     }
    32     public Float getSalary() {
    33         return salary;
    34     }
    35     public void setSalary(Float salary) {
    36         this.salary = salary;
    37     }
    38     public Set<Student> getStudents() {
    39         return students;
    40     }
    41     public void setStudents(Set<Student> students) {
    42         this.students = students;
    43     }
    44     @Override
    45     public String toString() {
    46         return "Teacher [id=" + id + ", name=" + name + ", salary=" + salary + "]";
    47     }
    48 }
     1 <hibernate-mapping package="cn.itcast.domain">
     2     <class name="Teacher" table="T_TEACHERS">
     3         <id name="id" column="id">
     4             <generator class="native"></generator>
     5         </id>
     6         <property name="name" column="NAME"></property>
     7         <property name="salary" column="SALARY"></property>
     8         <!-- 多对多关系映射 -->
     9         <set name="students" table="t_teacher_student_ref" cascade="save-update,delete" inverse="true">
    10             <key column="TEACHER_ID"/>
    11             <many-to-many class="Student" column="STUDENT_ID"/>
    12         </set>
    13     </class>
    14 </hibernate-mapping>
     1 /*
     2      * 保存操作
     3      * 需求:
     4      *     创建:1号学生  2号学生 3号学生
     5      *         1号教师  2号教师
     6      *     建立关联关系
     7      *         1号教师教过  1号学生和2号学生
     8      *         2号教师教过  2号学生和3号学生
     9      *    执行保存
    10      *
    11      * 多对多关系映射,在执行保存操作时,需要有一方放弃维护的权利。
    12      * 任意一方
    13      */
    14     @Test
    15     public void test1(){
    16         //准备数据
    17         Teacher t1 = new Teacher();
    18         t1.setName("hqy");
    19         t1.setSalary(500f);
    20         
    21         Teacher t2 = new Teacher();
    22         t2.setName("lx");
    23         t2.setSalary(1000f);
    24         
    25         Student s1 = new Student();
    26         s1.setName("王占青");
    27         s1.setGender("male");
    28         
    29         Student s2 = new Student();
    30         s2.setName("秦鹏飞");
    31         s2.setGender("male");
    32         
    33         Student s3 = new Student();
    34         s3.setName("郑恒明");
    35         s3.setGender("male");
    36         
    37         //建立关联关系
    38         t1.getStudents().add(s1);
    39         t1.getStudents().add(s2);
    40         s1.getTeachers().add(t1);
    41         s2.getTeachers().add(t1);
    42         
    43         t2.getStudents().add(s2);
    44         t2.getStudents().add(s3);
    45         s2.getTeachers().add(t2);
    46         s3.getTeachers().add(t2);
    47         
    48         Session s = HibernateUtil.getSession();
    49         Transaction tx = s.beginTransaction();
    50         
    51         //保存操作 
    52         s.save(t1);
    53         
    54         tx.commit();
    55         s.close();
    56     }

    b、删除操作

    未配置级联:

     1 /*
     2      * 删除操作:
     3      *     删除id为1的教师
     4      * 注意事项:
     5      *     在多对多的删除操作时,不要配置级联删除。
     6      */
     7     @Test
     8     public void test2(){
     9         Session s = HibernateUtil.getSession();
    10         Transaction tx = s.beginTransaction();
    11         Teacher t1 = s.get(Teacher.class, 1);
    12         s.delete(t1);
    13         tx.commit();
    14         s.close();
    15     }

    如果在Teacher.hbm.xml中配置了级联,那么删除时会先删除teacher1这个老师,然后去删关联表中的数据。由于有级联,还会去学生表中删除对应的学生数据,但是由于student2这个学生被teacher2老师引用着所以删不掉。会报错。

    进一步,如果我们配置了双向级联,那么结果将是所有数据全没了。

    结论:

     1、多对多映射,不要配置级联删除。(可以配置级联保存)

     2、双向多对多映射时,不要双向维护关系。或让任意一方放弃权利,注意在配置文件中配置

    3、一对一映射

    3.1、按照外键关联

    3.2、按照主键关联

  • 相关阅读:
    利用定时器实时显示<input type="range"/>的值
    javascript日常总结
    SpringCloud如何上传大文件
    SpringBoot如何上传大文件
    SpringMVC如何上传大文件
    CSharp如何上传大文件
    C#如何上传大文件
    C#.NET如何上传大文件
    .NET如何上传大文件
    word文档的图片怎么保存到动易CMS上
  • 原文地址:https://www.cnblogs.com/minihouseCoder/p/5605283.html
Copyright © 2011-2022 走看看