SpringData JPA多表操作(增,删)

Posted 路上的风景

tags:

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

一对多:

  示例:客户和联系人关系

  在实体类中,由于客户是少的一方,它应该包含多个联系人,所以实体类要体现出客户中有多个联系人的信息

/**
 * 客户的实体类
 */
@Entity
@Table(name = "cst_customer")
public class Customer implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @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(targetEntity = LinkMan.class, fetch = FetchType.LAZY)
    // @JoinColumn(name = "lkm_cust_id", referencedColumnName = "cust_id")
    /**
     * 放弃外键维护权
     *      mappedBy: 对方配置关系的属性名称
     *      cascade: 配置级联(可以配置到设置多表的映射关系的注解上)
     *          CascadeType.all         : 所有
     *                      MERGE       :更新
     *                      PERSIST     :保存
     *                      REMOVE      :删除
     * fetch : 配置关联对象的加载方式
     *          EAGER   :立即加载
     *          LAZY    :延迟加载
     */
    @OneToMany(mappedBy = "customer")
    private Set<LinkMan> linkMans = new HashSet<>(0);

    /************************ get/set方法 ************************/
}

  由于联系人是多的一方,在实体类中要体现出,每个联系人只能对应一个客户

/**
 * 联系人实体类
 */
@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)
    @JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
    private Customer customer;

    /************************ get/set方法 ************************/
}

  持久层接口

/**
 * 客户持久层接口
 * JpaRepository<实体类类型,主键类型>:用来完成基本CRUD操作
 * JpaSpecificationExecutor<实体类类型>:用于复杂查询(分页等查询操作)
 */
public interface CustomerDao extends JpaRepository<Customer, Long>, JpaSpecificationExecutor<Customer> {
}
/**
 * 联系人持久层接口
 */
public interface LinkManDao extends JpaRepository<LinkMan, Long>, JpaSpecificationExecutor<LinkMan> {
}

  测试新增记录

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

        /**
         * 配置联系人到客户的关系(多对一)
         *    只发送了两条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);
    }
}

  测试删除记录

@Test
@Transactional
@Rollback(false)
public void testDelete() {
    customerDao.delete(1L);
}

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

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

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

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

  @OneToMany(mappedBy = "customer", cascade = CascadeType.ALL)
    private Set<LinkMan> linkMans = new HashSet<>(0);
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class CascadeTest {
    @Autowired
    private CustomerDao customerDao;
    @Autowired
    private LinkManDao linkManDao;

    /**
     * 级联添加:保存一个客户的同时,保存客户的所有联系人
     *      需要在操作主体的实体类上,配置casacde属性
     */
    @Test
    @Transactional
    @Rollback(false)
    public void testCascadeAdd() {
        Customer customer = new Customer();
        customer.setCustName("百度");

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

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

多对多:

  示例:用户和角色关系

  多对多的表关系建立靠的是中间表,其中用户表和中间表的关系是一对多,角色表和中间表的关系也是一对多

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

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

/**
 * 用户实体类
 */
@Entity
@Table(name = "sys_user")
public class SysUser implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "user_id")
    private Long userId;
    @Column(name = "user_code")
    private String userCode;
    @Column(name = "user_name")
    private String userName;
    @Column(name = "user_password")
    private String userPassword;
    @Column(name = "user_state")
    private String userState;
  
/**   * 配置用户到角色的多对多关系   * 配置多对多的映射关系   * 1.声明表关系的配置   * @ManyToMany(targetEntity = SysRole.class) //多对多   * targetEntity:代表对方的实体类字节码   * 2.配置中间表(包含两个外键)   * @JoinTable   * name : 中间表的名称   * joinColumns:配置当前对象在中间表的外键   * @JoinColumn的数组   * name:外键名   * referencedColumnName:参照的主表的主键名   * inverseJoinColumns:配置对方对象在中间表的外键   */   @ManyToMany(targetEntity = SysRole.class)   @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<SysRole> roles = new HashSet<SysRole>(0);
  
/************************ get/set方法 ************************/
}

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

/**
 * 角色实体类
 */
@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 = "role_memo")
    private String roleMemo;
  
//多对多关系映射   @ManyToMany(mappedBy = "roles") // 对方配置关系的属性名称,表示由对方来维护中间表关系   private Set<SysUser> users = new HashSet<SysUser>(0);
  
/************************ get/set方法 ************************/ }

  持久层接口:

/**
 * 用户持久层接口
 */
public interface SysUserDao extends JpaRepository<SysUser, Long>, JpaSpecificationExecutor<SysUser> {
}
/**
 * 角色持久层接口
 */
public interface SysRoleDao extends JpaRepository<SysRole, Long>, JpaSpecificationExecutor<SysRole> {
}

  测试:

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

    @Autowired
    private SysRoleDao sysRoleDao;
    @Autowired
    private SysUserDao sysUserDao;

    /**
     * 保存一个用户,保存一个角色
     * 多对多放弃维护权:被动的一方放弃
     */
    @Test
    @Transactional
    @Rollback(false)
    public void testAdd() {
        SysUser sysUser = new SysUser();
        sysUser.setUserName("小强");

        SysRole sysRole = new SysRole();
        sysRole.setRoleName("java程序员");

        // 配置用户到角色关系,可以对中间表中的数据进行维护
        sysUser.getRoles().add(sysRole);

        // 配置角色到用户的关系,可以对中间表的数据进行维护(放弃了维护)
        sysRole.getUsers().add(sysUser);

        sysUserDao.save(sysUser);
        sysRoleDao.save(sysRole);
    }
}

 

    在多对多(保存)中,如果双向都设置关系,意味着双方都维护中间表,都会往中间表插入数据,中间表的2个字段又作为联合主键,主键重复,所以报错

    解决保存失败的问题:只需要在任意一方放弃对中间表的维护权即可,推荐在被动的一方放弃,配置如下:

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

多对多级联操作:

  和一对多一样,只需要在操作主体的注解上配置cascade

@ManyToMany(targetEntity = SysRole.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<SysRole> roles = new HashSet<SysRole>(0);

  测试:

/**
     * 测试级联添加(保存一个用户的同时保存用户的关联角色)
     */
    @Test
    @Transactional
    @Rollback(false)
    public void testCasCadeAdd() {
        SysUser sysUser = new SysUser();
        sysUser.setUserName("小李");

        SysRole sysRole = new SysRole();
        sysRole.setRoleName("java程序员");

        sysUser.getRoles().add(sysRole);
        sysRole.getUsers().add(sysUser);

        sysUserDao.save(sysUser);
    }

    /**
     * 删除操作
     *     在多对多的删除时,双向级联删除根本不能配置
     * 禁用
     *    如果配了的话,如果数据之间有相互引用关系,可能会清空所有数据
     *
     * 案例:删除id为1的用户,同时删除他的关联对象
     */
    @Test
    @Transactional
    @Rollback(false)
    public void testCasCadeRemove() {
        //查询1号用户
        SysUser user = sysUserDao.findOne(1L);
        //删除1号用户
        sysUserDao.delete(user);
    }

 

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

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

SpringData JPA实现增删改查

SpringData JPA多表查询

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

springdata+jpa+springside

Sprindata jpa