SpringData JPA多表关联操作

Posted 赵广陆

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringData JPA多表关联操作相关的知识,希望对你有一定的参考价值。


1 关联映射操作

1.1 多表之间的关系和操作多表的操作步骤

1.1.1 @OneToMany:

作用:建立一对多的关系映射
属性:
	targetEntityClass:指定多的多方的类的字节码
	mappedBy:指定从表实体类中引用主表对象的名称。
	cascade:指定要使用的级联操作
	fetch:指定是否采用延迟加载
	orphanRemoval:是否使用孤儿删除

1.1.2 @ManyToOne

作用:建立多对一的关系
属性:
	targetEntityClass:指定一的一方实体类字节码
	cascade:指定要使用的级联操作
	fetch:指定是否采用延迟加载
	optional:关联是否可选。如果设置为false,则必须始终存在非空关系。

1.1.3 @JoinColumn

 作用:用于定义主键字段和外键字段的对应关系。
 属性:
	name:指定外键字段的名称
	referencedColumnName:指定引用主表的主键字段名称
	unique:是否唯一。默认值不唯一
	nullable:是否允许为空。默认值允许。
	insertable:是否允许插入。默认值允许。
	updatable:是否允许更新。默认值允许。
	columnDefinition:列的定义信息。

表关系
	一对一:和一对多同理,就是看哪一方要维护外键.谁维护都行,主要是分清楚谁是主表,谁是从表
	一对多:
		一的一方:主表
		多的一方:从表
		外键:需要再从表上新建一列作为外键,他的取值来源于主表的主键
	多对多:
		中间表:中间表中最少应该由两个字段组成,这两个字段做为外键指向两张表的主键,又组成了联合主键

讲师对学员:一对多关系
		
实体类中的关系
	包含关系:可以通过实体类中的包含关系描述表关系
	继承关系

分析步骤
	1.明确表关系
	2.确定表关系(描述 外键|中间表)
	3.编写实体类,再实体类中描述表关系(包含关系)
	4.配置映射关系

1.2 一对多的关联关系

i.一对多操作
	案例:客户和联系人的案例(一对多关系)
		客户:一家公司
		联系人:这家公司的员工
	
		一个客户可以具有多个联系人
		一个联系人从属于一家公司
		
	分析步骤
		1.明确表关系
			一对多关系
		2.确定表关系(描述 外键|中间表)
			主表:客户表
			从表:联系人表
				* 再从表上添加外键
		3.编写实体类,再实体类中描述表关系(包含关系)
			客户:再客户的实体类中包含一个联系人的集合
			联系人:在联系人的实体类中包含一个客户的对象
		4.配置映射关系
			* 使用jpa注解配置一对多映射关系

	级联:
		操作一个对象的同时操作他的关联对象
		
		级联操作:
			1.需要区分操作主体
			2.需要在操作主体的实体类上,添加级联属性(需要添加到多表映射关系的注解上)
			3.cascade(配置级联)
		
		级联添加,
			案例:当我保存一个客户的同时保存联系人
		级联删除
			案例:当我删除一个客户的同时删除此客户的所有联系人

2、实体类

package cn.oldlu.domain;

import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;

/**
 * 1.实体类和表的映射关系
 *      @Eitity
 *      @Table
 * 2.类中属性和表中字段的映射关系
 *      @Id
 *      @GeneratedValue
 *      @Column
 */
@Entity
@Table(name="cst_customer")
public class Customer {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name="cust_id")
    private Long custId;
    @Column(name="cust_address")
    private String custAddress;
    @Column(name="cust_industry")
    private String custIndustry;
    @Column(name="cust_level")
    private String custLevel;
    @Column(name="cust_name")
    private String custName;
    @Column(name="cust_phone")
    private String custPhone;
    @Column(name="cust_source")
    private String custSource;

    //配置客户和联系人之间的关系(一对多关系)
    /**
     * 使用注解的形式配置多表关系
     *      1.声明关系
     *          @OneToMany : 配置一对多关系
     *              targetEntity :对方对象的字节码对象
     *      2.配置外键(中间表)
     *              @JoinColumn : 配置外键
     *                  name:外键字段名称 一般有这个的时候mysql会自动创建字段,不需要重复定义
     *                  referencedColumnName:参照的主表的主键字段名称
     *
     *  * 在客户实体类上(一的一方)添加了外键了配置,所以对于客户而言,也具备了维护外键的作用
     *
     */

//    @OneToMany(targetEntity = LinkMan.class)
//    @JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
    /**
     * 放弃外键维护权
     *      mappedBy:对方配置关系的属性名称\\
     * cascade : 配置级联(可以配置到设置多表的映射关系的注解上)
     *      CascadeType.all         : 所有
     *                  MERGE       :更新
     *                  PERSIST     :保存
     *                  REMOVE      :删除
     *
     * fetch : 配置关联对象的加载方式
     *          EAGER   :立即加载
     *          LAZY    :延迟加载

      */
    @OneToMany(mappedBy = "customer",cascade = CascadeType.ALL)
    private Set<LinkMan> linkMans = new HashSet<>();

    public Long getCustId() {
        return custId;
    }

    public void setCustId(Long custId) {
        this.custId = custId;
    }

    public String getCustAddress() {
        return custAddress;
    }

    public void setCustAddress(String custAddress) {
        this.custAddress = custAddress;
    }

    public String getCustIndustry() {
        return custIndustry;
    }

    public void setCustIndustry(String custIndustry) {
        this.custIndustry = custIndustry;
    }

    public String getCustLevel() {
        return custLevel;
    }

    public void setCustLevel(String custLevel) {
        this.custLevel = custLevel;
    }

    public String getCustName() {
        return custName;
    }

    public void setCustName(String custName) {
        this.custName = custName;
    }

    public String getCustPhone() {
        return custPhone;
    }

    public void setCustPhone(String custPhone) {
        this.custPhone = custPhone;
    }

    public String getCustSource() {
        return custSource;
    }

    public void setCustSource(String custSource) {
        this.custSource = custSource;
    }

    public Set<LinkMan> getLinkMans() {
        return linkMans;
    }

    public void setLinkMans(Set<LinkMan> linkMans) {
        this.linkMans = linkMans;
    }

    @Override
    public String toString() {
        return "Customer{" +
                "custId=" + custId +
                ", custAddress='" + custAddress + '\\'' +
                ", custIndustry='" + custIndustry + '\\'' +
                ", custLevel='" + custLevel + '\\'' +
                ", custName='" + custName + '\\'' +
                ", custPhone='" + custPhone + '\\'' +
                ", custSource='" + custSource + '\\'' +
                '}';
    }
}

package cn.oldlu.domain;

import javax.persistence.*;

@Entity
@Table(name = "cst_linkman")
public class LinkMan {

    @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;//联系人备注

    /**
     * 配置联系人到客户的多对一关系
     *     使用注解的形式配置多对一关系
     *      1.配置表关系
     *          @ManyToOne : 配置多对一关系
     *              targetEntity:对方的实体类字节码
     *      2.配置外键(中间表)
     *
     * * 配置外键的过程,配置到了多的一方,就会在多的一方维护外键
     *
     */
    @ManyToOne(targetEntity = Customer.class,fetch = FetchType.LAZY)
    @JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
    private Customer customer;

    public Long getLkmId() {
        return lkmId;
    }

    public void setLkmId(Long lkmId) {
        this.lkmId = lkmId;
    }

    public String getLkmName() {
        return lkmName;
    }

    public void setLkmName(String lkmName) {
        this.lkmName = lkmName;
    }

    public String getLkmGender() {
        return lkmGender;
    }

    public void setLkmGender(String lkmGender) {
        this.lkmGender = lkmGender;
    }

    public String getLkmPhone() {
        return lkmPhone;
    }

    public void setLkmPhone(String lkmPhone) {
        this.lkmPhone = lkmPhone;
    }

    public String getLkmMobile() {
        return lkmMobile;
    }

    public void setLkmMobile(String lkmMobile) {
        this.lkmMobile = lkmMobile;
    }

    public String getLkmEmail() {
        return lkmEmail;
    }

    public void setLkmEmail(String lkmEmail) {
        this.lkmEmail = lkmEmail;
    }

    public String getLkmPosition() {
        return lkmPosition;
    }

    public void setLkmPosition(String lkmPosition) {
        this.lkmPosition = lkmPosition;
    }

    public String getLkmMemo() {
        return lkmMemo;
    }

    public void setLkmMemo(String lkmMemo) {
        this.lkmMemo = lkmMemo;
    }

    public Customer getCustomer() {
        return customer;
    }

    public void setCustomer(Customer customer) {
        this.customer = customer;
    }

    @Override
    public String toString() {
        return "LinkMan{" +
                "lkmId=" + lkmId +
                ", lkmName='" + lkmName + '\\'' +
                ", lkmGender='" + lkmGender + '\\'' +
                ", lkmPhone='" + lkmPhone + '\\'' +
                ", lkmMobile='" + lkmMobile + '\\'' +
                ", lkmEmail='" + lkmEmail + '\\'' +
                ", lkmPosition='" + lkmPosition + '\\'' +
                ", lkmMemo='" + lkmMemo + '\\'' +
                '}';
    }
}

3、dao层接口编写

package cn.oldlu.dao;

import cn.oldlu.domain.Customer;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;

import java.util.List;

/**
 * 符合SpringDataJpa的dao层接口规范
 *      JpaRepository<操作的实体类类型,实体类中主键属性的类型>
 *          * 封装了基本CRUD操作
 *      JpaSpecificationExecutor<操作的实体类类型>
 *          * 封装了复杂查询(分页)
 */
public interface CustomerDao extends JpaRepository<Customer,Long> ,JpaSpecificationExecutor<Customer> {

}
package cn.oldlu.dao;

import cn.oldlu.domain.LinkMan;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

/**
 * 联系人的dao接口
 */
public interface LinkManDao extends JpaRepository<LinkMan,Long>, JpaSpecificationExecutor<LinkMan> {
}

4、测试查询


package cn.oldlu.test;

import cn.oldlu.dao.CustomerDao;
import cn.oldlu.dao.LinkManDao;
import cn.oldlu.domain.Customer;
import cn.oldlu.domain.LinkMan;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;

import java.util.Set;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class ObjectQueryTest {
    @Autowired
    private CustomerDao customerDao;

    @Autowired
    private LinkManDao linkManDao;

    //could not initialize proxy - no Session
    //测试对象导航查询(查询一个对象的时候,通过此对象查询所有的关联对象)
    @Test
    @Transactional // 解决在java代码中的no session问题
    public void  testQuery1() {
        //查询id为1的客户
        Customer customer = customerDao.getOne(1l);
        //对象导航查询,此客户下的所有联系人
        Set<LinkMan> linkMans = customer.getLinkMans();

        for (LinkMan linkMan : linkMans) {
            System.out.println(linkMan);
        }
    }

    /**
     * 对象导航查询:
     *      默认使用的是延迟加载的形式查询的
     *          调用get方法并不会立即发送查询,而是在使用关联对象的时候才会差和讯
     *      延迟加载!
     * 修改配置,将延迟加载改为立即加载
     *      fetch,需要配置到多表映射关系的注解上
     *
     */

    @Test
    @Transactional // 解决在java代码中的no session问题
    public void  testQuery2() {
        //查询id为1的客户
        Customer customer = customerDao.findOne(1l);
        //对象导航查询,此客户下的所有联系人
        Set<LinkMan> linkMans = customer.getLinkMans();

        System.out.println(linkMans.size());
    }

    /**
     * 从联系人对象导航查询他的所属客户
     *      * 默认 : 立即加载
     *  延迟加载:
     *
     */
    @Test
    @Transactional // 解决在java代码中的no session问题
    public void  testQuery3() {
        LinkMan linkMan = linkManDao.findOne(2l);
        //对象导航查询所属的客户
        Customer customer = linkMan.getCustomer();
        System.out.println(customer);
    }

}


5.测试删改查

package cn.oldlu.test;

import cn.oldlu.dao.CustomerDao;
import cn.oldlu.dao.LinkManDao;
import cn.oldlu.domain.Customer;
import cn.oldlu.domain.LinkMan;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class OneToManyTest {

    @Autowired
    private CustomerDao customerDao;

    @Autowired
    private LinkManDao linkManDao;

    /**
     * 保存一个客户,保存一个联系人
     *  效果:客户和联系人作为独立的数据保存到数据库中
     *      联系人的外键为空
     *  原因?
     *      实体类中没有配置关系
     */
    @Test
    @Transactional //配置事务
    @Rollback(false) //不自动回滚
    public void testAdd() {
        //创建一个客户,创建一个联系人
        Customer customer = new Customer();
        customer.setCustName("百度");

        LinkMan linkMan = 以上是关于SpringData JPA多表关联操作的主要内容,如果未能解决你的问题,请参考以下文章

SpringData JPA多表操作(增,删)

spring Data jpa 一对多关联 动态查询怎么写

springdata_多表关系中的一对一__小案例

JPA多表关联 去重 排序问题

springdata jpa

Sprindata jpa