JPA CriteriaBuilder Joins with extra ON clause parameters (predicate) - java

How to join with extra ON clause parameters?
I have an SQL:
select * from Address address left outer join AddressLine line
on line.id = address.lineId AND line.type = 'MA'
where address.id = 1;
I have code:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<AddressResult> query = cb.createQuery(AddressResult.class);
Root<Address> r = query.from(Address.class);
Join<Address, AddressLineMA >linesMA= r.join(Address_.linesMajor, JoinType.LEFT);
To get data from data base.
This query is not working as expected I get SQL like this:
select * from Address address left outer join AddressLine line
on line.id = address.lineId
where address.id = 1;
AND line.type = 'MA' is missing. Does some one knows how to fix this?
My AddressLineMA.class looks like this:
#Entity
#DiscriminatorValue(value = "MA")
public class AddressLineMA extends AddressLine {
}
#Entity
#Table(name = "AddressLine")
#DiscriminatorColumn(name = "TYPE", discriminatorType = DiscriminatorType.STRING)
public abstract class AddressLine {
private Long id;
private String type;
private String line;
}

JPA queries always return all subclasses by default. So if you want only instances of AddressLineMA, you must change your query and relationshipto be on AddressLineMA instead of the inheritance root class, AddressLine.
If there are other subclasses of AddressLineMA that you want to exclude, then you can use the TYPE operator added to JPA 2.0:
query.where(cb.equal(linesMA.type(), package.AddressLineMA.class));
Unfortunately, adding it into the ON clause is only supported in the yet to be released JPA 2.1 spec:
linesMA.on(cb.equal(linesMA.type(), package.AddressLineMA.class));

Related

Spring Data JPA Native Query N + 1 problem

I've run into some code which behaviour is not clear for me.
We have first entity:
#Data
#Entity
#Table(name = "Info")
public class Info {
#OneToOne
#JoinColumn(name = "data_id")
private Data data;
}
and second one:
#Data
#Entity
#Table(name = "Data")
public class Data {
#Id
private Long dataId
}
And we have next method for retrieving data:
#Query(value = "SELECT * FROM Info i inner join Data d on d.data_id=i.data_id",
nativeQuery = true)
List<Info> getInfo() {}
As nativeQuery = true is present I expect this method make just one SQL select and retrieve me data. But If I take a look at logs actually there are 2 selects:
SELECT * FROM Info i inner join Data d on d.data_id=i.data_id;
SELECT * FROM Data d where d.data_id = 123;
Why this is happening ? How to fix it to make only one select and retrieve all data ?
It's not possible to specify native query fetches without Hibernate specific APIs. I would suggest you to use a normal JPQL/HQL query:
#Query(value = "FROM Info i join fetch i.data")
List<Info> getInfo();
This will do the same as your native query but at the same time just run only a single query.

How to query OneToOne entity for specific value with CriteriaQuery

I have an entity (Person) which is a OneToOne to another entity (User). I need to find all Person entities which match User.name using CriteriaQuery.
I can do simple CriteriaQuery for direct attributes of Person just fine:
builder.like(builder.lower(root.get(column)), "%" + pattern.toLowerCase() + "%")
I'm a bit lost on how to do CriteriaQuery queries in this more complex case. From my searches here and elsewhere I think I have to use some kind of Join but I can't get my head wrapped around it.
#Entity()
public class Person extends ModelCore {
#Basic()
private String iD = null;
#OneToOne(cascade = { CascadeType.ALL })
#JoinColumns({ #JoinColumn(name = "T_User") })
private User user = null;
}
#Entity()
public class User extends ModelCore {
#Basic()
private String iD = null;
#Basic()
private String name = null;
}
#Entity()
public class ModelCore{
#Basic()
private Long dbID = null;
}
SOLVED
Nikos's solution works great (thank you!):
String username = ... // the criterion
EntityManager em = ...
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Person> query = cb.createQuery(Person.class);
Root<Person> root = query.from(Person.class);
Join<Person, User> joinUser = root.join("user");
query.where(cb.like(cb.lower(joinUser.get("name")), "%" + username.toLowerCase() + "%"));
Edit 1: Added ModelCore as base class.
Edit 2: Add working solution
Criteria API can be confusing as complexity grows. The first step I always follow is to write down the JPQL query. In this case:
SELECT p
FROM Person p
JOIN User u
WHERE LOWER(u.name) LIKE :username
Translating this to Criteria API is:
// These are initializations
String username = ... // the criterion
EntityManager em = ...
CriteriaBuilder cb = em.getCriteriaBuilder();
// Next line means "the result of the query is Person"
CriteriaQuery<Person> query = cb.createQuery(Person.class);
// This matches the "FROM Person p" part of the JPQL
Root<Person> root = query.from(Person.class);
// This matches the "JOIN User u" part of the JPQL
Join<Person, User> joinUser = root.join("user"); // if you have created the metamodel, adjust accordingly
// This is the "WHERE..." part
query.where(cb.like(cb.lower(joinUser.get("name")), "%" + username.toLowerCase() + "%"));
The WHERE part is confusing because you have to convert the infix SQL/JPQL operators to prefix (i.e. x LIKE y becomes cb.like(x, y)), but the mapping is straightforward.

Spring Data findTop10- Ten queries instead of one

I have Spring Data method like this:
List<RegionBasics> findTop10ByRegionMappingsActiveTrue();
What I expect is that it will find top 10 records from db in one query but What I see in logs is (I didn't paste whole logs in order to keep this readable but this select query is invoke 10 times):
select regionmapp0_.id as id1_2_1_, regionmapp0_.is_active as is_activ2_2_1_, regionmapp0_.region_basic_id as region_b3_2_1_, regionbasi1_.id as id1_1_0_, regionbasi1_.hotel_count as hotel_co2_1_0_, regionbasi1_.name_long as name_lon3_1_0_, regionbasi1_.name as name4_1_0_, regionbasi1_.type as type5_1_0_ from region_mappings regionmapp0_ left outer join region_basics regionbasi1_ on regionmapp0_.region_basic_id=regionbasi1_.id where regionmapp0_.region_basic_id=?
How can I ensure that this method will hit db only once (instead of 10)?
My Model:
#Data
#NoArgsConstructor
#Entity
#Table(name = "region_basics")
public class RegionBasics {
#Id
Integer id;
#Column
String type;
#Column
String name;
#Column(name = "name_long")
String longName;
#Column(name = "hotel_count")
Integer hotelCount;
#OneToOne(mappedBy="regionBasics")
RegionMappings regionMappings;
}
I think you should join fetching the RegionMappings:
Like this: #Query("SELECT rb FROM RegionBasics r JOIN FETCH r.regionMappings rm WHERE rm.active=true")
With pageable parameter new PageRequest(0,10)

JPA 2 + Criteria API - Defining a subquery

I try to convert a sql query to Criteria API without success so far. I can create two separate queries which return the values I need, but I don't know how to combine them in a single query.
Here is the sql statement which works:
select company.*, ticketcount.counter from company
join
(select company, COUNT(*) as counter from ticket where state<16 group by company) ticketcount
on company.compid = ticketcount.company;
This Criteria query returns the inner query results:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<intCompany> qTicket = cb.createQuery(intCompany.class);
Root<Ticket> from = qTicket.from(Ticket.class);
Path groupBy = from.get("company");
Predicate state = cb.notEqual(from.<State>get("state"), getStateById(16));
qTicket.select(cb.construct(
intCompany.class, cb.count(from),from.<Company>get("company")))
.where(state).groupBy(groupBy);
em.createQuery(qTicket).getResultList();
In the application I defined a small wrapper/helper class:
public class intCompany{
public Company comp;
public Long opentickets;
public intCompany(Long opentickets,Company comp){
this.comp = comp;
this.opentickets = opentickets;
}
public intCompany(){
}
}
So does anyone has an idea how to get this working?
Update
Thank you. I changed my criteria query as you suggested. I just had to add a loop at the end to get the information I wanted.
List<intCompany> result = em.createQuery(cq).getResultList();
List<Company> cresult = new ArrayList();
for(intCompany ic: result){
ic.comp.setOpentickets(ic.opentickets.intValue());
cresult.add(ic.comp);
}
return cresult;
Maybe it is just not possible to convert the original sql to Criteria API.
Another update
I figured out I had to change the original sql expression to
select company.*, ticketcount.counter from company
left join
(select company, COUNT(*) as counter from ticket where state<16 group by company) ticketcount
on company.compid = ticketcount.company;
Otherwise I do not get companies with no entries in the ticket table.
So are there any other suggestions?
You have almost everything done.
//---//
CriteriaBuilder cb = em.getCriteriaBuilder();
//Your Wrapper class constructor must match with multiselect arguments
CriteriaQuery<IntCompany> cq = cb.createQuery(IntCompany.class);
//Main table
final Root<Ticket> fromTicket= cq.from(Ticket.class);
//Join defined in Ticket Entity
final Path company = fromTicket.get("company");
//Data to select
cq.multiselect(cb.count(from), company);
//Grouping
cq.groupBy(company);
//Restrictions (I don't really understand what you're querying)
Predicate p = cb.lessThan(fromTicket.get("state"), 16);
//You can add more restrictions
// p = cb.and/or(p, ...);
cq.where(p);
List<IntCompany> results = entityManager.createQuery(cq).getResultList();
This should work as expected.
I had similar problem. My solution was to use left outer joins.
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Entity> query = cb.createQuery(Entity.class);
Root<Entity> root = query.from(Entity.class);
Join<Entity,ChildEntity> join = root.join(Entity_.children, JoinType.LEFT);
query.groupBy(root.get( Entity_.id ));
query.select(
cb.construct(
EntityDTO.class,
root.get( Entity_.id ),
root.get( Entity_.name ),
cb.count(join)
));
This JoinType.LEFT guarantees that you will get Entity records (companies) even if it doesn't have any child entities (tickets).
Entity class:
#Entity
public class Entity {
...
#OneToMany(targetEntity = ChildEntity.class, mappedBy = "parent", fetch = FetchType.LAZY, orphanRemoval = false)
private Set<ChildEntity> objects;
...
}
Static model:
#StaticMetamodel( Entity.class )
public class Entity_ {
public static volatile SingularAttribute<Entity, Long> id;
public static volatile SingularAttribute<Entity, String> name;
...
public static volatile SetAttribute<Entity, ChildEntity> objects;
}

JPA-2.0 Simple Select-Where question

I am stuck with a problem concerning JPA-2.0 queries with relationships. How would it be possible to select any Dataset with at least one Event with type = B?
#Entity
class Dataset {
#OneToMany(fetch = FetchType.LAZY, mappedBy = "dataset")
public List<Event> events;
}
#Entity
class Event {
#ManyToOne
#JoinColumn
public Dataset dataset;
public Type type;
}
enum Type {
A, B, C
}
My starting point is
CriteriaBuilder _builder = em.getCriteriaBuilder();
CriteriaQuery<Dataset> _criteria = _builder.createQuery(Dataset.class);
// select from
Root<Dataset> _root = _criteria.from(Dataset.class);
_criteria.select(_root);
// apply some filter as where-clause (visitor)
getFilter().apply(
_root, _criteria, _builder, em.getMetamodel()
);
// how to add a clause as defined before?
...
Any ideas on this. I tried to create a subqueries as well as a join, but I somehow did it wrong and always got all datasets as result.
Try
SELECT d FROM DataSet d WHERE EXISTS
(SELECT e FROM Event e WHERE e.dataSet = d and e.type = :type)
EDIT: As Pascal pointed out it looks like you are using the Criteria API. Not as familiar with this, but I'll have a stab.
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Dataset> criteria = builder.createQuery(Dataset.class);
Root<Dataset> root = criteria.from(Dataset.class);
criteria.select(root);
// build the subquery
SubQuery<Event> subQuery = criteria.subQuery(Event.class);
Root<Event> eventRoot = subQuery.from(Event.class);
subQuery.select(eventRoot);
ParameterExpression<String> typeParameter = builder.parameter(String.class);
Predicate typePredicate = builder.equal(eventRoot.get(Event_.type), typeParameter));
// i have not tried this before but I assume this will correlate the subquery with the parent root entity
Predicate correlatePredicate = builder.equal(eventRoot.get(Event_.dataSet), root);
subQuery.where(builder.and(typePredicate, correlatePredicate);
criteria.where(builder.exists(subQuery)));
List<DataSet> dataSets = em.createQuery(criteria).getResultList();
Phew that was hard work. I'm going back to Linq now.

Categories

Resources