Spring data JPA query behaves unexpectedly - java

I have these entities:
public class Order_status_sas {
private Order_sas order;
private Date lastModified;
...
}
public class Order_sas {
private long id;
...
}
My CrudRepository:
public interface StatusesWareHouseRepository extends CrudRepository<Order_status_sas, Long> {
Order_status_sas findFirstByOrderIdOrderByLastModifiedDesc(long id);
}
I expect that method findFirstByOrderIdOrderByLastModifiedDesc would return first row from table Order_status_sas, where order.id = <some_id> sorted by field lastModified, but in log I see this query:
Hibernate: select ...
from order_status_sas a
left outer join orders_sas b
on a.order_id=b.id
where b.id=?
order by a.last_modified desc
This query does not return me one row, but returns a list of rows. It seems that Spring Data do not look at word First in my method name. Also, I get an Exception:
org.springframework.dao.IncorrectResultSizeDataAccessException:
result returns more than one elements; nested exception is javax.persistence.NonUniqueResultException: result returns more than one elements
Please, tell me what I am doing wrong and how can I achieve my purpose?
EDITED:
I edited my StatusesWareHouseRepository with custom query:
#Query("select s from Order_status_sas s where s.order.id = ?1 order by s.lastModified desc limit 1")
Order_status_sas findFirstByOrderIdOrderByLastModifiedDesc(long id);
but the query, executed by Hibernate, haven't changed. It looks like this:
select ...
from order_status_sas s
where s.order_id=?
order by s.last_modified desc

OK, I understood #PriduNeemre point. Lets leave the DB model and come back to the JPA question. Here is another example:
#Entity
public class Client {
....
}
public interface ClientRepository extends CrudRepository<Client, Integer> {
Client findFirstByOrderByNameDesc();
}
Hibernate query still looks like this:
select ...
from clients c
order by c.name desc

Have you tried adding a #Query annotation (see here) on top of your findFirstByOrderIdOrderByLastModifiedDesc(..) method to specify the expected behaviour by hand? A (non-related) example on how this could work:
public interface InvoiceRepository extends JpaRepository<Invoice, Long> {
#Query("SELECT I FROM Invoice I JOIN I.customer C JOIN C.user U WHERE
U.username = :username")
public List<Invoice> findInvoicesByUsername(#Param("username")
String username);
}
Note that the query language used in the annotation body is in fact JPQL, not SQL. For more examples on the #Query annotation, see the Spring Data docs here.
PS: I'm also having conflicted feelings about your domain object structure, i.e. whether an instance of Order_sas should really be stored in an instance of Order_status_sas - shouldn't it be the other way around? Normally you would want to store the reference objects in your main domain object, not vice versa. (There's a slight possibility that I'm just not getting it right, though.)
EDIT: I would even go as far as to say that considering your current domain model, Hibernate is doing everything right except missing a LIMIT 1 clause to limit the expected resultset to one single row. The query itself is extremely inefficient, though, and could be improved by fixing your skewed domain model.

Related

How can I get the same result in JPA hibernate as querying "select * from table where ..."?

I'm new to ORM interface, and I'm trying to connect to my databases with Hibernate.
What I've figured out so far is:
With a serializable object, I can get a persistent object with
Person p = session.get(Person.class, serializable);
I can get all the objects by a list with
List people = session.createQuery("FROM Person").list();
What I need is to find a row that meets a certain condition, such as SELECT * FROM person WHERE name="Kim" AND age=30;
However, the above two aren't the ways to achieve this.
#Entity
#Table(name = "person")
public class Person {
#Id
private Integer id; // I can use this variable when using session.get(Person.class, serializable) , but I cannot know the id of my target row.
private String name;
private Integer age;
...
Should I iterate all the objects in people, and check whether all the member variables match what I want?
Is there any simple way to achieve this?
First and most importantly, never put user input in a query like this
SELECT * FROM person WHERE name="Kim" AND age=30;
You have to use Prepared Statements. Learn why from Bobby Tables.
Secondly, you should use the JPA interface EntityManager instead of Hibernate's Session as the second one anchors you to a specific implementation, rather than the wider standard.
With the EntityManager you get an object by id like this:
Person p = em.find(Person.class, id);
To get a list of People you can create a JPQL query like this:
TypedQuery<Person> query = em.createQuery("SELECT p FROM Person p WHERE p.name = :name AND p.age = :age", Person.class);
query.setParameter("name", "Kim"); // :param1 defines a parameter named "param1" in the query
query.setParameter("age", 30);
List<Person> results = query.getResultList();
You could also do this in one chain if you don't need to reuse the query with different parameters on a loop.
List<Person> results = em.createQuery(..., Person.class)
.setParameter("name", "Kim")
.setParameter("age", 30)
.getResultList();
The reason to put every call on a new row is in case an exception occurs it will give you the proper row to look for. If they're all in one row, then that's not very useful.
If your query is a SELECT, and it needs to return exactly one result every time, you can use getSingleResult() instead of getResultList(). If you do that and the query did return more than one result, it will throw a NonUniqueResultException. If the query did not return any results it will throw a NoResultException instead of returning null.
If your query is NOT a SELECT, then you have to use executeUpdate() to invoke it after setting the parameters.
There are many resources to get you started, but generally if its for a Hibernate version before 5.2 you should consider it outdated, and it will likely be more difficult.

Select only specific columns from joined tables (Many-to-Many) in Spring Data JPA

The purpose is to select columns from joined tables (Many-to-Many).
The problem i have is to select two columns from a joined Many-to-Many table.
I'am using Springboot 2.3 and Spring data Jpa.
I have this data model, and what i want to fetch are the blue boxed fields
So the native query could look like this (if i am right ...)
SELECT bg.id, bg.name, p.name, c.name, c.short_desc FROM boardgame as bg
JOIN boardgame_category bgc on bg.id = bgc.fk_game
JOIN publisher p on bg.fk_publisher = p.id
JOIN category c on bgc.fk_category = c.id
WHERE bg.id = :id
I first tried to work with dto in JPQL statment
public class BoardgameDto {
private long id;
private String name;
private String publisherName;
private Set<CatregoryDto> categoryDto;
// setter, getter etc...
}
public class CategoryDto {
private String name;
private String shortDesc;
// setter, getter etc...
}
The JQPL query could look like this , but it doesn't work (IDE shows errors on CategoryDto)
/* THIS DOESN'T WORK */
SELECT new org.moto.tryingstuff.dto.BoardgameDto(bg.id, bg.name, p.name,
new org.moto.tryingstuff.dto.CategoryDto(c.name, c.short_desc)) FROM Boardgame as bg, Publisher as p, Category as c
Well, I think the problem I have with this way of doing is that the dto's contructor can't receive a collection as written here, and i think neither another contructor in parameter.
Then i started looking at Criteria Queries, especialy multiselect, Tuple, Dto, but it look like i had same kind of problems so i didn't dive deeper into it.
Finally i used a JpaRepository and it's findById() method like this
public interface BoardgameRepository extends JpaRepository<Boardgame, Long> {
}
// In a test or service method
Boardgame game = repository.findById(long id);
Then i filter the fields i need to keep through mappings in Service or Controller layer. So the front only received need datas.
But it feel a bit overkill,
Am I missing something, any part of the framework that would allow me to select only specific columns?
As you wrote, you can't use a collection as the parameter of a constructor expression. That's because the expression gets applied to each record in the result set. These records are a flat data structure. They don't contain any collections. Your database returns a new record for each element in that collection instead.
But your constructor expression fails for a different reason. You're trying to combine 2 constructor expressions, and that's not supported. You need to remove the 2nd expression and perform that operation within the constructor of your DTO.
So, your query should look like this:
SELECT new org.moto.tryingstuff.dto.BoardgameDto(bg.id, bg.name, p.name, c.name, c.short_desc) FROM Boardgame as bg <Your JOIN CLAUSES HERE>
And the constructor of your BoardgameDto like this:
public class BoardgameDto {
public BoardgameDto(Long id, String gameName, String publisherName, String categoryName, String description) {
this.id = id;
this.name = gameName;
this.publisherName = publisherName;
this.category = new Category(categoryName, description);
}
...
}

Using registerFunction in JPA workaround

I am trying to implement a HQL query in Jpa which uses an MySQL DATE_ADD method to increase data, as apparently Jpa doesn't seem to support native add operator for timestamp.
I read some guides which suggest a class that extends the SQLDialect, and use registerFunction to register an SQL function which can be used in a query specified by that class. However, I want to instead use this SQL function inside my Jpa class instead of separate class. Does anyone know the solution to my problem? This is my code attempt, feel me to ask me for more information.
repository class
public interface CustomerRepository extends JpaRepository<Customer, Long> {
List<Conference> findByCreatorIdentifier(String creatorIdentifier);
#Query("SELECT c.id, FROM Customer c WHERE c.date > current_timestamp() AND c.creatorIdentifier = ?1 ORDER BY c.date")
List<Conference> findByRecentDate(String email);
//unsuccessful SQL query
// #Query("SELECT c.id FROM Customer c WHERE DATE_ADD(c.date, INTERVAL +8 HOUR) > current_timestamp() AND c.creatorIdentifier = ?1 ORDER BY c.date")
// List<Customer> findByRecentDate(String email);
}
Update
The Customer entity class stores a Date attribute as date, and is stored in UTC-time - 8h. I'm trying to get the mostRecentCustomer with reference to the current system time, which is intuitively LocalDateTime.now(), but the query doesn't seem to work too. I'm not sure if I am allowed to change the code's database schema.
registerFunction class
public class MySQLServerDialect extends MySQL5InnoDBDialect {
public MySQLServerDialect() {
registerFunction("conference.DATE_ADD", new SQLFunctionTemplate(StandardBasicTypes.TIMESTAMP, "DATE_ADD"));
// registerFunction("conference.DATE_ADD", new SQLFunctionTemplate(StandardBasicTypes.TIMESTAMP, "DATE_ADD"));
// registerFunction("addhours", new VarArgsSQLFunction(TimestampType.INSTANCE, "dateadd(minute,", ",", ")"));
}
}
You are not writing what the problem is you are facing, but I don't think that using dots in the function name is allowed. Use the following:
registerFunction("DATE_ADD", new SQLFunctionTemplate(StandardBasicTypes.TIMESTAMP, "DATE_ADD"));
Now the problem with MySQL is, that it requires an interval literal which requires a special keyword. There is no way to passthrough keywords from HQL to SQL directly, so you will have to use a special function e.g. DATE_ADD_HOURS:
registerFunction("DATE_ADD_HOURS", new SQLFunctionTemplate(StandardBasicTypes.TIMESTAMP, "DATE_ADD(?1, INTERVAL ?2 HOUR"));
and then use
#Query("SELECT c.id, FROM Customer c WHERE DATE_ADD_HOURS(c.date, 8) > current_timestamp() AND c.creatorIdentifier = ?1 ORDER BY c.date")
List<Conference> findByRecentDate(String email);

How to work with SQL Views in Spring JPA?

I'm currently working on a project with Spring and HANA DB.
I have already an Webservice which is working properly, but I need to select a View from my HANA DB(created outside of the Spring Webservice).
My view is made from 2 tables, 4 of its fields are SUM of one column from one of the tables(which counts how much of each of each 4 possible values and shows that as a number in each of the 4 fields).
I read about in some Spring JPA posts that it should work properly with a simple select, my questions are:
I usually make selects like this:
TypedQuery<name_of_the_model_class> query = em.createQuery("SELECT c FROM NameOfTheTable ORDER BY c.some_field DESC WHERE c.name =?1 ", name_of_the_model_class.class);
query.setParameter(1, name_to_search);
And then after that I just return the list to the Controller and the Controller shows that in a JSON.
The problem is, I have created this View in Eclipse, using directly SQL code in HANA DB, without creating it at the Webservice.
I would like to know, is there a way to select like I showed above, returning it without a model class? It would just need a class with exactly the same fields like a model class but without #Entity annotation on it?
OR is there a way to create a view like a model class in Spring JPA? If there is a way, what are the annotations to make it work?
Spring JPA would not accept me to return it without a class would it?
I read plenty posts but didn't found it, even in the Manual it doesn't give clear specification or examples.
Thanks a lot for your time, if I didn't explained it well pls tell me, I'm really stuck on this.
More info(my latest try):
My entire search function at the DAO:
public List<IncidentStatusResultsUser> getIncidentStatusByUser(String userID) throws ParseException {
List<IncidentStatusResultsUser> resultList = em.createQuery("SELECT c FROM USERSTATUS ORDER BY c.uploadDateTime DESC WHERE c.userID=?1 ").setMaxResults(48).getResultList();
Collections.reverse(resultList);
return resultList;
}
I have created a class to just receive the data with the same fields of the view:
public class IncidentStatusResultsUser {
private Date uploadDateTime ;
private int status1;
private int status2;
private int status3;
private int status4;
//All gets and sets ommited
public IncidentStatusResultsUser (Date uploadDateTime, long status1, long status2, long status3, long status4) {
this.uploadDateTime = uploadDateTime;
this.status1= status1;
this.status2= status2;
this.status3= status3;
this.status4= status4;
}
}
EDIT-2: Added public constructor inside the class.
Best Regards,
Famibica
You should be able to map your view to a JPA object as long as it has a primary key (or composite key). You can then map to a Spring JPA Reposiry so your query would go from -
TypedQuery<name_of_the_model_class> query = em.createQuery("SELECT c FROM NameOfTheTable ORDER BY c.some_field DESC WHERE c.name =?1 ", name_of_the_model_class.class);
query.setParameter(1, name_to_search);
To
findBySomeFieldOrderBySomeFieldDesc(String name_to_search)
If you want JPA returning a list of object directly, you need a DTO just like the IncidentStatusResultsUser you created. make sure you have a constructor taking all fields, something like:
public IncidentStatusResultsUser(Date uploadDateTime, int status1, int status2, int status3, int status4)
Then in your query String dtoQuery, you can do:
SELECT new IncidentStatusResultsUser(c.uploadDateTime, c.status1,c.status2,c.status3,c.status4)
FROM NameOfTheTable
ORDER BY c.some_field DESC
WHERE c.name =?1
Then you should be able to call your stuff:
List<IncidentStatusResultsUser> resultList = em.createQuery(dtoQuery).setMaxResults(48).getResultList();
Hope it works for you:)

Spring Data JPA: How can Query return Non- Entities Objects or List of Objects?

I am using spring data JPA in my project. I am playing with millions of records. I have a requirement where I have to fetch data for various tables and build a object and then paint it on a UI. Now how to achieve this my Spring data Repositories. I have read that it can be achieved by Named native queries.
If the named native query does not return an entity or a list of
entities, we can map the query result to a correct return type by
using the #SqlResultSetMapping annotation.
But when I am trying to use #SqlResultSetMapping it is taking another entityResult. Mean what I understand is that it is just transformation some query result to entity result set only, but I want a result set of non - entities objects.
#SqlResultSetMapping(
name="studentPercentile",
entities={
#EntityResult(
entityClass=CustomStudent.class,
fields={
#FieldResult(name="id", column="ID"),
#FieldResult(name="firstName", column="FIRST_NAME"),
#FieldResult(name="lastName", column="LAST_NAME")
}
)
}
)
#NamedNativeQuery(
name="findStudentPercentile",
query="SELECT * FROM STUDENT",
resultSetMapping="studentPercentile")
In above example I am just trying to get a results from student Entity into another pojo 'CustomStudent' which is not a entity. (This example I am trying to execute just for POC purpose, actual usecase is much complicated, with complicated query returning different resultset).
How to achieve above usecase? Is there any other way besides using name query that my repository method returning Non - Entities objects?
You can do something like
#NamedQuery(name="findWhatever", query="SELECT new path.to.dto.MyDto(e.id, e.otherProperty) FROM Student e WHERE e.id = ?1")
Then the MyDto object would just need a constructor defined with the correct fields i.e.
public MyDto(String id, String otherProperty) { this.id = id; this.otherProperty = otherProperty; }
I was deeply surprised when I came accross this for the first time but, yes, you can map query results using #SqlResultSetMapping only to scalars and managed entities.
The best you can do, I guess, is to skip automatic mapping. Query without mapping would return List<Object[]> and you can map it the way you need.
Another approach would be to use #MappedSuperclass. The class denoted as #MappedSuperclass (CustomStudent in your case) can be (not sure 100%, though) used in #SqlResultSetMapping. but you need to introduce inheritance hierarchy, that is your Student entity must extend CustomStudent. That would suck most of the time from the proper OO design, because inheritance would be a little bit artificial...
How about JPA 2.1 ConstructorResult ?
#SqlResultSetMapping(
name="studentPercentile",
classes={
#ConstructorResult(
targetClass=CustomStudent.class,
columns={
#ColumnResult(name="ID"),
#ColumnResult(name="FIRST_NAME"),
#ColumnResult(name="LAST_NAME")
}
)
}
)
#NamedNativeQuery(name="findStudentPercentile", query="SELECT * FROM STUDENT", resultSetMapping="studentPercentile")
We can also parse using JSON help.
Class level declaration.
#Autowired
private EntityManager em;
private ObjectMapper mapper = new ObjectMapper();
Main Code.
Query query = em.createNativeQuery(argQueryString);
NativeQueryImpl nativeQuery = (NativeQueryImpl) query;
nativeQuery.setResultTransformer(AliasToEntityMapResultTransformer.INSTANCE);
List<Map<String,Object>> result = nativeQuery.getResultList();
List<CustomStudent> resultList = result.stream()
.map(o -> {
try {
return
mapper.readValue(mapper.writeValueAsString(o),CustomStudent.class);
} catch (Exception e) {
ApplicationLogger.logger.error(e.getMessage(),e);
}
return null;
}).collect(Collectors.toList());

Categories

Resources