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.
Related
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.
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
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.
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.
I have the following model classes:
#Entity
#Table(name = "title")
public final class Title extends ModelData<Title>
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer titleID;
#Column(name = "title")
private String title;
#Column(name = "description")
private String description;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "title")
private Set<Book> books;
}
#Entity
#Table(name = "book")
public final class Book extends ModelData<Book>
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "bookID")
private int bookID;
#ManyToOne(cascade = CascadeType.PERSIST, fetch = FetchType.EAGER)
#JoinColumn(name = "titleID")
private Title title;
#Column(name = "edition")
private Integer edition;
#Column(name = "isbn")
private String ISBN;
}
I want to write a Criteria query that is equivalent to the following SQL;
Select
t.title, b.edition
from
books b, title t
where
b.titleID = t.titleID
and
(b.edition=4 OR t.title LIKE '%Java%);
I tried the following:
Criteria c = session.createCriteria(Book.class);
Criteria titleCriteria = c.createCriteria("title");
titleCriteria.add(Restrictions.like("title", "%Java%");
Criterion edition = Restrictions.eq("edition", 4);
LogicalExpression orExp = Restrictions.or(edition, titleCriteria); //cannot do this
How do I achieve the above?
Thanks.
public class MyDTO {
private String dtoTitle;
private String dtoEdition;
// + setters/getters
}
Criteria c = session.createCriteria(Book.class,"b");
c.createAlias("title", "t");
c.add(
Restrictions.disjunction()
.add( Restrictions.like("t.title", "%Java%") )
.add( Restrictions.eq("b.edition", 4) )
);
c.setProjection(
Projections.projectionList()
.add( Projections.property("t.title"), "dtoTitle" )
.add( Projections.property("b.edition"), "dtoEdition" )
);
c.setResultTransformer(Transformers.aliasToBean(MyDTO.class));
List<MyDTO> result = (List<MyDTO>)c.list();
Something like this should work fine.
On dao using many criterias you should consider using static imports.
Another idea
Convert yout criteria to formula field and evaluate as normal criterios
Add formula field to mapping file, or annotations to your classes
<property name="titlename" type="string"
formula="(Select title.title from title
where title.titleID= titleID)"/>
then
Criteria c = session.createCriteria(Book.class)
Criteria titleCriteria = c.createCriteria("title");
titleCriteria.add(Restrictions.like("titlename", "%Java%");
Criterion edition = Restrictions.eq("edition", 4);
LogicalExpression orExp = Restrictions.or(edition, titleCriteria); //CAN< do this!!!
I think you want this. I haven't tested it, so there may be some minor errors, but the basic idea is correct.
Criteria c = session.createCriteria(Book.class);
Criterion titleCriterion = Restrictions.like("title.title", "%Java%");
Criterion edition = Restrictions.eq("edition", 4);
c.add( Restrictions.or( edition, titleCriterion ));
For more than 2 OR conditions it's more readable to use:
c.add(
Restrictions.disjunction()
.add(Restrictions.eq(...))
.add(Restrictions.eq(...))
.add(Restrictions.eq(...))
)