Can not create specification that will check class of field - java

Entity Order contains field:
#ManyToOne(optional = false)
private AbstractRequester requester;
I wanna get data by type this field.
I created specification like in documentation, but on
page = orderRepository.findAll(spec, pageable);
Get Exception
org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.QueryException: could not resolve property: class of: ...order.Order
My specs:
#Override
public Predicate toPredicate(Root<Order> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Predicate megaPredicate = cb.conjunction();`
...
megaPredicate = cb.and(megaPredicate, cb.and(
cb.equal(root.get("requester").type(), cb.literal(PersonRequester.class))
...
return megaPredicate;
}

You are trying to use the discriminator column in your query and that's illegal.
Instead, execute your query on the PersonRequester directly and remove the offending clause from megaPredicate.

Related

Custom #Query annotated method for update query with spring data MongoRepository

I've read the question Custom method for update query with spring data MongoRepository and my result would be the very same that it is in que previously mentioned question, the only difference would be that I want to do it by using a #Query annotated method. Is it possible? If so, how?
I have a entity and I what to update all documents with a value if a determined criteria has been match.
My entity:
#Document("accrual")
public class Accrual extends AbstractEntity {
#Serial
private static final long serialVersionUID = 1175443967269096002L;
#Indexed(unique = true)
private Long numericUserId;
#Indexed(unique = true)
private Long orderIdentifier;
private boolean error;
// sets, gets, equals, hashCode and toString methods
}
I would like to update the boolean error in every document found by searching for them using a list of Longs matching orderIdentifier attribute.
If it was in a relational database I would have a method like:
#Query(value = "update accrual set error = 0 where order_identifier in :list", nativeQuery = true)
updateErrorByOrderIdentifier(#Param("list") List<Long> orderIdentifiers)
You can try to use #Query annotation for filtering documents to be updated and #Update for providing actual update query.
#Query("{ 'orderIdentifier' : { '$in': ?0 } }")
#Update("{ '$set' : { 'error' : 'false' } }")
void updateAllByOrderIdentifier(List<Long> orderIdentifiers);
More details can be found here

How can I get specific item from collection? Criteria API

I have two entities with one-to-many relationships (simplified):
public class Action{
#OneToMany
private Set<ActionDetailParameter> detailParameters = new HashSet<>(0);
}
public class ActionDetailParameter {
private String parameterName;
private String parameterValue;
}
I need to select all Actions where detailParameters item has parameterName equals "newserviceDepartmentName". I tried using this code:
...
SetJoin<Action, ActionDetailParameter> detailParameters = actionRoot.joinSet("detailParameters", JoinType.LEFT);
Predicate namePredicate = criteriaBuilder.equal(detailParameters.get("parameterName"), "newserviceDepartmentName");
QueryBuildingCriteria<Action> queryBuildingCriteria = getQueryBuildingCriteria(Action.class);
CriteriaQuery<Action> query = (CriteriaQuery<Action>) queryBuildingCriteria.getQuery();
getResultList(createQuery(query.select(actionRoot).where(namePredicate)));
...
there was the following exception:
org.hibernate.hql.internal.ast.QuerySyntaxException: Invalid path: 'generatedAlias1.parameterName' [select generatedAlias0 from common.dao.entities.Action as generatedAlias0 where generatedAlias1.parameterName=:param0]
please tell me what I am doing wrong
I believe your problem to be that you construct a new CriteriaQuery after creating the actionRoot. As you don't show the whole code, this is some speculation.
QueryBuildingCriteria<Action> queryBuildingCriteria = getQueryBuildingCriteria(Action.class);
CriteriaQuery<Action> query = (CriteriaQuery<Action>) queryBuildingCriteria.getQuery();
I have adjusted your mapping to something I had existing in my system and have simply replaced the working classes/field names with the ones from your question:
public void test() {
CriteriaBuilder cb = getSessionFactory().getCriteriaBuilder();
CriteriaQuery<Action> createQuery = cb.createQuery(Action.class);
Root<Action> x = createQuery.from(Action.class);
createQuery.select(x)
.where(cb.equal(x.joinList("detailParameters").get("parameterName"),
"newserviceDepartmentName"));
List<Action> resultList = getSession().createQuery(createQuery).getResultList();
System.out.println(resultList);
}
This should return all Actions which have an ActionDetailParameter where the parameterName equals newserviceDepartmentName

Java - JPA-Specification: how to create criteria/specification on a field that belongs to a nested object?

I am using jdk 1.8 , hibernate and jpa in my project. And using specification/criteria to build my search query.
I have a class A ( an hibernate entity) which has class B as an attribute. So, roughly, it looks like :
#Entity
class A {
Long id;
String comment;
#OneToOne
B b;
}
and...
#Entity
class B {
Long id;
String type;
}
My repository class looks like (roughly):
public interface ARepository extends PagingAndSortingRepository<A, Integer>,
JpaSpecificationExecutor<A> {
}
Most of the simple JPA queries are working as expected. Even the specification/criteria based directly on Class A is working. However, I need to create a dynamic query and that should be executed under "findAll" method of PagingAndSortingRepository class. This query should be equivalent to
select * from A a left join B b on a.b_id = b.id
where b.type='final' and a.comment='blah';
I created a similar logic as above in a specification like :
public Specification<A> getSpecification() {
return (itemRoot, query, criteriaBuilder) -> {
.........
List<Predicate> partialQueries = new ArrayList<>();
partialQueries.add(criteriaBuilder.equal(itemRoot.get("b.type"), "final"));
partialQueries.add(criteriaBuilder.equal(itemRoot.get("comment"), "blah"));
//Other queries to be added...
return criteriaBuilder.and(partialQueries.toArray(new Predicate[0]));
};
}
And getting error :
Unable to locate Attribute with the the given name [b.type] on this ManagedType [com.something.domain.A]
Any insight on how to create criteria/specification on a field that belongs to a nested object?
If you want to filter nested object. You can write
itemRoot.get("NestedTableName").get("nestedfieldname")
In your case - itemRoot.get("B").get("type")
itemRoot.get("Name of the nested object field in the root class").get("nestedfieldname");
Example:
cb.equal(root.get("b").get("type"),value)
In your case - itemRoot.get("b").get("type");

How to avoid joins using subqueries inside Specification?

I need to fetch some entites based on data that can only be found after 5 associations. I would like to avoid joining all the tables on the way and use the IN clause.
Here is a simple implementantion I found using only a couple of entities:
#Entity
public class Foo {
#Id
private Long idFoo;
private String name;
}
#Entity
public class Bar {
#Id
private Long idBar;
#ManyToOne
#JoinColumn(name = "idFoo")
private Foo foo;
}
Let's suppose I need to list all Foo objects according to a Bar property, let's say the idBar:
class FilterFooByIdBar extends Specification<Foo> {
public Predicate toPredicate(Root<Foo> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
var subquery = query.subquery(Foo.class);
var barRoot = subquery.from(Bar.class);
subquery.select(barRoot.get("foo"))
.where(builder.equal(barRoot.get("idBar"), 1L));
return root.in(subquery);
}
}
This works but the resulting SQL is something like that:
select foo0_.idFoo, foo0_.name
from Foo foo0_
where foo0_.idFoo in (
select bar1_.idFoo
from Bar bar1_ cross join Foo foo2_
where bar1_.idFoo=foo2_.idFoo
and bar1_.idBar=1
);
I think that the join inside the subquery is useless and goes against my goal, I would like to do something like:
select foo0_.idFoo, foo0_.name
from Foo foo0_
where foo0_.idFoo in (
select bar1_.idFoo
from bar bar1_
where bar1_.idBar=1
);
Is there anyway to change the Specification and achieve that?
Use an exists subquery instead which is usually faster anyway and will also allow to eliminate the join if you are using the latest hibernate version.
class FilterFooByIdBar extends Specification<Foo> {
public Predicate toPredicate(Root<Foo> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
var subquery = query.subquery(Foo.class);
var barRoot = subquery.from(Bar.class);
subquery.select(cb.literal(1))
.where(cb.and(
cb.equal(barRoot.get("idBar"), 1L),
cb.equal(barRoot.get("foo"), root)
);
return cb.exists(subquery);
}
}

Parameter value [1] did not match expected type [java.lang.Boolean]

I'm getting the error Exception in thread "main" org.springframework.dao.InvalidDataAccessApiUsageException: Parameter value [1] did not match expected type [java.lang.Boolean]; nested exception is java.lang.IllegalArgumentException: Parameter value [1] did not match expected type [java.lang.Boolean].
I'm confused by this because it's coming from the service method shown below that is commented out. When I comment it out, the error is avoided. The active column is a TINYINT(1) that's either 1 or 0.
Entity:
#Entity
#NamedQueries({
#NamedQuery(name="Workflow.findByUUID", query="SELECT w FROM Workflow w WHERE w.uuid = :uuid"),
#NamedQuery(name="Workflow.findByActive", query="SELECT w FROM Workflow w WHERE w.active = :active ORDER BY id ASC")
})
My Repository:
#Repository
public interface WorkflowRepository extends JpaRepository<Workflow, Integer> {
List<Workflow> findByActive(#Param("active") Integer active);
}
My Service:
#Service
public class WorkflowService {
#Autowired
WorkflowRepository workflowRepository;
/**
* Get active workflows
*/
#Transactional(readOnly = true)
public List<Workflow> findActive() {
//return workflowRepository.findByActive(1);
return null;
}
When I uncomment
You seem to have mapped Workflow.active attribute as a Boolean. So you repository should be like:
#Repository
public interface WorkflowRepository extends JpaRepository<Workflow, Boolean> {
List<Workflow> findByActive(#Param("active") Boolean active);
}
And the call workflowRepository.findByActive(true) should behave how you want it to.
The fact is that HQL sometimes make a type difference between database and Java mapping, because typing isn't the same in these environments. So check your Entity's active type to make the appropriate Repository.

Categories

Resources