在之前的文章中,我们已经了解了Hibernate实体的四种持久状态,我们注意到当标注了特定的注解之后Hibernate可以标识java 对象为持久状态。否则他们就是普通的java对象没有和数据库由直接的关系。当我们在java类上标注了JPA注解并使他们成为持久实体之后,我们面对的一个问题就是两个实体间可以有一种关系而且必须一个实体引用另一个实体,以一种直接或者间接的方式,在我们创建两个实体的引用之前先了解一些基本的事情。
理解实体和他们的关系
实体可以包含其他实体的引用,要么直接嵌入到属性或者字段中,要么间接的通过集合(arrays,sets,lists等)建立这种关系。这种关系代表了使用外键的关系,这些外键依赖于参与表的标识。仅仅当实体的一部分包含其他实体的引用时,这种关系不是直接的,如果关系是互相的,那么这种引用是双向的。
初级开发者一个常犯的错误是,当设计实体模型的时候,总是尝试着双向建立关系,记住“关系”并不是对象模式的原有部分,不应该强制把它加进去。Hibernate Query Language(HQL)提供了更常用的方式访问需要的信息。
理想情况下,我们表述关系一端的变化将会导致外键那一端的更新,确实Hibernate允许我们通过标注关系的另一端来这么做。在Hibernate的映射关系中,一个(仅仅一个)参与的类被指定为“管理关系”,另一个使用mappedBy属性指定为“被管理者”,我们不应该指定两端都为管理者,永远都不要这样做。
注意:mappedBy仅仅是实体间要保存的外键关系。和使用级联函数保存实体没有任何关系。尽管Hibernate允许我们关系一端的改变会导致数据库的改变,但是并不允许我们在java pojo上一端的改变会自动的反映到另一端上,如果这么做的话,我们必须使用级联关系。
用例子理解关系
我们快速的建立一个例子来理解我们已经读到的有关实体的关系,这里我使用了两个实体Account实体和EMPLOYEE实体,然后我创建一对一的关系实体,然后我们看一下在运行时的行为和复杂性
@Entity
@Table(name = "Account")
public class AccountEntity implements Serializable
{
private static final long serialVersionUID = 1L;
@Id
@Column(name = "ID", unique = true, nullable = false)
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Integer accountId;
@Column(name = "ACC_NO", unique = false, nullable = false, length = 100)
private String accountNumber;
//We will define the association here
EmployeeEntity employee;
//Getters and Setters are not shown for brevity
}
@Entity
@Table(name = "Employee")
public class EmployeeEntity implements Serializable
{
private static final long serialVersionUID = -1798070786993154676L;
@Id
@Column(name = "ID", unique = true, nullable = false)
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Integer employeeId;
@Column(name = "FIRST_NAME", unique = false, nullable = false, length = 100)
private String firstName;
@Column(name = "LAST_NAME", unique = false, nullable = false, length = 100)
private String lastName;
//We will define the association here
AccountEntity account;
//Getters and Setters are not shown for brevity
}
情形一:通过两个实体管理关系
在这种类型的关系中,我们将是用@OneToOne注解来定义关系
//Inside EmployeeEntity.java
@OneToOne
AccountEntity account;
//Inside AccountEntity.java
@OneToOne
EmployeeEntity employee;
有上边的关系,关系的两边都要管理这个关系,所以两边必须使用set方法更新对面的数据信息,如果你没有这么做,你讲不会获取关联实体的信息,并且会返回空
public class TestHibernate
{
public static void main(String[] args)
{
Session sessionOne = HibernateUtil.getSessionFactory().openSession();
sessionOne.beginTransaction();
// Create new Employee object
EmployeeEntity emp = new EmployeeEntity();
emp.setFirstName("Lokesh");
emp.setLastName("Gupta");
// Create new Employee object
AccountEntity acc = new AccountEntity();
acc.setAccountNumber("DUMMY_ACCOUNT");
emp.setAccount(acc);
//acc.setEmployee(emp);
sessionOne.save(acc);
sessionOne.save(emp);
sessionOne.getTransaction().commit();
Integer genEmpId = emp.getEmployeeId();
Integer genAccId = acc.getAccountId();
Session sessionTwo = HibernateUtil.getSessionFactory().openSession();
sessionTwo.beginTransaction();
EmployeeEntity employee = (EmployeeEntity) sessionTwo.get(EmployeeEntity.class, genEmpId);
AccountEntity account = (AccountEntity) sessionTwo.get(AccountEntity.class, genAccId);
System.out.println(employee.getEmployeeId());
System.out.println(employee.getAccount().getAccountNumber());
System.out.println(account.getAccountId());
System.out.println(account.getEmployee().getEmployeeId());
HibernateUtil.shutdown();
}
}
Output:
Hibernate: insert into Account (ACC_NO, employee_ID, ID) values (?, ?, ?)
Hibernate: insert into Employee (account_ID, FIRST_NAME, LAST_NAME, ID) values (?, ?, ?, ?)
Hibernate: select employeeen0_.ID as ID1_1_0_, employeeen0_.account_ID as account_4_1_0_, employeeen0_.FIRST_NAME as FIRST_NA2_1_0_,
employeeen0_.LAST_NAME as LAST_NAM3_1_0_, accountent1_.ID as ID1_0_1_, accountent1_.ACC_NO as ACC_NO2_0_1_,
accountent1_.employee_ID as employee3_0_1_, employeeen2_.ID as ID1_1_2_, employeeen2_.account_ID as account_4_1_2_,
employeeen2_.FIRST_NAME as FIRST_NA2_1_2_, employeeen2_.LAST_NAME as LAST_NAM3_1_2_ from Employee
employeeen0_ left outer join Account accountent1_ on employeeen0_.account_ID=accountent1_.ID
left outer join Employee employeeen2_ on accountent1_.employee_ID=employeeen2_.ID where employeeen0_.ID=?
20
DUMMY_ACCOUNT
10
Exception in thread "main" java.lang.NullPointerException
at com.howtodoinjava.test.TestHibernate.main(TestHibernate.java:43)
你可以看到,我已经在EMPLOYEE实体中set了account实体,所以我可以得到这个数值,如果我注解掉“acc.setEmployee(emp);”,这样不能在account实体中设置employee实体,那么就不能获取它。观察上边的查询语句,两个表分别有外键列employee_ID和account_ID,这是一个循环依赖的例子并且可以很容易让你的程序陷入瘫痪。为了正确的存储上边的关系,你不能注解那句话,没有注解那句话之后,你可以存储和取回你想要的关系,但是依旧有依赖关系
Hibernate: insert into Account (ACC_NO, employee_ID, ID) values (?, ?, ?)
Hibernate: insert into Employee (account_ID, FIRST_NAME, LAST_NAME, ID) values (?, ?, ?, ?)
Hibernate: update Account set ACC_NO=?, employee_ID=? where ID=?
Hibernate: select employeeen0_.ID as ID1_1_0_, employeeen0_.account_ID as account_4_1_0_, employeeen0_.FIRST_NAME as FIRST_NA2_1_0_,
employeeen0_.LAST_NAME as LAST_NAM3_1_0_, accountent1_.ID as ID1_0_1_, accountent1_.ACC_NO as ACC_NO2_0_1_,
accountent1_.employee_ID as employee3_0_1_, employeeen2_.ID as ID1_1_2_, employeeen2_.account_ID as account_4_1_2_,
employeeen2_.FIRST_NAME as FIRST_NA2_1_2_, employeeen2_.LAST_NAME as LAST_NAM3_1_2_ from Employee
employeeen0_ left outer join Account accountent1_ on employeeen0_.account_ID=accountent1_.ID
left outer join Employee employeeen2_ on accountent1_.employee_ID=employeeen2_.ID where employeeen0_.ID=?
20
DUMMY_ACCOUNT
10
20
情形二:通过单个实体维护关系
我们让Employee实体维护这个关系,代码如下:
//Inside EmployeeEntity.java
@OneToOne
AccountEntity account;
//Inside AccountEntity.java
@OneToOne (mappedBy = "account")
EmployeeEntity employee;
现在告诉Hibernate关系是有Employee实体维护的,我们在Account实体中将要增加mappedBy属性,来让他是可以管理的,下边仅仅使用“acc.setEmployee(emp);”来测试上边的代码,Employee实体维护这种关系,Account实体不需要知道任何事情
Session sessionOne = HibernateUtil.getSessionFactory().openSession();
sessionOne.beginTransaction();
// Create new Employee object
EmployeeEntity emp = new EmployeeEntity();
emp.setFirstName("Lokesh");
emp.setLastName("Gupta");
// Create new Employee object
AccountEntity acc = new AccountEntity();
acc.setAccountNumber("DUMMY_ACCOUNT");
emp.setAccount(acc);
//acc.setEmployee(emp);
sessionOne.save(acc);
sessionOne.save(emp);
sessionOne.getTransaction().commit();
Integer genEmpId = emp.getEmployeeId();
Integer genAccId = acc.getAccountId();
Session sessionTwo = HibernateUtil.getSessionFactory().openSession();
sessionTwo.beginTransaction();
EmployeeEntity employee = (EmployeeEntity) sessionTwo.get(EmployeeEntity.class, genEmpId);
AccountEntity account = (AccountEntity) sessionTwo.get(AccountEntity.class, genAccId);
System.out.println(employee.getEmployeeId());
System.out.println(employee.getAccount().getAccountNumber());
System.out.println(account.getAccountId());
System.out.println(account.getEmployee().getEmployeeId());
HibernateUtil.shutdown();
Output:
Hibernate: insert into Account (ACC_NO, ID) values (?, ?)
Hibernate: insert into Employee (account_ID, FIRST_NAME, LAST_NAME, ID) values (?, ?, ?, ?)
Hibernate: select employeeen0_.ID as ID1_1_0_, employeeen0_.account_ID as account_4_1_0_, employeeen0_.FIRST_NAME as FIRST_NA2_1_0_,
employeeen0_.LAST_NAME as LAST_NAM3_1_0_, accountent1_.ID as ID1_0_1_, accountent1_.ACC_NO as ACC_NO2_0_1_ from Employee employeeen0_
left outer join Account accountent1_ on employeeen0_.account_ID=accountent1_.ID where employeeen0_.ID=?
Hibernate: select employeeen0_.ID as ID1_1_1_, employeeen0_.account_ID as account_4_1_1_, employeeen0_.FIRST_NAME as FIRST_NA2_1_1_,
employeeen0_.LAST_NAME as LAST_NAM3_1_1_, accountent1_.ID as ID1_0_0_, accountent1_.ACC_NO as ACC_NO2_0_0_ from Employee employeeen0_
left outer join Account accountent1_ on employeeen0_.account_ID=accountent1_.ID where employeeen0_.account_ID=?
20
DUMMY_ACCOUNT
10
20
你可以看到你不需要告诉Account实体任何事情,Employee实体在两边管理这关系。另一个问题是仅仅只有一个外键,这样就没有了循环。
原文地址:http://howtodoinjava.com/2014/09/05/how-to-define-association-mappings-between-hibernate-entities/