SpringDataJpa(五):处理表的一对一、一对多和多对多关系,关联查询

27 篇文章 0 订阅
5 篇文章 0 订阅

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实体类,并配置一对多关联关系

一的一方:

  1. 添加一个属性,记录多个一方的信息。应该是一个集合属性。
  2. 在属性上添加一个注解@OneToMany
  3. 配置@JoinColumn(外键关联)。
    一定是多的一方记录外键,参照一的一方的主键。

多的一方:

  1. 在联系人的实体类中,添加一个Customer属性
  2. 在属性上添加注解@ManyToOne
  3. 在属性上添加一个@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);
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值