Spring JPA原生查询中使用Projection出现“ConverterNotFoundException”错误

34

我正在使用Spring JPA,需要进行本地查询。在该查询中,我只需要从表中获取两个字段,因此我尝试使用Projections。但它无法正常工作,这是我得到的错误:

org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [org.springframework.data.jpa.repository.query.AbstractJpaQuery$TupleConverter$TupleBackedMap] to type [com.example.IdsOnly]

我试图精确遵循我链接的那个页面的指示,我尝试使我的查询非本地化(顺便问一下,如果我使用投影,我是否实际需要它是本地化的?),但我总是会得到那个错误。
如果我使用接口,它可以工作,但结果是代理,我真的需要它们成为我可以转换为JSON的“正常结果”。

所以这里是我的代码。实体:

import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@Entity
@Table(name = "TestTable")
public class TestTable {

    @Id
    @Basic(optional = false)
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    @Column(name = "Id")
    private Integer id;
    @Column(name = "OtherId")
    private String otherId;
    @Column(name = "CreationDate")
    @Temporal(TemporalType.TIMESTAMP)
    private Date creationDate;
    @Column(name = "Type")
    private Integer type;
}

投影的类:

import lombok.Value;

@Value // This annotation fills in the "hashCode" and "equals" methods, plus the all-arguments constructor
public class IdsOnly {

    private final Integer id;
    private final String otherId;
}

代码仓库:

public interface TestTableRepository extends JpaRepository<TestTable, Integer> {

    @Query(value = "select Id, OtherId from TestTable where CreationDate > ?1 and Type in (?2)", nativeQuery = true)
    public Collection<IdsOnly> findEntriesAfterDate(Date creationDate, List<Integer> types);
}

试图获取数据的代码:

@Autowired
TestTableRepository ttRepo;
...
    Date theDate = ...
    List<Integer> theListOfTypes = ...
    ...
    Collection<IdsOnly> results = ttRepo.findEntriesAfterDate(theDate, theListOfTypes);  

感谢您的帮助。我真的不明白自己做错了什么。


1
你的 MyProjectionClass 类在代码中的哪里? - Jose Da Silva
抱歉,在实际编写代码时我已经更改了它的名称,使其成为“伪代码”。我编辑了问题,以便错误显示为“com.example.IdsOnly”。 - nonzaprej
5个回答

51

使用Spring Data,您可以省去中间人,只需简单定义即可

public interface IdsOnly {
  Integer getId();
  String getOtherId();
}

并使用类似原生查询的方式:

@Query(value = "Id, OtherId from TestTable where CreationDate > ?1 and Type in (?2)", nativeQuery = true)
    public Collection<IdsOnly> findEntriesAfterDate(Date creationDate, List<Integer> types);

请查看 https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections


@InêsGomes,它基本上运行本地SQL查询并将输出模式转换为POJO,因此您可以在此处执行与SQL相同的操作。我不太理解您的问题,但如果您能发送遇到的错误和问题,我会尝试帮助您。我知道这里的社区也很乐意提供帮助,所以也许您应该专门发布一个新问题来询问。 - shahaf
我更改了被接受的答案为这个,因为它实际上是针对本地查询的,并且比另一个答案拥有更多的投票。 - nonzaprej
您可以使用nativeQuery=true完全相同。 - razor
结果中的布尔字段怎么样?每当您尝试执行interface.getBooleanField时,它们似乎会破坏代码。 - Parth Manaktala
我可以使用连接(嵌套投影)吗? - Amirhosein Al

17

查询应该使用构造器表达式

@Query("select new com.example.IdsOnly(t.id, t.otherId) from TestTable t where t.creationDate > ?1 and t.type in (?2)")

我不了解Lombok,但请确保有一个构造函数接受这两个ID作为参数。

1
如果你还不知道Lombok,一定要去了解一下!>_>谢谢,我会试试的。所以我想我的查询不需要是本地的了。 - nonzaprej
1
它起作用了。非常感谢。顺便说一下,我还必须使用实体的变量名称编写列名称(因此,“id”,“otherId”,“creationDate”和“type”)。 - nonzaprej
44
注释:此解决方案不适用于nativeQuery。 - Grigory Kislin
好的,我选择@shahaf的回答作为被接受的答案,因为它是针对本地查询并且有更多票数。 - nonzaprej
1
问题是关于本地查询的。 - kiedysktos

5
JPA 2.1引入了一个有趣的ConstructorResult功能,如果您想保持原生状态,则可以使用它。

谢谢,我看到了,但对于我的简单需求来说似乎过于复杂了(而且我认为投影也可以与本地查询一起使用)。如果我确实需要我的查询是本地的,那我肯定会使用它。最终,我现在拥有的查询并不需要这样做。 - nonzaprej
3
除了提供 SqlResultSetMapping 并在 JPA 实体上使用 ConstructorResult,并直接在 EntityManager 上执行查询外,我发现没有其他方法可以将本地 SQL 查询结果映射到自定义类型。因此,很遗憾,在这些限制条件下(本地查询+自定义投影),无法使用 Spring Data 的 @Query 注释。 - johanwannheden

5
你可以在仓库类中的本地查询方法中返回对象数组(列表)作为返回类型。
@Query(
            value = "SELECT [type],sum([cost]),[currency] FROM [CostDetails] " +
                    "where product_id = ? group by [type],[currency] ",
            nativeQuery = true
    )
    public List<Object[]> getCostDetailsByProduct(Long productId);

for(Object[] obj : objectList){
     String type = (String) obj[0];
     Double cost = (Double) obj[1];
     String currency = (String) obj[2];
     }

2
@Query(value = "select  isler.saat_dilimi as SAAT, isler.deger as DEGER from isler where isler.id=:id", nativeQuery = true) 
List<Period> getById(@Param("id") Long id);


public interface Period{
    Long getDEGER(); 

    Long getSAAT();

}

如上所示的原生查询示例代码,将返回值转换为任何值,例如“SAAT”,“DEGER”,然后定义接口“period”,该接口具有getDEGER()和getSAAT()方法。即使我不明白为什么在get后面的参数必须大写,在小写的情况下它不能正常工作。例如,在我的情况下,使用getDeger()和getSaat()方法的接口无法正常工作。


所以你有 select isler.saat_dilimi as saat,而且 getSaat() 没有起作用?那很奇怪。 - nonzaprej
@nonzaprej 抱歉回复晚了。你是正确的。你知道是否有可能将本地查询结果设置为DTO而不是接口投影吗? - whitefang
不,我不知道。我在另一个项目中也需要这个,但最终放弃了,将接口和DTO的字段都设置为相同的(或者说类似的,接口只有方法,但你知道我的意思),并在DTO中创建了一个构造方法,该方法将接口中的所有值作为唯一参数传递。例如:public class PeriodDTO { private Long deger; private Long saat; public PeriodDTO(Period periodInterface) { this.deger = periodInterface.getDEGER(); this.saat = periodInterface.getSAAT(); } } 这很丑陋,但至少是个办法... - nonzaprej
@nonzaprej 感谢回复,是的,我也认为这个解决方案很丑陋。 - whitefang
这里的参数按顺序填充,因此请在您的界面或查询中更改字段顺序,以使它们匹配(第一个是SAAT,第二个是DEGER)。 - razor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接