I have a formula defines as below in one of the entity.
#ElementCollection
#Formula(
"(select concat_ws(' ', jnt.name, jnt.locale) as da from translations jnt where
jnt.id=internalId)")
private List<String> data;
OR
#ElementCollection
#Formula(
"(select jnt.name, jnt.locale from translations jnt where
jnt.id=internalId)")
private List<Object> data;
after defining any of the above formulas i am getting below exception while executing the query.
java.lang.IllegalArgumentException: org.hibernate.QueryException: not an entity
But, When try with below code in the place of above snippet. It works without any exceptions.
#Formula("(select jnt.name from translations jnt where jnt.id=1)")
private String data;
Can anyone help me what is wrong with the initial use of formula?
I think I see what you're trying to do, but you can't use #Formula and #ElementCollection together in this way.
What you could try is: treat this as a basic-typed mapping, with a #Formula that evaluates to a String, and then write an AttributeConverter that parses that string to a List.
Related
I have two entities Questions and UserAnswers. I need to make an api in spring boot which returns all the columns from both the entities based on some conditions.
Conditions are:
I will be give a comparator eg: >, <, =, >=, <=
A column name eg: last_answered_at, last_seen_at
A value of the above column eg: 28-09-2020 06:00:18
I will need to return an inner join of the two entities and filter based on the above conditions.
Sample sql query based on above conditions will be like:
SELECT q,ua from questions q INNER JOIN
user_answers ua on q.id = ua.question_id
WHERE ua.last_answered_at > 28-09-2020 06:00:18
The problem I am facing is that the column name and the comparator for the query needs to be dynamic.
Is there an efficient way to do this using spring boot and JPA as I do not want to make jpa query methods for all possible combinations of columns and operators as it can be a very large number and there will be extensive use of if else?
I have developed a library called spring-dynamic-jpa to make it easier to implement dynamic queries with JPA.
You can use it to write the query templates. The query template will be built into different query strings before execution depending on your parameters when you invoke the method.
This sounds like a clear custom implementation of a repository method. Firstly, I will make some assumptions about the implementation of your entities. Afterwards, I will present an idea on how to solve your challenge.
I assume that the entities look basically like this (getters, setters, equals, hachCode... ignored).
#Entity
#Table(name = "questions")
public class Question {
#Id
#GeneratedValue
private Long id;
private LocalDateTime lastAnsweredAt;
private LocalDateTime lastSeenAt;
// other attributes you mentioned...
#OneToMany(mappedBy = "question", cascade = CascadeType.ALL, orphanRemoval = true)
private List<UserAnswer> userAnswers = new ArrayList();
// Add and remove methods added to keep bidirectional relationship synchronised
public void addUserAnswer(UserAnswer userAnswer) {
userAnswers.add(userAnswer);
userAnswer.setQuestion(this);
}
public void removeUserAnswer(UserAnswer userAnswer) {
userAnswers.remove(userAnswer);
userAnswer.setQuestion(null);
}
}
#Entity
#Table(name = "user_answers")
public class UserAnswer {
#Id
#GeneratedValue
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "task_release_id")
private Question question;
}
I will write the code with the knowledge about the JPA of Hibernate. For other JPAs, it might work similarly or the same.
Hibernate often needs the name of attributes as a String. To circumvent the issue of undetected mistakes (especially when refactoring), I suggest the module hibernate-jpamodelgen (see the class names suffixed with an underscore). You can also use it to pass the names of the attributes as arguments to your repository method.
Repository methods try to communicate with the database. In JPA, there are different ways of implementing database requests: JPQL as a query language and the Criteria API (easier to refactor, less error prone). As I am a fan of the Criteria API, I will use the Criteria API together with the modelgen to tell the ORM Hibernate to talk to the database to retrieve the relevant objects.
public class QuestionRepositoryCustomImpl implements QuestionRepository {
#PersistenceContext
private EntityManager entityManager;
#Override
public List<Question> dynamicFind(String comparator, String attribute, String value) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Question> cq = cb.createQuery(Question.class);
// Root gets constructed for first, main class in the request (see return of method)
Root<Question> root = cq.from(Question.class);
// Join happens based on respective attribute within root
root.join(Question_.USER_ANSWER);
// The following ifs are not the nicest solution.
// The ifs check what comparator String contains and adds respective where clause to query
// This .where() is like WHERE in SQL
if("==".equals(comparator)) {
cq.where(cb.equal(root.get(attribute), value));
}
if(">".equals(comparator)) {
cq.where(cb.gt(root.get(attribute), value));
}
if(">=".equals(comparator)) {
cq.where(cb.ge(root.get(attribute), value));
}
if("<".equals(comparator)) {
cq.where(cb.lt(root.get(attribute), value));
}
if("<=".equals(comparator)) {
cq.where(cb.le(root.get(attribute), value));
}
// Finally, query gets created and result collected and returned as List
// Hint for READ_ONLY is added as lists are often just for read and performance is better.
return entityManager.createQuery(cq).setHint(QueryHints.READ_ONLY, true).getResultList();
}
}
I am trying to write a method in spring data to get last recod by text and ordered by updateDate.
My entity look like :
#Entity
public class Command {
#Id
#GeneratedValue
private Long id;
#Column(nullable = false)
private String text;
// epoch date
private Long updateDate;
/* Getters and Setters */
}
And this is the repository:
public interface CommandRepository extends JpaRepository<Command, Long> {
Command findByTextAndTopByUpdateDateDesc(String text);
}
Of course, find by text method must give me more thant one reocord but by filtering with top date will get only one record !
the methode above return the following error :
Caused by:
org.springframework.data.mapping.PropertyReferenceException: No
property TopByUpdateDateDesc found for type Command! at
org.springframework.data.mapping.PropertyPath.(PropertyPath.java:94)
I tried to check some stackoverflow postes to find any solution but nada !
Is there any solution to that case or i have to use native query ?
The ordering in your query method is wrong. You first need the limitations (Top/First) before you do the where part and you end with the ordering. This is also what the error, given a bit cryptic, is telling you.
Instead of findByTextAndTopByUpdateDateDesc you should use something like findFirstByTextOrderByUpdateDateDesc.
The ordering etc. is explained in the Spring Data JPA documentation.
I have three entities.
(I am using xxx as a place holder for this example)
I have set up all their #Entities with
#Entity
#Table(name = "xxx")
public class xxx {
#Id
#Column(name = "xxx_id")
int xxx_id;
However, I am able to do:
findById(int ...) for only ONE of the entities in their respective Repository.
When I try to do that for the other two entities, I get this problem:
"Invalid derived query! No property id found for type xxx!"
I am able to run it and get the proper results. But why am I getting this error for two entities but not the other?
I set up the entities and their repositories exactly the same way.
The ONLY difference is that in the entity whose repository does not return any errors, I am joining that entity with another entity whose repository fails, but I used "mappedBy" in the #JoinColumns section.
Could that have something to do with why that one has no problems?
How findBy... works?
Entity:
private int clientid;
private String firstname;
private String lastname;
Query:
findBy<Name_as_per_entity>
findByClientid(int clientid);
findByFirstnameAndLastname(String firstname, String lastname)
Solution
Because we treat the underscore character as a reserved character, we
strongly advise following standard Java naming conventions (that is,
not using underscores in property names but using camel case instead).
Doc
The underscore _ is a reserved character in Spring Data query derivation to potentially allow manual property path description.
Stick to the Java naming conventions of using camel-case for member variable names and everything will work as expected.
Also Refer this
My Data model is
#Getter
#Setter
public class Customer {
#Id private ID id;
#CreatedDate protected Instant createdAt;
#LastModifiedDate protected Instant updatedAt;
#CreatedBy protected String createdBy;
#LastModifiedBy protected String updatedBy;
#Version protected Long version;
private UUID orderId;
private String offer;
}
My Repository is
public interface CustomerRepository extends CrudRepository<Customer, UUID> {
#Query(
"SELECT ID, Offer FROM Customer WHERE orderId = :orderId ")
List<Customer> findCustomerByOrderId(
#Param("orderId") UUID orderId);
}
This will result in an exception saying 'orderId column not found [42122-190]'. So Spring expects you to always query all the columns. I understand that with JPA we have a strong mapping between the Entities and the Data Schema. But the whole point of spring data JDBC is avoiding the tight coupling between POJO's data model and database schema. Why not the EntityRowMapper is just mapping NULL to the properties which are not part of the query?
Is there a way to tell the RowMapper used, to ignore properties which are not part of the query? Creating separate RowMapper for these simple queries seems a lot of unnecessary work.
I still can work around this by changing the query like
#Query(
"SELECT ID, Offer, OrderId, null as CreatedAt, null as CreatedBy, null as UpdatedAt, null as UpdatedBy, null as Version FROM Customer WHERE orderId = :orderId ")
But this will still serialize the entire object with null values. Am I missing something obvious here?
Note This is not Spring Data JPA. Its Spring Data JDBC.
Edit
Looking more into it, the exception is from h2 database lib.
Caused by: org.h2.jdbc.JdbcSQLException: Column "orderid" not found [42122-190]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:345)
at org.h2.message.DbException.get(DbException.java:179)
at org.h2.message.DbException.get(DbException.java:155)
at org.h2.jdbc.JdbcResultSet.getColumnIndex(JdbcResultSet.java:3129)
at org.h2.jdbc.JdbcResultSet.get(JdbcResultSet.java:3217)
at org.h2.jdbc.JdbcResultSet.getObject(JdbcResultSet.java:522)
at com.zaxxer.hikari.pool.HikariProxyResultSet.getObject(HikariProxyResultSet.java)
at org.springframework.data.jdbc.core.EntityRowMapper.readFrom(EntityRowMapper.java:127)
You can't at least right now.
There are three solutions to this, two of which you already pointed out:
extend your select statement with , NULL as <column-name> for all the missing columns.
I'm not sure if
But this will still serialize the entire object with null values.
means that this isn't working for you in some way.
specify a RowMapper.
You could use a class containing exactly the fields returned by the query. It could even have getters for the other columns if you want an interface implemented by both your normal entity and the partial entity.
You write:
But the whole point of spring data JDBC is to avoid the tight coupling between pojo's data model and database schema.
This is not quite right.
An important goal of Spring Data JDBC is to not have a run time connection between entities and table rows.
This would require proxies or similar and brings a lot of complexity.
But the structural mapping between entities and table is probably going to be stronger (and certainly is right now) since all the variants of mappings available in JPA bring complexity.
And the main goal in Spring Data JDBC is to be conceptually simpler than JPA.
You also ask
Why not the EntityRowMapper is just mapping NULL to the properties which are not part of the query?
I'm not sure if I actively thought about it when I coded it but I don't like the idea of defaulting to NULL because this would make it easy to accidentally not load a column because you have a typo in an alias.
But I'm not against alternative solutions.
If you have an idea please create a feature request.
Is it possible to use an Array object as a parameter in Spring Repository #Query annotation?
I'm trying to retrieve all rows in a table whose column node is present in an String array. Is it possible to do it at a time using the #Query annotation in Spring repository?
Here is my Location Entity:
#Entity
#Table(name = "LOCATIONS")
public class Location extends Measurement{
private String latitude;
private String nsIndicator;
private String longitude;
private String ewIndicator;
#ManyToOne
#JoinColumn(name="node")
private Node node;
}
Where node references the Node class, and it is mapped in the database as a BIGINT.
I have a repository like this:
public interface LocationRepository extends CrudRepository<Location, Long>{
#Query(value=
"SELECT l1.node, l1.id, l1.latitude, l1.longitude " +
"FROM LOCATIONS l1 WHERE l1.node IN (:ids)",
nativeQuery=true)
List<Location> findMeasureByIds(#Param("ids") String[] ids);
}
There you can see the query that I'm trying to execute, but it's not working. I don't know if it's possible to use an array there, or parameters must be just Strings and/or Integers, I couldn't find it anywhere.
I've tried several combinations like using a simple String with the right format or a long array.. but nothing has worked so far.
Thanks in advance.
SOLUTION:
#Query(value="SELECT * FROM LOCATIONS l1 " +
"INNER JOIN (SELECT node, MAX(id) AS id FROM LOCATIONS GROUP BY node) l2 " +
"ON l1.node = l2.node AND l1.id = l2.id " +
"WHERE l1.node IN :ids", nativeQuery=true)
List<Location> findLastLocationByIds(#Param("ids") Set<Long> ids);
I've added more functionality to the query because I needed to retrieve the last row inserted for each node identifier. So there's the MAX function and the INNER JOIN to do that work.
Use a collection instead of an array (Set<String>), and make sure it's not empty (otherwise the query will be invalid.
Also, there's no reason to use a native query for that, and you shouldn't have parentheses around the parameter:
#Query("SELECT l1 FROM Location l1 WHERE l1.node.id IN :ids")
List<Location> findLocationsByNodeIds(#Param("ids") Set<String> ids);