How pass value with #Param annottaion to a method - spring-data-jpa

I decided move all my jpql queries to another class, for example
public class QueryUtils {
public static final String FIND_All_CLUBS= "select c from Club c order by c.club_name";
}
In repository I have this:
#Query(value = QueryUtils.FIND_All_CLUBS)
Iterable<Club> findAllAndAndOOrderByClub_name();
And it works fine. But I'm stuck with that thing : what if I have params in my jpql, f.e.
#Query(value = "select c from Comment c where c.post.post_id = :id order by c.replyTo.comment_id nulls last")
Iterable<Comment> findAllCommentsOfPost(#Param("id") long id);
I'm tried to write this:
public static String getFIND_ALL_COMMENTS_OF_POST(String id){
return new StringBuilder("select c from Comment c where c.post.post_id = ").append(id).append(" order by c.replyTo.comment_id nulls last").toString();
}
But how pass param from repository to this method, this code(which I tried is not valid):
#Query(QueryUtils.getFIND_ALL_COMMENTS_OF_POST(id))
Iterable<Comment> findAllCommentsOfPost(#Param("id") long id);
Pls, help me!!

Alternative to #Param, try this one
#Query(value = "select c from Comment c where c.post.post_id = ?1 order by c.replyTo.comment_id nulls last")
Iterable<Comment> findAllCommentsOfPost(long id);

Related

Multi-level subquery with JPA CriteriaBuilder

I have the following JPA entities
#Entity
#Table(name="application_user")
public class ApplicationUser {
#Id
#Column(name="user_id")
private String userid;
#Column(name="last_write_time")
private Instant lastWrite;
//other fields omitted
}
#Entity
#Table(name="demographic")
public class Demographic {
#Id
#Column(name="user_id")
private String userid;
//primary key is a foreign key link
#OneToOne
#PrimaryKeyJoinColumn(name="user_id", referencedColumnName="user_id")
private ApplicationUser user;
//other fields omitted
}
My goal is to retrieve all of the Demographics that contains users where the last write time is the max value in the column. I pretty much want to write the following SQL using the JPA CriteriaBUilder
select * from demographic where
userid in (
select userid from application_user where
last_write in (
select max(last_write) from application_user
)
)
I tried writing the following CriteriaBuilder Code to accomplish this goal and it compiles successfully. Note I am using the generated Metamodel classes.
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Demographic> c = cb.createQuery(Demographic.class);
Root<Demographic> root = c.from(Demographic.class);
root.fetch(Demographic_.user, JoinType.INNER);
Subquery<Instant> sqLatestUsers = c.subquery(Instant.class);
Root<ApplicationUser> subRootLatestUsers = sqLatestUsers.from(ApplicationUser.class);
sqLatestUsers.select(cb.greatest(subRootLatestUsers.<Instant>get(ApplicationUser_.LAST_WRITE)));
Predicate predicateLatestUsers = subRootLatestUsers.get(ApplicationUser_.LAST_WRITE).in(sqLatestUsers);
Subquery<ApplicationUser> sq = c.subquery(ApplicationUser.class);
Root<Demographic> subRoot = sq.from(Demographic.class);
sq.select(subRoot.<ApplicationUser>get(Demographic_.USER)).where(predicateLatestUsers);
Predicate containsUsers = subRoot.get(Demographic_.USER).in(sq);
c.select(root).where(containsUsers);
The code compiles and successfully deploys in Wildfly 14, but when I execute the code, the get the following error (with white space to improve readability):
Invalid path: 'generatedAlias2.user' : Invalid path: 'generatedAlias2.user'
...
Caused by: org.hibernate.hql.internal.ast.QuerySyntaxException: Invalid path: 'generatedAlias2.user' [
select generatedAlias0 from com.company.model.Demographic as generatedAlias0
inner join fetch generatedAlias0.user as generatedAlias1
where generatedAlias2.user in (
select generatedAlias2.user from com.company.model.Demographic as generatedAlias2 where generatedAlias3.lastWrite in (
select max(generatedAlias3.lastWrite) from com.company.model.StarfishUser as generatedAlias3
)
)
]
Is chaining subqueries (nested subqueries) allowed by the JPA spec? Did I find something that is syntactically correctly but not actually allowed?
I figure out how to get the subquery to work. First is my updated Utility method
public static <R, T> Subquery<T> getLatestSubelement(CriteriaBuilder cb, CriteriaQuery<R> c, Class<T> clazz, SingularAttribute<T, Instant> attribute) {
//Get latest timestamp
Subquery<Instant> sq = c.subquery(Instant.class);
Root<T> subRoot = sq.from(clazz);
sq.select(cb.greatest(subRoot.<Instant>get(attribute)));
//Get object with the latest timestamp
Subquery<T> sq2 = c.subquery(clazz);
Root<T> subRoot2 = sq2.from(clazz);
sq2.where(subRoot2.get(attribute).in(sq));
return sq2;
}
Here is the code that uses the utility method
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Demographic> c = cb.createQuery(Demographic.class);
Root<Demographic> root = c.from(Demographic.class);
joinType = JoinType.INNER;
//use fetch instead of join to prevent duplicates in Lists
root.fetch(Demographic_.user, joinType);
Subquery<ApplicationUser> sq = JpaUtil.getLatestSubelement(cb, c, ApplicationUser.class, ApplicationUser_.lastWrite);
c.where(root.get(Demographic_.user).in(sq));
TypedQuery<Demographic> q = em.createQuery(c);
Stream<Demographic> stream = q.getResultStream();

How to query #ElementCollection HashMap

I have an entity with different fields:
#Entity
public class TestEntity {
private int id;
private String name;
private String description;
#ElementCollection
private Map<String, String> parameter = new HashMap<>();
}
The resulting tables are the following:
TestEntity(id, name, description)
TestEntity_parameter(TestEntity_id, parameter, parameter_KEY)
Now I want to create a named query for this TestEntity that checks if there exists a parameter_KEY of value "test" and with a parameter :parameter.
I tried something like this:
select te from TestEntity te join TestEntity_parameter tep where tep.parameter_KEY = test AND tep.parameter = :parameter
But when I try to deploy, I get an error.
I'm relatively new to hibernate and java ee. Maybe my approach is wrong but I did not find anything how to access the fields of a map with a named query since it creates a new table for that map. So i thought that I need to join those tables.
Hope you guys can help me :)
Thanks a lot :)
Greetings
Simon
You could use the below query.
SELECT te FROM TestEntity te INNER JOIN te.parameter p WHERE KEY(p) = :YOUR_KEY
AND VALUE(p) = :YOUR_VALUE

How to map ALL names directly by JPA?

Given a ZIP-code-like hierarchical code/name schema.
For example:
code = 101010
Code:
100000 level 1 code (10....)
101000 level 2 code (..10..)
101010 level 3 code (....10)
Name (short name)
100000 - A
101000 - a
101010 - i
Name (FullQualifiedName)
100000 - A
101000 - A->a
101010 - A-a->i
EDIT
I wanna following code (JPA pseudo code), but CANNOT.
#Entity
public class CodeName{
// ....
String code; // 100101 levels = {100000, 100100, 100101}
String name; //
#HowToMapDirectedToNameOfCode('100000') // #SecondTable ?
String name1;
#HowToMapDirectedToNameOfCode('100100')
String name2;
#HowToMapDirectedToNameOfCode('100101')
String name3;
String getFullQualifiedName(){
return String.format("%s->%s->%s", name1, name2, name3);
}
// getter and setter
}
But it's relatively easier in native SQL:
SELECT (select p1.name from codename p1 where p1.code= concat( substring(p.code,1,2), "0000") ) province,
(select p2.name from codename p2 where p2.code= concat( substring(p.code,1,4), "00") ) city,
(select p3.name from codename p3 where p3.code=p.code) area
FROM codename p WHERE p.code = '100101';
So, I implements it as following snippet.
#Entity
public class CodeName{
// ....
String code; // 100000, 101000, 100101
String name; // province, city , area
#Transient
String name1; // mapping directly?
#Transient
String name2; // mapping directly?
#Transient
String name3; // mapping directly?
String getFullQualifiedName(){
return String.format("%s->%s->%s", name1, name2, name3);
}
// getter and setter
}
public interface CodeNameRepository extends CrudRepository<CodeName, Long>, CodeNameRepositoryCustom {
#Query(" FROM CodeName p " +
" WHERE p.code = CONCAT(SUBSTRING(?1, 1, 2), '0000') " +
" OR p.code = CONCAT(SUBSTRING(?1, 1, 4), '00') " +
" OR p.code = ?1")
List<CodeName> findAllLevelsByCode(String code);
}
#Component
public class CodeNameRepositoryImpl implements CodeNameRepositoryCustom {
#Autowired
private CodeNameRepository codeNameRepository ;
#Override
public CodeName CodeNamefindFullQualifiedNameByCode(String code) {
List<CodeName> codeNames= codeNameRepository .findAllLevelsByCode(code);
CodeName codeName;
// extra name1, name2, name3 from list,
// fill code, name, name1, name2, name3 to codeName and
return codeName;
}
}
But it have SO MANY limitations.
Most likely, I need getFullQualifiedName(), to display it on UI, but every time I must have an extra call to populate all names.
For each entity has CodeName as its children, no matter how deep the codeName is at, I MUST expand to the codeName and reload it with FQN.
Can we mapping all #Transient names directly by JPA?
You could technically model your code repository entity as follows:
public class CodeName {
#Id
#GeneratedValue(GenerationStrategy.AUTO)
#Column
private Long id;
#ManyToOne
private CodeName parent;
#OneToMany(mappedBy = "parent")
private List<CodeName> children;
#Column
private String name;
#Transient
public String getFullyQualifiedName() {
List<String> names = new ArrayList<>();
names.add(name);
CodeName theParent = parent;
while(theParent != null) {
names.add(theParent.getName());
theParent = theParent.parent;
}
Collections.reverse(names);
return StringUtils.join(names, "->");
}
}
Because the parent relationships will be fetched EAGERLY because they mapped as #ManyToOne, you can basically start at any child CodeName entity and traverse up it's parent/child relationship to the root. This basically allows the getFullyQualifiedName method to build the name for you at runtime.
If performance becomes a problem doing this, you can always datamine the names ahead of time in your entity as you described by adding a #Column private String fullyQualifiedName and make sure that field is inserted when you create your codes. Then the transient method I added to my the entity can be dropped since you're caching the names at data insertion.
It is possible to write a JPQL, which is equivalent to your SQL query. The only tricky part is to rewrite nested selects into cross joins, because nested selects are not supported by JPA and you need to join unrelated entities. On the other hand, functions CONCAT and SUBSTRING are supported by JPQL in the same way as in SQL. See the following JPQL query, which should give you the results as the SQL query in the question:
SELECT p1.name // province
, p2.name // city
, p.name // area
FROM CodeName p, CodeName p1, CodeName p2
WHERE p.code = '100101'
AND p1.code = concat( substring(p.code,1,2), "0000")
AND p2.code= concat( substring(p.code,1,4), "00")
The above query will give you 3 values in one row, which cannot be mapped into a single entity. The result of the query will therefore be a list of Object[] arrays. You may also add the original entity into the select clause: SELECT p1.name, p2.name, p.name, p FROM .... This way, you may later process the list of results and assign first three values into the transient fields of the entity:
Object[] rows = query.getResultList();
for (Object row : rows) {
CodeName c = (CodeName)row[3];
c.setName1((String)row[0]);
c.setName2((String)row[1]);
c.setName3((String)row[2]);
}

How to do a search with multiple ElementCollections

Given the following entity:
#Entity
#Table(name = "subscription")
public class Subscription implements Serializable {
private static final long serialVersionUID = 1L;
#ElementCollection
#CollectionTable(joinColumns= #JoinColumn(name="subscription"))
private Set<Code> mainCodes = new HashSet<>();
#ElementCollection
#CollectionTable(joinColumns= #JoinColumn(name="subscription"))
private Set<Code> otherCodes = new HashSet<>();
}
So a Subscription can have zero or more mainCodes or otherCodes it's interested in. I can get hold of the mainCode and otherCode of a certain object that passes by. The codes themselves are embeddables with only single String fields.
How do I create a JPA Query (or CriteriaBuilder) which searches in these collections with an "OR" mechanism?
So basically I'm looking for a query like this:
select s from subscription s where :myMainCode IN s.mainCodes OR :otherCode IN s.otherCodes
Is something like this doable with CriteriaBuilder or do I need to use a more explicit query? If so, what does the query look like?
EDIT: Tried this with CriteriaBuilder:
final CriteriaBuilder cb = this.entityManager.getCriteriaBuilder();
final CriteriaQuery<Subscription> cq = cb.createQuery(Subscription.class);
final Root<Subscription> root = cq.from(Subscription.class);
final Expression<Collection<Code>> mainCodes = root.get("mainCodes");
final Predicate containsMainCode = cb.isMember(obj.getClassCode(), mainCodes);
final Expression<Collection<Code>> otherCodes = root.get("otherCodes");
final Predicate containsOtherCode = cb.isMember(obj.getOtherCode(), otherCodes);
final Predicate searchPredicate = cb.or(containsMainCode, containsOtherCode);
cq.select(root).where(searchPredicate);
However, this creates an inner join of both collections involved, meaning that it will return no results if there is a row for mainCode, but not for otherCode in the database, it generates this query:
SELECT t0.ID
FROM Subscription_OTHERCODES t2, Subscription_MAINCODES t1, subscription t0
WHERE ((t1.CODESYSTEM = ?) AND (t1.CODE = ?)) OR ((t2.CODESYSTEM = ?) AND (t2.CODE = ?))) AND ((t1.subscription = t0.ID) AND (t2.subscription = t0.ID))
So even if it finds a matching mainCode, it fails if it doesn't have any otherCode.
It is other way around that in your example.
For example if the code has name property):
select s from Subscription s left join s.mainCodes m left join s.otherCodes o
where m.name IN :myMainCode or o.name IN :myOtherCode

How to call Named Query

I wrote a named query in the entity class Voter
NamedQuery(name = "Voter.findvoter", query = "SELECT count(*) FROM Voter v WHERE v.voterID = :voterID" and where v.password= : password),
I want to call this named query and I also need to set voterID and password.
Can you help me. Thank you
I assume you've missed the # symbol on your NamedQuery annotation?
In the code, you'd call it like this:
List results = em.createNamedQuery("Voter.findvoter")
.setParameter("voterID", "blah")
.setParameter("password","blahblahblah")
.getResultList();
There are two obvious issues with your named query that would cause a problems:
It is an annotation so it should be #NamedQuery not just NamedQuery
Your query is currently:
query = "SELECT count(*) FROM Voter v WHERE v.voterID = :voterID" and where v.password= : password.
The problem is that you terminate your String after :voterID, instead of after :password and you have "where" twice and you have a space between ":" and "password". Your query should look like this:
query = "SELECT count(*) FROM Voter v WHERE v.voterID = :voterID and v.password= :password"
(I have just moved the " to the end and removed the second "where" and the space after the ":")
The common steps are (named query or otherwise)
Create a query - em has five create methods.
Set the query up with parameters if needed - the query interface has these methods.
Execute the query - the query interface has 3 execution related methods.
with the above three steps you can run any JPA query.
Actually brent is right your NameQuery should be something like this,
#NamedQuery(name = "Voter.findvoter", query = "SELECT count(*) FROM Voter v WHERE v.voterID = :voterID AND where v.password = :password")
#Entity
public class Voter implements Serializable{ ... }
and somewhere else you should try this one (which Dick has already said)
public class VoterFasade{
public List<Voter> findVoter(long id,String password){
List<Voter> results = em.createNamedQuery("Voter.findvoter")
.setParameter("voterID", id)
.setParameter("password",password)
.getResultList();
return result;
}
}
then you could use it like
#Inject
VoterFasade voterFasade;
///
long id=12;
voterFasade.findVoter(id);
should actually working.(its an uncompiled code).
you could also do it with Repository, check the link below, part Repository Listing23.Example repository
enter link description here