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()
I know I can pass a list to named query in JPA, but how about NamedNativeQuery? I have tried many ways but still can't just pass the list to a NamedNativeQuery. Anyone know how to pass a list to the in clause in NamedNativeQuery? Thank you very much!
The NamedNativeQuery is as below:
#NamedNativeQuery(
name="User.findByUserIdList",
query="select u.user_id, u.dob, u.name, u.sex, u.address from user u "+
"where u.user_id in (?userIdList)"
)
and it is called like this:
List<Object[]> userList = em.createNamedQuery("User.findByUserIdList").setParameter("userIdList", list).getResultList();
However the result is not as I expected.
System.out.println(userList.size()); //output 1
Object[] user = userList.get(0);
System.out.println(user.length); //expected 5 but result is 3
System.out.println(user[0]); //output MDAVERSION which is not a user_id
System.out.println(user[1]); //output 5
System.out.println(user[2]); //output 7
The above accepted answer is not correct and led me off track for many days !!
JPA and Hibernate both accept collections in native query using Query.
You just need to do
String nativeQuery = "Select * from A where name in :names"; //use (:names) for older versions of hibernate
Query q = em.createNativeQuery(nativeQuery);
q.setParameter("names", l);
Also refer the answers here which suggest the same (I picked the above example from one of them)
Reference 1
Reference 2 which mentioned which cases paranthesis works which giving the list as a parameter
*note that these references are about jpql queries, nevertheless the usage of collections is working with native queries too.
A list is not a valid parameter for a native SQL query, as it cannot be bound in JDBC. You need to have a parameter for each argument in the list.
where u.user_id in (?id1, ?id2)
This is supported through JPQL, but not SQL, so you could use JPQL instead of a native query.
Some JPA providers may support this, so you may want to log a bug with your provider.
Depending on your database/provider/driver/etc., you can, in fact, pass a list in as a bound parameter to a JPA native query.
For example, with Postgres and EclipseLink, the following works (returning true), demonstrating multidimensional arrays and how to get an array of double precision. (Do SELECT pg_type.* FROM pg_catalog.pg_type for other types; probably the ones with _, but strip it off before using it.)
Array test = entityManager.unwrap(Connection.class).createArrayOf("float8", new Double[][] { { 1.0, 2.5 }, { 4.1, 5.0 } });
Object result = entityManager.createNativeQuery("SELECT ARRAY[[CAST(1.0 as double precision), 2.5],[4.1, 5.0]] = ?").setParameter(1, test).getSingleResult();
The cast is there so the literal array is of doubles rather than numeric.
More to the point of the question - I don't know how or if you can do named queries; I think it depends, maybe. But I think following would work for the Array stuff.
Array list = entityManager.unwrap(Connection.class).createArrayOf("int8", arrayOfUserIds);
List<Object[]> userList = entityManager.createNativeQuery("select u.* from user u "+
"where u.user_id = ANY(?)")
.setParameter(1, list)
.getResultList();
I don't have the same schema as OP, so I haven't checked this exactly, but I think it should work - again, at least on Postgres & EclipseLink.
Also, the key was found in: http://tonaconsulting.com/postgres-and-multi-dimensions-arrays-in-jdbc/
Using hibernate, JPA 2.1 and deltaspike data I could pass a list as parameter in query that contains IN clause. my query is below.
#Query(value = "SELECT DISTINCT r.* FROM EVENT AS r JOIN EVENT AS t on r.COR_UUID = t.COR_UUID where " +
"r.eventType='Creation' and t.eventType = 'Reception' and r.EVENT_UUID in ?1", isNative = true)
public List<EventT> findDeliveredCreatedEvents(List<String> eventIds);
can be as simple as:
#Query(nativeQuery =true,value = "SELECT * FROM Employee as e WHERE e.employeeName IN (:names)")
List<Employee> findByEmployeeName(#Param("names") List<String> names);
currently I use JPA 2.1 with Hibernate
I also use IN condition with native query. Example of my query
SELECT ... WHERE table_name.id IN (?1)
I noticed that it's impossible to pass String like "id_1, id_2, id_3" because of limitations described by James
But when you use jpa 2.1 + hibernate it's possible to pass List of string values. For my case next code is valid:
List<String> idList = new ArrayList<>();
idList.add("344710");
idList.add("574477");
idList.add("508290");
query.setParameter(1, idList);
In my case ( EclipseLink , PostGreSQL ) this works :
ServerSession serverSession = this.entityManager.unwrap(ServerSession.class);
Accessor accessor = serverSession.getAccessor();
accessor.reestablishConnection(serverSession);
BigDecimal result;
try {
Array jiraIssues = accessor.getConnection().createArrayOf("numeric", mandayWorkLogQueryModel.getJiraIssues().toArray());
Query nativeQuery = this.entityManager.createNativeQuery(projectMandayWorkLogQueryProvider.provide(mandayWorkLogQueryModel));
nativeQuery.setParameter(1,mandayWorkLogQueryModel.getPsymbol());
nativeQuery.setParameter(2,jiraIssues);
nativeQuery.setParameter(3,mandayWorkLogQueryModel.getFrom());
nativeQuery.setParameter(4,mandayWorkLogQueryModel.getTo());
result = (BigDecimal) nativeQuery.getSingleResult();
} catch (Exception e) {
throw new DataAccessException(e);
}
return result;
Also in query cannot use IN(?) because you will get error like :
Caused by: org.postgresql.util.PSQLException: ERROR: operator does not exist: numeric = numeric[]
'IN(?)' must be swapped to '= ANY(?)'
My solution was based on Erhannis concept.
In jpa, it worked for me
#Query(nativeQuery =true,value = "SELECT * FROM Employee as e WHERE e.employeeName IN (:names)")
List<Employee> findByEmployeeName(#Param("names") List<String> names);
Tried in JPA2 with Hibernate as provider and it seems hibernate does support taking in a list for "IN" and it works. (At least for named queries and I believe it will be similar with named NATIVE queries)
What hibernate does internally is generate dynamic parameters, inside the IN same as the number of elements in the passed in list.
So in you example above
List<Object[]> userList = em.createNamedQuery("User.findByUserIdList").setParameter("userIdList", list).getResultList();
If list has 2 elements the query will look like
select u.user_id, u.dob, u.name, u.sex, u.address from user u "+
"where u.user_id in (?, ?)
and if it has 3 elements it looks like
select u.user_id, u.dob, u.name, u.sex, u.address from user u "+
"where u.user_id in (?, ?, ?)
you should do this:
String userIds ="1,2,3,4,5";
List<String> userIdList= Stream.of(userIds.split(",")).collect(Collectors.toList());
Then, passes like parameter inside your query, like this:
#NamedNativeQuery(name="User.findByUserIdList", query="select u.user_id, u.dob, u.name, u.sex, u.address from user u where u.user_id in (?userIdList)")
It's not possible with standard JPA. Hibernate offers the proprietary method setParameterList(), but it only works with Hibernate sessions and is not available in JPA's EntityManager.
I came up with the following workaround for Hibernate, which is not ideal but almost standard JPA code and has some nice properties to it.
For starters you can keep the named native query nicely separated in a orm.xml file:
<named-native-query name="Item.FIND_BY_COLORS" result-class="com.example.Item">
<query>
SELECT i.*
FROM item i
WHERE i.color IN ('blue',':colors')
AND i.shape = :shape
</query>
</named-native-query>
The placeholder is wrapped in single quotes, so it's a valid native JPA query. It runs without setting a parameter list and would still return correct results when other matching color parameters are set around it.
Set the parameter list in your DAO or repository class:
#SuppressWarnings("unchecked")
public List<Item> findByColors(List<String> colors) {
String sql = getQueryString(Item.FIND_BY_COLORS, Item.class);
sql = setParameterList(sql, "colors", colors);
return entityManager
.createNativeQuery(sql, Item.class)
.setParameter("shape", 'BOX')
.getResultList();
}
No manual construction of query strings. You can set any other parameter as you normally would.
Helper methods:
String setParameterList(String sql, String name, Collection<String> values) {
return sql.replaceFirst(":" + name, String.join("','", values));
}
String getQueryString(String queryName, Class<?> resultClass) {
return entityManager
.createNamedQuery(queryName, resultClass)
.unwrap(org.hibernate.query.Query.class) // Provider specific
.getQueryString();
}
So basically we're reading a query string from orm.xml, manually set a parameter list and then create the native JPA query. Unfortunately, createNativeQuery().getResultList() returns an untyped query and untyped list even though we passed a result class to it. Hence the #SuppressWarnings("unchecked").
Downside: Unwrapping a query without executing it may be more complicated or impossible for JPA providers other than Hibernate. For example, the following might work for EclipseLink (untested, taken from Can I get the SQL string from a JPA query object?):
Session session = em.unwrap(JpaEntityManager.class).getActiveSession();
DatabaseQuery databaseQuery = query.unwrap(EJBQueryImpl.class).getDatabaseQuery();
databaseQuery.prepareCall(session, new DatabaseRecord());
Record r = databaseQuery.getTranslationRow();
String bound = databaseQuery.getTranslatedSQLString(session, r);
String sqlString = databaseQuery.getSQLString();
An alternative might be to store the query in a text file and add code to read it from there.
You can pass in a list as a parameter, but:
if you create a #NamedNativeQuery and use .createNamedQuery(), you don't use named param, you used ?1(positional parameter). It starts with 1, not 0.
if you use .createNativeQuery(String), you can use named param.
You can try this :userIdList instead of (?userIdList)
#NamedNativeQuery(
name="User.findByUserIdList",
query="select u.user_id, u.dob, u.name, u.sex, u.address from user u "+
"where u.user_id in :userIdList"
)
I'm trying to make a new calculated col with criteria query like this SQL:
SELECT *, SUM(quantity) + SUM(pledged) as total, SUM(pledged) as pledged FROM stock_position GROUP BY partner_id, product_id
I need to get this result and set to another entity, so I need to use the alias total and map to entity. But I don't find a way to do this. I tried the code below.
final CriteriaQuery<Stock> query = getCriteriaBuilder().createQuery(Stock.class);
final Root<StockPosition> root = query.from(StockPosition.class);
Expression<Double> quantity = getCriteriaBuilder().sum(root.<Double> get("quantity"));
Expression<Double> pledged = getCriteriaBuilder().sum(root.<Double>get("pledged"));
Expression<Double> total = getCriteriaBuilder().sum(quantity, pledged);
query.select(total); //THIS GIVES ME ERROR
query.groupBy(root.get("partner"), root.get("product"));
Of course the types are different, but I don't know how to map. Anyone can give a hand?
Thanks
Simple: You want to sum up some values, but the query expects an entity of the class Stock.
The correct declaration would be as follows:
final CriteriaQuery<Double> query = getCriteriaBuilder().createQuery(Double.class);
I'm trying to do the following with Jooq and can't for the life of me figure out how to do it properly:
select name, id in (
select capability_id
from a.capabilities_users
where user_id = ?)
from a.capabilities;
Basically I want to get all items (capabilities) and know whether each one applies to a particular user. It seems that all the condition type operators (like greater than or in) can only be used in the where and not the select. And I can't think of how else to express this.
Worst case, I can do a select count and then do the boolean logic in Java, but I was hoping to use fetchMap.
Depending on your database and schema meta data, a LEFT JOIN might be a better choice than a predicate in the projection. You should of course verify this in the execution plan.
Solving this with a LEFT JOIN:
-- NVL2 is Oracle syntax.
-- jOOQ will emulate NVL2 using CASE, if it's not available in your database
SELECT c.name, NVL2(cu.capability_id, 1, 0)
FROM a.capabilities c
LEFT OUTER JOIN a.capabilities_users cu
ON (c.id = cu.capability_id
AND cu.user_id = ?)
The above assumes, of course, that there is a unqiue constraint on cu(user_id, capability_id). This would then translate into jOOQ as such:
Capabilities c = CAPABILITIES.as("c");
CapabilitiesUsers cu = CAPABILITIES_USERS.as("cu");
Field<String> key = c.NAME.as("key");
Field<Boolean> value = nvl2(
CAPABILITIES_USER.CAPABILITY_ID, true, false
).as("value");
Map<String, Boolean> map =
DSL.using(configuration)
.select(key, value)
.from(c)
.leftOuterJoin(cu)
.on(c.ID.eq(cu.CAPABILITY_ID))
.and(cu.USER_ID.eq(...))
.fetchMap(key, value);
Solving this with a predicate in the projection:
If you really prefer a predicate in the projection, you might try DSL.field(Condition), which allows for precisely this:
Field<String> key = CAPABILITIES.NAME.as("key");
Field<Boolean> value = field(
CAPABILITIES.ID.in(
select(CAPABILITY_ID)
.from(CAPABILITIES_USERS)
.where(CAPABILITIES_USERS.USER_ID.eq(...))
)
).as("value");
Map<String, Boolean> map =
DSL.using(configuration)
.select(key, value)
.from(CAPABILITIES)
.fetchMap(key, value);
Note that if you're using a standards-compliant database, which doesn't allow for predicates to be treated as columns, DSL.field(Condition) will render an equivalent CASE statement for you.
I have a class that looks like this. I need to populate it from two database tables, which are also shown below. Is there any preferred way to do this?
My thought is to have a service class to select a List<> via a ResultSetExtractor from a DAO. Then do a foreach on that list, and select a List<> of emails for the individual person via another ResultSetExtractor, and attach it from with the foreach loop.
Is there a better way, or is this as good as it gets?
public class Person {
private String personId;
private String Name;
private ArrayList<String> emails;
}
create table Person (
person_id varchar2(10),
name varchar2(30)
);
create table email (
person_id varchar2(10),
email varchar2(30)
);
This is best solved by an ORM. With JDBC, you have to do by hand what an ORM would do for you. Executing N + 1 queries is very inefficient. You should execute a single query, and build your objects manually. Cumbersome, but not hard:
select person.id, person.name, email.email from person person
left join email on person.id = email.person_id
...
Map<Long, Person> personsById = new HashMap<>();
while (rs.next()) {
Long id = rs.getLong("id");
String name = rs.getString("name");
String email = rs.getString("email");
Person person = personsById.get(id);
if (person == null) {
person = new Person(id, name);
personsById.put(person.getId(), person);
}
person.addEmail(email);
}
Collection<Person> persons = personsById.values();
I was looking for something similar, and although the answer is perfectly valid I went with this nice library instead https://simpleflatmapper.org/0203-joins.html
It also integrates perfectly with Spring boot.
main advantage is that you have a clean repository layer, it uses your pojo and makes refactoring much easier, and like hibernate you can still map deep nested and complex one to many and still be in control of what is executed.
It also has a nice jdbctemplate CRUD and Java 13 finally brings support for multi-line string literals which is very good for sql statements readability. hope this helps someone :)
In my case, I had to use the LinkedHashMap to keep the query result ordered by the position field.
From JavaDoc:
LinkedHashMap: "This linked list defines the iteration ordering, which is normally the order in which keys were inserted into the map (insertion-order). Note that insertion order is not affected if a key is re-inserted into the map."
HashMap: "This class makes no guarantees as to the order of the map; in particular, it does not guarantee that the order will remain constant over time".
TIP: using the getOrDefault method eliminates the extra check for nullable object.
public List<BucketDto> findAll() {
var sql = """
SELECT
b.uuid bucket_uuid, b.position bucket_position, b.name bucket_name,
c.uuid card_uuid, c.position card_position, c.name card_name
FROM bucket AS b
LEFT JOIN card AS c ON c.bucket_id = b.id
ORDER BY b.position ASC, c.position ASC
""";
return jdbcTemplate.query(sql, rs -> {
Map<Double, BucketDto> resultMap = new LinkedHashMap<>();
while (rs.next()) {
var position = rs.getDouble("bucket_position");
var bucketDto = resultMap.getOrDefault(position, new BucketDto(
UUID.fromString(rs.getString("bucket_uuid")),
position,
rs.getString("bucket_name")));
if (Optional.ofNullable(rs.getString("card_uuid")).isPresent()) {
bucketDto.addCard(new CardDto(
UUID.fromString(rs.getString("card_uuid")),
rs.getDouble("card_position"),
rs.getString("card_name")));
}
resultMap.put(position, bucketDto);
}
return new ArrayList<>(resultMap.values());
});
}