1 概述
所有的代码都保证是可运行的完整project, 代码分享在github.com, 平时工作中也可以作为模板代码ctrl+c用.
https://github.com/ZhongjunTian/spring-hibernate-examples
本章内容在 intermediate-hibernate-1 文件夹下, 执行App.java即可运行例子.
最基本连表关系:OneToOne关系
一对一的关系是最基本的表与表之间的关系。
下面假设我们有3个表,分别为Person, Account, Address.
因为我们定义了他们之间的关系为一对一的关系,所以每个Person只有一个Account和一个Address.
2. Table定义
create table person_table (
id bigint generated by default as identity,
name varchar(255),
account_id bigint
);
create table account_table (
id bigint generated by default as identity,
balance decimal(19,2)
);
create table address_table (
id bigint generated by default as identity,
city varchar(255),
person_id bigint
);
这里Person和Account之间的OneToOne关系, 由Person拥有account的id,也就是account_id,
而Person和Address之间的关系, 我们让address拥有person的id, 也就是person_id (这样做是为了展示两种情况的代码)
2.1 Person类
首先我们会创建三个@Entity类 Person.java Account.java Address.java
Person有如下内容
@Entity(name="PersonTable")
public class Person {
@Id
@GeneratedValue
public Long id;
public String name;
//Person表拥有account_id,而Account表则没有person_id,这里与Address相反
@OneToOne(cascade = CascadeType.ALL)
public Account account;
//Address表拥有person_id
@OneToOne(mappedBy = "owner")
@JoinColumn
public Address address;
...
}
@Entity.name
这里为了避免读者在下文中混淆java的Person类和Person表, 我特地让类和表的名字不一样. name标明这个Entity对应 Person_table表
@Id
主键
@OneToOne
表面Account是一个Entity, 并且Person和Account是一一对应的关系, 任何一个Person最多有一个Account.
cascade (可选)
级联(串联)操作, 默认是不执行任何级联操作.
什么是级联操作? 这里用伪代码更好解释,
//假设我们从repository读取到了person
person = personRepository.find(1L);
person.name = "李四"
person.account.balance += 100;
personRepository.save(person);
personRepository.delete(person);
当你 更新/删除 person的时候, Hibernate并不能确定你是要操作person还是person和account两个一起操作. 有点类似于古代的连坐制度, 当person被xx的时候account也要被xx.
我们这里为了做个示范, 就直接级联所有操作了. 具体还有很多种, 常用的是Persist, Merge, Remove 和 All
而Address就没有设置cascade, 因此在使用的时候Person和address要分开保存, 详见最后面的public void oneToOneNoCascading1()
mappedBy
Mapped标明Person是无辜群众, 没有address的任何信息, 另一个Address表拥有外键(或者更专业的说法是 关系的拥有者为Address ).
并且由java的类Address.owner这个成员变量维护他们之间的关系.
隐式的Long accountId
这里有个小细节, 虽然PersonTable表里面有外键account_id但是Entity里面却没有,
实际上Hibernate会默认 public Account account;
的外键在PersonTable表里面为account_id.
如果这里是 public Account xx;
那么hibernate会认为PersonTable表里面有外键xx_id;
2.2 Account类
@Entity(name = "AccountTable")
public class Account {
@Id
@GeneratedValue
public Long id;
public BigDecimal balance;//余额
...
}
因为Person是关系的拥有者, 所以这个类就很简单了.
并且, 我们没有在Account类里面定义Person成员, Hibernate官方文档里面称这种关系为 unidirectional,
另一种关系为bi-directional双向关系, Person和Address的关系就是双向的.
2.3 Address类
@Entity(name = "AddressTable")
public class Address {
@Id
@GeneratedValue
public Long id;
public String city;
@OneToOne
@JoinColumn(name = "person_id") //如果没有这个就会认为外键为 PersonTable 表的 owner_id列
public Person owner;
...
}
@JoinColumn
标明外键的名字, 如果没有这个就会默认外键为AddressTable表的owner_id
3. 示范代码
@Service
public class Demo {
@Autowired
PersonRepository personRepository;
@Autowired
AccountRepository accountRepository;
@Transactional //one to one relationship
public void oneToOneCascading1() {
System.out.println("*******************开始");
//先创建account
Account account = Account.createAccount();
//再把person绑定address
Person person = Person.createPerson();
person.account = account;
person = personRepository.save(person);
System.out.println("直接同时创建 person 和 account: "+person);//
}
@Transactional
public void oneToOneCascading2() {
System.out.println("*******************开始");
//创建account
Account account = Account.createAccount();
account = accountRepository.save(account);
System.out.println("先创建account: "+account);//
//把person绑定address
Person person = Person.createPerson();
person.account = account;
person = personRepository.save(person);
System.out.println("再创建person, 同时绑定了前面的address: "+person);//
}
@Transactional
public void oneToOneNocascading1() {
System.out.println("*******************开始");
//把person绑定address
Person person = Person.createPerson();
person = personRepository.save(person);
System.out.println("先创建person: "+person);//
//创建account
Account account = Account.createAccount();
account = accountRepository.save(account);
System.out.println("再创建account: "+account);//
person.account = account;
person = personRepository.save(person);
System.out.println("再更新person: "+person);//
}
}