Spring Data JPA 框架总结(二)

第1章 Specification动态查询

JpaSpecificationExecutor 方法列表

	1.  T findOne(Specification<T> spec);  //查询单个对象

	2.  List<T> findAll(Specification<T> spec);  //查询列表

	    //查询全部,分页
	    //pageable:分页参数
    	//返回值:分页pageBean(page:是springdatajpa提供的)
	3.  Page<T> findAll(Specification<T> spec, Pageable pageable);

		//查询列表
		//Sort:排序参数
	4.  List<T> findAll(Specification<T> spec, Sort sort);

	5.  long count(Specification<T> spec);//统计查询

对JpaSpecificationExecutor接口基本上是围绕Specification接口定义的,Specification构造的就是查询条件。

1.1 使用Specification完成条件查询

1.1.1 根据客户名称查询

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

    @Autowired
    private CustomerDao customerDao;
    @Test
    public void testSpecifications() {
        //匿名内部类
        /**
         * 案例:根据客户名称查询,查询客户名为传智播客的客户
         *       查询条件
         *          1.查询方式
         *              cb:构建查询,添加查询方式
         *          2.比较的属性名称
         *              root:从实体Customer对象中按照CustName属性进行查询
         */
         //使用匿名内部类的方式,创建一个Specification的实现类,并实现toPredicate方法
        Specification<Customer> spec = new Specification<Customer>() {
            public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                //1.获取比较的属性
                Path<Object> custName = root.get("custName");
                //2.构造查询方式
                 return  cb.equal(custName,"传智播客");//精准匹配
            }
        };
        Customer customer = customerDao.findOne(spec);
        System.out.println(customer);
    }

1.1.2 多条件查询,根据客户名称或(和)所属行业进行查询

 /**
     * 多条件查询
     *      案例:根据客户名称和见客户所属行业查询
     */
    @Test
    public void testSpec2() {
        Specification <Customer> spec = new Specification<Customer>() {
            @Override
            public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                Path<Object> custName = root.get("custName");
                Path<Object> custIndustry = root.get("custIndustry");
                //1.构造查询
                //构造客户名称的精准匹配查询
                Predicate p1 = cb.equal(custName, "传智播客");
                //构造所属行业的精准匹配查询
                Predicate p2 = cb.equal(custIndustry, "政府");
                //2.将多个查询条件组合到一起   //(与 ,或)
                Predicate or = cb.or(p1, p2);//以与的形式拼接多个查询条件
                //cb.and(p1,p2);//以或的方式拼接多的查询条件
                return or;
            }
        };
        List<Customer> customers = customerDao.findAll(spec);
        for (Customer customer:customers
             ) {
            System.out.println(customer);
        }
    }

1.1.3 根据客户名称模糊查询

    /**
     * 案例:完成根据客户名称的模糊查询,返回客户列表
     *      equal:直接得到path对象(查询属性)进行比较即可
     *      gt,lt,ge,le,like:得到path对象(查询属性),根据path对象执行比较参数类型,再进行比较
     *      path.as(类型字节码)
     */
    @Test
    public void testSpec3() {
        Specification spec = new Specification<Customer>() {
            @Override
            public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) {
                //1.查询属性:客户名称
                Path<Object> custName = root.get("custName");
                //2.查询方式:模糊匹配
                Predicate like = cb.like(custName.as(String.class), "传智播客%");
                return like;
            }
        };
/*        List<Customer> customers = customerDao.findAll(spec);
        for (Customer customer:customers
        ) {
            System.out.println(customer);
        }*/
        //添加排序(根据id进行倒序)
                //Sort.Direction.DESC(倒序)
                 //Sort.Direction.ASC(升序)
        //创建排序对象
        Sort sort = new Sort(Sort.Direction.DESC,"custId");
        List<Customer> customers = customerDao.findAll(spec, sort);
        for (Customer customer:customers
        ) {
            System.out.println(customer);
        }
    }

1.1.4 分页查询

    /**
     * 分页查询
     *      Specification:查询条件
     *      Pageable:分页查询
     *          分页参数: 查询的页码,每页查询的条数
     *          findAll(Specification, Pageable): 带有条件的分页
     *          findAll(Pageable): 没有条件的分页
     *      返回:Page(Spring Data JPA为我们封装好的pageBean对象,数据列表,共条数)
     */
    @Test
    public void testSpec4() {
        Specification spec = new Specification<Customer>() {
            @Override
            public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb) {
                //1.查询属性:客户名称
                Path<Object> custName = root.get("custName");
                //2.查询方式:模糊匹配
                Predicate like = cb.like(custName.as(String.class), "传智播客%");
                return like;
            }
        };
        //PageRequest对象是Pageable接口实现类
        /**
         * 创建PageRequest的过程中,需要调用它的构造方法传入两个参数
         *      第一个参数:当前查询的页数
         *      第二个参数:每页查询的数量
         */
        Pageable pageable = new PageRequest(0,2);
        Page<Customer> page = customerDao.findAll(spec, pageable);//有条件的查询
//        Page<Customer> page2 = customerDao.findAll(pageable);//无条件的查询
        System.out.println(page.getTotalElements());//得到总条数
        System.out.println(page.getContent());//得到数据集合列表
        System.out.println(page.getTotalPages());//得到总页数
    }

第2章 多表设计

表的关系:

  • 一对一关系
  • 一对多关系
    • 一的一方:主表
    • 多的一方:从表
    • 外键:需要再从表上新建一列作为外键,取值来源于主表的主键
  • 多对多关系

实体类中的关系:

  • 包含关系:可以通过实体类中的包含关系描述表关系
  • 继承关系

分析步骤:

    1. 明确表关系
    1. 确定表关系
    1. 编写实体类,在实体类中描述表关系。
    1. 配置映射关系

第3章 JPA中的一对多

3.1 明确表关系

客户:指一家公司。
联系人:指公司员工。
关系:一对多

3.2 确定表关系

在这里插入图片描述

3.3 编写实体类建立映射关系配置

3.3.1 客户的实体类:

@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:外键字段名称
     *                              referencedColumnName:参照的主表的主键字段名称
     *
     */
    @OneToMany(targetEntity = LinkMan.class)
    @JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
    private Set<LinkMan> linkMans = new HashSet<LinkMan>();

    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 + '\'' +
                '}';
    }
}

3.3.2 联系人的实体类

@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.配置外键(中间表)
     *      @JoinColumn:配置外键
     *              name:外键字段名称
     *              referencedColumnName:参照的主表的主键字段名称
     *配置外键过程,配置到了多的一方就会再多的以法国维护外键
     */
    @ManyToOne(targetEntity = Customer.class)
    @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.4 映射的注解说明

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

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

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

3.5 一对多的操作

3.5.1 添加

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

        LinkMan linkMan = new LinkMan();
        linkMan.setLkmName("mi");

//设置了双向的关系(会产生一条多余的uodate语句:解决方法就是在一的一方放弃维护权)       customer.getLinkMans().add(linkMan);
LinkMan.setCustomer(customer);

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

3.5.2 如何放弃维护权

在一个一方中修改配置

	/**
	 *放弃外键维护权的配置将如下配置改为
	 */
    //@OneToMany(targetEntity=LinkMan.class)
	//@JoinColumn(name="lkm_cust_id",referencedColumnName="cust_id")	
	//设置为(mappedBy:指定从表实体类中引用主表对象的名称。)
	@OneToMany(mappedBy="customer")

3.5.3 删除

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

删除操作的说明如下:

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

删除主表数据:

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

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

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

3.5.4 级联操作

使用方法:在操作主体的注解上使用cascade

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

第4章 JPA中的多对多

4.1 明确表关系

用户:包含角色的集合。
角色:包含用户的集合。
关系:多对多

4.2 确定表关系

在这里插入图片描述

4.3 编写实体类建立映射关系配置

4.3.1 用户的实体类:

@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 = "user_age")
    private Integer age;

    /**
     * 配置用户到角色的多对多关系
     * @return
     */
    @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<Role>();

    public Set<Role> getRoles() {
        return roles;
    }

    public void setRoles(Set<Role> roles) {
        this.roles = roles;
    }

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

    @Override
    public String toString() {
        return "User{" +
                "userId=" + userId +
                ", userName='" + userName + '\'' +
                ", age=" + age +
                '}';
    }
}

4.3.2 角色的实体类

@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")
/*    @ManyToMany(targetEntity = User.class)
    @JoinTable(name = "sys_user_role",
                joinColumns = {@JoinColumn(name = "sys_role_id",referencedColumnName = "role_id")},
                inverseJoinColumns = {@JoinColumn(name = "sys_user_id",referencedColumnName = "user_id")})*/
    private Set<User> users;

    public Set<User> getUsers() {
        return users;
    }

    public void setUsers(Set<User> users) {
        this.users = users;
    }

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

    @Override
    public String toString() {
        return "Role{" +
                "roleId=" + roleId +
                ", roleName='" + roleName + '\'' +
                '}';
    }
}

4.4 映射的注解说明

@ManyToMany
作用:用于映射多对多关系
属性:
cascade:配置级联操作。
fetch:配置是否采用延迟加载。
targetEntity:配置目标的实体类。映射多对多的时候不用写。

@JoinTable
作用:针对中间表的配置
属性:
nam:配置中间表的名称
joinColumns:中间表的外键字段关联当前实体类所对应表的主键字段
inverseJoinColumn:中间表的外键字段关联对方表的主键字段

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

4.5 多对多的操作

4.5.1 保存

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class testManyToMany {
    @Autowired
    private UserDao userDao;
    @Autowired
    private RoleDao roleDao;

    /**
     * 需求:
     * 	保存用户和角色
     * 要求:
     * 	创建2个用户和3个角色
     * 	让1号用户具有1号和2号角色(双向的)
     * 	让2号用户具有2号和3号角色(双向的)
     *  保存用户和角色
     * 问题:
     *  在保存时,会出现主键重复的错误,因为都是要往中间表中保存数据造成的。
     * 解决办法:
     * 	让任意一方放弃维护关联关系的权利
     */
    @Test
    @Transactional
    @Rollback(false)
    public void test1() {
        //创建对象
        User u1 = new User();
        u1.setUserName("用户1");
        Role r1 = new Role();
        r1.setRoleName("角色1");

        //建立关联关系
        u1.getRoles().add(r1);

        //保存
        roleDao.save(r1);
        userDao.save(u1);
    }

在多对多保存中,如果双向都设置关,意味着双方都维护中间表,都会往中间表插入数据。中间表的2个字段又作为联合主键,所以报错。
解决:只需要任意一方放弃对中间表的维护权即可,

4.5.2 删除

    /**
     * 案例:删除id为1的用户,同时删除它的关联对象
     *   由于已经配置了级联,所有在删除的时候会把User,Role,和关联表也删除。
     */
    @Test
    @Transactional
    @Rollback(false)
    public void testCasCadeDel() {
        //查询1号用户
        User user = userDao.findOne(1l);
        //删除1号用户
        userDao.delete(user);
    }

第5章 Spring Data JPA中的多表查询

5.1 对象导航查询

对象导航查询利用类与类之间的关系来检索对象,根据已经加载的对象,导航到它的关联对象。

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

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

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

5.2 对象导肮查询中的延迟策略

在一对多中,对象导航查询默认为延迟加载。

  • 修改为立即加载方式 :
    在一的一方实体类中配置:fetch=FetchType.EAGER

在多对一中,对象导航查询默认为立即加载。

  • 修改为延迟加载方式:
    在多的一方实体类中配置:fetch=FetchType.LAZY

5.3 使用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);
		}
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值