在Spring Data JPA中使用@Query注解

news/2024/7/8 12:03:32 标签: 数据库, java, spring

目录

  • 前言
  • 示例
    • 简单示例
    • 只查询部分字段,映射到一个实体类中
    • 只查询部分字段时,也可以使用List<Object[]>接收返回值
    • 再复杂一些

前言

在以往写过几篇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查出这么一个结果:

[
	{
		"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则代表该月份区间共统计次数。

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

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


http://www.niftyadmin.cn/n/5537244.html

相关文章

note-网络是怎样连接的6 请求到达服务器,响应返回浏览器

助记提要 服务器程序的结构套接字的指代方式MAC模块的接收过程IP模块的接收过程TCP模块处理连接包TCP模块处理数据包TCP模块的断开操作URI转换为实际文件路径URI调用程序Web服务器访问控制响应内容的类型 6章 请求到达服务器&#xff0c;响应返回浏览器 1 服务器概览 在数据…

Flask自定义命令

Flask自定义命令 一、老版Flask自定义命令 Flask 1.x 和 2.x 版本 在Flask的老版本中&#xff0c;可以通过 flask-script 扩展来添加自定义命令。 安装所需库&#xff1a; pip3 install Flask-Script2.0.3 pip3 install flask1.1.4 pip3 install markupsafe1.1.1在 Flask 应…

oracle存储结构-----逻辑存储结构(表空间、段、区、块)

文章目录 oracle存储结构图&#xff08;逻辑存储物理存储&#xff09;oracle逻辑存储结构图逻辑存储结构、表空间、段、区、数据块的关系&#xff1a;1、数据 块&#xff08;block&#xff09;---逻辑存储最小单位2、 数据区&#xff08;extent&#xff09;--存储空间分配和回收…

文件扫描pdf怎么弄?5个简易高效的文件扫描方法

在繁忙的工作中&#xff0c;我们常常需要将纸质文件快速转换为电子文档&#xff0c;以便于编辑、存储或分享。 无论是合同、报告还是笔记&#xff0c;将这些纸质文件转换为Word格式&#xff0c;不仅能提高工作效率&#xff0c;还能确保信息的安全备份。然而&#xff0c;面对市…

python conda查看源,修改源

查看源 conda config --show-sources 修改源 可以直接vim .condarc修改源&#xff0c;

LabVIEW汽车ECU测试系统

开发了一个基于LabVIEW开发的汽车发动机控制单元&#xff08;ECU&#xff09;测试系统。该系统使用了NI的硬件和LabVIEW软件&#xff0c;能够自动执行ECU的功能测试和性能测试&#xff0c;确保其在不同工作条件下的可靠性和功能性。通过自动化测试系统&#xff0c;大大提高了测…

C#用反射机制调用dll文件的字段、属性和方法

1、创建dll文件 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;namespace CLStudent {public class Student{//字段public string Name = "Tom";//属性public double ChineseScore {get; s…

Administrators就最高了???system是什么??本地用户提权内网学习第三天 你知道uac是什么??

我们今天来说说本地用户提权的操作&#xff0c;我们在有webshell过后我们要进行进一步的提权操作&#xff0c;要不然对我们后期的内网渗透会有一些阻碍的操作。比如说我们使用mimikatz来进行抓取密码&#xff0c;就不能够成功。 Administrators与system的区别 我们来说说Admin…