Spring Data JPA多表操作(5)

Spring Data JPA多表操作(5)

数据库中多表之间的关系
  • 多对多
  • 一对多
  • 一对一
一对多示例
数据库设计示例
  • 实体示例

    客户:指的是一家公司,我们记为A。

    联系人:指的是A公司中的员工。

    在不考虑兼职的情况下,公司和员工的关系即为一对多

  • 表设计示例
    在这里插入图片描述

实体类关系设计

客户(公司)类(一)

	@Entity//表示当前类是一个实体类
	@Table(name="cst_customer")//建立当前实体类和表之间的对应关系
	public class Customer implements Serializable {
	
		@Id//表明当前私有属性是主键
		@GeneratedValue(strategy=GenerationType.IDENTITY)//指定主键的生成策略
		@Column(name="cust_id")//指定和数据库表中的cust_id列对应
		private Long custId;
		@Column(name="cust_name")//指定和数据库表中的cust_name列对应
		private String custName;
		@Column(name="cust_source")//指定和数据库表中的cust_source列对应
		private String custSource;
		@Column(name="cust_industry")//指定和数据库表中的cust_industry列对应
		private String custIndustry;
		@Column(name="cust_level")//指定和数据库表中的cust_level列对应
		private String custLevel;
		@Column(name="cust_address")//指定和数据库表中的cust_address列对应
		private String custAddress;
		@Column(name="cust_phone")//指定和数据库表中的cust_phone列对应
		private String custPhone;

    //配置客户和联系人的一对多关系
  	@OneToMany(targetEntity=LinkMan.class)
		@JoinColumn(name="lkm_cust_id",referencedColumnName="cust_id")
		private Set<LinkMan> linkmans = new HashSet<LinkMan>(0);
	}

一对多关系说明:

  1. @OneToMany : 配置一对多关系
    targetEntity :对方对象的字节码对象
  2. 配置外键(中间表)
    @JoinColumn : 配置外键
    name:外键字段名称
    referencedColumnName:参照的主表的主键字段名称
  3. 多方用set集合表示

在客户实体类上(一的一方)添加了外键了配置,所以对于客户而言,也具备了维护外键的作用

联系人类(多)

		@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(targetEntity=Customer.class)
			@JoinColumn(name="lkm_cust_id",referencedColumnName="cust_id")
			private Customer customer;//用它的主键,对应联系人表中的外键
		}

多对一关系说明

  1. 配置表关系
    @ManyToOne : 配置多对一关系
    targetEntity:对方的实体类字节码
  2. 配置外键(中间表)

配置外键的过程,配置到了多的一方,就会在多的一方维护外键
测试类
示例1.

@Test
@Transactional //配置事务
@Rollback(false) //不自动回滚
public void testAdd() {
    //创建一个客户,创建一个联系人
    Customer customer = new Customer();
    customer.setCustName("百度");

    LinkMan linkMan = new LinkMan();
    linkMan.setLkmName("小李");

    /**
     * 配置了客户到联系人的关系
     * 从客户的角度上:发送两条insert语句,发送一条更新语句更新数据库(更新外键)
     * 由于我们配置了客户到联系人的关系:客户可以对外键进行维护
     */
    customer.getLinkMans().add(linkMan);
    
    customerDao.save(customer);
    linkManDao.save(linkMan);
}

示例 2.

@Test
@Transactional //配置事务
@Rollback(false) //不自动回滚
public void testAdd1() {
    //创建一个客户,创建一个联系人
    Customer customer = new Customer();
    customer.setCustName("百度");

    LinkMan linkMan = new LinkMan();
    linkMan.setLkmName("小李");

    /**
     * 配置联系人到客户的关系(多对一)
     *    只发送了两条insert语句
     * 由于配置了联系人到客户的映射关系(多对一)
     *
     *
     */
    linkMan.setCustomer(customer);

    customerDao.save(customer);
    linkManDao.save(linkMan);
}

示例1存在问题如下:

会有一条多余的update语句。

由于一的一方可以维护外键:会发送update语句
解决此问题:只需要在一的一方放弃维护权即可

customer类修改:

@OneToMany(mappedBy = "customer")
private Set<LinkMan> linkMans = new HashSet<>();

示例3

@Test
@Transactional //配置事务
@Rollback(false) //不自动回滚
public void testAdd2() {
	 //创建一个客户,创建一个联系人
   Customer customer = new Customer();
   customer.setCustName("百度");
	
   LinkMan linkMan = new LinkMan();
   linkMan.setLkmName("小李");


    linkMan.setCustomer(customer);//由于配置了多的一方到一的一方的关联关系(当保存的时候,就已经对外键赋值)
    customer.getLinkMans().add(linkMan);//由于配置了一的一方到多的一方的关联关系(发送一条update语句)

    customerDao.save(customer);
    linkManDao.save(linkMan);
}

示例3存在问题如下

  • 由于配置了多的一方到一的一方的关联关系(当保存的时候,就已经对外键赋值)

  • 由于配置了一的一方到多的一方的关联关系(发送一条update语句)
    == 解决此问题:配置级联(可以配置到设置多表的映射关系的注解上==

    级联操作:指操作一个对象同时操作它的关联对象

修改customer类:

@OneToMany(mappedBy = "customer",cascade = CascadeType.ALL)
private Set<LinkMan> linkMans = new HashSet<>();
  • cascade : 配置级联(可以配置到设置多表的映射关系的注解上)

    1. CascadeType.all:所有
    2. MERGE:更新
    3. PERSIST:保存
    4. REMOVE:删除
  • fetch : 配置关联对象的加载方式

    1. EAGER :立即加载
    2. LAZY :延迟加载

删除操作的说明如下:
注意

  1. 删除从表数据:可以随时任意删除。

  2. 删除主表数据:

  • 有从表数据
    1、在默认情况下,它会把外键字段置为null,然后删除主表数据。如果在数据库的表 结构上,外键字段有非空约束,默认情况就会报错了。
    2、如果配置了放弃维护关联关系的权利,则不能删除(与外键字段是否允许为null, 没有关系)因为在删除时,它根本不会去更新从表的外键字段了。
    3、如果还想删除,使用级联删除引用

  • 没有从表数据引用:随便删

映射注解说明

@OneToMany:

作用:建立一对多的关系映射

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

@ManyToOne

作用:建立多对一的关系

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

@JoinColumn

作用:用于定义主键字段和外键字段的对应关系。

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

多对多示例
  • 实体示例:

    用户表: 一个用户可以有多个角色
    角色表:一个角色可以有多个用户

  • 实体Entity设计

  1. 用户(User)

     @Entity
     @Table(name = "sys_user")
     public class User {
    
         @Id
         @GeneratedValue(strategy = GenerationType.IDENTITY)
         @Column(name="user_id")
         private Long userId;
         @Column(name="user_name")
         private String userName;
         @Column(name="age")
         private Integer age;
     
    
     @ManyToMany(targetEntity = Role.class,cascade = CascadeType.ALL)
     @JoinTable(name = "sys_user_role",
             //joinColumns,当前对象在中间表中的外键
             joinColumns = {@JoinColumn(name = "sys_user_id",referencedColumnName = "user_id")},
             //inverseJoinColumns,对方对象在中间表的外键
             inverseJoinColumns = {@JoinColumn(name = "sys_role_id",referencedColumnName = "role_id")}
     )
     		private Set<Role> roles = new HashSet<>();
     }
    

    配置用户到角色的多对多关系

    1. 声明表关系的配置

        @ManyToMany(targetEntity = Role.class)  //多对多
       	 targetEntity:代表对方的实体类字节码
      
    2. 配置中间表(包含两个外键)

       @JoinTable
       	 name : 中间表的名称
       	 joinColumns:配置当前对象在中间表的外键
       @JoinColumn的数组
           name:外键名
           referencedColumnName:参照的主表的主键名
           inverseJoinColumns:配置对方对象在中间表的外键
      
    3. 角色(Role)

       	@Entity
       @Table(name = "sys_role")
       public class Role {
      
       @Id
       @GeneratedValue(strategy = GenerationType.IDENTITY)
       @Column(name = "role_id")
       private Long roleId;
       @Column(name = "role_name")
       private String roleName;
      
       //配置多对多
           @ManyToMany(targetEntity = User.class,cascade = CascadeType.ALL)
       @JoinTable(name = "sys_user_role",
               //joinColumns,当前对象在中间表中的外键
               joinColumns = {@JoinColumn(name = "sys_role_id",referencedColumnName = "role_id")},
               //inverseJoinColumns,对方对象在中间表的外键
               inverseJoinColumns = {@JoinColumn(name = "sys_user_id",referencedColumnName = "user_id")}
       private Set<User> users = new HashSet<>();
       }
      
  • 测试保存

      @Test
      @Transactional  //开启事务
      @Rollback(false)//设置为不回滚
      public void test1(){
      		//创建对象
      		SysUser u1 = new SysUser();
      		u1.setUserName("用户1");
      		SysRole r1 = new SysRole();
      		r1.setRoleName("角色1");
      		//建立关联关系
      		u1.getRoles().add(r1);
      		r1.getUsers().add(u1);
      		//保存
      		roleDao.save(r1);
      		userDao.save(u1);
      }
    

    需求:

    保存用户和角色

    要求:

    创建2个用户和3个角色
    让1号用户具有1号和2号角色(双向的)
    让2号用户具有2号和3号角色(双向的)
    保存用户和角色

    问题:

    在保存时,会出现主键重复的错误,因为都是要往中间表中保存数据造成的。

    解决办法:

    让任意一方放弃维护关联关系的权利
    

    在多对多(保存)中,如果双向都设置关系,意味着双方都维护中间表,都会往中间表插入数据,中间表的2个字段又作为联合主键,所以报错,主键重复,解决保存失败的问题:只需要在任意一方放弃对中间表的维护权即可,推荐在被动的一方放弃,配置如下

      //放弃对中间表的维护权,解决保存中主键冲突的问题
      @ManyToMany(mappedBy="roles")
      private Set<SysUser> users = new HashSet<SysUser>();
    
  • 测试删除

      	/**
       * 删除操作
       * 	在多对多的删除时,双向级联删除根本不能配置
       * 禁用
       *	如果配了的话,如果数据之间有相互引用关系,可能会清空所有数据
       */
      @Test
      @Transactional
      @Rollback(false)//设置为不回滚
      
      public void testDelete() {
      		userDao.delete(1l);
      }
    
  • 对象导航查询

    对象图导航检索方式是根据已经加载的对象,导航到他的关联对象。它利用类与类之间的关系来检索对象。例如:我们通过ID查询方式查出一个客户,可以调用Customer类中的getLinkMans()方法来获取该客户的所有联系人。对象导航查询的使用要求是:两个对象之间必须存在关联关系。

    测试1

    查询一个客户,获取该客户下的所有联系人

      @Autowired
      private CustomerDao customerDao;
      
      @Test
      //由于是在java代码中测试,为了解决no session问题,将操作配置到同一个事务中
      @Transactional 
      public void testFind() {
      	Customer customer = customerDao.findOne(5l);
      	Set<LinkMan> linkMans = customer.getLinkMans();//对象导航查询
      	for(LinkMan linkMan : linkMans) {
    			System.out.println(linkMan);
      	}
      }
    

    测试2

    查询一个联系人,获取该联系人的所有客户

      @Autowired
      private LinkManDao linkManDao;
      
      
      @Test
      public void testFind() {
      	LinkMan linkMan = linkManDao.findOne(4l);
      	Customer customer = linkMan.getCustomer(); //对象导航查询
      	System.out.println(customer);
      }
    

    对象导航查询的问题分析

    问题1:我们查询客户时,要不要把联系人查询出来?

    分析:如果我们不查的话,在用的时候还要自己写代码,调用方法去查询。如果我们查出来的,不使用时又会白白的浪费了服务器内存。

    解决:采用延迟加载的思想。通过配置的方式来设定当我们在需要使用时,发起真正的查询。

    配置方式:

      /**
       * 在客户对象的@OneToMany注解中添加fetch属性
       * 		FetchType.EAGER	:立即加载
       * 		FetchType.LAZY	:延迟加载
       */
      @OneToMany(mappedBy="customer",fetch=FetchType.EAGER)
      private Set<LinkMan> linkMans = new HashSet<>();
    

    问题2:我们查询联系人时,要不要把客户查询出来?

    分析:例如:查询联系人详情时,肯定会看看该联系人的所属客户。如果我们不查的话,在用的时候还要自己写代码,调用方法去查询。如果我们查出来的话,一个对象不会消耗太多的内存。而且多数情况下我们都是要使用的。

    解决: 采用立即加载的思想。通过配置的方式来设定,只要查询从表实体,就把主表实体对象同时查出来

    配置方式

      /**
       * 在联系人对象的@ManyToOne注解中添加fetch属性
       * 		FetchType.EAGER	:立即加载
       * 		FetchType.LAZY	:延迟加载
       */
      @ManyToOne(targetEntity=Customer.class,fetch=FetchType.EAGER)
      @JoinColumn(name="cst_lkm_id",referencedColumnName="cust_id")
      private Customer customer;
    
  • 使用Specification查询

      /**
       * Specification的多表查询
       */
      @Test
      public void testFind() {
      	Specification<LinkMan> spec = new Specification<LinkMan>() {
      		public Predicate toPredicate(Root<LinkMan> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
      			//Join代表链接查询,通过root对象获取
      			//创建的过程中,第一个参数为关联对象的属性名称,第二个参数为连接查询的方式(left,inner,right)
      			//JoinType.LEFT : 左外连接,JoinType.INNER:内连接,JoinType.RIGHT:右外连接
      			Join<LinkMan, Customer> join = root.join("customer",JoinType.INNER);
      			return cb.like(join.get("custName").as(String.class),"传智播客1");
      		}
      	};
      	List<LinkMan> list = linkManDao.findAll(spec);
      	for (LinkMan linkMan : list) {
      		System.out.println(linkMan);
      	}
      }
    
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是一个简单的 JPA查询代码案例: ``` @Entity @Table(name = "orders") public class Order { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String orderNumber; private Date orderDate; //... @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name="customer_id") private Customer customer; // getters and setters } @Entity @Table(name = "customers") public class Customer { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String firstName; private String lastName; //... @OneToMany(mappedBy = "customer") private List<Order> orders; // getters and setters } @Repository public interface OrderRepository extends JpaRepository<Order, Long> { @Query("SELECT o FROM Order o JOIN FETCH o.customer WHERE o.orderDate BETWEEN :startDate AND :endDate") List<Order> findOrdersByDateRangeWithCustomer(@Param("startDate") Date startDate, @Param("endDate") Date endDate); } ``` 在这个例子中,我们有两个实体类 Order 和 Customer,它们之间存在一对多的关系。在 Order 类中,我们使用 @ManyToOne 注解来示 Order 实体与 Customer 实体之间的关系,而在 Customer 类中,我们使用 @OneToMany 注解来示 Customer 实体与 Order 实体之间的关系。 在 OrderRepository 接口中,我们定义了一个名为 findOrdersByDateRangeWithCustomer 的方法,用来查询在指定日期范围内的所有订单,并且同时获取订单所属的客户信息。在该方法上,我们使用了 @Query 注解来指定查询语句,使用 JOIN FETCH 来进行关联查询,使用 @Param 注解来设置参数。 希望这个例子能够帮助你理解 JPA查询的实现方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值