Spring Data JPA-单向一对一关联映射
假如有一张客户表和一张银行账户表,如果想在客户表中关联该客户的银行账户信息,那么这就是一个单向一对一的映射关系。
配置映射关系
- 在客户domain中的银行账户domain对象的属性上添加@OneToOne注解,表示这是一个一对一关联关系,然后配置cascade属性,该属性的作用是设置关联关系在哪种持久化操作下有效,属性如下:
- ALL:所有持久化操作都执行关联操作,也就是说,如果执行的是删除操作,会把所有关联的数据都删除。我个人觉得尽可能不要设置为ALL,因为通常关联的数据都会被很多表关联,这样难免会造成数据错误。
- PERSIST:只有插入才会执行关联操作
- MERGE:只有修改才会执行关联操作
- REMOVE:只有删除才会执行关联操作
- 在客户domain中的银行账户domain对象的属性上添加@JoinColumn注解,name属性用来设置外键的字段名。
完成以上两步,一个单向一对一的关联关系就设置好了。
代码示例
示例客户domain:
@Entity
@Table(name = "tb_customer")
@Data
public class Customer {
@Id
@GeneratedValue
private Long id; //客户的主键
private String customerName;//客户名称
private String customerAddress;//客户地址
@OneToOne(cascade = CascadeType.PERSIST)
@JoinColumn(name = "account_id")
private Account account;
}
示例银行账户domain
@Entity
@Table(name = "tb_account")
@Data
public class Account {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
}
示例repository
public interface CustomerRepository extends JpaRepository<Customer, Long> {
}
测试
Insert
@Test
public void testInsert() {
// 初始化数据
Account account = new Account();
account.setUsername("ZMQ");
account.setPassword("123456");
Customer customer = new Customer();
customer.setCustomerName("朱茂强");
customer.setCustomerAddress("山东 - 青岛");
// 在客户表中存储客户对应账户的ID
customer.setAccount(account);
repository.save(customer);
}
程序运行日志
Hibernate: insert into tb_account (password, username) values (?, ?)
Hibernate: insert into tb_customer (account_id, address, name) values (?, ?, ?)
可以看到程序自动完成了关联插入,在客户表中插入了 @JoinColumn中name属性指定的外键字段。
Query
@Test
public void testQuery() {
Optional<Customer> customer = repository.findById(1L);
System.out.println(customer);
}
程序运行日志
Hibernate: select customer0_.id as id1_1_0_, customer0_.account_id as account_4_1_0_, customer0_.address as address2_1_0_, customer0_.name as name3_1_0_, account1_.id as id1_0_1_, account1_.password as password2_0_1_, account1_.username as username3_0_1_ from tb_customer customer0_ left outer join tb_account account1_ on customer0_.account_id=account1_.id where customer0_.id=?
Optional[Customer(id=1, customerName=朱茂强, customerAddress=山东 - 青岛, account=Account(id=1, username=ZMQ, password=123456))]
可以看到程序默认使用左外连接的方式将关联信息也查了出来,可以通过配置懒加载,默认不进行连表查询,等用到关联信息的时候再执行连表查询,有关懒加载我会在后面讲。
update
@Test
public void testUpdate() {
Optional<Customer> customerOptional = repository.findById(1L);
Customer customer = customerOptional.get();
customer.setCustomerName("zmq");
Account account = customer.getAccount();
account.setUsername("ZhuMaoqiang");
account.setPassword("123");
customer.setAccount(account);
repository.save(customer);
}
程序运行日志
Hibernate: select customer0_.id as id1_1_0_, customer0_.account_id as account_4_1_0_, customer0_.address as address2_1_0_, customer0_.name as name3_1_0_, account1_.id as id1_0_1_, account1_.password as password2_0_1_, account1_.username as username3_0_1_ from tb_customer customer0_ left outer join tb_account account1_ on customer0_.account_id=account1_.id where customer0_.id=?
Hibernate: select customer0_.id as id1_1_0_, customer0_.account_id as account_4_1_0_, customer0_.address as address2_1_0_, customer0_.name as name3_1_0_ from tb_customer customer0_ where customer0_.id=?
Hibernate: select account0_.id as id1_0_0_, account0_.password as password2_0_0_, account0_.username as username3_0_0_ from tb_account account0_ where account0_.id=?
Hibernate: update tb_customer set account_id=?, address=?, name=? where id=?
可以看到用户关联的银行账户信息并没有执行更新操作,原因是我们设置了持久化操作的关联操作只对插入有效。如果我们不改变
@OneToOne(cascade = CascadeType.PERSIST)并且还想当关联的数据设置为null或者修改为其他的关联数据时自动删除关联数据,可以通过@OneToOne中的属性orphanRemoval = true来完成。
delete
@Test
public void Customerdelete() {
repository.deleteById(1L);
}
程序运行日志
Hibernate: select customer0_.id as id1_1_0_, customer0_.account_id as account_4_1_0_, customer0_.address as address2_1_0_, customer0_.name as name3_1_0_, account1_.id as id1_0_1_, account1_.password as password2_0_1_, account1_.username as username3_0_1_ from tb_customer customer0_ left outer join tb_account account1_ on customer0_.account_id=account1_.id where customer0_.id=?
Hibernate: delete from tb_customer where id=?
可以看到用户关联的银行账户信息并没有执行删除操作,原因是我们设置了持久化操作的关联操作只对插入有效。
懒加载
上面提起:默认情况下,若获取维护关联关系的一方(客户信息),则会通过左外连接获取其关联的对象。加假如每个用户都有关联的角色信息,很多时候我们可能只是用到用户信息,那么就没必要每次都连接查询关联信息。可以通过添加 @OntToOne 的 fetch 属性来修改加载策略,如添加 fetch = FetchType.LAZY
,默认就不会左外连查询。如果session没有关闭之前,用到了关联信息就会自动连表查询。
示例:
假设你已经配置了fetch = FetchType.LAZY
@Test
/**
* 为什么懒加载要配置事务 :
* 当通过repository调用完查询方法,session就会立即关闭, 一旦session关闭就不能再执行查询操作,这个
* 时候如果你没有用到关系的对象,那么就还没进行查询。加了事务后, 就能让session直到事务方法执行完
* 毕后才会关闭。
*/
@Transactional(readOnly = true)
public void testQueryByLazy() {
Optional<Customer> customer = repository.findById(2L);
/*
* 由于设置了懒加载,程序执行到这一行并没有连表查询关联对象,当执行到下一行,get()会调用customer的
* toString方法,而toString 方法中有Account的信息,这时由于加了事务,session并没有关闭,程序会继续
* 执行关联查询。
*/
System.out.println(customer.get()); // toString
}