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.
Related
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);
}
...
}
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.
I'll try to include all the relevant bits of information without overloading with too much. I have this object (stripped of a lot of irrelevant stuff):
#Entity
#Table(name = "RoutingAliases")
public class RoutingAlias {
private Queue queue;
private Queue scheduleQueue;
#ManyToOne
#JoinColumn(name = "queue_id", referencedColumnName = "other_id")
public Queue getQueue() {
return queue;
}
#ManyToOne
#JoinColumn(name = "schedule_queue_id", referencedColumnName = "other_id")
public Queue getScheduleQueue() {
return scheduleQueue;
}
}
Note that "queue" and "scheduleQueue" refer to the same object type/table via different properties. Either or both could be NULL or not.
#Entity
#Table(name = "Queues")
public class Queue {
private Long id;
private Long queue
private Long otherId;
#Id
#Column(name = "queue_id")
public Long getId() {
return id;
}
#Basic
#Column(name = "queue")
public String getQueue() {
return queue;
}
#Basic
#Column(name = "other_id")
public Long getOtherId() {
return otherId;
}
}
I try to retrieve a list of all RoutingAliases from my DAO class:
return em.createQuery("SELECT ra FROM RoutingAlias AS ra " +
"LEFT OUTER JOIN FETCH ra.queue AS q " +
"LEFT OUTER JOIN FETCH ra.scheduleQueue AS sq ",
RoutingAlias.class).getResultList();
The SQL that gets generated results in a LEFT OUTER JOIN between RoutingAliases and Queue tables with two references to Queues under different table aliases. In other words, the initial query returns all the details necessary to populate RoutingAlias.queue and RoutingAlias.scheduleQueue with a single query. But what happens is, after all the RoutingAliases are retrieved (there are about 1150 in the database), Hibernate then makes an additional 1150 queries to the Queues table - the 1+N query problem. The generated SQL is valid, and I can copy-paste into a SQL window and it works fine. I've stripped out every extra bit of code from the actual code until all I've left is the above properties and the above HQL/JPQL statement. If I remove the reference to one of the Queue properties (either of them), it results in a single query. If I put the second one back in, it goes back to 1+N queries. If I remove the "JOIN FETCH" from the query and put a #Fetch on the properties instead, it does 1+(N*2) queries. Frankly, I'm stumped, as this is such a simple case that I'm out of things to try.
The only odd thing about the situation is that the FK between RoutingAlias and Queue is NOT via the primary key on the Queue table, but another property in the Queues table, which shouldn't make any difference since, as mentioned, pulling a list of Routing Aliases with just one Queue relationship works just fine in a single query.
EDIT: If I retrieve a single instance of RoutingAlias, (using the find() method to retrieve by ID) the system will retrieve it, and associated objects, in a single SQL query.
It looks like a bug in Hibernate - I can reproduce this issue as well.
However it works fine if I change the type of scheduleQueue from Queue to Queue2 and create a copy of the entity Queue named Queue2 (leaving it mapped to the same table !)
You can extract a superclass from these two classes
I have struggled a lot with such issues and wrote a small library for checking the number of queries generated by Hibernate - you might find it useful: https://github.com/bedrin/jdbc-sniffer
I've been using spring and hibernate for this past few weeks and I've always been learning something new there.
Right now I've got a problem that I want to solve with Projections in Hibernate.
Suppose there is a model Person and that model has many Car. The following are how the class definitions roughly gonna look like:
public class Person implements java.io.Serializable {
private Integer id;
private String name;
private List<Car> cars;
private Integer minYear; // Transient
private Integer maxYear; // Transient
}
public class Car implements java.io.Serializable {
private Integer id;
private Integer year;
}
The problem here is I want to get the minYear (maxYear) of each Person to be filled by the earliest year (latest year) of the cars they have.
Later I found a solution to use Projections but I stumbled upon org.hibernate.QueryException: could not resolve property: minYear of: model.Person and here is the code of the db operation:
Criteria criteria = sessionFactory.getCurrentSession().createCriteria("model.Person");
criteria.add(create(personInstance));
criteria.createAlias("minYear", "minYear");
criteria.setProjection(Projections.min("cars.year").as("minYear"));
Is there anyway to store the aggregation value in transient method using Projections because I just want to avoid using plain SQL and HQL as much as possible.
Never mind, I've found the solution.
First we need to create alias of the associated object like so
Criteria criteria = sessionFactory.getCurrentSession().createCriteria("model.Person");
criteria.createAlias("cars", "cars");
Select the needed using Hibernate Projections
ProjectionList projections = Projections.projectionList();
projections.add(Projections.property("id").as("id"));
projections.add(Projections.property("name").as("name"));
projections.add(Projections.property("cars").as("cars"));
Group the result based on the root entity (in this case using its id, Person.id), this is needed especially when used with aggregation to group the aggregation
projections.add(Projections.groupProperty("id"));
Use the aggregate function
projections.add(Projections.min("cars.year").as("minYear"));
projections.add(Projections.max("cars.year").as("maxYear"));
Set the projection
criteria.setProjection(projections);
Use result transformer AliasToBeanResultTransformer to map the result fields (as specified in step 2 & 4) to the POJO
criteria.setResultTransformer(new AliasToBeanResultTransformer(Person.class));
Get the result
List<Person> results = (List<Person>) criteria.list();
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).