数据库中多表之间存在着三种关系。
系统设计的三种实体关系分别为:多对多、一对多和一对一关系。注意:一对多关系可以看为两种: 即一对多,多对一。所以说四种更精确。
基本步骤如下:
第一步:首先确定两张表之间的关系。
第二步:在数据库中实现两张表的关系
第三步:在实体类中描述出两个实体的关系
第四步:配置出实体类和数据库表的关系映射(重点)
思路分析:
表关系建立
在一对多关系中,我们习惯把一的一方称之为主表,把多的一方称之为从表。在数据库中建立一对多的关系,需要使用数据库的外键约束。
什么是外键
指的是从表中有一列,取值参照主表的主键,这一列就是外键。
场景分析
在实体类中,由于客户是少的一方,它应该包含多个联系人,所以实体类要体现出客户中有多个联系人的信息
客户实体类
/** * 客户的实体类 * 明确使用的注解都是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)
原文:https://www.cnblogs.com/zhaochengf/p/12127889.html