23 表之间关系之一对多

数据库中多表之间存在着三种关系。

系统设计的三种实体关系分别为:多对多、一对多和一对一关系。注意:一对多关系可以看为两种: 即一对多,多对一。所以说四种更精确。

基本步骤如下:

 第一步:首先确定两张表之间的关系。
 第二步:在数据库中实现两张表的关系
 第三步:在实体类中描述出两个实体的关系
 第四步:配置出实体类和数据库表的关系映射(重点)

思路分析:

表关系建立
在一对多关系中,我们习惯把一的一方称之为主表,把多的一方称之为从表。在数据库中建立一对多的关系,需要使用数据库的外键约束。

什么是外键
指的是从表中有一列,取值参照主表的主键,这一列就是外键。

场景分析
在实体类中,由于客户是少的一方,它应该包含多个联系人,所以实体类要体现出客户中有多个联系人的信息

客户实体类

/**
 * 客户的实体类
 * 明确使用的注解都是JPA规范的
 * 所以导包都要导入javax.persistence包下的
 */
@Entity//表示当前类是一个实体类
@Table(name="customer")//建立当前实体类和表之间的对应关系
@Data
public class Customer implements Serializable {
    
    @Id//表明当前私有属性是主键
    @GeneratedValue(strategy=GenerationType.IDENTITY)//指定主键的生成策略
    @Column(name="cid")//指定和数据库表中的cid列对应
    private Long cId;
    @Column(name="cname")//指定和数据库表中的cname列对应
    private String cName;
    @Column(name="source")//指定和数据库表中的source列对应
    private String source;
    @Column(name="industry")//指定和数据库表中的industry列对应
    private String industry;
    @Column(name="level")//指定和数据库表中的level列对应
    private String level;
    @Column(name="address")//指定和数据库表中的address列对应
    private String address;
    @Column(name="phone")//指定和数据库表中的phone列对应
    private String phone;
    
    //配置客户和联系人的一对多关系
    @OneToMany(targetEntity=LinkMan.class)
    @JoinColumn(name="cust_id",referencedColumnName="cust_id")
    private Set<LinkMan> linkmans = new HashSet<LinkMan>(0);
}

联系人实体类

/**
 * 联系人的实体类(数据模型)
 */
@Entity
@Table(name="linkman")
@Data
public class LinkMan implements Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="lid")
    private Long lId;
    @Column(name="lname")
    private String lName;
    @Column(name="lgender")
    private String lGender;
    @Column(name="lphone")
    private String lPhone;
    @Column(name="lmobile")
    private String lMobile;
    @Column(name="lemail")
    private String lEmail;
    @Column(name="lposition")
    private String lPosition;
    @Column(name="lmemo")
    private String lMemo;

    //多对一关系映射:多个联系人对应客户
    @ManyToOne(targetEntity=Customer.class)
    @JoinColumn(name="cust_id",referencedColumnName="cust_id")
    private Customer customer;//用它的主键,对应联系人表中的外键
}

映射的注解说明

@OneToMany:
       作用:建立一对多的关系映射
    属性:
        targetEntityClass:指定多的多方的类的字节码
        mappedBy:指定从表实体类中引用主表对象的名称。
        cascade:指定要使用的级联操作
        fetch:指定是否采用延迟加载
        orphanRemoval:是否使用孤儿删除

@ManyToOne
    作用:建立多对一的关系
    属性:
        targetEntityClass:指定一的一方实体类字节码
        cascade:指定要使用的级联操作
        fetch:指定是否采用延迟加载
        optional:关联是否可选。如果设置为false,则必须始终存在非空关系。

@JoinColumn
     作用:用于定义主键字段和外键字段的对应关系。
     属性:
        name:指定外键字段的名称
        referencedColumnName:指定引用主表的主键字段名称
        unique:是否唯一。默认值不唯一
        nullable:是否允许为空。默认值允许。
        insertable:是否允许插入。默认值允许。
        updatable:是否允许更新。默认值允许。
        columnDefinition:列的定义信息。

测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath*:*applicationContext.xml")
public class  OneToManyTest {
@Autowired
private CustomerDao customerDao;
@Autowired
private LinkManDao linkManDao;
      /**
       保存操作
       需求:
       保存一个客户和一个联系人
       要求:
       创建一个客户对象和一个联系人对象
       建立客户和联系人之间关联关系(双向一对多的关联关系)
     *  先保存客户,再保存联系人
     * 问题:
     * 当我们建立了双向的关联关系之后,先保存主表,再保存从表时:
     * 会产生2条insert和1条update.
     * 而实际开发中我们只需要2条insert。 
 */

@Test
@Transactional  //开启事务
@Rollback(false)//设置为不回滚
public  void testAdd() {
Customer c
= new Customer(); c.setCustName("张三"); c.setCustLevel("VIP客户"); c.setCustSource("58同城"); c.setCustIndustry("商业办公"); c.setCustAddress("软件园"); c.setCustPhone("010-10021123");
LinkMan l
= new LinkMan(); l.setLkmName("张总"); l.setLkmGender("男"); l.setLkmMobile("15511111111"); l.setLkmEmail("15511111111@qq.com"); l.setLkmPosition("董事长"); l.setLkmMemo("和蔼可亲");
c.getLinkMans().add(l);
l.setCustomer(c); customerDao.save(c); linkManDao.save(l); } }

 通过保存的案例,我们可以发现在设置了双向关系之后,会发送两条insert语句,一条多余的update语句,那我们的解决是思路很简单,就是一的一方放弃维护权

    /**
     *放弃外键维护权的配置将如下配置改为
     */
//@OneToMany(targetEntity=LinkMan.class)
//@JoinColumn(name="cust_id",referencedColumnName="cust_id")    
//设置为
    @OneToMany(mappedBy="customer")

删除

@Autowired
    private CustomerDao customerDao;
    
    @Test
    @Transactional
    @Rollback(false)//设置为不回滚
    public void delete() {
        customerDao.delete(1l);
    }   

删除操作的说明如下:

删除从表数据:可以随时任意删除。

删除主表数据:
 如果有从表数据
  1、在默认情况下,它会把外键字段置为null,然后删除主表数据。如果在数据库的表结构上,外键字段有非空约束,默认情况就会报错了。
  2、如果配置了放弃维护关联关系的权利,则不能删除(与外键字段是否允许为null,没有关系)因为在删除时,它根本不会去更新从表的外键字段了。
  3、如果还想删除,使用级联删除引用
没有从表数据引用:随便删
在实际开发中,级联删除请慎用!(在一对多的情况下)

级联操作:指操作一个对象同时操作它的关联对象

使用方法:只需要在操作主体的注解上配置cascade

  /**
     * cascade:配置级联操作
     *         CascadeType.MERGE    级联更新
     *         CascadeType.PERSIST    级联保存:
     *         CascadeType.REFRESH 级联刷新:
     *         CascadeType.REMOVE    级联删除:
     *         CascadeType.ALL        包含所有
     */
    @OneToMany(mappedBy="customer",cascade=CascadeType.ALL)

 

posted @ 2019-12-31 23:16  zhaochengf  阅读(457)  评论(0编辑  收藏  举报