How to set columns from a joined entity in JPAQuery and Querydsl - java

I am in the process of internationalizing an existing software using. As part of it I have a table which, reduced to the simplest case, is:
ID AUTONUMERIC
ID_OF_TEXT NUMBER
TEXT VARCHAR
The text column must be translated to new languages (which may or may not be present). So there is another new table with colunmns:
ID_OF_TEXT NUMBER
LANGUAGE_CODE VARCHAR
TRANSLATED_TEXT VARCHAR
There is already an entity to represent the base table and this entity cannot be changed. It's generated code looks like:
class QMyBaseEntity extends EntityPathBase<MyBaseEntity> {
NumberPath<Long> id = createNumber("id", Long.class);
NumberPath<Long> toTranslateId = createNumber("toTranslateId", Long.class);
StringPath text = createString("text");
}
and the generated code for the translations look up table looks like:
#Generated("com.mysema.query.codegen.EntitySerializer")
class QTranslationLookup extends EntityPathBase<TranslationLookup> {
NumberPath<Long> id = createNumber("id", Long.class);
NumberPath<Long> translationId = createNumber("translationId", Long.class);
StringPath languageCode = createString("languageCode");
StringPath translated_text = createString("translated_text");
}
I want the field text of MyBaseEntity to take the value of the translation table (if it exists) instead of its original one. There is already a very complex query that I cannot change except for adding a join like:
String languageCode = "de";
JPAQuery query = new JPAQuery(entityManager);
query.from(qMyBaseEntity);
// add lots of other joins and stuff here
query.leftJoin(qTranslationLookup)
.on(qTranslationLookup.translationId.eq(qMyBaseEntity.toTranslateId)
.and(qTranslationLookup.languageCode.eq(languageCode)));
List<MyBaseEntity> results = query.list(qMyBaseEntity);
With the minimum change possible to the existing code, how do I set the value of the translation in the results list so that the returned entity instances contain the translation in the text column instead of the original value from the old (untranslated) table?

Mysema isn't a framework, QueryDSL is. QueryDSL used to be developed by Mysema, but isn't anymore and the package has since moved to the com.querydsl group id.
You can't override the values of managed properties in an entity projection. The values will always be the actual field values from the entity. Entities cannot be used as DTO's. If you want to project different kinds of expressions, you need to use tuple projection.
List<Tuple> results = query.from(qMyBaseEntity)
.leftJoin(qTranslationLookup)
.on(qTranslationLookup.translationId.eq(qMyBaseEntity.toTranslateId)
.and(qTranslationLookup.languageCode.eq(languageCode)))
.select(qMyBaseEntity, qTranslationLookup.translatedText.coalesce(qMyBaseEntity.text))
.fetch()
Alternatively, you could for example return a mapping:
Map<MyBaseEntity, String> results = query.from(qMyBaseEntity)
.leftJoin(qTranslationLookup)
.on(qTranslationLookup.translationId.eq(qMyBaseEntity.toTranslateId)
.and(qTranslationLookup.languageCode.eq(languageCode)))
.transform(GroupBy.groupBy(qMyBaseEntity).as(
qTranslationLookup.translatedText.coalesce(qMyBaseEntity.text))
.fetch()
Or you could use QueryDSL's DTO projection:
List<ResultDTO> results = query.from(qMyBaseEntity)
.leftJoin(qTranslationLookup)
.on(qTranslationLookup.translationId.eq(qMyBaseEntity.toTranslateId)
.and(qTranslationLookup.languageCode.eq(languageCode)))
.select(Projections.constructor(ResultDTO.class, qMyBaseEntity, qTranslationLookup.translatedText.coalesce(qMyBaseEntity.text))
.fetch()

Related

Unable to map JSON field from postgres DB to POJO class

I have below code in place where I am trying to get only 1 column (JSON type) from postgres DB and map it to a POJO class. But, in the result I am getting correct count but with null values only, even though data is present. Any Help/Suggestion is appreciated.
POJO Class:
#JsonIgnoreProperties(ignoreUnknown = true)
public class EmployeePayload {
private String empName;
private String empID;
//getters and setters
}
Query Execution Code ::
String query = "SELECT emp_payload FROM EmpDetails";
ResultSetHandler<List<EmployeePayload>> employeePayload = new BeanListHandler<EmployeePayload>(EmployeePayload.class);
List<EmployeePayload> resultList = runner.query(dbConnection, query, employeePayload);
log.info(":: resultList is :: " + resultList);
Data in DB ::
"{""empName"":""james"",""empID"":""008""}",
"{""empName"":""bond"",""empID"":""007""}"
Result in Log ::
resultList is :: [EmployeePayload{empName='null', empID='null'}, EmployeePayload{empName='null', empID='null'}]
The BeanListHandler comes from apache dbutils (docs)
By looking at the docs / source, it's implemented by mapping single columns to single properties of the Bean. You are essentially trying to map a single column to multiple properties which does not work. Now there are two ways to go about this:
Writing your own RowProcessor
Rewriting the query to return multiple columns.
In this situation I would favor the second solution for it's simplicity as postgres has this functionality built-in for its json field types.
Your query could look like this:
String query = "SELECT emp_payload->'empName' AS empName, emp_payload->'empID' AS empID FROM EmpDetails";
(The AS ... might not be necessary but I don't know how psql generates the column names for extracted json values).
If you would execute this query directly in postgres, you would get a result set with column names empName and empID which is exactly what the BeanProcessor (the default processor for BeanListHandler) expects.

Storing the keysets from a JPQL query result in a java list

I was successfully able to execute a jpql query and print the result which is stored in a queryResults variable. What I want to achieve next is storing just the IDs (primary key column) in a list without the date (value), but I am not too sure if this is possible; perhaps using something like a java map. Is it possible? If yes, how can this be easily achieved?
private static final TestDao Test_DAO = new TestDao();
#Test
public void testById() {
List<TestEntity> queryResults = TEST_DAO.findById(""); //The record from the sql query is stored in queryResults and findById("") is the method that executes the query in a TestDao class and it is called here
for (TestEntity qResult: queryResults) { // looping through the query result to print the rows
System.out.println(qResult.getId());
System.out.println(qResult.getDate());
}
System.out.println("This is the sql result " + queryResults );
}
Output:
This is the result [TestEntity(id=101, date=2020-01-19 15:12:32.447), TestEntity(id=102, date=2020-09-01 11:04:10.0)]// I want to get the IDs 101 and 102 and store in a list without the Dates
I tried using a map this way:
Map<Integer, Timestamp> map= (Map<Integer, Timestamp>) queryResults.get(0); but I got an exception:
java.lang.ClassCastException: TestEntity cannot be cast to java.util.Map
There are some points before the implementation.
Why are you defining DAO as static? I think this is a bad implementation unless I am missing a particular reason you declared it static. You should define this as a member variable and not a static member
The naming of the method - findById() translated in English is - find Something by this Id, but you are fetching a list of Records, so naming is not correct.
Point 2 becomes invalid if ID property is not a Primary Key in your table, then it makes sense, but still naming is bad. Id is something we use to define Primary Key in the Database and should be and will be unique. But your comments suggest that ID is unique and the Primary Key. So read about how Databases work
And even if not unique, if you pass an Id to find some records, why will get different ids in the Records !!!
About implementation:
Changing in your existing code:
private TestDao Test_DAO = new TestDao();
#Test
public void testById() {
List<TestEntity> queryResults = TEST_DAO.findById("");
List<Long> listOfIds = new ArrayList<>(); // Assuming Id is Long type, same logic for any type
for (TestEntity qResult: queryResults) {
System.out.println(qResult.getId());
listOfIds.add(qResult.getId()); // Just add it to the list
System.out.println(qResult.getDate());
}
}
In case you want to be efficient with the query:
You can use JPQL and hibernate
You can then write a query like:
String query = "select te.id from TestEntity te";
// Create the TypedQuery using EntityManager and then get ResultSet back
List<Long> ids = query.getResultList();
In case of using Spring-Data-Jpa, you can define the repository and define the method and pass the query with #Query annotation. Spring Data JPA

Hibernate - Query columns map into object, the rest of the columns set to null or default value

Basically if we want to query specific columns, we do this:
Query query =
session.createQuery("SELECT tr.review from TravelReview as tr");
List<String> reviews = query.list();
And if we want the original object (TravelReview) instead of List of String, we do this:
String QUERY = "SELECT new City(tr.title, tr.review ) from TravelReview as tr";
List<City> cities = session.createQuery(QUERY).list();
I found it very troublesome to purposely create a Java constructor for the purpose above. (It will end up I have many constructors for this.)
Is there a way to map the selected columns automatically and return the original object (TravelReview), only the attributes that match the selected columns have values, the rest of the attributes will be null (or default value)? Basically something like Spring JdbcTemplate BeanPropertyRowMapper which is very useful and convenient.

How to best map results from an SQL query to a non-entity Java object using Hibernate?

I have a Hibernate managed Java entity called X and a native SQL function (myfunc) that I call from a Hibernate SQL query along these lines:
SQLQuery q = hibernateSession.createSQLQuery(
"SELECT *, myfunc(:param) as result from X_table_name"
);
What I want to do is to map the everything returned from this query to a class (not necessarily managed by Hibernate) called Y. Y should contain all properties/fields from X plus the result returned by myfunc, e.g. Y could extend class X and add a "result" field.
What I've tried:
I've tried using q.addEntity(Y.class) but this fails with:
org.hibernate.MappingException: Unknown entity com.mycompany.Y
q.setResultTransformer(Transformers.aliasToBean(Y.class)); but this fails with: org.hibernate.PropertyNotFoundException: Could not find setter for some_property. X has a field called someProperty with the appropriate getter and setter but in this case it doesn't seem like Hibernate maps the column name (some_property) to the correct field name.
q.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP); returns a Map but the values are not always of the type expected by the corresponding field in X. For example fields in X of type enum and Date cannot be mapped directly from the Map returned by the SQL query (where they are Strings).
What's the appropriate way to deal with this situation?
See the chapter of the documentation about SQL queries.
You can use the addScalar() method to specify which type Hibernat should use for a given column.
And you can use aliases to map the results with the bean properties:
select t.some_property as someProperty, ..., myfunc(:param) as result from X_table_name t
Or, (and although it require some lines of code, it's my preferred solution), you can simply do the mapping yourself:
List<Object[]> rows = query.list();
for (Object[] row : rows) {
Foo foo = new Foo((Long) row[0], (String) row[1], ...);
}
This avoids reflection, and lets you control everything.
Easy. Cast the rows to Map<String, Object>:
final org.hibernate.Query q = session.createSQLQuery(sql);
q.setParameter("geo", geo);
q.setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
final List<Map<String, Object>> src = q.list();
final List<VideoEntry> results = new ArrayList<VideoEntry>(src.size());
for (final Map<String, Object> map:src) {
final VideoEntry entry = new VideoEntry();
BeanUtils.populate(entry, map);
results.add(entry);
}
First of all you need to declare the entity in the hibernate configuration xml file something like this: .....
class="path to your entity"
Or you can do the same thing programatically before you make the query.

Hibernate Criteria: Return different entity type than rooted entity?

I have entities similar to:
ProductLine: id, name
ProductLineContents: content_id, product_line_id
Content: id, text, updated_time
What I'd like to do is: for each product line, get the latest content (so if theres two content entries associated to one product line, the latest updated_time is rturned, and if one content item is associated to two product lines, it is returned twice). Something similar to:
select content.* from productline
inner join productlinecontents
inner join content;
However I can't seem to figure out how to have Hibernate Criteria return a different entity than the original one it was created with. So if I wanted to start the criteria at the product line with createCriteria(ProductLine.class) along with the proper joins, then it only returns ProductLine objects, but I need Content objects.
What's the best way to accomplish this?
The actual data model is much more complex and can't be modified
ALIAS_TO_ENTITY_MAP map worked:
criteria.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP);
List itemsList = criteria.list();
if (itemsList == null || itemsList.isEmpty()) {
throw new EntityNotFoundException();
}
List<Content> content = new ArrayList<Content>();
Iterator iter = itemsList.iterator();
while ( iter.hasNext() ) {
Map map = (Map) iter.next();
content.add((Content) map.get("contentAlias"));
}
return content;
Or you could do like this http://docs.jboss.org/hibernate/stable/core/reference/en/html_single/#querycriteria-associations
Use a resulttransformer Alias to entitymap
But Hql seems to be the most appropiate to use.

Categories