Closed teopapath closed 1 year ago
Last I checked, DTO via construction in the query requires a FQDN class definition. Does your POJO definition really NOT have a package declaration? If so, I'd recommend testing out if putting it into a package and then putting that into the query resolves the issue.
I've also run this query through the HQL parser, and our parser has no issue with the <>
operator. It along with NOT IN()
both pass our parser. And I don't see the parser throwing any sort of exception as if if did.
You may also want to turn on Hibernate logging to see more of what's happening as well.
Yes indeed, i just put pojo in the example for the sake of simplicity. The actual code of course contains a fully qualified name. I can update the code snippet but this is not the issue.
After some debugging it seems that the query executes successfully and correct results are returned. It seems that the error is in the result processing.
Can you post your repository definition along with the domain definition?
Repository:
@Repository
public interface AuthorityRepository extends JpaRepository<Authority, Long>, AuthorityCustomRepository {
@Transactional(readOnly = true)
@Query(name = "exampleQuery")
List<AuthorityRoleDTO> query(Long accountId);
}
Domain:
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@Entity
@EntityListeners({AuditingEntityListener.class})
@NamedQuery(
name = "exampleQuery",
query = "select new api.authorization.core.domain.dto.AuthorityRoleDTO("
+ "au.userId, au.status, r.name, r.code, au.creationDate) "
+ "from Authority au "
+ "join Role r on r.code = au.code "
+ "where au.accountId = :accountId "
+ "and au.status <> 'PENDING' ")
public class Authority implements GrantedAuthority {
@Id
@SequenceGenerator(name = "au_authority_id_generator", sequenceName = "au_authority_seq", allocationSize = 1)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "au_authority_id_generator")
private Long id;
@EqualsAndHashCode.Include
@NotNull
@Column(name = "user_id")
private String userId;
@EqualsAndHashCode.Include
@Column(name = "code")
private String code;
@NotNull
@Enumerated(EnumType.STRING)
@Column(name = "status")
private AuthorityStatus status;
@EqualsAndHashCode.Include
@Column(name = "account_id")
private Long accountId;
@Column(name = "uuid")
private String uuid;
@NotNull
@Column(name = "creation_date")
@CreatedDate
private LocalDateTime creationDate;
@NotNull
@Column(name = "created_by")
private String createdBy;
@Builder.Default
@OneToMany(mappedBy = "authority", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
private List<AuthorityPermission> authorityPermissions = new ArrayList<>();
public void addPermission(AuthorityPermission authorityPermission) {
if (authorityPermissions == null) {
authorityPermissions = new ArrayList<>();
}
authorityPermission.setAuthority(this);
authorityPermissions.add(authorityPermission);
}
@Override
public String getAuthority() {
return getCode();
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class AuthorityRoleDTO {
private String userId;
private AuthorityStatus authorityStatus;
private String roleName;
private String roleCode;
private LocalDateTime creationDate;
}
I can't see anything obvious in the code listings you have provided.
Is there perhaps some way to build a reproducer, staged on github, ideally using https://start.spring.io, H2 (or Testcontainers for PostGreSQL, etc.), and the version of Spring Boot aligned with your issue?
spring boot version is 3.1.0. I will try to find some time to implement a test project
created a test project that reproduces the error in https://github.com/teopapath/spring-boot-error. Changing the spring-boot version makes the test successful.
Simply switching from:
@Transactional(readOnly = true)
@Query(name = Authority.NAMED_QUERY_FIND_NON_PENDING_AUTHORITY_ROLE_LIST)
List<AuthorityRoleDTO> findNonPendingAuthorityRoleList();
to
@Transactional(readOnly = true)
@Query("select new com.example.demo.AuthorityRoleDTO("
+ "au.userId, au.status, r.name, r.code, au.creationDate) "
+ "from Authority au "
+ "join Role r on r.code = au.code "
+ "where au.status <> 'PENDING' ")
List<AuthorityRoleDTO> findNonPendingAuthorityRoleList();
seems to solve the problem and make the test go green. So apparently the issue has to do with named queries vs. directly provided ones.
We have tons of such queries in our application and is impossible to test them (or change) all. Will a fix be provided for this? Moreover i think that the root-cause may be more complicated because if the query is changed to "where au.status not in ('PENDING') " it works as expected.
@teopapath Sorry, I wasn't suggesting that you alter your app. I'm simply tracking that this might be where the discrepancy lies. I'm trying to track down precisely where it diverges.
I figured it out. It's a little pesky. You see the @NamedQuery
annotation (from JPA) feeds your defined query into Spring Data JPA's NamedQuery
, which executes the following statement to extract the HQL from Hibernate:
String queryString = extractor.extractQueryString(query);
This taps into Hibernate directly, asking for the query. Apparently Hibernate when parsing:
@NamedQuery(
name = Authority.NAMED_QUERY_FIND_NON_PENDING_AUTHORITY_ROLE_LIST,
query = "select new com.example.demo.AuthorityRoleDTO("
+ "au.userId, au.status, r.name, r.code, au.creationDate) "
+ "from Authority au "
+ "join Role r on r.code = au.code "
+ "where au.status <> 'PENDING' ")
...transforms <>
some sort of internal NOT_EQUALS
operator, which then gets re-rendered into !=
(not under our control). This re-rendered query is later run through our HQL parser for any additional processing (applied sorts, etc.) before getting sent to the entity manager.
However, in Spring Data JPA 3.1.0 (part of Spring Boot 3.1.0), we didn't have proper support for !=
within our own HQL parser. That would also explain why it fails on <>
but passes when using the semantically equivalent NOT IN ('PENDING')
.
This was patched with #2970 via https://github.com/spring-projects/spring-data-jpa/commit/6d56a2525c18a29f2fdc68d3b7ff47296a0aba1d. The good news is that this Friday, there is a service release scheduled for Spring Data 2023.0.1
. When that goes out, you can apply:
<properties>
<spring-data-bom.version>2023.0.1</spring-data-bom.version>
</properties>
...and pick up that patch.
You can also choose to wait until the 22nd for Spring Boot's scheduled release of 3.1.1, which should pick up Spring Data's service release.
Or you can choose to jump to snapshots, as you've already seen.
Since this issue simply duplicates #2970, I'm going to close it.
@gregturn it seems that the issue still exists in 3.1.2. Test case can be found in error2990_2. Still works in 3.0.x release. Opened https://github.com/spring-projects/spring-data-jpa/issues/3085 for this
Well, the scenario this ticket as closed against was lack of support for <>
operator, which now operates.
The other scenario where the NOT IN
isn't working for an EnumMap
-based relationship sounds like a different scenario, as tracked by #3085.
I have a NamedQuery like:
When query is executed a ConverterNotFoundException is thrown.
If i change the query to
everything works as expected. It seems that there is a problem in parsing this part: and au.status <> 'PENDING' The same query was executing successfully in spring-boot 3.0.7
Stacktrace: