SpringDataJpa
处理一对一关系
步骤
1、确定表和表之间的关系。
2、对应每个表创建实体类。
3、在实体类中使用注解配置关联关系。
4、测试。
一、工程搭建
1)创建一个maven工程
2)添加jar包
3)配置框架整合。
4)创建实体类、dao
二、一对一关系实现方案
1)使用外键关联
2)使用主键关联,包装两个表的主键相同。
外键方案
一、使用客户表和客户扩展表。
二、配置关联关系:
1)在Customer实体类中添加一个属性CustomerExt
类型
2)在属性上添加一个@OneToOne
代表是一个一对一的关系
3)在属性上添加一个@JoinColumn
- name:存储外键的字段的名称
- referencedColumnName:对方表的主键字段的名称(可以不用配置,默认关联主键)
在Customer类上配置一对一关系
import javax.persistence.*;
//代表是一个jpa的实体类
@Entity
//配置实体类和数据库中表的映射关系,name属性对应表名
@Table(name = "cst_customer")
public class Customer {
//配置主键的生成策略
//GenerationType.IDENTITY代表自增长
@GeneratedValue(strategy = GenerationType.IDENTITY)
//标记主键
@Id
//配置属性和字段的映射关系,如果属性名和字段完全一样,则不需配置
@Column(name = "cust_id")
private long custId;
@Column(name = "cust_name")
private String custName;
@Column(name = "cust_source")
private String custSource;
@Column(name = "cust_industry")
private String custIndustry;
@Column(name = "cust_level")
private String custLevel;
@Column(name = "cust_address")
private String custAddress;
@Column(name = "cust_phone")
private String custPhone;
//表示一对一的关联关系
@OneToOne
//连接的字段
//name:当前表存储外键字段的名称
@JoinColumn(name = "extid", referencedColumnName = "ext_id")
private CustomerExt ext;
//省略getset
}
在CustomerExt上也配置一对一关系
import javax.persistence.*;
@Entity
@Table(name = "cst_customer_ext")
public class CustomerExt {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ext_id")
private long extId;
private String memo;
private String info;
//表示一对一的关联关系
@OneToOne
//连接的字段
//name:当前表存储外键字段的名称
@JoinColumn(name = "custid")
private Customer customer;
//省略getset
}
三、测试
1)创建一个Customer对象
2)创建一个CustomerExt对象
3)配置对象的关联关系。
4)把对象写入数据库。
插入数据应该开启事务。可以使用@Transactional注解开启事务。
@Test
@Transactional
@Commit
public void test1(){
Customer customer = new Customer();
customer.setCustName("小王123");
customer.setCustAddress("浙江绍兴");
customer.setCustLevel("vip");
CustomerExt customerExt = new CustomerExt();
customerExt.setMemo("扩展信息");
customerExt.setInfo("高级!");
customer.setExt(customerExt);
customerExt.setCustomer(customer);
customerDao.save(customer);
customerExtDao.save(customerExt);
}
注意:此时发现在两张表中同时存在对方的外键,这样做不好,无需在两个表中都添加外键。只需要在一个表中记录外键即可。
可以在一方放弃维护权:
- 删除@JoinColumn注解
- 在@OneToOne注解中添加mappedBy属性,值应该是对方实体类维护关联关系属性的名称。
//表示一对一的关联关系
@OneToOne(mappedBy = "ext")
//连接的字段
//name:当前表存储外键字段的名称
//@JoinColumn(name = "custid")
private Customer customer;
四、级联操作
发现在测试时,又要在Customer表中插入ext,又要在ext中插入Customer,很麻烦。
如果需要只在Customer表中插入ext就能实现一对一映射,则需要级联操作
需要在@OneToOne注解中添加一个属性cascade
:
CascadeType.PERSIST
:级联添加CascadeType.MERGE
:级联更新CascadeType.REMOVE
:级联删除CascadeType.ALL
:增删改都级联处理。
@Test
@Transactional
@Commit
public void test1(){
Customer customer = new Customer();
customer.setCustName("小王123");
customer.setCustAddress("浙江绍兴");
customer.setCustLevel("vip");
CustomerExt customerExt = new CustomerExt();
customerExt.setMemo("扩展信息");
customerExt.setInfo("高级!");
customer.setExt(customerExt);
customerExt.setCustomer(customer);
customerDao.save(customer);
//customerExtDao.save(customerExt);
}
测试级联更新操作
@Test
@Transactional
@Commit
public void test2(){
//查询客户信息
Customer customer = customerDao.findOne(1L);
//根据客户信息取扩展信息
CustomerExt ext = customer.getExt();
//修改扩展信息
ext.setInfo("初级!");
//更新到数据库
customerDao.save(customer);
}
测试级联删除操作
@Test
@Transactional
@Commit
public void test3(){
customerDao.delete(1L);
}
主键方案
使用方法:
- 不使用@JoinColumn注解
- 使用
@PrimaryKeyJoinColumn
注解,不需要配置属性。 - 双方都需要添加
新的Customer类
import javax.persistence.*;
//代表是一个jpa的实体类
@Entity
//配置实体类和数据库中表的映射关系,name属性对应表名
@Table(name = "cst_customer")
public class Customer {
//配置主键的生成策略
//GenerationType.IDENTITY代表自增长
@GeneratedValue(strategy = GenerationType.IDENTITY)
//标记主键
@Id
//配置属性和字段的映射关系,如果属性名和字段完全一样,则不需配置
@Column(name = "cust_id")
private long custId;
@Column(name = "cust_name")
private String custName;
@Column(name = "cust_source")
private String custSource;
@Column(name = "cust_industry")
private String custIndustry;
@Column(name = "cust_level")
private String custLevel;
@Column(name = "cust_address")
private String custAddress;
@Column(name = "cust_phone")
private String custPhone;
//表示一对一的关联关系
@OneToOne(cascade = CascadeType.ALL)
//连接的字段
//name:当前表存储外键字段的名称
//@JoinColumn(name = "extid")
//使用主键关联
@PrimaryKeyJoinColumn
private CustomerExt ext;
//省略getset
}
新的CustomerExt类
@Entity
@Table(name = "cst_customer_ext")
public class CustomerExt {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ext_id")
private long extId;
private String memo;
private String info;
//表示一对一的关联关系
@OneToOne/*(mappedBy = "ext")*/
//连接的字段
//name:当前表存储外键字段的名称
//@JoinColumn(name = "custid")
@PrimaryKeyJoinColumn
private Customer customer;
//省略getset
}
测试方法与之前相同
@Test
@Transactional
@Commit
public void test1(){
Customer customer = new Customer();
customer.setCustName("小王123");
customer.setCustAddress("浙江绍兴");
customer.setCustLevel("vip");
CustomerExt customerExt = new CustomerExt();
customerExt.setMemo("扩展信息");
customerExt.setInfo("高级!");
customer.setExt(customerExt);
customerExt.setCustomer(customer);
customerDao.save(customer);
//customerExtDao.save(customerExt);
}
@Test
@Transactional
@Commit
public void test2(){
//查询客户信息
Customer customer = customerDao.findOne(1L);
//根据客户信息取扩展信息
CustomerExt ext = customer.getExt();
//修改扩展信息
ext.setInfo("初级!");
//更新到数据库
customerDao.save(customer);
}
@Test
@Transactional
@Commit
public void test3(){
customerDao.delete(1L);
}
处理一对多关系
采用客户和联系人来模拟一对多关系场景。
一、创建Customer和LinkMan实体类,并配置一对多关联关系
一的一方:
- 添加一个属性,记录多个一方的信息。应该是一个集合属性。
- 在属性上添加一个注解
@OneToMany
- 配置
@JoinColumn
(外键关联)。
一定是多的一方记录外键,参照一的一方的主键。
多的一方:
- 在联系人的实体类中,添加一个Customer属性
- 在属性上添加注解
@ManyToOne
- 在属性上添加一个
@JoinColumn
注解
Customer类
package com.itheima.jpa.entity;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
//代表是一个jpa的实体类
@Entity
//配置实体类和数据库中表的映射关系,name属性对应表名
@Table(name = "cst_customer2")
public class Customer2 {
//配置主键的生成策略
//GenerationType.IDENTITY代表自增长
@GeneratedValue(strategy = GenerationType.IDENTITY)
//标记主键
@Id
//配置属性和字段的映射关系,如果属性名和字段完全一样,则不需配置
@Column(name = "cust_id")
private long custId;
@Column(name = "cust_name")
private String custName;
@Column(name = "cust_source")
private String custSource;
@Column(name = "cust_industry")
private String custIndustry;
@Column(name = "cust_level")
private String custLevel;
@Column(name = "cust_address")
private String custAddress;
@Column(name = "cust_phone")
private String custPhone;
@OneToMany
//JoinColumn配置关联的字段信息
//name :外键字段的名称
//referencedColumnName :参照一的一方的主键
@JoinColumn(name = "custid" , referencedColumnName = "cust_id")
private Set<LinkMan> linkManSet = new HashSet<>();
//省略getset
}
LinkMan类
package com.itheima.jpa.entity;
import javax.persistence.*;
import java.io.Serializable;
/**
* 联系人的实体类(数据模型)
*/
@Entity
@Table(name="cst_linkman")
public class LinkMan implements Serializable {
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
@Column(name="lkm_id")
private Long lkmId;
@Column(name="lkm_name")
private String lkmName;
@Column(name="lkm_gender")
private String lkmGender;
@Column(name="lkm_phone")
private String lkmPhone;
@Column(name="lkm_mobile")
private String lkmMobile;
@Column(name="lkm_email")
private String lkmEmail;
@Column(name="lkm_position")
private String lkmPosition;
@Column(name="lkm_memo")
private String lkmMemo;
@ManyToOne
@JoinColumn(name = "custid", referencedColumnName = "cust_id")
private Customer2 customer;
//省略getset
}
二、测试
1、首先,对应每个实体类创建dao
省略
2、测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestOneToMany {
@Autowired
private Customer2Dao customer2Dao;
@Autowired
private LinkManDao linkManDao;
@Test
@Transactional
@Commit
public void addCustomer2(){
//创建实体类
Customer2 customer2 = new Customer2();
customer2.setCustName("小明");
customer2.setCustAddress("浙江绍兴");
customer2.setCustLevel("vip");
//创建多个联系人信息
LinkMan linkMan1 = new LinkMan();
linkMan1.setLkmName("张三");
linkMan1.setLkmPhone("12345678910");
LinkMan linkMan2 = new LinkMan();
linkMan1.setLkmName("张3");
linkMan1.setLkmPhone("98765432101");
//配置客户和联系人信息
customer2.getLinkManSet().add(linkMan1);
customer2.getLinkManSet().add(linkMan2);
linkMan1.setCustomer(customer2);
linkMan2.setCustomer(customer2);
//使用dao把数据写入数据库
customer2Dao.save(customer2);
linkManDao.save(linkMan1);
linkManDao.save(linkMan2);
}
}
三、优化
只需要在一方维护关联关系即可,一方放弃维护。
只由多的一方进行维护:
- 删除
@JoinColumn
注解 - 在
@OneToMany
注解中配置mappedBy
属性
//代表是一个jpa的实体类
@Entity
//配置实体类和数据库中表的映射关系,name属性对应表名
@Table(name = "cst_customer2")
public class Customer2 {
//配置主键的生成策略
//GenerationType.IDENTITY代表自增长
@GeneratedValue(strategy = GenerationType.IDENTITY)
//标记主键
@Id
//配置属性和字段的映射关系,如果属性名和字段完全一样,则不需配置
@Column(name = "cust_id")
private long custId;
@Column(name = "cust_name")
private String custName;
@Column(name = "cust_source")
private String custSource;
@Column(name = "cust_industry")
private String custIndustry;
@Column(name = "cust_level")
private String custLevel;
@Column(name = "cust_address")
private String custAddress;
@Column(name = "cust_phone")
private String custPhone;
//找LinkMan中关联的属性
@OneToMany(mappedBy = "customer")
//JoinColumn配置关联的字段信息
//name :外键字段的名称
//referencedColumnName :参照一的一方的主键
//@JoinColumn(name = "custid" , referencedColumnName = "cust_id")
private Set<LinkMan> linkManSet = new HashSet<>();
//省略getset
}
四、级联操作
只对主表进行操作即可,从表可以自动更新。
在@OneToMany
中添加cascade
属性
//找LinkMan中关联的属性
@OneToMany(mappedBy = "customer", cascade = CascadeType.ALL)
//JoinColumn配置关联的字段信息
//name :外键字段的名称
//referencedColumnName :参照一的一方的主键
//@JoinColumn(name = "custid" , referencedColumnName = "cust_id")
private Set<LinkMan> linkManSet = new HashSet<>();
修改测试代码
@Test
@Transactional
@Commit
public void addCustomer2(){
//创建实体类
Customer2 customer2 = new Customer2();
customer2.setCustName("小明");
customer2.setCustAddress("浙江绍兴");
customer2.setCustLevel("vip");
//创建多个联系人信息
LinkMan linkMan1 = new LinkMan();
linkMan1.setLkmName("张三");
linkMan1.setLkmPhone("12345678910");
LinkMan linkMan2 = new LinkMan();
linkMan2.setLkmName("张3");
linkMan2.setLkmPhone("98765432101");
//配置客户和联系人信息
customer2.getLinkManSet().add(linkMan1);
customer2.getLinkManSet().add(linkMan2);
linkMan1.setCustomer(customer2);
linkMan2.setCustomer(customer2);
//使用dao把数据写入数据库
customer2Dao.save(customer2);
// linkManDao.save(linkMan1);
// linkManDao.save(linkMan2);
}
处理多对多关系
采用用户和角色的关系为例,需要第三张表记录关联关系。
一、配置实体类和关联关系
多对多的关联关系使用注解@ManyToMany
配置。
1、 在实体机中添加一个集合属性。
2、在属性上添加@ManyToMany
注解。
3、使用@JoinTable
注解配置关联关系。
@JoinTable
注解就是中间表的定义。
- name:中间表的名称
- 当前表和中间表的映射关系
- 对方表和中间表的映射关系
注意:在多对多关联关系中,只能一方维护关联关系,另外一方放弃维护权。因为中间表是联合主键,如果两方都进行维护,就会导致插入两次重复的数据,从而报错。
用户类
/**
* 用户的数据模型
*/
@Entity
@Table(name = "sys_user")
public class SysUser implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column( name = "user_id")
private long userId;
@Column(name = "username")
private String username;
@Column(name = "password")
private String password;
@ManyToMany
//相当于中间表的定义
//name:中间表的表名
@JoinTable(name = "sys_user_role",
joinColumns = @JoinColumn(
//中间表和当前表映射的字段的名称
name = "userid",
//参照的当前表的主键字段
referencedColumnName = "user_id"),
inverseJoinColumns = @JoinColumn(
//中间表和对方表映射的字段的名称
name = "roleid",
referencedColumnName = "role_id"))
private Set<SysRole> roles = new HashSet<>();
//省略getset
}
角色类:放弃了这一方的维护关系,并添加mappedBy = "roles"
属性
/**
* 角色的数据模型
*/
@Entity
@Table(name = "sys_role")
public class SysRole implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "role_id")
private long roleId;
@Column(name = "role_name")
private String roleName;
@Column(name = "memo")
private String memo;
@ManyToMany(mappedBy = "roles")
/*@JoinTable(name = "sys_user_role",
joinColumns = @JoinColumn(
//中间表和当前表映射的字段的名称
name = "roleid",
//参照的当前表的主键字段
referencedColumnName = "role_id"),
inverseJoinColumns = @JoinColumn(
name = "userid",
referencedColumnName = "user_id"))*/
private Set<SysUser> users = new HashSet<>();
//省略getset
}
二、测试
1、首先创建每个实体类的dao,此步省略。
2、测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestManyToMany {
@Autowired
private SysUserDao userDao;
@Autowired
private SysRoleDao roleDao;
@Test
@Transactional
@Commit
public void test1(){
SysUser user1 = new SysUser();
user1.setUsername("小明1");
user1.setPassword("123");
SysUser user2 = new SysUser();
user2.setUsername("小明2");
user2.setPassword("123");
SysRole role1 = new SysRole();
role1.setRoleName("管理员");
SysRole role2 = new SysRole();
role2.setRoleName("用户");
user1.getRoles().add(role1);
user1.getRoles().add(role2);
user2.getRoles().add(role1);
user2.getRoles().add(role2);
role1.getUsers().add(user1);
role1.getUsers().add(user2);
role2.getUsers().add(user1);
role2.getUsers().add(user2);
userDao.save(user1);
userDao.save(user2);
roleDao.save(role1);
roleDao.save(role2);
}
}
三、级联操作
在@ManyToMany
注解中配置cascade
属性。
修改用户表即可
/**
* 用户的数据模型
*/
@Entity
@Table(name = "sys_user")
public class SysUser implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column( name = "user_id")
private long userId;
@Column(name = "username")
private String username;
@Column(name = "password")
private String password;
@ManyToMany(cascade = CascadeType.ALL)
//相当于中间表的定义
//name:中间表的表名
@JoinTable(name = "sys_user_role",
joinColumns = @JoinColumn(
//中间表和当前表映射的字段的名称
name = "userid",
//参照的当前表的主键字段
referencedColumnName = "user_id"),
inverseJoinColumns = @JoinColumn(
//中间表和对方表映射的字段的名称
name = "roleid",
referencedColumnName = "role_id"))
private Set<SysRole> roles = new HashSet<>();
//省略getset
}
@Test
@Transactional
@Commit
public void test1(){
SysUser user1 = new SysUser();
user1.setUsername("小明1");
user1.setPassword("123");
SysUser user2 = new SysUser();
user2.setUsername("小明2");
user2.setPassword("123");
SysRole role1 = new SysRole();
role1.setRoleName("管理员");
SysRole role2 = new SysRole();
role2.setRoleName("用户");
user1.getRoles().add(role1);
user1.getRoles().add(role2);
user2.getRoles().add(role1);
user2.getRoles().add(role2);
role1.getUsers().add(user1);
role1.getUsers().add(user2);
role2.getUsers().add(user1);
role2.getUsers().add(user2);
userDao.save(user1);
userDao.save(user2);
}
注意:级联删除需要小心,如果删除的user,对应的role有其他user使用,则无法被删除
关联查询
一、通过客户查询联系人(相当于一对多查询)
使用“对象.属性”方式进行查询。
一对多的查询默认是延迟加载。所以必须添加@Transactional
注解,否则会进行延迟加载时,会没有会话
@Test
@Transactional
public void testQuery(){
Customer2 customer2 = customer2Dao.findOne(1l);
System.out.println(customer2);
//根据客户查询联系人信息
Set<LinkMan> linkManSet = customer2.getLinkManSet();
for (LinkMan linkMan : linkManSet) {
System.out.println(linkMan);
}
}
如果想修改默认行为:在@OneToMany
注解中添加一个属性fetch
FetchType.LAZY
:延迟加载FetchType.EAGER
:及时加载
二、通过联系人查询客户(相当于一对一查询)
发现不添加@Transactional
,没有报错,此操作默认为即时加载
@Test
public void testQuery2(){
LinkMan linkMan = linkManDao.findOne((long) 2);
System.out.println(linkMan);
Customer2 customer = linkMan.getCustomer();
System.out.println(customer);
}
也可以配置成延迟加载,不再演示。
三、 使用Specification方式查询
1)需求
根据联系人的手机号查询客户信息。
2)sql
SELECT
*
FROM
cst_customer2 a
LEFT JOIN cst_linkman b ON a.cust_id = b.custid
WHERE
b.lkm_phone = '12345678910'
3)代码
@Test
public void testQuery3(){
Customer2 customer = customer2Dao.findOne(new Specification<Customer2>() {
@Override
public Predicate toPredicate(Root<Customer2> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
//使用root关联联系人表
//参数1:Customer2实体类中关联的属性名称
//参数2:连接方式
Join<Object, Object> join = root.join("linkManSet", JoinType.LEFT);
//创建查询条件
Predicate predicate = cb.equal(join.get("lkmPhone"), "12345678910");
//返回查询条件
return predicate;
}
});
System.out.println(customer);
}