在Spring Data JPA中使用@Query注解

目录

前言

在以往写过几篇spring data jpa相关的文章,分别是
Spring Data JPA 使用JpaSpecificationExecutor实现多条件查询(分页查询和非分页查询)
Spring Data JPA实现分页多条件查询2

都是通过代码而不是sql来完成查询的,但是在做复杂情况的查询时,难免会用到@Query写sql语句。

示例

简单示例

在@Query中用:paramName标识参数,再用@Param来指定方法参数与查询语句中的参数之间的映射关系。

例如:

java 复制代码
@Query("select r from RelationDO r where r.indexCode in :idList")
List<RelationDO> findByIdListIn(@Param("idList") Collection<String> idList);

只查询部分字段,映射到一个实体类中

注意类的路径写完整

java 复制代码
@Query("SELECT new com.xxx.service.impl.bo.RecordBO(e.code, e.day, e.total, e.success, e.fail, e.app) " +
            "FROM RecordDO e " +
            "WHERE e.code = :code AND e.day = :day " +
            "AND e.app in :appCodes")
List<RecordBO> findCalendarDetail(@Param("code") String code,
                                        @Param("day") String day,
                                        @Param("appCodes") List<String> appCodes);

这里为什么映射了一个新的BO出来呢... 是RecordDO中有一个id字段,在实体类中添加了@Id注解(实体必须有@Id,不然会报错),这个id字段本来设计的是不会重复的,但是后续经过一些改动,它在某些情况下会重复了,这个时候就会有一个问题,我直接select整个RecordDO,id字段重复的它会当成同一条记录(不确定为什么,但是实际跑出来确实是这样),但我又不想再去改表结构,因此这里我select的时候直接省略了id字段,就正常了。(可能不是一个很好的解决方案,但是确实是可以这么做的)

只查询部分字段在表字段较多,所需字段比较少的时候还是可以用的。

只查询部分字段时,也可以使用List<Object[]>接收返回值

例如我现在需要用code和month查出这么一个结果:

json 复制代码
[
	{
		"day":"20240601",
		"result":[
			{
		        "rate": 98.77
		        "app": "0001"
		    },
		    {
		        "rate": 95.32
		        "app": "0002"
			}
		]
	},
	{
		"day":"20240602",
		"result":[
			{
		        "rate": 95.65
		        "app": "0001"
		    },
		    {
		        "rate": 96.89
		        "app": "0002"
			}
		]
	},
	......
]

也就是说要把月份中的每一天抽取出来,再在下面放每个app对应的明细

这个时候写sql:

java 复制代码
@Query("SELECT e.day, e.app, e.success, e.total" +
        "FROM RecordDO e " +
        "WHERE e.code = :code AND SUBSTRING(e.day, 1, 6) = :month AND e.total > 0")
List<Object[]> findByMonth(@Param("code") String code,
                           @Param("month") String month);

调用上述方法后封装返回数据:

java 复制代码
List<CalendarBO> calendarBOS = Lists.newArrayList();
List<Object[]> resultList = recordRepository.findByMonth(code,month);

if (!CollectionUtils.isEmpty(resultList)){
    for (Object[] result : resultList) {
        String day = (String) result[0];
        String app = (String) result[1];
        Integer success = (Integer) result[2];
        Integer total = (Integer) result[3];
        double rate = (double) success * 100 / total ;
        double roundedRate = Math.round(rate * 100.0) / 100.0;
        CalendarBO.Result result = CalendarBO.Result.builder().app(app).rate(roundedRate).build();

        // 组装返回内容
        Optional<CalendarBO> optionalBO = calendarBOS.stream()
                .filter(bo -> bo.getDay().equals(day))
                .findFirst();

        // 该日期值不存在则创建 存在则添加不同app的记录
        if (!optionalBO.isPresent()) {
            CalendarBO calendarBO = CalendarBO.builder().day(day)
                    .result(Collections.singletonList(result)).build();
            calendarBOS.add(calendarBO);
        }else {
            CalendarBO calendarBO = optionalBO.get();
            List<CalendarBO.Result> results = calendarBO.getResult();
            results.add(result);
            calendarBO.setAssessResult(results);
        }
    }
}

再复杂一些

通过beginMonth、endMonth和appCodes筛选,需要返回的数据格式如下

这里的pass是有一个标准rate,当data中success/total(rate) > 标准rate时单项视为pass,而total中的total则代表该月份区间共统计次数。

json 复制代码
{
    "total": [
	    {
	        "total": 13,
	        "pass": 13,
	        "app": "0001"
		},
		{
	        "total": 13,
	        "pass": 12,
	        "app": "0002"
		}
	],
    "data": [
        {
            "code": "101",
			"month": 202406,
            "result": [
                {
                    "total": 13,
                    "success": 13,
                    "rate": 100,
                    "app": "0001"
                },
                {
                    "total": 12,
                    "success": 11,
                    "rate": 92,
                    "app": "0002"
                }
            ]
        },
        {
            "code": "102",
			"month": 202406,
            "result": [
                {
                    "total": 15,
                    "success": 15,
                    "rate": 100,
                    "app": "0001"
                }
            ]
        },
        ......
	]
}

此时的sql:

java 复制代码
@Query("SELECT e.code, e.app, SUBSTRING(e.day, 1, 6), COUNT(e.statId), " +
        "SUM(CASE WHEN (CAST(e.success AS double) / e.total) >= :rate THEN 1 ELSE 0 END) " +
        "FROM RecordDO e " +
        "WHERE e.code = :code" +
        "    AND SUBSTRING(e.day, 1, 6) BETWEEN :beginMonth AND :endMonth " +
        "    AND ((:appCodes) IS NULL OR e.app IN (:appCodes)) AND e.total > 0 " +
        "GROUP BY e.code, e.app, SUBSTRING(e.day, 1, 6)")
List<Object[]> findByCodeGroupBy(@Param("code") String code,
                                 @Param("beginMonth") String beginMonth,
                                 @Param("endMonth") String endMonth,
                                 @Param("appCodes") List<String> appCodes,
                                 @Param("rate") Double rate);

这样就直接把总数和pass的计数给取出来了(statId和总数可以对应)

调用上述方法后封装返回数据,和之前基本一致:

java 复制代码
// 根据分类计算总数的映射
Map<String, Integer> totalCounts = new HashMap<>();
Map<String, Integer> passCounts = new HashMap<>();
//返回的明细对象
List<DataBO> dataList = new ArrayList<>();

//假设已获取到code和标准rate的对应关系passRate
for (Map.Entry<String, Double> entry : passRate.entrySet()) {
    List<Object[]> resultList = recordRepository.findByCodeGroupBy(entry.getKey(), reqBO.getBeginMonth(),
                reqBO.getEndMonth(), reqBO.getAppCodeList(), entry.getValue());
    if (CollectionUtils.isEmpty(resultList)) {
        continue;
    }
    for (Object[] result : resultList) {
        String code = (String) result[0];
        String app = (String) result[1];
        String month = (String) result[2];
        Long totalLong = (Long) result[3];
        String total = totalLong.toString();
        Long successLong = (Long) result[4];
        String success = successLong.toString();

        double rateDouble = Double.parseDouble(success) / Double.parseDouble(total);
        String rate = String.format("%.2f", rateDouble * 100);

        DataBO.Result result = DataBO.Result.builder().total(total)
                .success(success).rate(rate).app(app).build();
                
        //查看dataList中是否该编码和月份的数据已存在 不存在则新建 存在则获取
        DataBO data = dataList.stream()
                .filter(a -> a.getCode().equals(code) && a.getMonth().equals(month))
                .findFirst().orElseGet(() -> {
                    DataBO newAccount = new DataBO();
                    newAccount.setCode(code);
                    newAccount.setMonth(month);
                    accountList.add(newAccount);
                    return newAccount;
                });

        if (data.getResult() == null) {
            data.setResult(Lists.newArrayList());
        }
        data.getResult().add(result);

        // 更新统计
        totalCounts.put(app, totalCounts.getOrDefault(app, 0) + Integer.parseInt(total));
        passCounts.put(app, passCounts.getOrDefault(app, 0) + Integer.parseInt(success));
    }
}
//组装统计类
totalCounts.entrySet().stream()
        .map(entry -> {
            String app = entry.getKey();
            int total = entry.getValue();
            int pass = passCounts.getOrDefault(app, 0);

            TotalCountBO totalCount = new TotalCountBO();
            totalCount.setAppCode(app);
            totalCount.setTotal(String.valueOf(total));
            totalCount.setPass(String.valueOf(pass));

            return totalCount;
        }).collect(Collectors.toList());
        
return RespBO.builder().data(dataList).total(totalCounts).build();

匆忙所写,不确定有没有问题,有的话联系我~

相关推荐
又该洗头了2 分钟前
Swagger
java·spring·swagger
bingbingyihao14 分钟前
Linux安装ftp、Java的FTP上传下载文件工具类
java·linux·centos
啊猪是的读来过倒19 分钟前
Redis深度解析:核心数据类型与键操作全攻略
数据库·redis·缓存·核心数据类型·详细操作+案例
u01040583621 分钟前
如何利用Java Stream API简化集合操作?
java·开发语言
G皮T23 分钟前
【MyBatis】MyBatis 理论 40 问(二)
java·数据库·spring boot·spring·mybatis·关系映射
小羊子说31 分钟前
Android 开发中 C++ 和Java 日志调试
android·java·c++
TechQuester36 分钟前
解决GPT-4o耗电难题!DeepMind新算法训练效率提升13倍,能耗降低10倍!
java·c++·人工智能·python·算法·chatgpt
球球King1 小时前
工厂模式之简单工厂模式
java·jvm·简单工厂模式
续亮~1 小时前
6、Redis系统-数据结构-06-跳表
java·数据结构·数据库·redis·后端·缓存
码界领航1 小时前
【Spring Cloud】一个例程快速了解网关Gateway的使用
spring·spring cloud·gateway