假如现在有一个用户的注册功能,这个功能可能要涉及到10个字段的填写,那么这个时候如果上来就填写10个信息,那么可能注册的人就少了。现在的开发往往都会提供一个快速注册和一个信息维护。那么这种情况就可以用一对一关系来描述。
【定义数据库的创建脚本】
DROP TABLE IF EXISTS member_details;
DROP TABLE IF EXISTS member_login;
CREATE TABLE member_login(
mid VARCHAR(50),
password VARCHAR(32),
CONSTRAINT pk_mid PRIMARY KEY(mid)
);
CREATE TABLE member_details(
mid VARCHAR(50),
name VARCHAR(50),
email VARCHAR(50),
phone VARCHAR(50),
CONSTRAINT pk_mid2 PRIMARY KEY(mid),
CONSTRAINT fk_mid FOREIGN KEY(mid) REFERENCES member_login(mid) ON DELETE CASCADE
);
很明显看出,一张表为主表,而另一张表为子表,但是在子表里面mid这个列不允许重复必须是外键,那么就表示一个主表的数据只能和一个字表的数据关联在一起。
基于*.hbm.xml文件的实现
如果要想实现一对一的关系就必须同时选择好两张关联的表。
【观察MemberLogin.java类和MemberDetails.java类】
@SuppressWarnings("serial")
public class MemberLogin implements Serializable {
private String mid;
private String password;
private MemberDetails memberDetails = new MemberDetails();
//方法略
}
@SuppressWarnings("serial")
public class MemberDetails implements Serializable {
private String mid;
private String name;
private String email;
private String phone;
private MemberLogin memberLogin = new MemberLogin();
//方法略
}
一对一关联仅靠类的关联关系是远远不足的,还需要涉足到配置文件之中。
【观察MemberLogin.hbm.xml文件】
<class name="com.gub.vo.MemberLogin" table="member_login" schema="company">
<id name="mid" column="mid"/>
<property name="password" column="password"/>
<!--明确表示了此处要配置一对一的映射关系-->
<!--
name:表示类中的属性
class:表示属性对应的类型
-->
<one-to-one name="memberDetails" class="com.gub.vo.MemberDetails" />
</class>
【观察MemberDeatils.hbm.xml文件】
<class name="com.gub.vo.MemberDetails" table="member_details" schema="company">
<id name="mid" column="mid"/>
<property name="name" column="name"/>
<property name="email" column="email"/>
<property name="phone" column="phone"/>
<!--
一对一映射关系,其中
name:表示MemberDetails中的属性
class:属性对应的类型
constrained:约束属性
-->
<one-to-one name="memberLogin" class="com.gub.vo.MemberLogin" constrained="true" />
</class>
在配置one-to-one关系的时候,有时候会出现一个“constraint=true”的配置,此属性表示先增加主表数据,再增加子表的数据。(如果没有此配置,则表示先增加子表数据,后再增加主表数据)
【数据增加】
public static void main(String[] args) {
MemberLogin login = new MemberLogin();
login.setMid("用户9871574255");
login.setPassword("123456");
HibernateSessionFactory.getSession().save(login);
HibernateSessionFactory.getSession().beginTransaction().commit();
HibernateSessionFactory.closeSession();
}
发现栈溢出了,因此需要修改MemberLogin.java和MebmerDetails.java类,去掉关键字new。再次执行发现增加成功(因为没有涉及到MemberDetails数据对象的保存,所以只保存了MemberLogin的数据)。
【保存完整信息】
public static void main(String[] args) {
MemberLogin login = new MemberLogin();
login.setMid("用户9871574255");
login.setPassword("123456");
MemberDetails details = new MemberDetails();
details.setMid("用户9871574255");
details.setEmail("123@789.com");
details.setName("没名字");
details.setPhone("没电话");
details.setMemberLogin(login);
login.setMemberDetails(details);
HibernateSessionFactory.getSession().save(login);
HibernateSessionFactory.getSession().beginTransaction().commit();
HibernateSessionFactory.closeSession();
}
因为Hibetnate不支持数据级联操作(如果此时执行程序,只会增加主表数据,子表并不会增加),所以必须修改MemberLogin.hbm.xml文件,加入数据集连的配置。
<!--
one-to-one:明确表示了此处要配置一对一的映射关系
name:表示类中的属性
class:表示属性对应的类型
cascade:表示MemberLogin操作的时候MemberDetails要一起进行级联操作
-->
<one-to-one name="memberDetails" class="com.gub.vo.MemberDetails" cascade="all" />
执行代码发现数据增加成功:
观察控制台输出发现,Hibernate在执行增加操作时先查询member_details表中的数据,如果不存在则进行增加,如果存在则进行修改。查询后首先增加主表数据,主表数据增加完成之后再增加子表数据。
对于数据的修改分为两种情况:如果存在member_details表的数据如何修改?如果member_details表的数据不存在又如何修改?
【数据修改】
public static void main(String[] args) {
MemberLogin login = new MemberLogin();
login.setMid("用户9871574255");
login.setPassword("hahaha");
MemberDetails details = new MemberDetails();
details.setMid("用户9871574255");
details.setEmail("123@789.com");
details.setName("名字-1");
details.setPhone("电话-1");
details.setMemberLogin(login);
login.setMemberDetails(details);
HibernateSessionFactory.getSession().update(login);
HibernateSessionFactory.getSession().beginTransaction().commit();
HibernateSessionFactory.closeSession();
}
如果原来没有MemberDetails数据,在运行程序之后会为其添加数据:
如果原本已经存在MemberDetails对应的数据,现在完整更新:
通过观察发现,Hibernate在执行update()方法时先查询判断子表中存不存在指定的数据,若子表没有对应的数据,则发出增加子表数据的操作,随后发出跟更新主表数据的操作,若查询到子表存在有对应的数据,则先更新主表的数据,随后在更新子表的数据。
【数据查询】
public static void main(String[] args) {
MemberLogin memberLogin = (MemberLogin)HibernateSessionFactory.getSession().get(MemberLogin.class,"用户98715f74255");
System.out.println(memberLogin);
HibernateSessionFactory.closeSession();
}
MemberLogin{mid=‘用户98715f74255’, password=‘hahaha’, memberDetails=MemberDetails{mid=‘用户98715f74255’, name=‘名字-1’, email=‘123@789.com’, phone=‘电话-1’}}
通过观察sql语句发现,虽然数据查询成功,但是用的是多表查询(Hibernate使用的查询模式就是多表查询),性能较低。
【修改查询模式】
在MemberLogin.hbm.xml文件中修改fetch属性,fetch属性主要是指表级联数据的处理模式,有两种方式:
属性值 | 解释 |
---|---|
fetch=“join” | 使用多表连接进行组合查询 |
fetch=“select” | 使用单查询实现 |
<one-to-one
name="memberDetails"
class="com.gub.vo.MemberDetails"
cascade="all"
fetch="select"/>
观察SQL语句发现变成了两个查询,第一个查询的是主表,第二个查询的是子表。
【使用Query接口查询】
public static void main(String[] args) {
String hql = "FROM MemberLogin WHERE mid=? ";
Query query = HibernateSessionFactory.getSession().createQuery(hql);
query.setParameter(0,"用户9871574255");
MemberLogin login = (MemberLogin)query.uniqueResult();
System.out.println(login);
HibernateSessionFactory.closeSession();
}
执行代码后发现执行的sql语句与使用get()方法查询相同,一对一关系虽然分为了两张数据表,但是只有一行数据。
基于Annotation的配置
【配置MemberLogin类】
MemberLogin类中的注解按照常规配置即可,主要是在getMemberDetails()方法上添加OneToOne注解
@OneToOne(fetch=FetchType.LAZY,mappedBy = "memberLogin",cascade = CascadeType.ALL)
public MemberDetails getMemberDetails() {
return memberDetails;
}
【配置MemberDetails类】
MemberDetails表是MemberLogin的子表,同理需要在getMemberLogin()方法上配置注解
@OneToOne(fetch = FetchType.LAZY)
@PrimaryKeyJoinColumn
public MemberLogin getMemberLogin() {
return memberLogin;
}
执行以上全部测试方法,发现与hbm.xml文件配置一值。