#Query annotation not working in JPA Repository - jpa

#Query(value = "SELECT * FROM MEMBER m " +
"LEFT JOIN COMPANY_TYPE c ON c.TYPE_CODE = m.TYPE_CODE " +
"LEFT JOIN COMPANY_LOCATION l on l.LOCATION_CODE = m.LOCATION_CODE " +
"WHERE l.LOCATION_CODE = :location AND c.TYPE_CODE = :type", nativeQuery = true)
List<Member> findByOption(#Param("type")Long companyType, #Param("location")Long companyLocation);
This is the method I'm trying to use in MemberRepository interface that extends JPARepository<Member, Long>.
#ManyToOne
#JoinColumn(name = "TYPE_CODE")
private CompanyType company_Type;
#ManyToOne
#JoinColumn(name = "LOCATION_CODE")
private CompanyLocation company_Location;
And this is part of entity class Member.
I am trying to get filtered list of members according to the company type and location code(id). However, the slice test results always return all of members.
I want to know if there are other settings that needs to be set in order for the #Query annotation to work, and if the native query I put in the annotation is wrong.
Any other ideas of how to get filtered results in Spring Data JPA is also welcomed.

This is a very subtle mistake, but rather than using SELECT * you should be using SELECT m.*:
Query(value = "SELECT m.* FROM MEMBER m " +
"LEFT JOIN COMPANY_TYPE c ON c.TYPE_CODE = m.TYPE_CODE " +
"LEFT JOIN COMPANY_LOCATION l on l.LOCATION_CODE = m.LOCATION_CODE " +
"WHERE l.LOCATION_CODE = :location AND c.TYPE_CODE = :type", nativeQuery = true)
List<Member> findByOption(#Param("type")Long companyType, #Param("location")Long companyLocation);
The reason for this is that SELECT m.* returns only the columns from the Member entity/table. SELECT * instructs JPA to return columns from all tables in the query. You would need a method return signature of List<Object[]> in that case.

Related

dynamically choose columns in select with R2DB2

I have been trying to pass the columns I want to select in but out of the box it appears it is not possible. I have tried things like
#Query("SELECT :columns FROM USERS u WHERE u.LOCALE = :locale AND u.id IN (:ids)")
Flux<Users> retrieveExportData(#Param("columns") String columns,
#Param("locale") String locale,
#Param("ids") String[] ids);
and with
private final R2dbcEntityTemplate template;
I tried to create my own query and but that was not working because it has to be of type Criteria and that was just creating a complexity that just was not worth it.
It would be nice if I could add the columns like
criteriaList.add(
Criteria.where("LOCALE").is(locale)
);
Criteria criteria = Criteria.from(criteriaList);
and execute it like
Flux<Users> users = this.template.select(User.class)
.matching(Query.query(criteria))
.all();
or just calling the repository like in my first example.
Has anyone been able to do this successfully?
----- update 1 -----
I tried doing like so:
import org.springframework.r2dbc.core.DatabaseClient;
DatabaseClient databaseClient = DatabaseClient.create(connectionFactory);
String sql = "SELECT " + columns + " FROM USERS u WHERE u.LOCALE=" + locale + " AND u.id IN (" + ids + ")";
return databaseClient.sql(sql)
.fetch()
.all().cast(User.class);
but Since Spring 4.3.6.RELEASE, LinkedCaseInsensitiveMap doesn't extend LinkedHashMap and HashMap, but only implements Map interface.
This results in a
Cannot cast org.springframework.util.LinkedCaseInsensitiveMap to User at java.base/java.lang.Class.cast
error.
I then tried the jooq approach suggested in the answers but it just produces syntax errors. Example
private final DSLContext ctx = DSL.using(connectionFactory);
private final Users users = ctx.newRecord(Users.USERS); <-- USERS not found
#Query("SELECT :columns FROM USERS u WHERE u.LOCALE = :locale AND u.id IN (:ids)")
public Flux<Users> retrieveExportData(
List<Field<?>> columns,
String locale,
String[] ids
) {
return Flux.from(ctx
.select(columns)
.from("USERS")
.where(users.LOCALE.eq(locale)) <--- LOCALE not found
.and(users.ID.in(ids)) <--- ID not found
).map(r -> r.into(Users.class)); <---- into not found
}
the library look promising. I will try to get it working.
You cannot replace a bind parameter (:columns) by syntactic elements like this, other than actual bind values. For this type of dynamic SQL, you'll have to resort to some sort of query building mechanism.
Perhaps look at jOOQ, which has R2DBC support? Your implementation would then look like this:
#Query("SELECT :columns FROM USERS u WHERE u.LOCALE = :locale AND u.id IN (:ids)")
public Flux<Users> retrieveExportData(
List<Field<?>> columns,
String locale,
String[] ids
) {
return Flux.from(ctx
.select(columns)
.from(USERS)
.where(USERS.LOCALE.eq(locale))
.and(USERS.ID.in(ids))
).map(r -> r.into(Users.class));
}
Disclaimer: I work for the company behind jOOQ.
you can write dynamic SQL like so.
import org.springframework.r2dbc.core.DatabaseClient;
DatabaseClient databaseClient = DatabaseClient.create(connectionFactory);
String sql = "SELECT " + columns + " FROM USERS u WHERE u.LOCALE=" + locale + " AND u.id IN (" + ids + ")";
return databaseClient.sql(sql)
.fetch()
.all().cast(User.class);
and you will need this dependency
implementation "org.springframework.boot:spring-boot-starter-data-r2dbc:2.6.2"

javax.persistence.NamedQuery is not a repeatable annotation type

Normally in Java 8 the #NamedQuery is repeatable.
Nevertheless when I compile I have the error:
javax.persistence.NamedQuery is not a repeatable annotation type
Here is my source code:
#NamedQuery(name = "listDocumentsByStatus", query = "FROM Document d WHERE d.status = :STATUS ")
#NamedQuery(name = "listDocumentsByNameAndType", query = "FROM Document d WHERE d.type = :TYPE AND UPPER(d.name) LIKE :NAME ")
public abstract class Document implements Serializable {
...
}
Do I misunderstood someething?
Please try this
#Entity
#NamedQueries(#NamedQuery(name = "findAllExtensionsSorted", query = "select e from Abc e order by e.c")
#NamedQuery(name = "findByType", query = "select e from Abc e where e.t = :type"))
public class Abc...

Making raw SQL safe in Entity Framework

var retval = db.TestTable.SqlQuery("SELECT * FROM dbo.TestTable WHERE " + aColumn + " = '" + passedInValue + "'");
// normally when using parameters I would do something like this:
var valueParam = SqlParameter("aValue", passedInValues);
var retval = db.TestTable.SqlQuery("SELECT * FROM dbo.TestTable WHERE Column1 = #aValue", valueParam);
// NOTE: I would not do this at all. I know to use LINQ. But for this question, I'm concentrating on the issue of passing variables to a raw sql string.
But since both the column and value are "parameters" in:
var retval = db.TestTable.SqlQuery("SELECT * FROM dbo.TestTable WHERE " + aColumn + " = '" + passedInValue + "'");
, is there to prevent sql injection for both?
First: whilelist aColumn: this has to be added via string concatenation but you know what columns are in your database (or you can check using schema views).
Second: In entity framework – as you show – you can use parameters for values in the query. However, rather than creating SqlParameter instances you can pass the values and use #p0, #p1, ….
Right way to prevent SQL injection is to use SqlParameter and SqlQuery<T>:
var parameter = new SqlParameter("#title", value);
var result = context.Database.SqlQuery<Book>("SELECT * FROM Books WHERE Title LIKE #title", parameter);
http://ignoringthevoices.blogspot.ru/2013/07/sql-injection-with-entity-framework-5.html

JPA Criteria api query fails while JPQL goes through

I'm using JPA to manage my persistency layer.
One of my my Criteria API throws an exception. I re-wrote it in JPQL and it works just fine so I guess I missed something in my criteria api version.
Here it is, my criteria api query:
public FoodItemTagsOverrideRule findByFoodItemIdAndType(long foodItemId, RuleTypes ruleType) {
CriteriaQuery<Rule> c = getCriteriaQuery();
Root<Rule> rule =
c.from(Rule.class);
Predicate foodItemIdCondition =
cb.equal(rule.get(Rule_.foodItemId), foodItemId);
Predicate typeCondition =
cb.equal(rule.get(Rule_.ruleType),
ruleType.toString());
c.where(foodItemIdCondition, typeCondition);
TypedQuery<Rule> q =
entityManager.createQuery(c);
List<Rule> result = q.getResultList();
if (result.isEmpty()) {
return null;
}
return result.get(0);
}
The JPQL version that works just fine:
public Rule findByFoodItemIdAndType(long foodItemId, RuleTypes ruleType) {
TypedQuery<Rule> query = getEntityManager().createQuery(
"SELECT rule " + "FROM " + Rule.class.getSimpleName() + " rule " + "WHERE rule.foodItemId = :foodItemId "
+ "AND rule.ruleType = :ruleType", Rule.class);
query.setParameter("foodItemId", foodItemId);
query.setParameter("ruleType", ruleType.toString());
List<Rule> result = query.getResultList();
if (result.isEmpty()) {
return null;
}
return result.get(0);
}
Can you see a difference there? Did I put something wrong in the criteria api query?
Thakns!
You can try the below code. I tried to fit to your code, you can alter accordingly.
CriteriaBuilder qb = em.getCriteriaBuilder();
CriteriaQuery cq = qb.createQuery();
Root<Rule> rule = cq.from(Rule.class);
List<Predicate> predicates = new ArrayList<Predicate>();
predicates.add(qb.equal(rule.get("foodItemId"), foodItemId));
predicates.add(qb.equal(rule.get("ruleType"), ruleType));
cq.select(rule).where(predicates.toArray(new Predicate[]{}));
em.createQuery(cq).getResultList();
Edit : For type-safe predicate definition
predicates.add(qb.equal(rule.get(Rule_.foodItemId), foodItemId));
predicates.add(qb.equal(rule.get(Rule_.ruleType), ruleType));

JPA criteria: count from multiselect query

I want to implement a table component with pagination. The result in the table is retrieved by a multiselect-query like this:
SELECT DISTINCT t0.userId,
t0.userName,
t1.rolleName
FROM userTable t0
LEFT OUTER JOIN roleTable t1 ON t0.userId = t1.fkUser
WHERE(t0.userType = 'normalUser' AND t1.roleType = 'loginRole')
This result I can get via a multiselect-query.
Now for the pagination I have to retrieve the total rowcount at first.
Is there anybody who can define a criteriaquery for one of this sql? I failed because a subquery does not support multiselects and I do not know how to get this distinct into a count statement.
SELECT COUNT(*) FROM
(
SELECT DISTINCT t0.userId,
t0.userName,
t1.rolleName
FROM userTable t0
LEFT OUTER JOIN roleTable t1 ON t0.userId = t1.fkUser
WHERE(t0.userType = 'normalUser' AND t1.roleType = 'loginRole')
)
or
SELECT COUNT(DISTINCT t0.userId || t0.userName || t1.rolleName)
FROM userTable t0
LEFT OUTER JOIN roleTable t1 ON t0.userId = t1.fkUser
WHERE(t0.userType = 'normalUser' AND t1.roleType = 'loginRole')
Thanks in advance!
Btw. I am using OpenJpa on a WebSphere AppServer
The following is not tested but should work:
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Long> query = builder.createQuery(Long.class);
Root<User> t0 = query.from(User.class);
Join<User, Role> t1 = t0.join("roles", JoinType.LEFT);
query.select(builder.concat(t0.get(User_.userId), builder.concat(t0.get(User_.userName), t1.get(Role_.rolleName))).distinct(true);
query.where(cb.equal(t0.get("userType"), "normalUser"), cb.equal(t1.get("roleType"), "loginRole"));
TypedQuery<Long> tq = em.createQuery(query);
Due to known issue https://jira.spring.io/browse/DATAJPA-1532 Multiselect does not work with repo.findall method. I handled this by autowiring entity manager to service class.
#Autowired
EntityManager entityManager;
public List<?> getResults() throws ParseException
{
//ModelSpecification modelSpecification = new ModelSpecification();
CriteriaQuery<DAO> query = modelSpecification.getSpecQuery();
TypedQuery<DAO> typedQuery = entityManager.createQuery(query);
List<?> resultList = typedQuery.getResultList();
//List<DAO> allData = entityManager.createQuery(query).getResultList();
return resultList;
}
You can find working code here https://github.com/bsridharpatnaik/CriteriaMultiselectGroupBy