spring-data-commons
spring-data-commons copied to clipboard
Querydsl web binding support can not bind ListPath query parameters [DATACMNS-1546]
XhstormR opened DATACMNS-1546 and commented
I have a Repository class like this:
@Repository
interface ContractRepository :
BaseRepository<Contract, QContract, Long> {
@JvmDefault
override fun customize(bindings: QuerydslBindings, root: QContract) {
}
}
BaseRepository inherits QuerydslPredicateExecutor and QuerydslBinderCustomizer, by the way this is kotlin code.
And i have a Project entity that contains a one-to-many relationship like this:
@Entity
@DynamicInsert
@DynamicUpdate
class Project(
var startTime: LocalDateTime? = null,
var deadline: LocalDateTime? = null,
var finishTime: LocalDateTime? = null,
var finish: Boolean = false
) : BaseSequenceEntity() {
@OneToMany(mappedBy = "project", cascade = [CascadeType.PERSIST])
var subsystems = mutableListOf<Subsystem>()
}
Now I have a binding like this, but I found it doesn't work, use only the most basic equals operator:
@Repository
interface ContractRepository :
BaseRepository<Contract, QContract, Long> {
@JvmDefault
override fun customize(bindings: QuerydslBindings, root: QContract) {
bindings.bind(root.project.subsystems.any().name).first { path, value ->
println(111)
path.contains(value)
}
}
}
After I send a request to the server, request like this:
http://127.0.0.1:8080/api/search/contract?page=0&size=10&sort=createdAt,desc&project.subsystems.name=ffff
Server output is:
2019-06-19 08:39:23.142 INFO 415552 --- [nio-8080-exec-4] c.x.e.a.c.handler.RequestInterceptor : GET [uri=/api/search/contract;client=127.0.0.1;session=039EB2C355C8CE222CC9FEC789F672DB;[email protected]]
2019-06-19 08:39:23.145 DEBUG 415552 --- [nio-8080-exec-4] org.hibernate.SQL : /* select contract
from Contract contract
where exists (select 1
from contract.project.subsystems as contract_project_subsystems_0
where contract_project_subsystems_0.name = ?1)
order by contract.createdAt desc */ select contract0_.id as id1_5_, contract0_.created_at as created_2_5_, contract0_.created_by_id as created_9_5_, contract0_.updated_at as updated_3_5_, contract0_.updated_by_id as updated10_5_, contract0_.optlock as optlock4_5_, contract0_.company_id as company11_5_, contract0_.file_id as file_id12_5_, contract0_.finish_date as finish_d5_5_, contract0_.name as name6_5_, contract0_.start_date as start_da7_5_, contract0_.urgency as urgency8_5_ from erp_dev.contract contract0_ where exists (select 1 from erp_dev.project project1_ cross join erp_dev.subsystem subsystems2_ where contract0_.id=project1_.contract_id and project1_.id=subsystems2_.project_id and subsystems2_.name=?) order by contract0_.created_at desc limit ?
The main SQL is:
select contract
from Contract contract
where exists (select 1
from contract.project.subsystems as contract_project_subsystems_0
where contract_project_subsystems_0.name = ?1)
order by contract.createdAt desc
But I found that if my binding is like this, the binding will take effect:
@Repository
interface ContractRepository :
BaseRepository<Contract, QContract, Long> {
@JvmDefault
override fun customize(bindings: QuerydslBindings, root: QContract) {
bindings.bind(String::class.java).all(MultiValueBinding<StringPath, String> { path, values ->
println(222)
if (root.project.subsystems.any().name == path) Optional.of(path.contains(values.single()))
else Optional.empty()
})
}
}
Server output is:
2019-06-19 08:42:19.862 INFO 416072 --- [nio-8080-exec-1] c.x.e.a.c.handler.RequestInterceptor : GET [uri=/api/search/contract;client=127.0.0.1;session=820AB7D7035D193D33711DEEAF9486BD;[email protected]]
222
2019-06-19 08:42:19.865 DEBUG 416072 --- [nio-8080-exec-1] org.hibernate.SQL : /* select contract
from Contract contract
where exists (select 1
from contract.project.subsystems as contract_project_subsystems_0
where contract_project_subsystems_0.name like ?1 escape '!')
order by contract.createdAt desc */ select contract0_.id as id1_5_, contract0_.created_at as created_2_5_, contract0_.created_by_id as created_9_5_, contract0_.updated_at as updated_3_5_, contract0_.updated_by_id as updated10_5_, contract0_.optlock as optlock4_5_, contract0_.company_id as company11_5_, contract0_.file_id as file_id12_5_, contract0_.finish_date as finish_d5_5_, contract0_.name as name6_5_, contract0_.start_date as start_da7_5_, contract0_.urgency as urgency8_5_ from erp_dev.contract contract0_ where exists (select 1 from erp_dev.project project1_ cross join erp_dev.subsystem subsystems2_ where contract0_.id=project1_.contract_id and project1_.id=subsystems2_.project_id and (subsystems2_.name like ? escape '!')) order by contract0_.created_at desc limit ?
The main SQL is:
select contract
from Contract contract
where exists (select 1
from contract.project.subsystems as contract_project_subsystems_0
where contract_project_subsystems_0.name like ?1 escape '!')
order by contract.createdAt desc
1 votes, 4 watchers
stevenwanuk commented
experienced same issue after upgraded from spring boot 1.x (1.13.8.release of spring-data-common) to spring-boot 2 (2.1.9.release spring-data-common)
the following codes register a binding with path tity.childrenEntities)
bindings.bind(parentEntity.childrenEntities.any.childProperty).first(....)
becuase of this method from 2.1.9 release here:
return path.toString().substring(rootPath.getMetadata().getName().length() + 1);
with following runtime values:
path: "any(parentEntity.childrenEntities"
rootPath.getMetadata().getName(): "parentEntity"
here is the link of how it's handled at 1.13.8.release.
https://github.com/spring-projects/spring-data-commons/blob/1.13.8.RELEASE/src/main/java/org/springframework/data/querydsl/QueryDslUtils.java#L43
I think, at least, it should have an additional check before performs a substring.
stevenwanuk commented
this bug / issue breaks our exising bindings. couldn't find a proper fix for it without using relfection.
however, a hacky fix is:
@SuppressWarnings("unchecked")
@Override
public void customize(QuerydslBindings bindings, QPrivacyNotice privacyNotice) {
bindings.bind(parent.children.any().name).as("children.name").first((path, value) ->
path.like("%" + value + "%"));
//adding correct path to pathSpecs by using reflection.
Field field = ReflectionUtils.findField(QuerydslBindings.class, "pathSpecs");
field.setAccessible(true);
Map<String, Object> map = (Map<String, Object>)ReflectionUtils.getField(ReflectionUtils.findField(QuerydslBindings.class, "pathSpecs"), bindings);
Path<?> toBeFixedPath = parent.children.any().name;
map.put(QuerydslUtils.toDotPath(toBeFixedPath), map.get(fromRootPath(toBeFixedPath)));
}
// this is current path (which is wrong)
private static String fromRootPath(Path<?> path) {
Path<?> rootPath = path.getMetadata().getRootPath();
if (rootPath == null) {
throw new IllegalStateException(String.format("Couldn't find root path on path %s!", path));
}
return path.toString().substring(rootPath.getMetadata().getName().length() + 1);
}
slangeberg commented
Seems that adding alias to match the web request params:
bindings.bind(parent.children.any().name).as("children.name")
gets the bindings picked up again in our project
This bug affects me as well, maybe to fix we can remove the "any()" from the string, and apply the current substring removing the "root.", to match the String that is searched in getBindingForPath; Basic Example:
return path.toString()
.replace("any(", "").replace(")", "")
.substring(rootPath.getMetadata().getName().length() + 1);