This is not classical N+1 problem. My issue is conserning using projections and DTO objects in Jpa.
I have next method with JPA Query:
public List<MeterDTO> getAllBrokenMeterByHouseServ(House house, Serv serv, Date dt) {
Query query =em.createQuery("select new MeterDTO(m, g.kart.lsk, nvl(e.tp,0)) from Meter m "
+ "join m.exs e with m.id=e.meter.id "
+ "join m.meterLog g with m.meterLog.id=g.id "
+ "join g.kart k with g.kart.id=k.id and :dt between k.dt1 and k.dt2 "
+ "join g.serv s with g.serv.id=s.id "
+ "join k.kw kw with k.kw.id=kw.id "
+ "join kw.house h with kw.house.id=h.id "
+ "where s.id = :servId "
+ "and kw.house.id = :houseId "
+ "and :dt between e.dt1 and e.dt2 and nvl(e.tp,0) in (2,3,4) "
+ "");
query.setParameter("servId", serv.getId());
query.setParameter("houseId", house.getId());
query.setParameter("dt", dt);
return query.getResultList();
}
I fetch records from the query above into
data transfer object:
meterDao.getAllBrokenMeterByHouseServ(house, serv, dt2).stream().forEach(t-> {
log.info("meter.id={}, lsk={}, tp={} ", t.getMeter().getId(), t.getLsk(), t.getTp());
});
MeterDTO:
#Getter #Setter
public class MeterDTO {
private Meter meter;
private Integer lsk;
private Double tp;
public MeterDTO(Meter meter, Integer lsk, Double tp) {
super();
this.meter = meter;
this.lsk = lsk;
this.tp = tp;
}
}
Why does hibernate produce one main query:
select
meter0_.ID as col_0_0_,
kart3_.lsk as col_1_0_,
nvl(exs1_.TP,
0) as col_2_0_
from
MT.METER meter0_
inner join
MT.METER_EXS exs1_
on meter0_.ID=exs1_.FK_METER
and (
meter0_.ID=exs1_.FK_METER
)
inner join
MT.METER_LOG meterlog2_
on meter0_.FK_METER_LOG=meterlog2_.ID
and (
meter0_.FK_METER_LOG=meterlog2_.ID
)
inner join
AR.KART kart3_
on meterlog2_.FK_KLSK_OBJ=kart3_.FK_KLSK_OBJ
and (
kart3_.lsk=kart3_.lsk
and (
? between kart3_.DT1 and kart3_.DT2
)
)
inner join
AR.KW kw6_
on kart3_.FK_KW=kw6_.ID
and (
kart3_.FK_KW=kw6_.ID
)
inner join
AR.HOUSE house7_
on kw6_.FK_HOUSE=house7_.ID
and (
kw6_.FK_HOUSE=house7_.ID
)
inner join
TR.SERV serv5_
on meterlog2_.FK_SERV=serv5_.ID
and (
meterlog2_.FK_SERV=serv5_.ID
)
where
serv5_.ID=?
and kw6_.FK_HOUSE=?
and (
? between exs1_.DT1 and exs1_.DT2
)
and (
nvl(exs1_.TP, 0) in (
2 , 3 , 4
)
)
and multiple queries with different bind argument "?" to load every entity:
select
meter0_.ID as ID1_44_0_,
meter0_.FK_K_LSK as FK_K_LSK2_44_0_,
meter0_.FK_METER_LOG as FK_METER_LOG4_44_0_,
meter0_.TRANS_RATIO as TRANS_RATIO3_44_0_
from
MT.METER meter0_
where
meter0_.ID=?
How to avoid this issue? I want to load all entities Meter in one main query.
Is it possible?
I use:
<spring-framework.version>5.0.5.RELEASE</spring-framework.version>
<hibernate.version>5.1.0.Final</hibernate.version>
Any help would be greatly appreciated.
upd1
I simplified my JPA query code to this:
public List<MeterDTO> getAllBrokenMeterByHouseServ(House house, Serv serv, Date dt) {
Query query =em.createQuery("select new com.ric.bill.dto.MeterDTO(m) from Meter m ");
}
But it still produces mutiple queries:
select
meter0_.ID as ID1_44_0_,
meter0_.FK_K_LSK as FK_K_LSK2_44_0_,
meter0_.FK_METER_LOG as FK_METER_LOG4_44_0_,
meter0_.TRANS_RATIO as TRANS_RATIO3_44_0_
from
MT.METER meter0_
where
meter0_.ID=?
20-04-2018 12:52:49.482 [main] DEBUG o.h.l.p.e.p.i.ResultSetProcessorImpl - Starting ResultSet row #0
20-04-2018 12:52:49.482 [main] DEBUG org.hibernate.SQL -
select
meter0_.ID as ID1_44_0_,
meter0_.FK_K_LSK as FK_K_LSK2_44_0_,
meter0_.FK_METER_LOG as FK_METER_LOG4_44_0_,
meter0_.TRANS_RATIO as TRANS_RATIO3_44_0_
from
MT.METER meter0_
where
meter0_.ID=?
<Skipped>
very strange!
upd2 Meter entity:
#SuppressWarnings("serial")
#Entity
#Table(name = "METER", schema="MT")
#Getter #Setter
public class Meter extends Base implements java.io.Serializable, Storable {
public Meter (){
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID", updatable = false, nullable = false)
protected Integer id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="FK_METER_LOG", referencedColumnName="ID")
private MeterLog meterLog ;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval=true)
#JoinColumn(name="FK_METER", referencedColumnName="ID")
#BatchSize(size = 50)
private List<Vol> vol = new ArrayList<Vol>(0);
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name="FK_METER", referencedColumnName="ID")
#BatchSize(size = 50)
private List<MeterExs> exs = new ArrayList<MeterExs>(0);
#Column(name = "TRANS_RATIO", updatable = true, nullable = true)
private Double trRatio;
}
In DTO you have 'Meter meter' field, in meter field you have 'MeterLog meterlog' etc. In this case Hibernate is additionally loading for field for full object. This DTO is to much complex. Try to create more flat object:
public class MeterDTO {
private Integer meterId
private Double meterTrRatio
private Integer lsk;
private Double tp;
(...)
And query will be:
(...) new MeterDTO(m.id, m.trans_ratio, g.kart.lsk (...)
And after that you can extending your DTO for the next fields you want.
The accepted answer suggests changing the DTO, which would not always an acceptable solution.
Here is a solution with no need to change your DTO.
Write your HQL like this:
from Meter m
join m.exs e with m.id=e.meter.id
join m.meterLog g with m.meterLog.id=g.id
join g.kart k with g.kart.id=k.id and :dt between k.dt1 and k.dt2 "
join g.serv s with g.serv.id=s.id "
join k.kw kw with k.kw.id=kw.id "
join kw.house h with kw.house.id=h.id "
(more joins and wheres)
Note that there should not be any select.
getResultList will give you List<Object[]>. Each entry is an array of {Meter, m.exs, m.meterLog, g.kart, ....}. Pick the ones you need and make your MeterDTO.
In my case:
jpa repo
#Query("from Bind bind "
+ "left join Employee employee "
+ "with bind.empCode = employee.empCode "
+ "where bind.accountName = :hiveAccount and bind.disabled = 1 ")
List<Object[]> listMembers(#Param("hiveAccount") String hiveAccount);
DTO
public class BindDTO {
Bind bind;
Employee emp;
public BindDTO(Object[] objs) {
this((Bind) objs[0], (Employee) objs[1]);
}
service
myRepo.listMembers(hiveAccount).stream().map(BindDTO::new).collect(Collectors.toList());
Related
I have three tables (I'll list only important fields):
Format
id
template_id
parameterisation_id
Param
name
value
parameterisation_id
ParamDict
name
template_id
I need to map params into formats into one-to-many association, but they do not have direct connection and needed to be joined through ParamDict table.
I currently did it with #JoinTable like so:
#Entity
#Table(schema = "123", name = "format")
public class Format implements Serializable {
#Id
private Long id;
private String template_id;
private String parameterisation_id;
#OneToMany(fetch = FetchType.EAGER)
#JoinTable(name = "paramDict", schema = "123",
joinColumns = {#JoinColumn(name = "template_id", referencedColumnName =
"template_id",insertable = false,updatable = false)},
inverseJoinColumns = {#JoinColumn(name = "param_name", referencedColumnName =
"param_name",insertable = false,updatable = false)})
private Set<Param> params = new HashSet<>()
}
And in sql it looks like this
Select * from
Format f
left join ParamDict pd on f.template_id = pd.template_id
left join Param p on p.name = pd.name;
But now i need to add another condition in Param join:
Select * from
Format f
left join ParamDict pd on f.template_id = pd.template_id
left join Param p on (p.name = pd.name and p.parameterisation_id = f.parameterisation_id);
I tried to add where clause into #Query annotation in my Repository:
public interface FormatRepository extends Repository<Format, String> {
#Query("select n from format f "
+ "left join fetch f.params p "
+ "where f.parameterisation_id in (?1) "
+ "and f.parameterisation_id = p.parameterisation_id"
)
Set<Format> findBypParameterisationIdIn(List<String> ids);
}
And it created right sql query, but Hibernate does not count that condition (f.parameterisation_id = p.parameterisation_id) when mapping query results into class objects, and i get wrong results in Param set (there are items in the params set, that does not have the same parameterisation_id as Format).
How can i do it via Hibernate mapping?
In my Spring boot application I have a query which should return a distinct List of Focus' (works perfectly in MySQL)
#Query(value = "SELECT DISTINCT * FROM Focus F " +
"JOIN FrameworkFoci FF on FF.focusID = F.focusID " +
"JOIN FocusGroups FG on FF.frameworkID = FG.frameworkID " +
"JOIN GroupMembers GM on FG.groupID = GM.groupID " +
"JOIN Users U on GM.userID = U.userID " +
"WHERE U.userID = :userID", nativeQuery = true)
List<Focus> findByUserID(#Param("userID") Long userID);
However this does not return distinct values, duplicates are contained in the resulting list. Another issue is that I can't return a whole entity using #Query annotation - changing my query to SELECT DISTINCT(F) FROM Focus F gives the error java.sql.SQLSyntaxErrorException: Unknown column 'F' in 'field list'.
Furthermore, I tried changing adjusting the query to the following
#Query(value = "SELECT DISTINCT * FROM FrameworkFoci FF " +
"JOIN FocusGroups FG on FF.frameworkID = FG.frameworkID " +
"JOIN GroupMembers GM on FG.groupID = GM.groupID " +
"JOIN Users U on GM.userID = U.userID " +
"WHERE U.userID = :userID", nativeQuery = true)
however this produced the error java.sql.SQLException: Column 'focusCategory' not found.
Why is the query not returning distinct values? And why can't I return a whole entity nor use the second query?
Focus Entity:
#Entity
public class Focus {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long focusID;
#Column(name = "focusCategory")
private String focusCategory;
private String focusName;
private String focusExplanation;
#OneToMany(mappedBy = "focus")
private Set<Rating> ratings;
#ManyToMany
#JoinTable(name = "FrameworkFoci",
joinColumns = #JoinColumn(
name = "focusID"),
inverseJoinColumns = #JoinColumn(
name = "frameworkID"))
private Set<Framework> frameworks;
//image
protected Focus(){}
public Focus(String focusName, String focusCategory, String focusExplanation) {
this.focusCategory = focusCategory;
this.focusName = focusName;
this.focusExplanation = focusExplanation;
}
public Focus(String focusCategory, String focusName, String focusExplanation, Set<Rating> ratings){
this.focusCategory = focusCategory;
this.focusName = focusName;
this.focusExplanation = focusExplanation;
this.ratings = ratings;
}
public Long getFocusId() {
return focusID;
}
public void setFocusId(Long focusID) {
this.focusID = focusID;
}
public String getFocusCategory() {
return focusCategory;
}
public void setFocusCategory(String focusCategory) {
this.focusCategory = focusCategory;
}
EDIT:
I've switched from SQL to JPQL with the following query:
#Query(value = "SELECT DISTINCT focus FROM Focus focus " +
"WHERE focus.frameworks.groups.groupMembers.user.userID =:userID ")
I now get an error org.hibernate.QueryException: illegal attempt to dereference collection [focus0_.focusID.frameworks] with element property reference [groups]
Framework entity:
#Entity
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,
property = "frameworkID")
public class Framework {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long frameworkID;
private String frameworkName;
#ManyToMany
#JoinTable(name = "FrameworkFoci",
joinColumns = #JoinColumn(
name = "frameworkID"),
inverseJoinColumns = #JoinColumn(
name = "focusID"))
private Set<Focus> frameworkFoci = new HashSet<>();
#OneToMany(mappedBy = "framework", fetch = FetchType.EAGER, cascade = CascadeType.REMOVE)
private Set<Group> groups;
public Framework(){}
The following query solves the issue
#Query(value = "SELECT DISTINCT focus FROM Focus focus " +
"JOIN focus.frameworks frameworks " +
"JOIN frameworks.groups groups " +
"JOIN groups.groupMembers groupMembers "+
"WHERE groupMembers.userID =:userID ")
List<Focus> findByUserID(#Param("userID") Long userID);
Frameworks and GroupMembers are collections and hence needed to be joined, otherwise illegal attempt to dereference collection [focus0_.focusID.frameworks] with element property reference [groups] was produced
you should write your query like this:
'
SELECT DISTINCT f FROM Focus F '
The problem stems from you using SQL by specifying nativeQuery = true. SQL doesn't know about entities, just tables.
Since you presumably have many FrameworkFoci rows (and rows in all the other tables) for each Focus row, each Focus row gets repeated for each matching row in FrameworkFoci. This kind of duplicates the Focus row but the resulting rows are still distinct, because they differ in the columns from the other tables.
And then each row gets turned into a Focus entity, probably with a single element in the framework set.
So therefore query doesn't so much return duplicate results as results split into multiple entities.
Fortunately the solution should be fairly simple: Use JPQL which should be perfectly possible, since you're using only simple joins.
The following should give you a start:
#Query(value = "SELECT DISTINCT * FROM Focus F " +
"WHERE F.framework.groupMembers.user.id=:userID")
List<Focus> findByUserID(#Param("userID") Long userID);
I created some reports for my system, and that report is made up of many tables. For this, I create a Domain class with an #Entity annotation and implement a JpaRepository repository, I'm using the native query with #Query, as shown below.
My problem is that for each domain class a table is being created by hibernate, how do I stop it?
My Domain class:
#Entity
#Immutable
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#IdClass(WidgetDailyReportCompositeKey.class)
public class WidgetDailyReportDomain{
#Id
#Column(updatable = false, insertable = false)
private UUID id_construction;
#Id
#Column(updatable = false, insertable = false)
private String name;
#Id
#Column(updatable = false, insertable = false)
private Date dt_cost;
#Column(updatable = false, insertable = false)
private Double total;
}
My Repository:
public interface WidgetRepository extends JpaRepository<WidgetDailyReportDomain, UUID>{
#Query(value = " SELECT ct.id AS id_construction, " +
" ct.name, " +
" sm.dt_service AS dt_cost, " +
" sum(smi.nu_value * stiv.amount) AS total " +
" FROM service_measurement sm " +
" INNER JOIN service_measurement_item smi ON smi.id_service_measurement = sm.id " +
" INNER JOIN service s ON s.id = sm.id_service " +
" INNER JOIN service_type_item_service stiv ON stiv.id_service = sm.id_service " +
" AND stiv.id_service_type_item = smi.id_service_item " +
" INNER JOIN construction ct ON ct.id = s.id_construction " +
" WHERE s.id_construction IN ( " +
" select s.id_construction " +
" from service_measurement sm " +
" INNER JOIN service_measurement_item smi ON smi.id_service_measurement = sm.id " +
" INNER JOIN service s ON s.id = sm.id_service " +
" INNER JOIN service_type_item_service stiv ON stiv.id_service = sm.id_service " +
" AND stiv.id_service_type_item = smi.id_service_item " +
" INNER JOIN construction ct on ct.id = s.id_construction " +
" WHERE sm.dt_service BETWEEN :minDate AND :maxDate " +
" GROUP BY s.id_construction " +
" ORDER BY sum(smi.nu_value * stiv.value) DESC " +
" limit :limit " +
" ) " +
" AND sm.dt_service BETWEEN :minDate AND :maxDate " +
" GROUP BY ct.id, sm.dt_service " +
" HAVING sum(smi.nu_value * stiv.amount) > 0 " +
" ORDER BY sm.dt_service;", nativeQuery = true)
List<WidgetDailyReportDomain> findTopExpensiveConstruction(#Param("minDate") Date minDate, #Param("maxDate") Date maxDate, #Param("limit") int limit);
//....
Your WidgetDailyReportDomain is actually projection. You don't need to mark it as #Entity.
And your #Query could belong to any other really existing repository.
You can remove all the javax.persistence annotations like #Column, #Id, #Entity. These annotations represent properties of a table, which you seem to not want it to be.
Then you can use the WidgetDailyReportDomain object as a DTO to be your projection and not have it attached to the EntityManager:
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections.dtos
EDIT: Also, do not forget to build a constructor for that object so that Spring JPA loads the values into the object (like described on the documentation).
If you don't want to build a constructor, maybe you can change it into an interface and use it as your projection: https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections.interfaces
It looks like you're using Spring due to the JpaRepository in your question.
If you're using Spring Boot, then you can add
spring:
jpa:
hibernate:
ddl-auto: none
to your application.yml file, or
spring.jpa.hibernate.ddl-auto=none
to your application.properties file.
If you're using a persistence.xml file, you could add a property to disable it there, too:
<property name="hibernate.hbm2ddl.auto" value="none"/>
Disabling the generation of the schema tables like this means that you'll have to make sure they're created by some other means before your application will work, though.
After #Zagarh answer, i did a lith of search about DTO, and i came up with a not very elegant solution, but that is working:
The Domain class :
public class WidgetDailyReportDomain{
private UUID id_construction;
private String name;
private Date dt_cost;
private Double total;
}
I have to create a custom result mapping, for the JPA be able of mapping de result, i use the annotation #SqlResultSetMapping. But for some reason she is only identify in a class that is annotated with # Entity. For not to get disorganized, i create a class exclusive to annotation with # SqlResultSetMapping, because i gona have a lot of mapping to do. The class looked like this:
#MappedSuperclass
#SqlResultSetMapping(
name = "widget_daily_mapping",
classes = {
#ConstructorResult(
targetClass = WidgetDailyReportDomain.class,
columns = {
#ColumnResult(name="id_construction", type = UUID.class),
#ColumnResult(name = "name", type = String.class),
#ColumnResult(name = "dt_cost", type = Date.class),
#ColumnResult(name = "total", type = Double.class)
}
)
}
)
public abstract class ResultSetMappingConfig {
}
And then i create a custom implementation of Jpa Repository
public interface WidgetRepositoryCustom {
List<WidgetDailyReportDomain> findTopExpensiveConstruction(Date minDate, Date maxDate, int limit);
}
#Repository
#Transactional(readOnly = true)
public class AR_serviceRepositoryImpl implements AR_serviceRepositoryCustom{
#PersistenceContext
private EntityManager em;
#Override
public List<AR_serviceDomain> getListOfService(UUID id_construction) {
Query query = em.createNativeQuery("
//Native Query Here...
", "widget_daily_mapping");// Put the result mapping Here
query.setParameter(1, id_construction //..Parameters Here);
return query.getResultList();
}
}
Ps: 1) If any one have a better solution please let me know. 2) Sorry for my english, i'm using google translate.
I have a HQL query with a JOIN but the where clause (instrPrice.date BETWEEN :dateFrom AND :dateTo ) on the joined entity doesn't work. The query always returns all the records of instrumentPrice instead of limiting the result by the dates.
NamedQuery
#NamedQuery(name = "findAllPrices",
query = "SELECT DISTINCT taPat FROM TaPatternInstrument taPat "
+ "LEFT JOIN FETCH taPat.instrument instr "
+ "LEFT JOIN instr.instrumentPriceList instrPrice "
+ "WHERE taPat.id = :taPatternInstrumentId "
+ "AND instrPrice.date BETWEEN :dateFrom AND :dateTo ")
Service which calls the Query
public TaPatternInstrument findAllPrices(int taPatternInstrumentId, LocalDate dateFrom, LocalDate dateTo) {
TypedQuery<TaPatternInstrument> typedQuery = createNamedQuery("findAllPrices",
TaPatternInstrument.class);
typedQuery.setParameter("taPatternInstrumentId", taPatternInstrumentId);
typedQuery.setParameter("dateFrom", dateFrom);
typedQuery.setParameter("dateTo", dateTo);
return typedQuery.getSingleResult();
}
Entities
public abstract class BaseEntity implements Serializable {
#Id
#Column(name = "id")
#GeneratedValue(strategy =
GenerationType.IDENTITY)
protected int id; ...
}
public class TaPatternInstrument extends BaseEntity {
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "instrument_id", nullable = false, foreignKey = #ForeignKey(name =
"tapatterninstrument_instrument_fk"))
private Instrument instrument;
}
public class Instrument extends BaseEntity {
#OneToMany(mappedBy = "instrument", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<InstrumentPrice> instrumentPriceList;
}
Generated SQL
SELECT DISTINCT tapatterni0_.id AS id1_34_0_,
...
FROM tapatterninstrument tapatterni0_
LEFT OUTER JOIN instrument instrument1_
ON tapatterni0_.instrument_id = instrument1_.id
LEFT OUTER JOIN instrumentprice instrument2_
ON instrument1_.id = instrument2_.instrument_id
WHERE tapatterni0_.id = ?
AND ( instrument2_.date BETWEEN ? AND ? )
The solution is to add a FETCH on instrumentPriceList : LEFT JOIN FETCH instr.instrumentPriceList instrPrice
#NamedQuery(name = "findAllPrices",
query = "SELECT DISTINCT taPat FROM TaPatternInstrument taPat "
+ "LEFT JOIN FETCH taPat.instrument instr "
+ "LEFT JOIN FETCH instr.instrumentPriceList instrPrice "
+ "LEFT JOIN taPat.taPatternInstrumentPriceList taPatpr "
+ "WHERE taPat.id = :taPatternInstrumentId "
+ "AND instrPrice.date BETWEEN :dateFrom AND :dateTo ")
The FETCH forces Hibernate to retrieve the Entity (InstrumentPrice) immediately at the first DB request. And thus the where clause is taken into account.
Without FETCH, the Entity InstrumentPrice is only retrieved from the DB when the method getInstrumentPriceList of the Entity Instrument is called (an additional call to the DB is performed). And with this additional call to the DB, the where clause is not taken into account anymore, thus retrieving all records from Entity instrumentPrice.
I have a JPA database setup like the following:
#Entity
public class Contact {
#Id
private Long id;
#Column(length = 64)
private String firstname;
#Column(length = 64)
private String surname;
#OneToMany(mappedBy = "contact")
private List<Address> adresses = new ArrayList<Address>();
#OneToMany(mappedBy = "contact")
private List<Telephone> telephones = new ArrayList<Telephone>();
}
#Entity
public class Address {
#Id
private Long id;
#Column(length = 128)
private String street;
#Column(length = 16)
private String plz;
#ManyToOne
private Contact contact;
}
#Entity
public class Telephone {
#Id
private Long id;
#Column(length = 32)
private String number;
#ManyToOne
private Contact contact;
}
Now I have the following query for my search form:
em.createQuery("SELECT DISTINCT c FROM Contact c LEFT JOIN c.addresses a LEFT JOIN c.telephones t "
+ "WHERE c.surnameLIKE '%"+var_surname+"%' "
+ "AND c.firstname LIKE '%"+var_firstname+"%' "
+ "AND a.street LIKE '%"+var_street+"%'"
+ "AND t.nummer LIKE '%"+var_telephone+"%'"
).getResultList();
My question is now how can I join all 3 tables in a single JPA-query to look up for the firstname, street and number for example? I tried already with LEFT JOIN, but I don't get any results when for example the Telephone table is empty (it properly works if all tables have appropriate entries). I would like to have a result list with the Contacts even when there are no phone numbers and just one result even if a contact has more than one phone number for example.
Many thanks for your help in advance.
Did you try using "LEFT OUTER JOIN" e.g. below?
"SELECT DISTINCT c FROM Contact c LEFT OUTER JOIN c.addresses a LEFT OUTER JOIN c.telephones t "
+ "WHERE c.surnameLIKE '%"+var_surname+"%' "
+ "AND c.firstname LIKE '%"+var_firstname+"%' "
+ "AND a.street LIKE '%"+var_street+"%'"
+ "AND t.nummer LIKE '%"+var_telephone+"%'"
).getResultList();
The DISTINCT should remove the duplicates,
for getting results if there is no relationship you need to use an OR,
+ "AND ((a.id is null) OR (a.street LIKE '%"+var_street+"%'))"
Thanks for your helpful answers. Finally I had to solve the problem with a dynamic buildup of my search string, selectively add/remove some parts of the join operands depending on which fields the user left empty.
public List<Person> querySearch(String snachname, String svorname, String srechtsform, String sadresse, String sort, String sland, String stelefon, String semail, LoadGroup group) {
if(srechtsform.equals("Alle")) {
srechtsform = "";
}
if(sland.equals("Alle")) {
sland = "";
}
String q = "SELECT DISTINCT * FROM Person p LEFT OUTER JOIN Adresse a ON (a.PERSON_ID=p.ID) LEFT OUTER JOIN Telefon t ON (t.PERSON_ID=p.ID) LEFT OUTER JOIN Email e ON (e.PERSON_ID=p.ID) WHERE p.nachname1 LIKE '%"+snachname+"%'AND p.vorname LIKE '%"+svorname+"%' AND p.rechtsform LIKE '%"+srechtsform+"%' ";
if(sadresse.equals("") && sort.equals("") && sland.equals("")) {
q += "AND (a.ID IS NULL OR (a.strasse LIKE '%"+sadresse+"%' AND a.ort LIKE '%"+sort+"%' AND a.land LIKE '%"+sland+"%')) ";
} else {
q += "AND a.strasse LIKE '%"+sadresse+"%' AND a.ort LIKE '%"+sort+"%' AND a.land LIKE '%"+sland+"%' ";
}
if(stelefon.equals("")) {
q += "AND (t.ID IS NULL OR t.nummer LIKE '%"+stelefon+"%') ";
} else {
q += "AND t.nummer LIKE '%"+stelefon+"%' ";
}
if(semail.equals("")) {
q += "AND (e.ID IS NULL OR e.mailadresse LIKE '%"+semail+"%') ";
} else {
q += "AND e.mailadresse LIKE '%"+semail+"%' ";
}
q += "GROUP BY p.ID";
Query query = em.createNativeQuery(q, Person.class);
if(group!=null) { query.setHint(QueryHints.LOAD_GROUP, group); }
return query.getResultList();
}
"LEFT OUTER JOIN a ON b" style syntax is not supported in JPA (see http://chrisiecorner.blogspot.fi/2012/12/jpa-and-outer-joins.html)
You must do native query.