problem with "select new Object ... join ... where" - java

I'm having a problem with an HQL query
Three classes
ClassOne is my BusinessObject
public class ClassOne {
private int id;
private int status;
private Set<ClassTwo> classTwos;
+ other fields/getters/setters/constructor etc
}
ClassTwo is referenced in a set of ClassOne and is kind of the history of an object of ClassOne
public class ClassTwo {
private int id;
private int oldStatus;
private int newStatus;
private String message;
//+ getters/setters/constructor etc
}
ClassThree is my DTO/VO with just one classTwo (not the whole history)
public class ClassThree {
private int id;
private int status;
private ClassTwo classTwo;
public ClassThree(int pId, int pStatus, ClassTwo pClassTwo) {
id=pId;
status=pStatus;
classTwo=pClassTwo;
}
//+ getters/setters etc
}
Now I'd like to create an HQL query like this:
I'd like to get all objects of ClassThree with a certain status and if it exists the newest ClassTwo with a certain newStatus.
For example:
I'd like to get all the DTOs (ClassThree) of ClassOne whose status is now 1, but earlier in their history it has been 2 and I'd like to have the latest ClassTwo object which has 2 as newStatus.
SELECT new ClassThree(c1.id, c1.status, c2)
FROM ClassOne c1
LEFT JOIN c1.classtwos c2 (...)
and (...) is where I don't know what to do, I'm not even sure if it's a join / join fetch
Looked around and tried quite a lot already, but no clue. Especially with the join fetch I get some Hibernate errors like org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list.
Fetching the BusinessObject like that is no problem
SELECT distinct(c1)
FROM ClassOne c1
LEFT OUTER JOIN FETCH c1.classtwos c2
and I get the ClassTwos as my field.
Thanks in advance,
Jacob
P.S.: One thing might be important, ClassTwo has no reference to ClassOne!!
P.P.S : The simple SQL query which resolves my problem looks more or less like that:
select * from classone as c1 left join (select * from classtwo where newstatus = 2) c2 on c1.id=c2.id_classone whete c1.status = 1
This query works and gets all the information needed on my PostGreSQL DB, but I'd really like to have an HQL to continue to work with, especially for maintenance reasons and so on...
Update with workaround solution:
Getting the ids of all the ClassOnes with a status 1
Collection<Integer> ids = null;
ids = (Collection<Integer>) getHibernateTemplate().execute(
new HibernateCallback() {
public Object doInHibernate(Session pSession) throws HibernateException, SQLException {
return getDocumentIds(pSession, pStatus);
}
}
);
Now I get all the DTOs which have been in status 2 (thanks to Ivan) with:
Named query Document.dto.with.transfer
SELECT new DocumentDTO(d.id, d.status, histo)
FROM Document d
LEFT JOIN d.histories histo
WHERE
d.id in (:ids)
AND
(histo.id =
SELECT MAX(innerhisto.id)
FROM Document innerd
JOIN innerd.histories innerhisto
WHERE d.id = innerd.id AND innerhisto.newStatus = 21)
(in my code I use some named queries)
List<DocumentDTO> lRes = new ArrayList<DocumentDTO>();
Query lQuery = getSession(false).getNamedQuery("Document.dto.with.transfer");
lQuery.setParameterList("ids", ids);
lResultList.addAll(lQuery.list());
afterwards I remove all the IDs already found from my list ids
for (DocumentDTO dto : lResultList) {
ids.remove(dto .getId());
}
I do a third query using a second constructor for the DTO, initializing my history with a dummy-object.
Named query Document.dto.simple
SELECT new DocumentDTO(d.id, d.status)
FROM Document d
WHERE
d.id in (:ids)
(another named query)
lQuery = getSession(false).getNamedQuery("Document.dto.simple");
lQuery.setParameterList("ids", ids);
lResultList.addAll(lQuery.list());
and it's done.

To include Documents with no history we should use LEFT JOIN and test for empty collection, then we use subquery (SELECT COUNT(...)) to detect all documents that have never been in status 2. The last OR-clause is for fetching the last history with the specified status.
Here is the HQL query:
SELECT new DocumentDto(doc.id, doc.status, hist)
FROM Document doc
LEFT JOIN doc.histories hist
WHERE doc.status = :docStatus
AND (size(doc.histories) = 0
OR (SELECT COUNT(innerhist)
FROM Document innerdoc JOIN innerdoc.histories innerhist
WHERE innerdoc.id=doc.id AND innerhist.newStatus = :historyStatus) = 0
OR (hist.newStatus = :historyStatus AND hist.id =
(SELECT max(innerhist.id)
FROM Document innerdoc
JOIN innerdoc.histories innerhist
WHERE innerdoc.status = :docStatus AND innerhist.newStatus = :historyStatus))
Then call setParameter("historyStatus", 2) and setParameter("docStatus", 1) on your query to get the correct result.
That's it!
Please note, I've made an assumption, that we can use a value of id attribute of History as an indicator of the order in which objects were put in your database.

Related

Is it possible to query with list of objects using JPA like foreach tag?

Let's say I have a query like this:
Query object
public class myObject {
private String code;
private String name;
}
query method
#Query(nativeQuery = true,value = "...")
findByAcodeAndBname(List<MyObject> queryObject);
Pseudocode of the query
select a.code, b.name from tableA a
left join table b on a.column = b.column
where a.code = ${} b.name = ${}
Since it could cause performance issues just simply using foreach code in Java to do the query, how do I map every pair of a.code and b.name using native JPA query like mybatis foreach tag? Or is it posssible?
I think you don't need native query here, because the costs of querying data from the database and transfering it over network are higher than any related Java operations.
Instead of native query you can use JPQL query, e.g.
#Query("select new com.tsypanov.domain.HasIdAndNameDto(e.id, e.name)" +
" from ManyFieldsEntity e " +
"where e.name = :name")
List<HasIdAndName> findAllByNameUsingDto(#Param("name") String name);
Here we call constructor of DTO from JPQL query passing the fields from our entity. The DTO class must have the specified constructor:
#RequiredArgsConstructor
public class HasIdAndNameDto {
private final Long id;
private final String name;
}

Select only specific columns from joined tables (Many-to-Many) in Spring Data JPA

The purpose is to select columns from joined tables (Many-to-Many).
The problem i have is to select two columns from a joined Many-to-Many table.
I'am using Springboot 2.3 and Spring data Jpa.
I have this data model, and what i want to fetch are the blue boxed fields
So the native query could look like this (if i am right ...)
SELECT bg.id, bg.name, p.name, c.name, c.short_desc FROM boardgame as bg
JOIN boardgame_category bgc on bg.id = bgc.fk_game
JOIN publisher p on bg.fk_publisher = p.id
JOIN category c on bgc.fk_category = c.id
WHERE bg.id = :id
I first tried to work with dto in JPQL statment
public class BoardgameDto {
private long id;
private String name;
private String publisherName;
private Set<CatregoryDto> categoryDto;
// setter, getter etc...
}
public class CategoryDto {
private String name;
private String shortDesc;
// setter, getter etc...
}
The JQPL query could look like this , but it doesn't work (IDE shows errors on CategoryDto)
/* THIS DOESN'T WORK */
SELECT new org.moto.tryingstuff.dto.BoardgameDto(bg.id, bg.name, p.name,
new org.moto.tryingstuff.dto.CategoryDto(c.name, c.short_desc)) FROM Boardgame as bg, Publisher as p, Category as c
Well, I think the problem I have with this way of doing is that the dto's contructor can't receive a collection as written here, and i think neither another contructor in parameter.
Then i started looking at Criteria Queries, especialy multiselect, Tuple, Dto, but it look like i had same kind of problems so i didn't dive deeper into it.
Finally i used a JpaRepository and it's findById() method like this
public interface BoardgameRepository extends JpaRepository<Boardgame, Long> {
}
// In a test or service method
Boardgame game = repository.findById(long id);
Then i filter the fields i need to keep through mappings in Service or Controller layer. So the front only received need datas.
But it feel a bit overkill,
Am I missing something, any part of the framework that would allow me to select only specific columns?
As you wrote, you can't use a collection as the parameter of a constructor expression. That's because the expression gets applied to each record in the result set. These records are a flat data structure. They don't contain any collections. Your database returns a new record for each element in that collection instead.
But your constructor expression fails for a different reason. You're trying to combine 2 constructor expressions, and that's not supported. You need to remove the 2nd expression and perform that operation within the constructor of your DTO.
So, your query should look like this:
SELECT new org.moto.tryingstuff.dto.BoardgameDto(bg.id, bg.name, p.name, c.name, c.short_desc) FROM Boardgame as bg <Your JOIN CLAUSES HERE>
And the constructor of your BoardgameDto like this:
public class BoardgameDto {
public BoardgameDto(Long id, String gameName, String publisherName, String categoryName, String description) {
this.id = id;
this.name = gameName;
this.publisherName = publisherName;
this.category = new Category(categoryName, description);
}
...
}

Problems mapping Hibernate entities - native query containing left join with condition

This should be straight-forward though can't get my Hibernate entities to play nice for the following scenario with a simple two table structure:
I'm attempting to get all config names and matching config values for a given currency code (and null's where not matching).. so have written a native query to retrieve the following like so:
SELECT * FROM CONFIG_NAME LEFT JOIN CONFIG_VALUE ON CONFIG_NAME.ID =
CONFIG_VALUE.CONFIG_ID AND CONFIG_VALUE.CURRENCY_CODE = '<CURRENCY_CODE>'
ORDER BY CONFIG_NAME.ID
This query doesn't seem to play nice with my Hibernate mapping as it appears to be essentially ignoring the CURRENCY_CODE clause in the join.
Essentially, for the following subset of data:
CONFIG_NAME:
CONFIG_VALUE:
There is no value defined for 'FREE_SHIPPING_ENABLED' for 'USD' so running the query above for both currency code returns as expected:
QUERY RESULTS FOR 'CAD':
QUERY RESULTS FOR 'USD':
I'm running the above query as a native query in a JpaRepository for the ConfigName entity. But what I appear to be getting is that it seems to ignore the currency_code clause in the JOIN condition. As the list of config values defined has both values for USD and CAD where they're populated. Is there an Hibernate annotation to factor this in that I'm unaware of?
It's worth bearing in mind there will only ever be ONE value defined for each config for a given currency - there's a unique constraint across CONFIG_VALUE.CONFIG_ID/CONFIG_VALUE.CURRENCY_CODE so potentially ConfigValue on the ConfigName entity would not need to be a map.
Mappings as are follows:
ConfigName - Entity
#OneToMany(mappedBy = "config")
private Set<ConfigValue> configValue;
ConfigValue - Entity
#ManyToOne(optional = false)
#JoinColumn(name="CONFIG_ID")
#Property(policy=PojomaticPolicy.NONE)
private ConfigName config;
Doesn't need to be strictly unidirectional either.. as I'm only concerned with the values from the ConfigName entity either being populated or null.
Think I'm missing something simple, so hope someone can help.
EDIT: Am querying using JpaRepository:
Am using JpaRepository to query:
#Repository
public interface ConfigNameRepository extends JpaRepository<ConfigName, Long>
{
static final String SQL_QUERY = "SELECT * FROM CONFIG_NAME "
+ "LEFT JOIN CONFIG_VALUE ON CONFIG_NAME.ID = CONFIG_VALUE.CONFIG_ID "
+ "AND CONFIG_VALUE.CURRENCY_CODE = ?1 ORDER BY CONFIG_NAME.ID";
#Query(value = SQL_QUERY, nativeQuery = true)
List<ConfigName> findConfigValuesByCurrencyCode(final String currencyCode);
}
As mentioned by #Ouney, your JPA relations are not taken in account if you use a native query.
You declared a SELECT * and List<ConfigName> (the real sql result contains ConfigName+ConfigValue). So with this query, Hibernate fetchs all the ConfigName. Then, when you try to access to the set of configValue, it fetchs all the related ConfigValue.
I think this should be better/easier to use a JPQL query instead (but you need Hibernate 5.1+) :
SELECT n, v
FROM ConfigName n
LEFT JOIN ConfigValue v
ON v.config = n AND v.currencyCode = :currencyCode
ORDER BY n.id
With this method signature :
List<Object[]> findConfigValuesByCurrencyCode(#Param("currencyCode") String currencyCode);
Where the result will be :
o[0] // ConfigName
o[1] // ConfigValue (nullable)
You may want to do this prettier with a wrapper :
SELECT new my.package.MyWrapper(n, v)
...
MyWrapper constructor :
public MyWrapper(ConfigName configName, ConfigValue configValue) {
...
}
Method signature with the wrapper :
List<MyWrapper> findConfigValuesByCurrencyCode(#Param("currencyCode") String currencyCode);
(update)
I think in this case, your query can be :
SELECT n, v // or new my.package.MyWrapper(n, v)
FROM ConfigName n
LEFT JOIN n.configValue v
WITH v.currencyCode = :currencyCode
ORDER BY n.id

Mapping Entity using sql query via Hibernate

I would like to map Entity into another Entity (1:1) and specifying condition in sql expression using Hibernate.
I have ERA scheme with tables Position and Subject. Subject has relationship 1:N to positions.
Each Position has date of creation. I would like to map last created position (one from N with same subject_id) as entity into entity Subject via hibernate.
I tried #Formula but wasn't successful.
#Entity
public class Position {
..
}
#Entity
public class Subject {
..
#Basic(fetch = FetchType.LAZY)
#Formula("select p.* from position p inner join (select subject_id, max(position_date) maxdate from position where subject_id = 3743073 group by subject_id ) pmaxgrouped on p.subject_id = pmaxgrouped.subject_id and p.position_date = pmaxgrouped.maxdate")
private Position lastPosition;
}
Hibernate mismatches result sql query.
SELECT *
FROM
(SELECT giposobj0_.POSOBJ_ID AS POSOBJ1_1688_,
giposobj0_.SYS_AGENDA_ID AS SYS2_1688_,
giposobj0_.SYS_INS_DATE AS SYS3_1688_,
giposobj0_.SYS_LOGIN_ID_INS AS SYS4_1688_,
giposobj0_.SYS_LOGIN_ID_UPD AS SYS5_1688_,
giposobj0_.SYS_UPD_DATE AS SYS6_1688_,
giposobj0_.SYS_LOGIN_ID_OWNER AS SYS7_1688_,
giposobj0_.SYS_ORGUNIT_ID AS SYS8_1688_,
giposobj0_.SYS_PUBKIND_LEVEL AS SYS9_1688_,
giposobj0_.POSOBJ_CODE AS POSOBJ10_1688_,
giposobj0_.POSOBJ_IMEI AS POSOBJ11_1688_,
giposobj0_.POSOBJ_LASTSTATUS AS POSOBJ12_1688_,
giposobj0_.POSOBJ_MODEL AS POSOBJ13_1688_,
giposobj0_.POSOBJ_NOTIF AS POSOBJ14_1688_,
SELECT p.*
FROM position p
INNER JOIN
(SELECT giposobj0_.posobj_id,
MAX(giposobj0_.position_date) giposobj0_.maxdate
FROM position
WHERE giposobj0_.posobj_id = 3743073
GROUP BY giposobj0_.posobj_id
) giposobj0_.pmaxgrouped
ON p.posobj_id = pmaxgrouped.posobj_id
AND p.position_date = pmaxgrouped.maxdate AS formula11_
FROM eira_gi.VS_POSOBJ giposobj0_
WHERE giposobj0_.POSOBJ_IMEI=111111111111111
);
Option1: You create a ordered list with #OrderBy. Subsequently, you can return the last object from that collection. Here is a very nice tutorial.
Option2: Save the correlated "lastPosition" every time you saveOrUpdate a Subject.

Entity Manager Hibernate / Spring Roo Error - Cannot create TypedQuery

Using Spring Roo (but manually creating these methods so that may be of no relevance) I am trying to display two tables in one list. I have the below method to get all people with their usernames in the class people.java. I believe this method works correctly.
public static TypedQuery<Person> findAllPeople() {
String queryStr = "SELECT o.name, b.username FROM Person o INNER JOIN o.users b";
TypedQuery<Person> query = entityManager().createQuery(queryStr, Person.class);
return query;
}
Here is the code for the controller
#RequestMapping(produces = "text/html")
public String list(#RequestParam(value = "page", required = false) Model uiModel) {
uiModel.addAttribute("people", Person.findAllPeople());
return "people/list";
}
When I try to run this through a list.jspx I get the error "Cannot create TypedQuery for query with more than one return using requested result type [com.test.peopletest.Person]; "
Does anyone know how I can return this query with all the results output properly?
You are not querying for a Person object, you're query appears to return two strings
SELECT o.name, b.username...
In order to return a Person, you should have
SELECT o FROM Person o
Just from looking at your query, it looks like you can drill down to the users/usernames once you have your person object.
It seems like you can use something like ...
SELECT o FROM Person o FETCH JOIN o.users b
when you walk the person.users all the users are loaded from memory (no DB round trip) will be done.
Or, theoretically (read untested!!), you could
SELECT o.name, b.username FROM Person o FETCH JOIN o.users b
TypedQuery<String[]> query = entityManager().createQuery(queryStr, String[]);

Categories

Resources