上次有介绍Spring Data JPA 接口实现的第一个接口JpaRepository,这次说一下动态查询, 第二个接口JpaSpecificationExecutor进行操作和Spring Data JPA 如何操作多表
接口有简单几个方法:主要是参数就是查询条件有关于分页的查询,需要实现对应的Pageable接口,可以看到有一个都是使用的接口Specification,下面来说一下:
进入接口可以看见它只实现了一个方法
若是多个参数进行对此进行规则构造 然后将多个属性进行逻辑拼接
此方法有三个参数:
Root:是根对象,属性都可以从中获取 使用 Entity e = root.get(xxx);
CriteriaQuery:顶层的查询对象,自定义查询对象 有逻辑方法,可以自定义去设计
CriteriaBuilder:查询构造器,封装的是规则
Predicate predicate = cb.equal(e,“xxxx”)
返回predicate即可
可以看到里面就是我们平时使用的规则
基本方法列表:
方法名称 | Sql对应关系 |
---|---|
equle | filed = value |
gt(greaterThan ) | filed > value |
lt(lessThan ) | filed < value |
ge(greaterThanOrEqualTo ) | filed >= value |
le( lessThanOrEqualTo) | filed <= value |
notEqule | filed != value |
like | filed like value |
notLike | filed not like value |
基本使用:
@Test
public void test() {
Specification<Student> specification = new Specification(){
public Predicate toPredicate(Root root, CriteriaQuery criteriaQuery, CriteriaBuilder criteriaBuilder) {
//使用root根获取我们需要的属性
Path name = root.get("StuName");
//使用构造进行查询和返回Predicate
//并且执行是使用JPQL语句 :name = ?
Predicate predicate = criteriaBuilder.equal(name,"xxx");
return Predicate;
}
};
}
多个查询条件使用cb对象连接:
Path name = root.get("StuName");
Path age = root.get("StuAge");
Predicate p1 = criteriaBuilder.like(name,"xxx");
Predicate p2 = criteriaBuilder.equal(age,xxx);
return criteriaBuilder.and(p1,p2);//拼接
Sort接口:有封装很多方法常用有这个枚举
Specification<Customer> specification = new Specification() {
public Predicate toPredicate(Root root, CriteriaQuery criteriaQuery, CriteriaBuilder criteriaBuilder) {
Path StuID = root.get("StuID");
};
Sort sort = new Sort(Sort.Direction.DESC,StuId);
List list = stuDao.findAll(specification,sort);
分页接口封装了我们常使用的方法:
实现类有用PageRequest类,
分页返回page对象 主要有getContent方法、getTotalPages方法、getTotalElements方法
Specification<Customer> specification = new Specification() {
public Predicate toPredicate(Root root, CriteriaQuery criteriaQuery, CriteriaBuilder criteriaBuilder) {
Path xxx = root.xxx("xxx");
};
Pageable pageable = new PageRequest(0,1);//每次查询1个,从第0页开始
Page<Student> all = StuDao.findAll(specification, pageable);
System.out.println(all.getContent());
System.out.println(all.getTotalElements());
System.out.println(all.getTotalPages());
多表关系操作多表
一对多操作:客户和联系人
一般将一的一方称作为主表,多的一方称作从表,在数据库中建立一对多的关系需要使用到外键约束
客户(主)对应对个联系人(从)
在实体类中体现方式为
主表有一个属性集合,存放的就是从表数据,采用注解方式进行配置
声明关系:一对多关系
参数是多的一方的实体字节码文件
@OneToMany(targetEntity=从表实体.class)
然后配置外键(中间表)
@JointColumn(name=“数据库外键”,referencedColumn=“主表的主键”)
@OneToMany(targetEntity=Link.class) //主表配置一对多关系
@JoinColumn(name="link_cust_id",ReferencedColumnName="cust_id")//配置中间表
private Set<Link> linkSet;//主表的属性结合
从表实体中体现方式是有一个客户对象
声明关系:一对多关系
@ManyToOne(targetEntity=主表实体.class)
配置外键(中间表)
@JointColumn(name=“数据库外键”,referencedColumn=“主表的主键”)
配置外键的过程,配置到了多的一方,就会在多的一方维护外键
@ManyToOne(targetEntity=Customer.class) //从表配置多对一关系
@JoinColumn(name="link_cust_id",ReferencedColumnName="cust_id")//配置中间表
private Customer customer;
在两方都进行配置就是双向的一对多关系
注解详情:
- @OneToMany
- 用于建立一对多关系
- targetEntityClass:指定多的方的字节码文件
- mappedBy:指定从表实体类引用主表对象的名称
- cascade:指定是否需要使用级联操作
- fetch:指定是否延迟加载
- @ManyToOne
- 用于建立多对一关系
- targetEntityClass:指定多的方的字节码文件
- cascade:指定是否需要使用级联操作
- fetch:指定是否延迟加载
- @JoinColumn
- 用于定义主键字段与外键字段的对应关系
- name:指定外键字段的名称
- referencedColumnName:指定用于主表的主键字段名称
- unique:是否唯一,默认允许
- nullable:是否允许为空,默认允许
- insertable:是否允许插入,默认允许
- updatable:是否允许更新表,默认允许
- columnDefinition:列的定义信息
测试:
添加操作:
要求:创建用户对象和联系人对象、建立关系(一对多关系)、保存(先保存主表信息,再存从表信息)
@Transactional //事务
@Rollback(value = false)//回滚false
//必须添加实体之间的关系然后再进行添加数据 否则不会建立连接 就是外键无效
Customer customer = new Customer();
LinkMan linkMan = new LinkMan();
customer.getLinkMans().add(linkMan);//此方法建立一对多关系联系,产生两个insert和一个update
linkMan.setCustomer(customer);//此方法建立多对一关系联系,产生两个insert
//保存
customerDao.save(customer);
linkManDao.save(linkMan);
产生的问题:
两条insert语句,和一条维护的表关系update语句,而在实际开发中,我们只需要两个inset语句
保存语句 主表配置关系会有新的sql语句,update产生
而从表建立关系没有产生多余的update语句
那我们的解决是思路很简单,在一对多的一的方向放弃维护关系
@OneToMany(mappedBy=“customer”)
删除操作:删除操作也有约束
-
删除从表时候可以进行任意删除
-
删除主表数据时候
- 从表有数据情况
- 默认情况下,将外键字段设置为null,然后将主表数据删除,如果数据库默认外键约束不为空,则会出现报错
- 如果配置了主表放弃维护外键关系,则不能删除(与数据库设置不为null无关),因为再删除时候就不会去更新从表的数据
- 从表没有数据,随便删除
在实际开发中,级联删除慎用
级联删除:
操作一个同时操作它的关联对象
1.注意操作主体 主需要在操作主体上进行配置cascade
2.在操作的主题类上进行添加主题属性(配置在多表映射的注解上 OneToMany)
3.cascade(配置级联) CascadeType.范围
配置信息:
CascadeType.MERGE 级联更新
* CascadeType.PERSIST 级联保存:
* CascadeType.REFRESH 级联刷新:
* CascadeType.REMOVE 级联删除:
* CascadeType.ALL 包含所有
@OneToMany(mappedBy = "customer",cascade = CascadeType.ALL,fetch = FetchType.EAGER)
添加
保存客户也保存联系人
删除
删除客户也保存联系人
多对多和一对多基本差不多,简单说一下
比如一个人与角色的关系就是多对多的
一个人可以有多个角色:所以User实体类里存角色对象就是一个集合:
@ManyToMany(targetEntity=Role.class,cascade=CascadeType.ALL)//使用多对多注解
@JoinTable(name="tbl_user_role",//中间表的名称
JoinColumns={ @JointColumns(name="tab_user_role_userid",referencedColunmName="user_id")}, //当前对象在中间表的字段和中间表字段名称对应关系 @inverseJoinColumns{@JoinCloulmn(name="tab_user_role_userid"),referenedColumnName="role_id"}//对方对象在中间表的字段和中间表字段名称对应关系
)
private Set<Role> roleSet = new HashSet<Role>();
一个角色可以对应多个人,也是集合:
@ManyToMany(mapperBy="roleSet") //设置映射
private Set<User> userSet = new HashSet<User>();
@ManyToMany:设置多对多关系映射表
@JointTable:
- 针对中间表的配置
- name:中间表的名称
- @JoinColumns:中间表的外键字段与当前对象实体类所对应的表的主键
- @inverseJoinColumns:中间表的外键字段与关联对象实体类所对应的表的主键
多对多操作:
还是放弃两边同时维护中间表,只一方进行维护,防止出现主键冲突问题
在多对多的删除时,双向级联删除根本不能配置。禁用
如果配了的话,如果数据之间有相互引用关系,可能会清空所有数据
删除:先删除中间表信息
Spring Data JPA的多表查询:
多表的查询:
对象导航查询:OGNL 查询一个对象同时通过此对象查询关联的对象
对象.getLinkMans方法 需要添加@Transcation注解 解决no session问题
默认使用的是延迟加载
修改配置:设置加多表映射的注解上:fetch
当查询为一对多的时候采用延迟加载,而是多对一时候,使用立即加载
Specification查询:
Specification spec = new Specification() {
public Predicate toPredicate(Root root, CriteriaQuery criteriaQuery, CriteriaBuilder criteriaBuilder) {
Join<LinkMan,Customer> customer = root.join("customer", JoinType.INNER);//连接查询
return criteriaBuilder.like(customer.<String>get("custName").as(String.class),"1");//查了名称为1的关联对象
}
};
List all = linkManDao.findAll(spec);
System.out.println(all);
也可以实现关联查询:
Join:代表链接查询,通过root对象获取
创建的过程中,第一个参数为关联对象的属性名称,第二个参数为连接查询的方式(left,inner,right)
JoinType.LEFT : 左外连接;JoinType.INNER:内连接;JoinType.RIGHT:右外连接