Solving JPA query finding the last entry in connected list - java

Following class structure is given:
class Job
{
String description;
Collection<JobHistory> history;
}
class JobHistory
{
Date assignDate;
User jobOwner;
}
class JobOwner
{
String name;
String id;
}
This class-structure is accessible on the db via JPA. In the DAO-Layer I can write queries in JPA syntax.
The Problem: I want a list with Job and JobHistory entries for a given owner with given id and who is the last one in the Jobhistory of the job (ordered by assignDate). Sounds quite complicated, perhaps simpler: give me all jobs and JobHistory where specified owner is the actual owner of the job.
Update: for clarity I will slightly change the names of the classes.
class Job
{
String description;
Collection<JobOwnerHistory> history;
}
class JobOwnerHistory
{
Date assignDate;
User jobOwner;
}
class JobOwner
{
String name;
String id;
}
Every Job has a history of his owners sorted by assignDate. The actual owner got the job last assigned (i.e. MAX(assignDate)). I want find for every job the JobOwnerHistory entry with MAX(assignDate) for a specific user User.

I found the following answer for the query:
SELECT j, h FROM Job j JOIN j.history h JOIN h.jobOwner u
WHERE u.name = :name AND
(SELECT MAX(h2.assignDate) FROM Job j2 JOIN j2.history h2
WHERE h2 member of j.history) = h.assignDate
The most important part in the query is the subselect with MAX(h2.assignDate) because I want to get the job and the newest entry in the owner-history.

Try:
SELECT j, j.history FROM Job j JOIN User u WHERE u.name = :name
If I were to do this in EclipseLink, I would change it slightly:
public List<Job> getAllJobsForUser(String username) {
List<Job> jobs = entityManager
.createQuery("SELECT j FROM Job j JOIN User u WHERE u.name = :name")
.setParameter("name", username)
.setHint(QueryHints.BATCH, "j.history")
.queryForList();
}
The difference? In the first version, you're returning two objects, so you have to retrieve them from a List or Object arrays whereas in the second, the query hint just loads all the job histories from an (assumedly) lazyy one-to-many relationship.
I don't know if Hibernate has an equivalent to this. Toplink Essentials doesn't. But it's one of my favourite features of EclipseLink.
Oh and obviously you can (and probably should) use a named query instead of an adhoc query like I've done (since those can be verified during the build).

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.

How to delete/getList using JPA in Spring Boot Application

I have an entity:
#Entity
#Table(name ="cats")
public class Cat {
#Id
#Column(name="name")
private String name;
#Column(name="age")
private int age;
#Column(name="color")
private String color;
#Column(name="weight")
private int weigth;
..
}
1. I need to delete it from database using EntityManager:
#Override
public void delete(Cat cat) {
entityManager.remove(cat);
}
Problem: I have a Map<String, Cat> which contains all this elements. I get it by name from map IllegalArgumentException -> "Removing a detached instance com.entities.Cat#cats".
Question: How can i do it without getting from database by key?
2. I need to getList with limit and offset.
To get all the elements i can just use:
entityManager.createNativeQuery("SELECT name, age, color, weight FROM cats");
Without entityManager i used prepatedStatement with:
"SELECT name, age, color, weight FROM cats LIMIT ?,?"
Question:
How can i do it using entityManager?
Do entityManager have something like preparedStatement?
With EntityManager you can use Query objects. It provides you with several different methods to build your queries, which you can see in the Docs.
From there, you can use a Query to perform a select or execute an update into the db.
Update example:
//:id is a parameter you can set
Query query = entityManager.createQuery("delete from Entity e where e.id = :id");
query = query.setParameter("id", id);
query.executeUpdate();
Select example (using TypedQuery which implements Query:
String sql = "select e from Entity e";
TypedQuery<Entity> query = entityManager.createQuery(sql, Entity.class);
System.out.println(query.getResultList());
You can determine limit and offset like this:
query = query.setFirstResult(offset);
query = query.setMaxResults(limit);
If you have an entity at hand you can (and should) delete it using your EntityManager with remove(). You're getting that error because your entity is detached - that is, your EntityManager isn't aware of its existence.
To "attach" entities to your manager you can use merge(). However, if said entity doesn't exist in the database it will be inserted, and if it exists but has different fields from your object it will be updated.
public void delete(Cat cat) {
if(!entityManager.contains(cat)) {
entityManager.merge(cat);
}
entityManager.remove(cat);
}
To insert entities for the first time you can also use persist(). For the differences between merge() and persist(), see this.
If you need to use EntityManager, then simply use reference:
entityManager.remove(entityManager.getReference(Cat.class, id));
This way the entity won't be fetched from db, but will be deleted.
Using query is also an option:
Query query = entityManager.createQuery("delete from Entity e where e = :entity");
query = query.setParameter("entity", entity);
query.executeUpdate();
You can create Query using EntityManager#createQuery. Then set parameters: firstResult and maxResults:
query.setFirstResult(10).setMaxResults(20);
This will take 20 entities starting from 10th.

Spring data JPA query behaves unexpectedly

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.

hibernate how to retrieve a hierarchy object

I have the following beans Task, ServerDetails and ApplicationDetails.
I wish to retrieve all tasks, their server details and application details based on a specific application name.
From the result i expect to be able to retrieve the data in a manner such as:
task.getServers().getApplicationDetails()
In actuality, I get what seems to be flat data's representation as an Object[].
Is there any way to do what i propose?
Following is my code...
class Task {
private String taskId;
private Set<ServerDetails> servers;
}
class ServerDetails {
private String id;
private Set<ApplicationDetails> applications;
}
class ApplicationDetails {
private String id;
}
HQL:
StringBuilder hql = new StringBuilder(256);
hql.append("FROM Task h, ServerDetails ser, ApplicationDetails app ");
hql.append("WHERE h.executionDate > ");
hql.append("to_date('");
hql.append(DBDateFormatter.getInstance().formatDate(cal));
hql.append("', '");
hql.append(DBDateFormatter.getInstance().getOracleDateFormat());
hql.append("') and h.id = ser.task.id and ser.id = app.server and app.name = 'XXX'");
hql.append(" order by h.executionDate desc");
String hql = hql.toString();
Query query = session.createQuery(hql);
results = (List<Object[]>) query.list();
You should just retrieve the main object.
For the other, you can:
navigate to them while the Session has not be closed (runs additional queries as needed, known as lazy ; this is ideal for ease of use)
retrieve them in the original query using the fetch keyword.
Example:
SELECT h
FROM Task h
JOIN FETCH h.serveurs ser
JOIN FETCH ser.applications app
WHERE h.executionDate >
.... // no need to specify the joins
You will be able to retrieve the data in a manner such as:
task.getServers().getApplicationDetails()
You can retrieve the object graph as the others have said using LEFT JOIN FECH. One crevent I have found when retrieving object graphs, when walking down a many-to-one relationship you can not walk back up without additional database access.

Use fewer columns on SQL query through Hibernate Projections on Entity with ManyToOne relation

I'm trying to build a smaller SQL, to avoid the "select * from A" that is being build by default for hibernate Criteria.
If I use simple fields (no relation), through "Transformers", I have can manage to have this SQL:
select description, weight from Dog;
Hi, I have this Entity:
#Entity
public class Dog
{
Long id;
String description;
Double weight;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "person_id", nullable = false)
Person owner;
}
#Entity
public class Person
{
Long id;
String name;
Double height;
Date birthDate;
}
My goal is to have this:
select description, weight, owner.name from Dog
I tried this with with Criteria (and subcriteria):
Criteria dogCriteria = sess.createCriteria(Dog.class);
ProjectionList proList = Projections.projectionList();
proList.add(Projections.property("description"), description);
proList.add(Projections.property("weight"), weigth);
dogCriteria.setProjection(proList);
Criteria personCriteria = dogCriteria.createCriteria("owner");
ProjectionList ownerProList = Projections.projectionList();
ownerProList.add(Projections.property("name"), description);
dogCriteria.setProjection(ownerProList); //After this line, debugger shows that the
//projection on dogCriteria gets overriden
//and the query fails, because "name" is
//not a field of Dog entity.
How should I use Projections, to get a smaller SQL, less columns ?
Thanks in advance.
First of all,
select description, weight, owner.name from Dog
is not valid SQL. It would have to be something like
select description, weight, Person.name
from Dog join Person on Dog.person_id = Person.id
instead. Secondly, why? While it's possible to do what you want (see below), it's extremely verbose to do so via Criteria API and you gain nothing to show for it. Savings on data transfer for a couple of columns are negligible unless said columns are huge blobs or you're selecting hundreds of thousands of records. In either case there are better ways to deal with this issue.
Anywho, to do what you want for criteria, you need to join linked table (Person) via alias and specify projection on main criteria using said alias:
Criteria criteria = session.createCriteria(Dog.class, "dog")
.createAlias("owner", "own")
.setProjection( Projections.projectionList()
.add(Projections.property("dog.description"))
.add(Projections.property("dog.weight"))
.add(Projections.property("own.name"))
);
There's a description and an example of the above in Criteria Projections documentation. Keep in mind that, when executed, the above criteria would return a list of object arrays. You'll need to specify a ResultTransformer in order to have results converted into actual objects.
I didn't tried it yet by myself, but I think you can also use another constructor in your Entity (Pojo) and pass the columns there.
See https://www.thoughts-on-java.org/hibernate-best-practices/ chapter "1.2 Pojo" for a detailed instruction.
Altough for me it's not yet clear if this also works for ManyToOne relationships too. I will have a try.

Categories

Resources