Right join using JPA criteria api - java

I'm trying to implement this sql query using jpa criteria api :
SELECT F.* FROM PF right join F on F.FID = PF.FID WHERE PF.PFID is null;
Which also can be written as:
SELECT F.* FROM F left join PF on F.FID = PF.FID WHERE PF.FID is null;
This is what I tried:
public List<F> listFWithoutP() {
final CriteriaBuilder builder = getCriteriaBuilder();
final CriteriaQuery<F> query = builder.createQuery(F.class);
final Root<PF> from = query.from(PF.class);
Join<PF, F> join = from.join(PF_.f, JoinType.RIGHT);
query.select(join.get(PF_.f))
.where(builder.isNull(from.get(PF_.pFId)));
final TypedQuery<F> typedQuery = getEntityManager().createQuery(query);
return typedQuery.getResultList();
}
But it doesn't work, I get the following error in this line : query.select(join.get(PF_.f))
The method get(SingularAttribute<? super F,Y>) in the type Path<F> is not applicable for the arguments (SingularAttribute<PF,F>)
How can I solve this ?
Update:
These are my entities :
public class F extends AbstractDomain<Long> {
#Id
#Column
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "idgen")
private Long fId;
#Column(nullable = false)
private String lib;
}
public class PF extends AbstractDomain<Long> {
#Id
#Column
private Long pFId;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(nullable = false)
private P p;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(nullable = false)
private F f;
}
Update 2:
It seems like RIGHT JOIN is not supported by the jpa criteria api, so this can be done using the second query.

final Session session = entityManager.unwrap(Session.class);
final Criteria criteria = session.createCriteria(PF.class, "pf")
.createAlias("pf.f", "f")
.add(Restrictions.eqProperty("pf.pFId",
"f.fId"))
.add(Restrictions.isNull("pf.pFId"));
Hello Aimad,
please try this criteria query and let me know if that works,this criteria would exactly replicate your SQL query.

Related

How to perform left join on two sql queries using criteria API

I have two tables and their classes as follows:
#Entity
#Table(name="Master")
public class Master {
#Column(name = "master_col_one")
private String masterColumnOne;
#Column(name = "master_col_two")
private String masterColumnTwo;
// Other fields and so on
#NotNull
#JoinColumn(name = "slave_col_three", nullable = false)
#ManyToOne(optional = false)
private Slave slaveData;
}
#Entity
#Table(name="slave")
public class Slave {
#Column(name = "slave_col_one")
private String slaveColumnOne;
#Column(name = "slave_col_two")
private String slaveColumnTwo;
// Other fields
}
My native SQL query is like this:
SELECT * FROM master m LEFT JOIN
(SELECT DISTINCT slave_col_one, slave_col_two FROM slave) s ON m.master_col_one = s.slave_col_one
AND m.master_col_two = s.slave_col_two
I need to build a criteria query for the above native SQL query.
I have built this much query so far:
final CriteriaBuilder criteriaBuilder = getEntityManager().getCriteriaBuilder();
final CriteriaQuery<Master> criteriaQuery = criteriaBuilder
.createQuery(Master.class);
final Root<Master> root = criteriaQuery.from(Master.class);
Join<Master, Slave> join = root.join(Master_.slaveData);
I think my join is not correct as it's not fetching distinct selective columns from slave table as per requirements, I searched the internet a lot but couldn't come up with a solution.

Mapstruct - Send nested entity having (one-to-many relation) in the response

I have 2 entities CallRecords and CallRecordOperators with one-to-many relation as given below
public class CallRecords {
#Id
#Column(name = "id", unique = true)
private String id;
#Column(columnDefinition = "varchar(255) default ''")
private String callerNumber = "";
#OneToMany(mappedBy="callrecord")
private List<CallRecordOperators> callRecordOperators = new ArrayList<CallRecordOperators>();
//getter setters
}
public class CallRecordOperators {
#Id
#Column(name = "id", length = 50, unique = true, nullable = false, insertable = false, updatable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#JsonIgnore
#ManyToOne
#JoinColumn(name = "callRecordId")
private CallRecords callrecord;
#ManyToOne
#JoinColumn(name = "operatorId")
private Operator operator;
#Formats.DateTime(pattern = "yyyy-MM-dd HH:mm:yy")
#Column(columnDefinition = "TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP")
private Date startTime = new Date();
#Column(columnDefinition = "varchar(100) default ''")
private String dialStatus;
//getter setter
}
So if the user ask for all "CallRecords" data I also have to give "CallRecordOperators" as they are related.
Current code for Mapper and DTOs
#Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface CallRecordsMapper {
CallRecordsMapper INSTANCE = Mappers.getMapper(CallRecordsMapper.class);
#Mapping(source="callRecordOperators",target = "operators")
CallRecordsDto callRecordsToCallRecordsDto(CallRecords callRecords);
public abstract CallRecordOperatorsDto toTarget(CallRecordOperators source);
List<CallRecordsDto> callRecordsToCallRecordsDtos(List<CallRecords> callRecords);
}
public class CallRecordsDto {
private String callerNumber;
private List<CallRecordOperatorsDto> operators;
//getter setters
}
public class CallRecordOperatorsDto {
private String callRecordsId;
private String operatorId;
private String operatorName;
private String currentTime;
// getter setter
}
But for above code I am getting
{
"callerNumber": "9898989898",
"operators": [{
"callRecordsId": null,
"operatorId": null,
"operatorName": null,
"currentTime": null
}, {
"callRecordsId": null,
"operatorId": null,
"operatorName": null,
"currentTime": null
}]
}
the values of operator array are null. what could be he issue?
It seems your are lacking the mappings from CallRecordOperators to CallRecordOperatorsDto:
#Mapper
public interface CallRecordsMapper {
CallRecordsMapper INSTANCE = Mappers.getMapper(CallRecordsMapper.class);
#Mapping(source="callRecordOperators",target = "operators")
CallRecordsDto callRecordsToCallRecordsDto(CallRecords callRecords);
#Mapping(target = "callRecordsId", source = "callrecord.id")
#Mapping(target = "operatorId", source = "operator.id")
#Mapping(target = "operatorName", source = "operator.name")
#Mapping(target = "currentTime", source = "startTime")
CallRecordOperatorsDto callRecordOperatorsToDto(CallRecordOperators source);
}
When you do a Hibernate query of A elements, you can fetch the related B elements of the bs collection using different strategies. Some of them are:
If you use HQL to construct your queries, you can do a JOIN FETCH or LEFT JOIN FETCH to populate the bs collection:
String hql = "SELECT DISTINCT a FROM " + A.class.getName()
+ " a LEFT JOIN FETCH a.bs WHERE ...";
This query will load all data using a single SQL query.
Use eager fetching of the bs collection, changing the #OneToMany annotation:
#OneToMany(fetch=FetchType.EAGER)
private List<B> bs;
In this case, when you run a query of A elements, a SQL query will be launched to retrieve the A data, and for each A object in the result, a SQL query will be executed to load the corresponding bs collection.
If you use Criteria to build the query, you can change the fetch mode of the bs collection in a way similar to the HQL JOIN FETCH:
Criteria c = session.createCriteria(A.class);
c.setFetchMode("bs", FetchMode.JOIN);
c.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
How about switching to a slightly different approach that also performs better? By using Blaze-Persistence Entity Views you can define your mapping directly on the DTO classes and apply that onto a query builder to generate efficient queries that perfectly fit your DTO structure.
#EntityView(CallRecords.class)
public interface CallRecordsDto {
// The id of the CallRecords entity
#JsonIgnore
#IdMapping("id") String getId();
String getCallerNumber();
#Mapping("callRecordOperators")
List<CallRecordOperatorsDto> getOperators();
}
#EntityView(CallRecordOperators.class)
public interface CallRecordOperatorsDto {
// The id of the CallRecordOperators entity
#JsonIgnore
#IdMapping("id") Long getId();
#Mapping("callrecord.id")
String getCallRecordId();
#Mapping("operator.id")
String getOperatorId();
#Mapping("operator.name")
String getOperatorName();
#Mapping("startTime")
String getCurrentTime();
// Whatever properties you want
}
See how you can map the entity attributes right in your DTOs? And here comes the code for querying
EntityManager entityManager = // jpa entity manager
CriteriaBuilderFactory cbf = // query builder factory from Blaze-Persistence
EntityViewManager evm = // manager that can apply entity views to query builders
CriteriaBuilder<User> builder = cbf.create(entityManager, CallRecords.class)
.where("callerNumber").eq("123456789");
List<CallRecordsDto> result = evm.applySetting(
builder,
EntityViewSetting.create(CallRecordsDto.class)
).getResultList();
Note that this will roughly generate the following optimized query
SELECT
c.id,
c.callerNumber,
o.callrecord.id,
o.id,
o.startTime,
op.id,
op.name
FROM CallRecords c
LEFT JOIN c.callRecordOperators o
LEFT JOIN o.operator op
WHERE c.callerNumber = :param_1

Hibernate/JPA JPQL to wrong SQL when querying Map<String,String> field

This is my Entity configuration
#Entity
#NamedQuery(name = "Payment.findByEmail", query = "SELECT p FROM Payment p JOIN p.additionalAuthData a " +
"WHERE KEY(a) = 'email' AND VALUE(a) = ?1 AND (p.paymentType = 4 OR p.paymentType = 10)")
public class Payment {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
#Column(name = "payment_type")
private Integer paymentType;
/** other properties, getters and setters */
#ElementCollection
#CollectionTable(name = "additional_auth_data")
#MapKeyJoinColumn(name = "id", referencedColumnName = "id")
#MapKeyColumn(name = "field")
#Column(name = "data_value")
private Map<String, String> additionalAuthData;
}
The NamedQuery findByEmail("test#example.com") generates the following SQL
select -- all fields ...
from payment payment0_ inner join additional_auth_data additional1_ on payment0_.id=additional1_.id
where
additional1_.field='email' and (select additional1_.data_value from additional_auth_data additional1_ where payment0_.id=additional1_.id)='test#example.com' and (payment0_.payment_type=4 or payment0_.payment_type=10)
which is wrong: it may work if you have only one row but it blows up otherwise. H2 complains Scalar subquery contains more than one row and PostgreSQL more than one row returned by a subquery used as an expression. In fact, query's where condition compares a scalar value ('test#example.com') with a subquery.
The correct SQL should be:
select -- all fields
from payment payment0_ inner join additional_auth_data additional1_ on payment0_.id=additional1_.id
where additional1_.field='payerEmail' and additional1_.data_value='test#example.com' and (payment0_.payment_type=4 or payment0_.payment_type=10)
Is the HSQL correct? Is there a way to instruct Hibernate to generates a clever, better SQL? Is this a Hibernate bug?
Note: Hibernate shipped with Spring Boot Starter 1.3.7.RELEASE
Edit:
Using an #Embeddable class
#ElementCollection
#JoinTable(name = "additional_auth_data", joinColumns = #JoinColumn(name = "id"))
#MapKeyColumn(name = "field")
#Column(name = "data_value")
private Set<AdditionalData> additionalAuthData;
#Embeddable
public static class AdditionalData {
#Column(name = "field", nullable = false)
private String field;
#Column(name = "data_value")
private String dataValue;
protected AdditionalData() {
}
public AdditionalData(String field, String dataValue) {
this.field = field;
this.dataValue = dataValue;
}
/** Getters, setters; equals and hashCode on "field" */
}
#NamedQuery(name = "Payment.findByEmail", query = "SELECT p FROM Payment p JOIN p.additionalAuthData a " +
"WHERE a.field = 'email' AND a.dataValue = ?1 AND (p.paymentType = 4 OR p.paymentType = 10)")
solves the problem, and the SQL is correct, but it looks just plain wrong, like shooting a fly with a bazooka...
It generates correct SQL without value().
Use just a=?1
But I would expect is should generate it simple also with it.

Java CriteriaBuilder Join - cannot be solved or is not a field

I'm trying to do a select using a join in CriteriaBuilder, but I'm getting this error in Eclipse. How can I fix it?
Hibernate version: hibernate-jpa-2.0-api<br />
Java Version: 1.8
fonte cannot be solved or is not a field
NotificacaoDao.java
#Stateless
public class NotificacaoDao {
#PersistenceContext(unitName = "PostgreSQLDS")
private EntityManager em;
#EJB
private NotificacaoDao NotificacaoDao;
public List<Notificacao> getResultList(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String, Object> filters) throws ApplicationException{
try {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Notificacao> cq = cb.createQuery(Notificacao.class);
Metamodel m = em.getMetamodel();
EntityType<Notificacao> Notificacao_ = m.entity(Notificacao.class);
Root<Notificacao> myObj = cq.from(Notificacao_);
Join<Notificacao, Fonte> fontes = myObj.join(Notificacao_.fonte); // HERE I'M GETTING THE ERROR
cq.where(NotificacaoDao.getFilterCondition(cb, myObj, filters));
Predicate filterCondition = NotificacaoDao.getFilterCondition(cb, myObj, filters);
filterCondition = cb.and(filterCondition, cb.equal(myObj.get("excluido"), "N"));
cq.where(filterCondition);
if (sortField != null) {
if (sortOrder == SortOrder.ASCENDING) {
cq.orderBy(cb.asc(myObj.get(sortField)));
} else if (sortOrder == SortOrder.DESCENDING) {
cq.orderBy(cb.desc(myObj.get(sortField)));
}
}
return em.createQuery(cq).setFirstResult(first).setMaxResults(pageSize).getResultList();
} catch(Exception e) {
throw new ApplicationException("myException", e);
}
}
Notificacao.java
#Entity
#Table(name = "tb_notificacao", schema = "indicadores")
#NamedQuery(name = "Notificacao.findAll", query = "SELECT n FROM Notificacao n")
#FilterDef(name="notificacaoNaoExcluido", defaultCondition="excluido = 'N'")
public class Notificacao implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#SequenceGenerator(name = "tb_notificacao_codnotificacao_seq", sequenceName = "TB_NOTIFICACAO_CODNOTIFICACAO_SEQ", schema = "indicadores", allocationSize = 1)
#GeneratedValue(generator = "tb_notificacao_codnotificacao_seq")
#Column(name = "codnotificacao", nullable = false)
private Integer codnotificacao;
private String descricao;
private String excluido;
private String nome;
// bi-directional many-to-one association to CargaNotificacao
#OneToMany(mappedBy = "notificacao")
private List<CargaNotificacao> cargaNotificacoes;
// bi-directional many-to-one association to Fonte
#Inject
#ManyToOne
#JoinColumn(name = "codfonte")
private Fonte fonte;
// bi-directional many-to-one association to UsuarioNotificacao
#OneToMany(mappedBy = "notificacao")
#Filter(name="usuarioNaoExcluido", condition="excluido = 'N'")
private List<UsuarioNotificacao> usuarioNotificacoes;
public Notificacao() {
}
// getters and setters
}
Fonte.java
#Entity
#Table(name = "tb_fonte", schema = "indicadores")
#NamedQuery(name = "Fonte.findAll", query = "SELECT f FROM Fonte f")
public class Fonte implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#SequenceGenerator(name = "tb_fonte_codfonte_seq", sequenceName = "TB_FONTE_CODFONTE_SEQ", schema = "indicadores", allocationSize = 1)
#GeneratedValue(generator = "tb_fonte_codfonte_seq")
#Column(name = "codfonte", nullable = false)
private Integer codfonte;
private String nome;
// bi-directional many-to-one association to Indicador
#OneToMany(mappedBy = "fonte")
#Filter(name="indicadorNaoExcluido", condition="excluido = 'N'")
private List<Indicador> indicadores;
// bi-directional many-to-one association to Notificacao
#OneToMany(mappedBy = "fonte")
#Filter(name="notificacaoNaoExcluido", condition="excluido = 'N'")
private List<Notificacao> notificacoes;
public Fonte() {
}
// getters and setters
}
Well, on Metamodels there are basically three approaches to use:
Using IDE based metamodel generation tools
Using Static Canonical Metamodel Classes
Using em.getMetamodel() API i.e. the one you are using.
The solution I am proposing for you to use which is closer to what you were doing is on Point 3.
Point 3 Solution :
Replace the below code :
Metamodel m = em.getMetamodel();
EntityType<Notificacao> Notificacao_ = m.entity(Notificacao.class);
Root<Notificacao> myObj = cq.from(Notificacao_);
Join<Notificacao, Fonte> fontes = myObj.join(Notificacao_.fonte); // HERE I'M GETTING THE ERROR
With new code :
Metamodel m = em.getMetamodel();
EntityType<Notificacao> notificacao_ = m.entity(Notificacao.class);
Root<Notificacao> myObj = cq.from(notificacao_);
Join<Notificacao, Fonte> fontes = myObj.join(notificacao_.getSingularAttribute("fonte",Fonte.class));
Points 1 & 2 Solutions
Please note the Notificacao_ must be a class either static or generated and must never be an instance of em.getMetamodel(). Also note in your case before Notificacao_ was a variable instead of a class as shown:
EntityType<Notificacao> Notificacao_ = m.entity(Notificacao.class);
If you need more info, let me know please.

JPA CriteriaQuery projection with lists

I'm trying to make a query using a wrapper class as result type. Sorry for the long post but I want to make my question as complete as possible.
The root class has 3 lists that I want to retrieve:
#Entity
#Table(name = "cash")
public final class Cash extends BaseSimpleModel {
#Id
#SequenceGenerator(name = "id", sequenceName = "cash_seq")
#GeneratedValue(strategy = GenerationType.AUTO, generator = "id")
private Long cashID;
#Column(length = 50, unique = true)
private String description;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "cashID")
private List<CashAllowedValue> allowedValueList;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "cashID")
private List<CashAllowedCurrency> allowedCurrencyList;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "cashID")
private List<CashAllowedCashier> allowedCashierList;
... getters and setters
}
Here is my wrapper class:
public class CashQueryResult extends QueryResult {
private Long cashID;
private String description;
private List<CashAllowedValue> allowedValueList;
private List<CashAllowedCurrency> allowedCurrencyList;
private List<CashAllowedCashier> allowedCashierList;
public CashQueryResult(Long id, String description, List<CashAllowedValue> allowedValueList, List<CashAllowedCurrency> allowedCurrencyList, List<CashAllowedCashier> allowedCashierList)
{
this.cashID = id;
this.description = description;
this.allowedValueList = allowedValueList;
this.allowedCurrencyList = allowedCurrencyList;
this.allowedCashierList = allowedCashierList;
}
... getters
}
And here is my query:
public final List<CashQueryResult> getQRList(final CashQueryParameter cashQP, final QueryHint queryHint) {
List<CashQueryResult> cashQRL = null;
try {
final CriteriaBuilder cb = em.getCriteriaBuilder();
final PredicateBuilder pb = new PredicateBuilder(cb);
final CriteriaQuery<CashQueryResult> cq = cb.createQuery(CashQueryResult.class);
final Root<Cash> cash = cq.from(getModelClass());
// Joins.
final ListJoin<Cash, CashAllowedValue> allowedValueList = cash.join(Cash_.allowedValueList, JoinType.LEFT);
final ListJoin<Cash, CashAllowedCurrency> allowedCurrencyList = cash.join(Cash_.allowedCurrencyList, JoinType.LEFT);
final ListJoin<Cash, CashAllowedCashier> allowedCashierList = cash.join(Cash_.allowedCashierList, JoinType.LEFT);
// Paths.
final Path<ValueTypeEnum> valueType = allowedValueList.get(CashAllowedValue_.valueType);
final Path<Currency> currency = allowedCurrencyList.get(CashAllowedCurrency_.currency);
final Path<IntranetUser> intranetUser = allowedCashierList.get(CashAllowedCashier_.cashier);
// Expressions. Just for testing purposes.
final Expression<List<CashAllowedValue>> cashAllowedValueListExpression = cash.get(Cash_.allowedValueList);
final Expression<List<CashAllowedCurrency>> cashAllowedCurrencyExpression = cash.get(Cash_.allowedCurrencyList);
final Expression<List<CashAllowedCashier>> cashAllowedCashierExpression = cash.get(Cash_.allowedCashierList);
cq.distinct(true);
cq.select(cb.construct(CashQueryResult.class, cash.get(Cash_.cashID), cash.get(Cash_.description),
// cash.get(Cash_.allowedValueList), cash.get(Cash_.allowedCurrencyList), cash.get(Cash_.allowedCashierList) // does not work
// cashAllowedValueListExpression, cashAllowedCurrencyExpression, cashAllowedCashierExpression // does not work
allowedValueList, allowedCurrencyList, allowedCashierList // does not work
));
// cq.multiselect(cash.get(Cash_.cashID), cash.get(Cash_.description),
// allowedValueList, allowedCurrencyList, allowedCashierList
// ); // does not work
cq.where(cb.and(pb.like(cash.get(Cash_.description), cashQP.getDescription()), pb.equal(valueType, cashQP.getValueType()), pb.equal(currency.get(Currency_.currencyID), cashQP.getCurrencyID()), pb.equal(intranetUser.get(IntranetUser_.agentID), cashQP.getIntranetUserID())));
cq.orderBy(cb.asc(cash.get(Cash_.description)));
final TypedQuery<CashQueryResult> tq = em.createQuery(cq);
tq.setFirstResult(queryHint.getFirstResult());
tq.setMaxResults(queryHint.getMaxResults());
cashQRL = tq.getResultList();
}
catch (Throwable t) {
throw new EJBException(t.getMessage());
}
return cashQRL;
}
Finally, the exception (it varies depending on what select method I try but it's always something like this):
2011-09-12 16:12:26,165 ERROR [org.hibernate.hql.PARSER] (http-127.0.0.1-8080-2) Unable to locate appropriate constructor on class [com.ebizlink.adonis.erp.model.support.CashQueryResult] [cause=org.hibernate.PropertyNotFoundException: no appropriate constructor in class: com.ebizlink.adonis.erp.model.support.CashQueryResult]
2011-09-12 16:12:26,169 SEVERE [javax.enterprise.resource.webcontainer.jsf.context] (http-127.0.0.1-8080-2) javax.ejb.EJBException: org.hibernate.hql.ast.QuerySyntaxException:
Unable to locate appropriate constructor on class [com.ebizlink.adonis.erp.model.support.CashQueryResult]
[select distinct new com.ebizlink.adonis.erp.model.support.CashQueryResult(generatedAlias0.cashID, generatedAlias0.description, generatedAlias1, generatedAlias2, generatedAlias3)
from com.ebizlink.adonis.erp.model.Cash as generatedAlias0
left join generatedAlias0.allowedValueList as generatedAlias1
left join generatedAlias0.allowedCurrencyList as generatedAlias2
left join generatedAlias0.allowedCashierList as generatedAlias3
where ( 1=1 ) and ( 1=1 ) and ( 1=1 ) and ( 1=1 )
order by generatedAlias0.description asc]
For reference, here is the PredicateBuilder just in case.
I searched the web but I couldn't find somebody that is trying to retrieve many lists.
Am I missing something obvious? Multiple fetches are a no go (hibernate bug), and I can't make the lists Eager either.
Another related question would be:
Can I make a query so, that the lists of my wrapper class are also entity wrappers and not entities? (For example: Instead of using Cashiers, just get first name and last name, and use a list of CashierWrapper which holds both strings)
I really thank you and I hope you can help me.
It is likely that you cannot do that, what specification says about arguments to constructor expression:
constructor_expression ::= NEW constructor_name ( constructor_item {,constructor_item}* )
constructor_item ::=
single_valued_path_expression | scalar_expression | aggregate_expression | identification_variable
And lists (collection_valued_field) you try to offer to constructor is not any of the constructor_item.

Categories

Resources