【实战】JPA通过onetomany、manytoone,使用Specification进行三表联查(非自定义sql)

场景:数据库已存在三张表(包含一张中间表),代码已对应建立三个实体,现通过JPA方式进行对对多联表查询
简化之后的表结构如下,表名和实体的对应关系在下面,具体的属性与实际意义见下面的tostring方法,就不赘述了
简化之后的表结构
等级表实体RiskSegment(t_risk_segment)、关联表实体RiskActionSegmentMapping(t_risk_action_r_segment)、动作表实体DisposalAction(t_risk_disposal_action)。

要通过JPA实现多表查询,在不考虑自定义sql的情况下,使用Specification实现,首先在实体类上增加onetomany,manytoone的配置。
在RiskSegment上增加一对多配置

    //targetEntity 指明集合类中保存的具体类型 ,mappedBy的值应该为RiskActionSegmentMapping中“一”那方的对象
    //json序列化时忽略此属性,序列化和反序列化都受影响(貌似这个配置没起作用)
    @OneToMany(targetEntity = RiskActionSegmentMapping.class, mappedBy = "riskSegment")
    @JsonIgnore   
    private List<RiskActionSegmentMapping> riskActionSegmentMappings;

    //重写tostring方法,去掉 List<RiskActionSegmentMapping>,避免循环调用导致栈溢出
    @Override
    public String toString() {
        return "RiskSegment{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", level='" + level + '\'' +
                ", min=" + min +
                ", max=" + max +
                '}';
    }

在DisposalAction上增加一对多配置

    //targetEntity 指明集合类中保存的具体类型 ,mappedBy的值应该为为RiskActionSegmentMapping中“一”那方的对象
    //json序列化时忽略此属性,序列化和反序列化都受影响(貌似这个配置没起作用)
    @OneToMany(targetEntity = RiskActionSegmentMapping.class, mappedBy = "disposalAction")
    @JsonIgnore
    private List<RiskActionSegmentMapping> riskActionSegmentMappings;

    //重写tostring方法,去掉 List<RiskActionSegmentMapping>,避免循环调用导致栈溢出
    @Override
    public String toString() {
        return "DisposalAction{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", describe='" + describe + '\'' +
                 '}';
    }

在RiskActionSegmentMapping上增加两个多对一的配置

    //targetEntity 指明保存的具体类型 
    //name是另一个表指向本表的外键,不设置name的值,则 name=关联表的名称+“_”+ 关联表主键的字段名 
    //referencedColumnName标注的是所关联表中(登记表)的字段名,若不指定则使用的所关联表的主键字段名作为外键
    //notfound是联表查询时找不到引用的外键数据时忽略
    @ManyToOne(targetEntity = RiskSegment.class)
    @JoinColumn(name = "levelId", referencedColumnName = "id", insertable = false, updatable = false)
    @JsonIgnore
    @NotFound(action= NotFoundAction.IGNORE)
    private RiskSegment riskSegment;

    @ManyToOne(targetEntity = DisposalAction.class)
    @JoinColumn(name = "actionId", referencedColumnName = "id", insertable = false, updatable = false)
    @JsonIgnore
    @NotFound(action=NotFoundAction.IGNORE)
    private DisposalAction disposalAction;

    @Override
    public String toString() {
        return "RiskActionSegmentMapping{" +
                "id='" + id + '\'' +
                ", actionId='" + actionId + '\'' +
                ", levelId='" + levelId + '\'' +
                ", preliminarySelection='" + preliminarySelection + '\'' +
                ", selected='" + selected + '\'' +
                ", riskSegment=" + riskSegment +
                ", disposalAction=" + disposalAction +
                '}';
    }

在实体上的配置就完成了,现在进行多表联查,先讲涉及到两个表的查询
1.已知等级表的主键id(2),和中间表的条件(preliminary_selection=0,selected=1),查询对应的动作列表
理想的查询sql

select disposal.* 
from t_risk_disposal_action disposal 
inner join t_risk_action_r_segment riskaction on disposal.id=riskaction.action_id 
where riskaction.level_id=? and riskaction.preliminary_selection=? and riskaction.selected=?

通过root.join的方式创建连接

@Test
public void test1() {
    Specification<DisposalAction> specification2 = (Specification<DisposalAction>) (root, query, cb) -> {
        List<Predicate> predicates = new ArrayList<>();
            
        //root.join第一个参数是连接的属性名称,从DisposalAction对象里面找到riskActionSegmentMappings,第二个属性是连接方式,包括左连接
        //Join<T,T>第一个泛型是源类型,第二个泛型是连接目标的类型,joinRiskActionSegmentMapping 实体(可以理解为何root相似)
        Join<DisposalAction, RiskActionSegmentMapping> joinRiskActionSegmentMapping = root.join("riskActionSegmentMappings", JoinType.INNER);
            
        //构建查询条件,levelId是中间表中等级表的外键,另外两个条件是中间表的其他条件(这个看不懂建议先查看Specification的基础用法)
        predicates.add(cb.equal(joinRiskActionSegmentMapping.get("levelId"), "2"));
        predicates.add(cb.equal(joinRiskActionSegmentMapping.get("preliminarySelection"), "0"));
        predicates.add(cb.equal(joinRiskActionSegmentMapping.get("selected"), "1"));
        return cb.and(predicates.toArray(new Predicate[predicates.size()]));
    };
    log.info(disposalActionRepository.findAll(specification2).toString());
}

生成的sql和目标sql一致,实际上相当于查询了一次一对多的联查

Hibernate: 
select disposalac0_.id as id1_8_, disposalac0_.cnname as cnname2_8_, disposalac0_.describe as describe3_8_, 
disposalac0_.label as label4_8_,disposalac0_.n1 as n5_8_, disposalac0_.n2 as n6_8_, disposalac0_.n3 as n7_8_, 
disposalac0_.n4 as n8_8_, disposalac0_.n5 as n9_8_,disposalac0_.n6 as n10_8_, disposalac0_.n7 as n11_8_, 
disposalac0_.name as name12_8_, disposalac0_.status as status13_8_, disposalac0_.type as type14_8_ 
from t_risk_disposal_action disposalac0_ 
inner join t_risk_action_r_segment riskaction1_ on disposalac0_.id=riskaction1_.action_id 
where  riskaction1_.level_id=? and riskaction1_.preliminary_selection=? and riskaction1_.selected=?

2.三个表的查询,查询(73)在的等级表的等级,并通过中间表查询到动作列表,中间表条件依旧是(preliminary_selection=0,selected=1)
第一种查询方式,通过query拼接sql

@Test
public void test() {
    Specification<DisposalAction> specification = (Specification<DisposalAction>) (root, query, cb) -> {
        List<Predicate> predicates = new ArrayList<>();
            
        //通过query拼接sql的方式查询,比较好理解,缺点是只能生成笛卡尔积
        //riskActionSegmentMappingRoot 实体
        Root<RiskActionSegmentMapping> riskActionSegmentMappingRoot = query.from(RiskActionSegmentMapping.class);
        Root<RiskSegment> riskSegmentRoot = query.from(RiskSegment.class);
            
        //构建查询条件
        // AB两表的关联条件,就是sql join 中的on条件
        predicates.add(cb.equal(riskActionSegmentMappingRoot.get("actionId"), root.get("id"))); 
        // AB两表的关联条件,就是sql join 中的on条件
        predicates.add(cb.equal(riskActionSegmentMappingRoot.get("levelId"), riskSegmentRoot.get("id"))); 
        predicates.add(cb.equal(riskActionSegmentMappingRoot.get("preliminarySelection"), "0"));
        predicates.add(cb.equal(riskActionSegmentMappingRoot.get("selected"), "1"));
        predicates.add(cb.greaterThan(riskSegmentRoot.get("max"), "73"));
        predicates.add(cb.lessThan(riskSegmentRoot.get("min"), "73"));
        return cb.and(predicates.toArray(new Predicate[predicates.size()]));
    };
    List<DisposalAction> all = disposalActionRepository.findAll(specification);
    log.info(all.toString());
}

这种方式得到的sql联表查询的结果是笛卡尔积,用得还算比较少

Hibernate: 
select disposalac0_.id as id1_8_, disposalac0_.cnname as cnname2_8_, disposalac0_.describe as describe3_8_, 
disposalac0_.label as label4_8_, disposalac0_.n1 as n5_8_, disposalac0_.n2 as n6_8_, disposalac0_.n3 as n7_8_, 
disposalac0_.n4 as n8_8_, disposalac0_.n5 as n9_8_,disposalac0_.n6 as n10_8_, disposalac0_.n7 as n11_8_, 
disposalac0_.name as name12_8_, disposalac0_.status as status13_8_, disposalac0_.type as type14_8_ 
 from t_risk_disposal_action disposalac0_ 
 cross join t_risk_action_r_segment riskaction1_ 
 cross join t_risk_segment risksegmen2_ 
 where riskaction1_.action_id=disposalac0_.id and riskaction1_.level_id=risksegmen2_.id 
 and riskaction1_.preliminary_selection=? and riskaction1_.selected=? 
 and risksegmen2_.max>73 and risksegmen2_.min<73

第二种查询方式,还是通过root.join方式查询,可以看得到第一次连接是内连接,第二次连接是左连接

@Test
public void test3() {
    Specification<DisposalAction> specification2 = (Specification<DisposalAction>) (root, query, cb) -> {
        List<Predicate> predicates = new ArrayList<>();
        //root.join第一个参数是连接的属性名称,从DisposalAction对象里面找到riskActionSegmentMappings,第二个属性是连接方式
        //Join<T,T>第一个泛型是源类型,第二个泛型是连接目标的类型,joinRiskActionSegmentMapping 实体(可以理解为何root相似)
        Join<DisposalAction, RiskActionSegmentMapping> joinRiskActionSegmentMapping = root.join("riskActionSegmentMappings", JoinType.INNER);

        //joinRiskActionSegmentMapping.join第一个参数是连接的属性名称,从RiskActionSegmentMapping对象里面找到riskSegment
        //Join<T,T>第一个泛型是源类型,第二个泛型是连接目标的类型,segmentMappingRiskSegmentJoin 实体(可以理解为何root相似)
        Join<RiskActionSegmentMapping, RiskSegment> segmentMappingRiskSegmentJoin = joinRiskActionSegmentMapping.join("riskSegment", JoinType.LEFT);

        //构建查询条件
        predicates.add(cb.equal(joinRiskActionSegmentMapping.get("preliminarySelection"), "0"));
        predicates.add(cb.equal(joinRiskActionSegmentMapping.get("selected"), "1"));
        predicates.add(cb.greaterThan(segmentMappingRiskSegmentJoin.get("max"), "73"));
        predicates.add(cb.lessThan(segmentMappingRiskSegmentJoin.get("min"), "73"));
        return cb.and(predicates.toArray(new Predicate[predicates.size()]));
    };
    List<DisposalAction> all2 = disposalActionRepository.findAll(specification2);
    log.info(all2.toString());
}

而得到的sql也是第一次是内连接,第二次是左连接,满足三表联查的需要,其实多表查询,就是从上一个表找连接的下一个表的拼接sql的过程,找到了表,就可以引用表中的字段作为查询条件,而这里转换成了实体而已,从上一个实体找连接的下一个实体,找到了实体,就可以引用实体中的属性作为查询条件。

Hibernate: 
select disposalac0_.id as id1_8_, disposalac0_.cnname as cnname2_8_, disposalac0_.describe as describe3_8_,
disposalac0_.label as label4_8_, disposalac0_.n1 as n5_8_, disposalac0_.n2 as n6_8_, disposalac0_.n3 as n7_8_,
disposalac0_.n4 as n8_8_, disposalac0_.n5 as n9_8_, disposalac0_.n6 as n10_8_, disposalac0_.n7 as n11_8_, 
disposalac0_.name as name12_8_, disposalac0_.status as status13_8_, disposalac0_.type as type14_8_ 
from t_risk_disposal_action disposalac0_ 
inner join t_risk_action_r_segment riskaction1_ on disposalac0_.id=riskaction1_.action_id 
left outer join t_risk_segment risksegmen2_ on riskaction1_.level_id=risksegmen2_.id 
where riskaction1_.preliminary_selection=? and riskaction1_.selected=? 
and risksegmen2_.max>73 and risksegmen2_.min<73

因为这里是简化了很多表属性,原本的查询结果很多,所以就没有展示出来,但是查询出来的结果都是一致的,验证过了。

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 可以使用JPASpecification实现三表连接模糊查询,示例代码如下: ``` public Specification<User> fuzzyQuery(String keyword) { return (root, query, cb) -> { Join<User, Order> orderJoin = root.join("orders", JoinType.LEFT); Join<Order, Product> productJoin = orderJoin.join("products", JoinType.LEFT); Predicate predicate = cb.like(root.get("name"), "%" + keyword + "%"); predicate = cb.or(predicate, cb.like(orderJoin.get("orderNo"), "%" + keyword + "%")); predicate = cb.or(predicate, cb.like(productJoin.get("name"), "%" + keyword + "%")); return predicate; }; } ``` 这段代码实现了对User、Order、Product三张表的连接查询,并且可以根据关键字进行模糊查询。其中,root表示User表的根节点,orderJoin表示User表与Order表的连接,productJoin表示Order表与Product表的连接。使用cb.like方法实现模糊查询,最后返回Predicate对象。 ### 回答2: 在使用JPASpecification实现三表连接模糊查询时,可以按照以下步骤进行操作: 1. 创建实体类:首先创建需要查询的三个实体类,并在实体类中定义三个表之间的关联关系。 2. 继承Specification接口:创建一个自定义Specification接口,该接口需要继承JpaSpecificationExecutor接口,用于实现Specification的各种查询条件。 3. 实现Specification的方法:在自定义Specification接口中,实现toPredicate方法,用来设置各种查询条件和连接条件。该方法需要返回一个Predicate对象,该对象将作为查询条件传递给JpaSpecificationExecutor接口。 4. 使用Specification进行查询:在使用JpaRepository时,可以使用Specification对象进行查询。示例代码如下: ```java @Repository public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> { } @Service public class UserService { @Autowired private UserRepository userRepository; public List<User> searchUsers(String keyword) { Specification<User> spec = new Specification<User>() { @Override public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) { Join<User, Order> orderJoin = root.join("orders", JoinType.INNER); Join<Order, Product> productJoin = orderJoin.join("product", JoinType.INNER"); Predicate p1 = cb.like(root.get("name"), "%" + keyword + "%"); Predicate p2 = cb.like(orderJoin.get("orderId"), "%" + keyword + "%"); Predicate p3 = cb.like(productJoin.get("productName"), "%" + keyword + "%"); return cb.or(p1, p2, p3); } }; return userRepository.findAll(spec); } } ``` 在上述示例中,首先使用Specification接口实现了toPredicate方法,在该方法中定义了三个表之间的连接关系和模糊查询的条件。然后在UserService中注入了UserRepository,并使用findAll方法传入Specification对象进行查询操作。 通过以上步骤,我们可以使用JPASpecification实现三表连接模糊查询,根据需求定义查询条件和连接关系,灵活地查询数据。 ### 回答3: 使用JPASpecification实现三表连接模糊查询可以通过以下步骤实现: 1.定义三个实体类,分别表示三个表,假设为表A、表B和表C。 2.在每个实体类中定义对应的属性和关联关系,例如表A中有一个外键关联到表B,表B中有一个外键关联到表C。 3.创建一个自定义Specification类,继承JpaSpecificationExecutor接口,并实现Specification接口。 4.在自定义Specification类中重写toPredicate方法,通过CriteriaBuilder构建查询条件,使用root进行连接查询和模糊查询。 5.在业务层中调用Specification定义的方法,传入条件参数并调用repository的findAll方法即可实现查询。 示例代码如下: ```java @Entity @Table(name = "table_a") public class TableA { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "name") private String name; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "table_b_id") private TableB tableB; // getters and setters } @Entity @Table(name = "table_b") public class TableB { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "type") private String type; @OneToMany(mappedBy = "tableB", cascade = CascadeType.ALL) private List<TableA> tableAList; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "table_c_id") private TableC tableC; // getters and setters } @Entity @Table(name = "table_c") public class TableC { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "title") private String title; @OneToMany(mappedBy = "tableC", cascade = CascadeType.ALL) private List<TableB> tableBList; // getters and setters } public class MySpecification implements Specification<TableA> { private String keyword; public MySpecification(String keyword) { this.keyword = keyword; } @Override public Predicate toPredicate(Root<TableA> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) { Join<TableA, TableB> joinAB = root.join("tableB"); Join<TableB, TableC> joinBC = joinAB.join("tableC"); return criteriaBuilder.or( criteriaBuilder.like(root.get("name"), "%" + keyword + "%"), criteriaBuilder.like(joinAB.get("type"), "%" + keyword + "%"), criteriaBuilder.like(joinBC.get("title"), "%" + keyword + "%") ); } } public class MyService { @Autowired private TableARepository tableARepository; public List<TableA> search(String keyword) { MySpecification specification = new MySpecification(keyword); return tableARepository.findAll(specification); } } ``` 以上示例代码中的TableARepository为自定义JpaRepository,在其继承的JpaRepository类中已经包含了JpaSpecificationExecutor接口,因此可以直接调用findAll方法并传入自定义Specification实现类进行查询。在MyService中调用MySpecification定义的方法,并传入查询关键字,即可进行三表连接模糊查询。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值