1. 程式人生 > 實用技巧 >Spring Data JPA 連表動態條件查詢

Spring Data JPA 連表動態條件查詢

多表查詢在spring data jpa中有兩種實現方式,第一種是利用hibernate的級聯查詢來實現(使用較為複雜,查詢不夠靈活),第二種是使用原生sql查詢。

JPA原生SQL連表查詢

@Repository
public class SqlQueryRepository implements BaseQueryRepository {
    private static final String COUNT_REPLACEMENT_TEMPLATE = "select count(%s) $5$6$7";
    /**
     * 匹配第三組,換成count(*),目前只用到simple
     */
    private static final String SIMPLE_COUNT_VALUE = "$3*";
    /**
     * 複雜查詢,count(主物件)
     */
    private static final String COMPLEX_COUNT_VALUE = "$3$6";

    private static final Pattern COUNT_MATCH;
    private static final String IDENTIFIER = "[\\p{Alnum}._$]+";
    private static final String IDENTIFIER_GROUP = String.format("(%s)", IDENTIFIER);

    @PersistenceContext
    private EntityManager entityManager;

    // (select\s+((distinct )?(.+?)?)\s+)?(from\s+[\p{Alnum}._$]+(?:\s+as)?\s+)([\p{Alnum}._$]+)(.*)
    static {
        StringBuilder builder = new StringBuilder();
        // from as starting delimiter
        builder.append("(?<=from)");
        // at least one space separating
        builder.append("(?: )+");
        // Entity name, can be qualified (any
        builder.append(IDENTIFIER_GROUP);
        // exclude possible "as" keyword
        builder.append("(?: as)*");
        // at least one space separating
        builder.append("(?: )+");
        // the actual alias
        builder.append("(\\w*)");

        builder = new StringBuilder();
        builder.append("(select\\s+((distinct )?(.+?)?)\\s+)?(from\\s+");
        builder.append(IDENTIFIER);
        builder.append("(?:\\s+as)?\\s+)");
        builder.append(IDENTIFIER_GROUP);
        builder.append("(.*)");

        COUNT_MATCH = compile(builder.toString(), CASE_INSENSITIVE);
    }


    /**
     * 封裝原生sql分頁查詢,自動生成countSql
     *
     * @param pageable 分頁引數
     * @param querySql 查詢sql,不包含排序
     * @param orderSql 排序sql
     * @param paramMap 引數列表
     * @param clazz    返回物件class
     * @param <T>      返回物件
     * @return PageImpl
     */
    @Override
    public <T> Page<T> queryPageable(String querySql, String orderSql, Map<String, Object> paramMap, Pageable pageable, Class<T> clazz) {
        String countSql = createCountQuery(querySql);
        Query countQuery = (Query)this.entityManager.createNativeQuery(countSql);
        Query query = (Query)this.entityManager.createNativeQuery(querySql + orderSql);

        // 設定引數
        if (paramMap != null && paramMap.size() > 0) {
            for (Map.Entry<String, Object> entry : paramMap.entrySet()) {
                countQuery.setParameter(entry.getKey(), entry.getValue());
                query.setParameter(entry.getKey(), entry.getValue());
            }
        }

        BigInteger totalCount = (BigInteger) countQuery.getSingleResult();

        query.setFirstResult((int) pageable.getOffset());
        query.setMaxResults(pageable.getPageSize());
        // 不使用hibernate轉bean,存在資料型別問題
        //query.setResultTransformer(Transformers.aliasToBean(clazz));
        query.setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
        List<T> resultList = JSON.parseArray(JSON.toJSONString(query.getResultList(), SerializerFeature.WriteMapNullValue), clazz);

        return new PageImpl<>(resultList, pageable, totalCount.longValue());
    }


    /**
     * 根據查詢sql自動生成countSql,正則匹配
     *
     * @param sql 查詢sql
     * @return countSql
     */
    public String createCountQuery(String sql) {
        Matcher matcher = COUNT_MATCH.matcher(sql);
        return matcher.replaceFirst(String.format(COUNT_REPLACEMENT_TEMPLATE, SIMPLE_COUNT_VALUE));
    }

}

使用示例

@Repository
public class RiskChangeDaoImpl implements RiskChangeNativeDao {
    @Autowired
    private BaseQueryRepository baseQueryRepository;


    @Override
    public Page<RiskChange> getRiskChangePageable(String tenantId, String userId, RiskMonitorDTO riskMonitorDTO, PageRequest pageable) {
        // 拼接查詢sql
        StringBuilder selectSql = new StringBuilder();
        selectSql.append("SELECT rc.id id, rc.pk_risk_change pkRiskChange, rc.pk_risk pkRisk, rc.change_date changeDate, rc.company_id companyId, ");
        selectSql.append("rc.company_name companyName, rc.risk_level riskLevel, rc.risk_type riskType, rc.risk_content riskContent ");
        selectSql.append("FROM dv_risk_sub rs,dv_risk_change rc  ");
        selectSql.append("WHERE  rs.user_id = :userId  AND rs.tenant_id = :tenantId AND rs.dr = 0 ");
        selectSql.append("AND rs.company_id = rc.company_id  AND rc.change_date >= rs.sub_date ");

        HashMap<String, Object> paramMap = new HashMap<>(16);
        paramMap.put("userId", userId);
        paramMap.put("tenantId", tenantId);

        StringBuilder whereSql = new StringBuilder();
        // 企業名稱模糊篩選
        if (StringUtils.isNotBlank(riskMonitorDTO.getCompanyName())) {
            whereSql.append(" AND rs.company_name like :companyName ");
            paramMap.put("companyName", "%" + riskMonitorDTO.getCompanyName() + "%");
        }

        // 風險型別篩選 in
        if (!CollectionUtils.isEmpty(riskMonitorDTO.getRiskTypes())) {
            whereSql.append(" AND rc.risk_type in :riskType ");
            paramMap.put("riskType",  riskMonitorDTO.getRiskTypes());
        }

        if (StringUtils.isNotBlank(riskMonitorDTO.getStartChangeDate())) {
            whereSql.append(" AND rc.change_date >= :startChangeDate ");
            paramMap.put("startChangeDate",  riskMonitorDTO.getStartChangeDate());
        }
        if (StringUtils.isNotBlank(riskMonitorDTO.getEndChangeDate())) {
            whereSql.append(" AND rc.change_date <= :endChangeDate ");
            paramMap.put("endChangeDate",  riskMonitorDTO.getEndChangeDate());
        }

        // 新增排序
        String orderSql = " ORDER BY changeDate desc, companyId desc ";
        String querySql = selectSql.append(whereSql).toString();

        return baseQueryRepository.queryPageable(querySql, orderSql, paramMap, pageable, RiskChange.class);
    }
}