SpringDataJpa(三)(下)

简介: SpringDataJpa(三)(下)

SpringDataJpa(三)(上)+https://developer.aliyun.com/article/1556647

9.5 一对多的操作

9.5.1 添加

test/OneToManyTest.java

package cn.itcast.test;
import cn.itcast.dao.CustomerDao;
import cn.itcast.dao.LinkManDao;
import cn.itcast.domain.Customer;
import cn.itcast.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 = new LinkMan();
        linkMan.setLkmName("小李");
        /**
         * 配置了客户到联系人的关系
         *      从客户的角度上:发送两条insert语句,发送一条更新语句更新数据库(更新外键)
         * 由于我们配置了客户到联系人的关系:客户可以对外键进行维护
         */
        customer.getLinkMans().add(linkMan);
        customerDao.save(customer);
        linkManDao.save(linkMan);
    }
    @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);
    }
    /**
     * 会有一条多余的update语句
     *      * 由于一的一方可以维护外键:会发送update语句
     *      * 解决此问题:只需要在一的一方放弃维护权即可
     *
     */
    @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);
    }
    /**
     * 级联添加:保存一个客户的同时,保存客户的所有联系人
     *      需要在操作主体的实体类上,配置casacde属性
     */
    @Test
    @Transactional //配置事务
    @Rollback(false) //不自动回滚
    public void testCascadeAdd() {
        Customer customer = new Customer();
        customer.setCustName("百度1");
        LinkMan linkMan = new LinkMan();
        linkMan.setLkmName("小李1");
        linkMan.setCustomer(customer);
        customer.getLinkMans().add(linkMan);
        customerDao.save(customer);
    }
    /**
     * 级联删除:
     *      删除1号客户的同时,删除1号客户的所有联系人
     */
    @Test
    @Transactional //配置事务
    @Rollback(false) //不自动回滚
    public void testCascadeRemove() {
        //1.查询1号客户
        Customer customer = customerDao.findOne(1l);
        //2.删除1号客户
        customerDao.delete(customer);
    }
}

通过保存的案例,我们可以发现在设置了双向关系之后,会发送两条insert语句,一条多余的update语句,那我们的解决是思路很简单,就是一的一方放弃维护权

Customer.java

/**
   *放弃外键维护权的配置将如下配置改为
   */
    //@OneToMany(targetEntity=LinkMan.class)
    //@JoinColumn(name="lkm_cust_id",referencedColumnName="cust_id")  
    //设置为
  @OneToMany(mappedBy="customer")

9.5.2 删除

@Autowired
  private CustomerDao customerDao;
  
  @Test
  @Transactional
  @Rollback(false)//设置为不回滚
  public void testDelete() {
    customerDao.delete(1l);
  }

删除操作的说明如下:

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

删除主表数据:

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

在实际开发中,级联删除请慎用!(在一对多的情况下)

9.5.3 级联操作

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

使用方法:只需要在操作主体的注解上配置cascade

/**
   * cascade:配置级联操作
   *    CascadeType.MERGE 级联更新
   *    CascadeType.PERSIST 级联保存:
   *    CascadeType.REFRESH 级联刷新:
   *    CascadeType.REMOVE  级联删除:
   *    CascadeType.ALL   包含所有
   */
  @OneToMany(mappedBy="customer",cascade=CascadeType.ALL)

test/ObjectQueryTest.java

package cn.itcast.test;
import cn.itcast.dao.CustomerDao;
import cn.itcast.dao.LinkManDao;
import cn.itcast.domain.Customer;
import cn.itcast.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);
    }
}

十、JPA中的多对多

10.1 示例分析

我们采用的示例为用户和角色。

用户:指的是咱们班的每一个同学。

角色:指的是咱们班同学的身份信息。

比如A同学,它是我的学生,其中有个身份就是学生,还是家里的孩子,那么他还有个身份是子女。

同时B同学,它也具有学生和子女的身份。

那么任何一个同学都可能具有多个身份。同时学生这个身份可以被多个同学所具有。

所以我们说,用户和角色之间的关系是多对多。

10.2 表关系建立

多对多的表关系建立靠的是中间表,其中用户表和中间表的关系是一对多,角色表和中间表的关系也是一对多,如下图所示:

10.3 实体类关系建立以及映射配置

代码结构如下:

一个用户可以具有多个角色,所以在用户实体类中应该包含多个角色的信息,代码如下:

daomain/User.java

package cn.itcast.domain;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
@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;
    /**
     * 配置用户到角色的多对多关系
     *      配置多对多的映射关系
     *          1.声明表关系的配置
     *              @ManyToMany(targetEntity = Role.class)  //多对多
     *                  targetEntity:代表对方的实体类字节码
     *          2.配置中间表(包含两个外键)
     *                @JoinTable
     *                  name : 中间表的名称
     *                  joinColumns:配置当前对象在中间表的外键
     *                      @JoinColumn的数组
     *                          name:外键名
     *                          referencedColumnName:参照的主表的主键名
     *                  inverseJoinColumns:配置对方对象在中间表的外键
     */
    @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<>();
    public Long getUserId() {
        return userId;
    }
    public void setUserId(Long userId) {
        this.userId = userId;
    }
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public Set<Role> getRoles() {
        return roles;
    }
    public void setRoles(Set<Role> roles) {
        this.roles = roles;
    }
}

一个角色可以赋予多个用户,所以在角色实体类中应该包含多个用户的信息,代码如下:

daomain/Role.java

package cn.itcast.domain;
import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;
@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(mappedBy = "roles")  //配置多表关系
    private Set<User> users = new HashSet<>();
    public Long getRoleId() {
        return roleId;
    }
    public void setRoleId(Long roleId) {
        this.roleId = roleId;
    }
    public String getRoleName() {
        return roleName;
    }
    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }
    public Set<User> getUsers() {
        return users;
    }
    public void setUsers(Set<User> users) {
        this.users = users;
    }
}

10.4 映射的注解说明

@ManyToMany

作用:用于映射多对多关系

属性:

cascade:配置级联操作。

fetch:配置是否采用延迟加载。

targetEntity:配置目标的实体类。映射多对多的时候不用写。

@JoinTable

作用:针对中间表的配置

属性:

nam:配置中间表的名称

joinColumns:中间表的外键字段关联当前实体类所对应表的主键字段

inverseJoinColumn:中间表的外键字段关联对方表的主键字段

@JoinColumn

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

属性:

name:指定外键字段的名称

referencedColumnName:指定引用主表的主键字段名称

unique:是否唯一。默认值不唯一

nullable:是否允许为空。默认值允许。

insertable:是否允许插入。默认值允许。

updatable:是否允许更新。默认值允许。

columnDefinition:列的定义信息。

10.5 多对多的操作

10.5.1 保存

test/ManyToManyTest.java

package cn.itcast.test;
import cn.itcast.dao.RoleDao;
import cn.itcast.dao.UserDao;
import cn.itcast.domain.Role;
import cn.itcast.domain.User;
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 ManyToManyTest {
    @Autowired
    private UserDao userDao;
    @Autowired
    private RoleDao roleDao;
    /**
     * 保存一个用户,保存一个角色
     *
     *  多对多放弃维护权:被动的一方放弃
     */
    @Test
    @Transactional
    @Rollback(false)
    public void  testAdd() {
        User user = new User();
        user.setUserName("小李");
        Role role = new Role();
        role.setRoleName("java程序员");
        //配置用户到角色关系,可以对中间表中的数据进行维护     1-1
        user.getRoles().add(role);
        //配置角色到用户的关系,可以对中间表的数据进行维护     1-1
        role.getUsers().add(user);
        userDao.save(user);
        roleDao.save(role);
    }
    //测试级联添加(保存一个用户的同时保存用户的关联角色)
    @Test
    @Transactional
    @Rollback(false)
    public void  testCasCadeAdd() {
        User user = new User();
        user.setUserName("小李");
        Role role = new Role();
        role.setRoleName("java程序员");
        //配置用户到角色关系,可以对中间表中的数据进行维护     1-1
        user.getRoles().add(role);
        //配置角色到用户的关系,可以对中间表的数据进行维护     1-1
        role.getUsers().add(user);
        userDao.save(user);
    }
    /**
     * 案例:删除id为1的用户,同时删除他的关联对象
     */
    @Test
    @Transactional
    @Rollback(false)
    public void  testCasCadeRemove() {
        //查询1号用户
        User user = userDao.findOne(1l);
        //删除1号用户
        userDao.delete(user);
    }
}

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

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

十一 Spring Data JPA中的多表查询

11.1 对象导航查询

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

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

@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);
    }
  }

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

@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<>(0);

问题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;

11.2 使用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);
    }
  }


目录
相关文章
|
5月前
|
SQL Java 数据库
SpringDataJpa(一)(下)
SpringDataJpa(一)
27 1
|
5月前
|
Java 数据库连接 API
SpringDataJpa(一)(上)
SpringDataJpa(一)
41 0
|
5月前
|
SQL 安全 Java
SpringDataJpa(三)(上)
SpringDataJpa(三)(上)
30 0
|
5月前
|
SQL Java 数据库连接
SpringDataJpa(二)
SpringDataJpa(二)
31 0
|
SQL 存储 Java
springDataJPA的基本使用
springDataJPA的基本使用
106 0
|
Java
SpringDataJPA如何CRUD?
SpringDataJPA如何CRUD?
104 0
|
SQL Java 数据库连接
SpringDataJpa:JpaRepository增删改查
SpringDataJpa:JpaRepository增删改查
311 0
SpringDataJpa:JpaRepository增删改查
SpringDataJPA之PagingAndSortingRepository接口
PagingAndSortingRepository 接口继承于 CrudRepository 接口,拥有CrudRepository 接口的所有方法, 并新增两个功能:分页和排序。 但是这两个方法不能包含筛选条件。
SpringDataJPA之PagingAndSortingRepository接口
|
SQL 测试技术 API
SpringDataJPA之CrudRepository接口
上篇我们介绍了Repository接口,发现该接口并没有提供完成的DML操作,SpringDataJPA中完成的CRUD操作是通过CrudRepository接口来实现的。
SpringDataJPA之CrudRepository接口
|
测试技术
SpringDataJPA之自定义Repository
有时候系统提供的接口中的方法并不足以满足我们的需求,这时我们就可以通过自定义Repository来扩展。
SpringDataJPA之自定义Repository