fetching elementcollecion from hibernate based on memeber of condition - java

I have two entities Customer & Tester. Where a customer can request for tests and a tester can assign it to itself which will be set to the currentTestId. Also all the test id would be added to assignedTests list for old tests references. Testers have hierarchy as test lab manager, lab employee and whenevr a tester accept the request lab mangers are also assigned the test and so the ManyToOne(customer) and OneToMany(Tester) relationships are added.
DataSet:
Customer Entity
#Column(name = "test_id",nullable = false)
private long testId;
#ManyToOne(optional = false)
#JoinColumn(name = "tester_id")
private Tester tester;
Tester Entity
#Column(name = "test_id",nullable = false)
private long currentTestId;
#OneToMany(mappedBy = "tester")
private Set<Customer> customers = new HashSet<>();
#ElementCollection
#CollectionTable(name="assigned_tests",
joinColumns = #JoinColumn(name="tester_id", referencedColumnName = "id"))
#Column(name="test_id", nullable = false)
private Set<Long> assignedTests = new HashSet<>();
Problem Statement:
I want to fetch all customers who are part of the tester assigned tests. So since it is a one to many relation ideally I should have something like this
Select c.id from Customer c, Tester t
where c.testId memeber of t.assignedTests
and t.id = :testerId
But this gives me the error:
The basic field cannot be used as a state field path
The Query compilation work if I pass the testId but unfortunately in the code I cannot pass it(just tried to test if it will work)
Select c.id from Customer c, Tester t
where :testId memeber of t.assignedTests
and t.id = :testerId
I am new to JPA and learning how to use ElementCollection. I am using Postgres database and JPA 2.1. Also I cannot use native query or the criteria Api. It has to be using the JPQL but still suggestions would be really helpful.

How about:
SELECT c.id FROM Customer c
JOIN c.tester t
JOIN t.assignedTests at
WHERE t.id = :testerId
?

Related

Java JPA Hibernate OneToOne vs HashMap - Which One Should I Use and Why?

I have a question about performance and common practice, if someone could explain this to me.
I have recently started using JPA and hibernate and have come across an Entity that has a foreign key and I need to get some data from it. So for example: CustomerAddress has a City and that city has a lot of detail and also a name.
SQL:
select
CA.Id, CI.Name
from
CustomerAddress as CA
inner join City as CI
on CA.CityID = CI.Id
So now in Java JPA Entity I can have a one-to-many annotation:
#Entity
#Table(name = "CustomerAddress")
public class CustomerAddressEntity {
#Id
#Column(name = "Id", unique = true, nullable = false)
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#NotFound(action = NotFoundAction.IGNORE)
#JoinColumn(name = "city", referencedColumnName = "id", insertable = false, updatable = false)
private City city;
}
Where City is also an #Entity with #Id and simple object.
Which in my opinion does not turn out the best because it makes a lot of SQL requests.
And then I have the option having just two findAll() calls at the beginning, where I would collect all the City Entities in a HashMap<String, City> and when needing the name I would just call hashmap.get(key).getName().
EDIT (thanks for the heads up :)):
And when using this HashMap I can use a simpler Entity without the #JoinColumn
#Entity
#Table(name = "CustomerAddress")
public class CustomerAddressEntity {
#Id
#Column(name = "Id", unique = true, nullable = false)
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "CityID")
private Long cityId;
}
In the hashmap case I only get two SQL calls and I think it works much faster. Is there a way to get this behavior also using JPA and hibernate?
If my question and code needs some more refinement please let me know.. I can edit the question with more details and perhaps if necessary provide a working example. Thank you for your thoughts :)
And the same would go for OneToMany, where the hashmap would be: new HashMap<String, List<City>> for example - I mean the whole example should be created a bit differently - I guess it could even be a HashMap<String, HashMap<String,City>> - if one would need quick access to the City by Id or sth... but i digress :) I will edit the question and respond to comments as I will go.. and refine the question if necessary.. I would just like to hear some thoughts and where my thinking is wrong :) and what am I failing to see and missing :)
EDIT: For example a code that would create a lot of SQL requests:
public interface CustomerAddressRepository extends JpaRepository<CustomerAddressEntity, Long> {
#Override
List<CustomerAddressEntity> findAll();
}
This for example creates an SQL Query (I would use findAll() at the beginning to list all - or most of the Entities for the user) and you would get an SQL query for every Entity because it would want to find the Name of the City as well - because the ID of the City Entity does not really help to the user.
Also - I like to have all the Entities in my RAM so I can do a quick search for the user more responsive - So a search does not always do SQL Query + #(found results) Queries.
The HashMap has nothing to do with the fact that Hibernate issues a query when you want to get the name of the City object.Here's why it's happening.
In your CustomerAddressEntity you have a OneToOne with City , and since you have a #JoinColumn there ,it means that CustomerAddressEntity database Table will have the Primary Key of the City table as a foreign key , and since you specified in your class that it should be fetched LAZY,Hibernate will create a Proxy object wrapping the City object,ready to get queried from the database in case you call any getMethod ,like getName() ,(excluding the getId() method since the ID exists prealably in the proxy object,you can check the sql query logs and see that the query selects the foreign key with all the other fields of CustomerAddressEntity ),that's why when you trigger the getName() method Hibernate will fetch that entity from the database.

Spring / JPA join on namespaced key

I am creating a Spring 4 / Spring Data application for an existing database. The database structure and data are defined by a closed source software.
One aspect of the existing system is that you can create a comment on any other item in the system. This means, that an article, a document, a media file (all entities in the system) can have any number of comments, and each comment is exactly for one entity in the system. All comments are in the same comment table.
The way this is implemented is that the table comment has a column comment_for that holds a concatenated/namespaced/prefixed reference to the actual entity it is a comment for. The current system seems to just builds the join query by prefixing the primary key with the table name:
+----+-------------------+----------------+
| id | comment_for | comment |
+----+-------------------+----------------+
| 1| article:12345 | This is nice...|
| 2| document:42 | Cool doc! |
+----+-------------------+----------------+
This sample shows two comments, one for an Article with an article.id of 12345 and one for a document with document.id of 42. I created #Entities matching the database tables and the corresponding Repository Interfaces with the query methods I need.
I would like to make use of Spring Data Repositories / Entities to populate the collections of my entities with the corresponding comments, like this (pseudocde) for Entity Article.
#OneToMany(mappedBy = "comment_for", prefix = "article:")
private List<Comment> comment = new ArrayList<>();
I only need it unidirectional. My entities (at the moment Article, Document and Mediafile) should hold a collection of their comments. I don't need comments to hold a reference back to the entity.
Is there a way to do this? The resulting SQL query should be something like
SELECT * FROM .... WHERE comment.comment_for = concat('<entityname>:', <entity>.id);
I looked at #JoinColumn but I can't modify the used value for the join, only the column name. The only solution I have at the moment are manual #Querys on the CommentRepository Interface, which gives me an ArrayList of all comments for a certain Entity / ID combination. But I would like to have the comments automatically joined as part of my Business Entity.
Update : It looks like I am able to split the namespace and id from comment_for into two new columns without interrupting the existing software. The two columns are now comment_for_id and comment_for_entityname
You could also break out comment_for to contain only the id like your entities. Adding an additional column like entity_type would allow you to avoid duplicate id values between different entities.
Also you could use #JoinColumn on the owner side of the relationship between Entity and Comments. It looks like in your case that would be the Comment entity/table, since there are many comments per each entity.
Example:
#Entity
#NamedQueries({ #NamedQuery(name = "Comments.findAll", query = "select o from Comments o") })
#IdClass(CommentsPK.class)
public class Comments implements Serializable {
private static final long serialVersionUID = 4787438687752132432L;
#Id
#Column(name = "COMMENT_TEXT", nullable = false, length = 30)
private String commentText;
#Id
#Column(name = "ENTITY_TYPE", nullable = false, length = 30)
private String entityType;
#ManyToOne
#Id
#JoinColumn(name = "COMMENT_FOR")
private EntityDemo entityDemo;
Note that I set the combination of all three fields as the primary key, I am not sure what criteria is used as the PK in your current set up.
Here is an example of an Entity. The attributes have been made up for the purpose of demonstration.
#Entity
#NamedQueries({ #NamedQuery(name = "EntityDemo.findAll", query = "select o from EntityDemo o") })
#Table(name = "ENTITY_DEMO")
public class EntityDemo implements Serializable {
private static final long serialVersionUID = -8709368847389356776L;
#Column(length = 1)
private String data;
#Id
#Column(nullable = false)
private BigDecimal id;
#OneToMany(mappedBy = "entityDemo", cascade = { CascadeType.PERSIST, CascadeType.MERGE })
private List<Comments> commentsList;

Needless Left Outer Join Created on FK's using JPA/Hibernate

I have an Entity such as:
#Entity
class Brand {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(nullable = false, columnDefinition = "BIGINT")
private Long id;
#NotNull(message = NOT_NULL_CODE)
#Size(min = 5, max = 150, message = SIZE_CODE)
#Column(nullable = false, length = 150)
private String name;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "organization_id", nullable = false)
#JsonBackReference("organization-brands")
private Organization organization;
// getters and setters
}
My repository has the following defined:
Page<Brand> findAllByOrganization(Organization organization, Pageable pageable);
This results in the following query:
select
brand0_.id as id1_1_,
brand0_.name as name2_1_,
brand0_.organization_id as organiza3_1_
from
brand brand0_
left outer join
organization organizati1_
on brand0_.organization_id=organizati1_.id
where
organizati1_.id=? limit ?
I'm wondering why there is a left outer join created? I thought maybe it was because the organization_id was part of the select so I created a DTO Projection that only included the name and id columns, but that still included the left outer join. I found this bug report filed but there's been zero movement on it.
The reason this is concerning for me is because the left outer join adds, on average, 200ms to the query. I haven't done any benchmarking to determine if this grows exponentially with more rows of data or not. But with my current dataset, it's 200ms. Yikes.
This is most likely related to:
http://stackoverflow.com/a/17987718/1356423
From the presence of your nullable = false on the column definition it looks like the relationship is non-optional so try annotating as #ManyToOne(fetch = FetchType.LAZY, optional = false)
Without theoptional = false Hibernate has to check the association table to see whether to set either a proxy or null for the associated Organisation: hence the join.
assuming Organization class PK is named as id
then
Page<Brand> findAllByOrganizationId(String organizationId, Pageable pageable);
should do what you want

jpa query join fetch from a onetomany

I have the following 2 classes using JTA transaction type with openjpa & a derby embedded db. What I want is to get the parent Organisation & the requested WSpace in one query when I only have the WSpace id. I am quite new to JPA so am learning as I go and have been struggling with 'q2' query. I have been using queries 'q0' & 'q1' to debug and check the items do exist in the db. 'q0' returns 1 object as does 'q1', whereas 'q2' returns 0 objects
I have tried a variety of entity setups and different queries but nothing has worked yet.
Orignally the WSpace class did not have an Organisation field as it didn't seem necessary for persisting or selecting, but I added it (along with the mappedby parameter) incase it was needed for the query to work, but nothing has changed.
back to the original question how can I get this to work so it returns the parent object with the single child being requested
SELECT o FROM Organisation o JOIN FETCH o.spaces w WHERE w.id = :id
Here are my classes
#Entity
public class Organisation implements MyObjects
{
#Id
#NotNull
private Integer id;
private String name;
#OneToMany( mappedBy = "organisation",
cascade = { CascadeType.PERSIST, CascadeType.MERGE } )
private List<WSpace> spaces;
//getters/setter below
}
And
#Entity
public class WSpace implements MyObjects
{
#Id
#NotNull
private Integer id;
private String name;
#ManyToOne
private Organisation organisation;
#OneToMany
private List<Application> apps;
//getters/setter below
}
class DAO
{
...
public void foo( Integer id )
{
....
String q0 = "SELECT o FROM Organisation o WHERE o.id = 49068";
List<Organisation> res0 = em.createQuery( q0, Organisation.class ).getResultList();
String q1 = "SELECT w FROM WSpace w WHERE w.id = " + id;
List<WSpace> res1 = em.createQuery( q1, WSpace.class ).getResultList();
String q2 = "SELECT o FROM Organisation o "
+ "JOIN FETCH o.spaces w WHERE w.id = " + id;
List<Organisation> res2 = em.createQuery( q2, Organisation.class ).getResultList();
...
}
}
Have you tried to look in the logs for output of your q2 query?
I am learning JPA too and was dealing with Criteria and QL queries quite recently.
So after having pretty same problems with joins, I started checking logs and it was pretty clear, what the issues were, since logs showed up translated to SQL queries.
Another thing to look, how are you generating your Entities? I used Netbeans generating it for me. Also, many to many relations mignt have helper class generated too, I saw it in one of the projects.
Hope it helps..
The query you're looking for is probably this:
SELECT w FROM WSpace w LEFT JOIN FETCH w.organisation where w.id=:id
with query.setParameter("id", id); to bind the id parameter to the query. This effectively tells the persistence provider to fetch the WSpace.organisation relation while querying for WSpace entities in the same query. Using the LEFT [OUTER] keword (OUTER being optional) in front of the JOIN FETCH keywords tells your persistence provider to select WSpace objects even if there are no matching Organisation records for your WSpace.organisation relation.

Hibernate Query.list returns actual Object instance instead of expected type

When performing a hibernate query in HQL using a join and subsequently calling query.list to return the list of matched objects, I am ending up with a list of actual Object instances (i.e. query.list().get(0).getClass() == Object.getClass()) instead of instances of the expected object.
Running the query without the join returns objects of the expected type correctly and they can be cast and used appropriately.
So far my searches have not turned up anything about what could be causing this. Is there something else I need to do when using a join in hql to ensure the object is mapped correctly?
Edit: Added code excerpts below. I had to change all the names and attempt to extract only the relevant portions (the real code is not really about cars).
Working query:
from Car car where car.name like :name
Non-working query:
from Car car left join car.occupants occupant where car.name like :name OR (occupant.name like :oname)
Car entity:
#Entity
#Table(uniqueConstraints = {#UniqueConstraint(columnNames = {"someId"}),
#UniqueConstraint(columnNames = {"someOtherId"})})
public class Car extends SomeParentEntity
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false, length = 64)
private String someId;
#Column(length = 64)
private String name;
// ... Many columns and mappings removed ...
#OneToMany(mappedBy = "car", fetch = FetchType.LAZY)
private List<Occupant> occupants;
// ...
}
Occupant entity:
#Entity(name = "car.Occupant")
#Table(uniqueConstraints = {#UniqueConstraint(columnNames = { "name" }) })
public class User extends SomeParentEntity
{
#ManyToOne
#JoinColumn(name = "carId", nullable = false)
private Car car;
#Column(length = 64, nullable = false)
private String name;
// ... Many mappings / columns removed ...
}
Your JOIN in the HQL is making Hibernate retrieve two types of objects. You can see this if you activate the SQL logging.
Assuming you have a X-to-X relation in your entity, the problem should go away if you change the query to use
... JOIN FETCH entity.relation ...
Why are you using explicit left join btw.
from Car car left join car.occupants occupant where car.name like :name OR (occupant.name like :oname)
I think we can simple use:
from Car car where car.name like :name or car.occupant.name like :oname
Then the query.list should give you List of object which should be casted back to List of car
+1's all around to the other answerers for your help, it is much appreciated.
However, the solution turned out to be very simple. The query just needed to have the select specified at the beginning for the join case in order to narrow the field (I assume).
So the non-working query:
from Car car left join car.occupants occupant where car.name like :name OR (occupant.name like :oname)
Becomes:
select car from Car car left join car.occupants occupant where car.name like :name OR (occupant.name like :oname)

Categories