Selecting a collection does not result in exception in JPA 2 - orm

I am having a bit of a trouble understanding this line of code from the book Pro JPA 2
According to the book on page 181.
The result type of a select query cannot be a collection; it must be a
single valued object such as an entity instance or persistent field
type. Expressions such as e.phones are illegal in the SELECT clause
because they would result in Collection instances (each occurrence of
e.phones is a collection, not an instance). Therefore, just as with
SQL and tables, if we want to navigate along a collection association
and return elements of that collection, we must join the two entities
together.
Please consider this entities below with relationship mapping
#Entity
public class Employee {
..
#OneToMany(mappedBy="employee", cascade=CascadeType.ALL, targetEntity=Phone.class)
private Collection<Phone> phones = new ArrayList<Phone>();
..
}
#Entity
public class Phone {
..
#OneToOne
private Employee employee;
..
}
Now in a test class i tried it with this test case
#Test
public void selectCollectionTest(){
TypedQuery<Object> query = em.createQuery("select e.phones from Employee e where e.id = 1", Object.class);
List<Object> empList = query.getResultList();
for(Object temp: empList){
System.out.println(temp);
}
}
I was expecting that an exception would be thrown but nothing is happening and I was able to select the collection?
Is this correct? Can somebody explain or clear out my understanding?
Eclipselink

EclipseLink allows this, it is an extension, the JPA spec does not support it.
It is the same as the query,
select p from Employee e join e.phones p where e.id = 1

Try to run your query with below code by removing the where clause:
select e.phones from Employee e, Object.class
The point i am trying to make is may be your result for emp id 1 contains only single phone object.

Related

JPA 2 Relationship JOIN Named Query

I'm using SpringBoot 2.4 with JPA 2.0 and I have a Model like following:
#Entity
#Data
public class Nation {
#Id
#GeneratedValue (strategy = GenerationType.IDENTITY)
private Integer id;
#OneToMany(mappedBy = "nation")
private List<Country> country;
}
And:
#Entity
#Data
public class Country {
#Id
#GeneratedValue (strategy = GenerationType.IDENTITY)
private Integer id;
#ManyToOne
private Nation nation;
}
Now I would to like to find all Nation filtered by their ID and by country ID. In pure SQL is simply something like:
select * from nation n, country c where n.id = [nation_id] AND c.id = [country_id];
Therefore I thought about doing this way with JPA:
#Query("select n from Nation n JOIN n.country c where n.id = ?1 AND c.id = ?2)
public List<Nation> find(Integer nationID, Integer countryID);
But it doesn't work; it is filtered by Nation but not by countries.
If I print the Hibernate generate SQL by adding:
spring.jpa.show.sql=true
I can see that the query is exactly the same I posted above in pure SQL. The problem occours when I invoke nation.getCountry(), it generates another query that load all country connected to given Nation id.
Is there a way to solve this?
I believe that you can use JPA DTO projections for this case ...
So, in your Nation class create a constructor like this:
/**
* Copy Constructor. It creates a "detached" copy of the
* given nation with only a copy of the provided country.
*/
public Nation(Nation n, Country c) {
super();
this.id = n.id;
// copy other nation values ...
this.country.add( new Country(c) );
}
Modify your query to invoke such constructor ... something like this (assuming that Nation is declared in the java package my.domain):
#Query("select new my.domain.Nation(n, c) from Nation n JOIN n.country c where n.id = ?1 AND c.id = ?2)
Disclaimer:
I have done this using JPA and hibernate. So far, I haven't tested with spring, but I guess this does not matter because your JPA provider is probably hibernate too.
I have done this using only parts (or attributes) of the target entities (as described in the provided link) ... I never have pass the full entities (as I suggest to do in the query). Let me explain, in the cases where I have applied this, I have a constructor like Nation(String name, int population) and in the query I do something like: SELECT new my.domain.Nation(n.name, c.population) ... Try to pass the full entities to see if it works ... if it fails, fallback to create a constructor that receives only the attributes that you require for your business case.

MyBatis Insert a complex object which has associations

Hi Guys i have a complex object as below and would like to know how to write mapper.xml for the same.
I have knowledge to insert a simple object, but i am not getting this with 2 levels of Hierarchy.
Below is my domain object
class Org{
id=O1
str1;
List<Division>
}
class Divison{
id = D1
org = O1
str2;
List<Employees>
List<Managers>
}
class Employees{
str3;
id = E1
divId = D1
}
class Managers{
str3;
id = M1
divId = D1
}
So Org has multiple Divisons, Each Divison has multiple Employees and Managers.
How can i write my mybatis mapper.xml so that i can insert Organization, Divison, Employees and Managers to respective tables in one transaction

How do I flatten a hierarchy in LINQ to Entities?

I have a class Org, which has ParentId (which points to a Consumer) and Orgs properties, to enable a hierarchy of Org instances. I also have a class Customer, which has a OrgId property. Given any Org instance, named Owner, how can I retrieve all Customer instances for that org? That is, before LINQ I would do a 'manual' traversal of the Org tree with Owner as its root. I'm sure something simpler exists though.
Example: If I have a root level Org called 'Film', with Id '1', and sub-Org called 'Horror' with ParentId of '1', and Id of 23, I want to query for all Customers under Film, so I must get all customers with OrgId's of both 1 and 23.
Linq won't help you with this but SQL Server will.
Create a CTE to generate a flattened list of Org Ids, something like:
CREATE PROCEDURE [dbo].[OrganizationIds]
#rootId int
AS
WITH OrgCte AS
(
SELECT OrganizationId FROM Organizations where OrganizationId = #rootId
UNION ALL
SELECT parent.OrganizationId FROM Organizations parent
INNER JOIN OrgCte child ON parent.Parent_OrganizationId = Child.OrganizationId
)
SELECT * FROM OrgCte
RETURN 0
Now add a function import to your context mapped to this stored procedure. This results in a method on your context (the returned values are nullable int since the original Parent_OrganizationId is declared as INT NULL):
public partial class TestEntities : ObjectContext
{
public ObjectResult<int?> OrganizationIds(int? rootId)
{
...
Now you can use a query like this:
// get all org ids for specific root. This needs to be a separate
// query or LtoE throws an exception regarding nullable int.
var ids = OrganizationIds(2);
// now find all customers
Customers.Where (c => ids.Contains(c.Organization.OrganizationId)).Dump();
Unfortunately, not natively in Entity Framework. You need to build your own solution. Probably you need to iterate up to the root. You can optimize this algorithm by asking EF to get a certain number of parents in one go like this:
...
select new { x.Customer, x.Parent.Customer, x.Parent.Parent.Customer }
You are limited to a statically fixed number of parent with this approach (here: 3), but it will save you 2/3 of the database roundtrips.
Edit: I think I did not get your data model right but I hope the idea is clear.
Edit 2: In response to your comment and edit I have adapted the approach like this:
var rootOrg = ...;
var orgLevels = new [] {
select o from db.Orgs where o == rootOrg, //level 0
select o from db.Orgs where o.ParentOrg == rootOrg, //level 1
select o from db.Orgs where o.ParentOrg.ParentOrg == rootOrg, //level 2
select o from db.Orgs where o.ParentOrg.ParentOrg.ParentOrg == rootOrg, //level 3
};
var setOfAllOrgsInSubtree = orgLevels.Aggregate((a, b) => a.Union(b)); //query for all org levels
var customers = from c in db.Customers where setOfAllOrgsInSubtree.Contains(c.Org) select c;
Notice that this only works for a bounded maximum tree depth. In practice, this is usually the case (like 10 or 20).
Performance will not be great but it is a LINQ-to-Entities-only solution.

Sub-optimal queries over many-to-many relations with HQL

I have two entities, Location and Industry, and a link-table between them. I've configured a many-to-many relationship, in both directions, between the two entities.
In a search query, I'm trying to select Locations that are associated with a list of industries.
After days and days of trying to wrangle the criteria API, I've decided to drop down to HQL and abandon the criteria API. But even that isn't going well for me - it seems, regardless of whether I hand-write this HQL query, or let the criteria API do it, I end up with the same result.
I managed to produce the right result in two ways - like this:
var q = Data.Query("select distinct loc from Location loc join loc.Industries ind where ind in (:ind)");
q.SetParameterList("ind", new Industry[] { Data.GetIndustry(4), Data.GetIndustry(5) });
And (better) like that:
var q = Data.Query("select distinct loc from Location loc join loc.Industries ind where ind.id in (:ind)");
q.SetParameterList("ind", new int[] { 4, 5 });
Unfortunately, both result in a sub-optimal query:
select distinct
location0_.Id as Id16_,
location0_.Name as Name16_,
(etc.)
from Location location0_
inner join LocationIndustry industries1_
on location0_.Id=industries1_.LocationId
inner join Industry industry2_
on industries1_.IndustryId=industry2_.Id
where
industry2_.Id in (? , ?)
Why the extra join?
Is NH not smart enough to know that the Industry.Id property, being the only Industry-property involved in the query, is stored in the LocationIndustry link-table, and there is no need for the extra join to the Industry table itself?
Or am I doing something wrong?
Ideally, the most intuitive thing for me would be to write:
from Location loc where loc.Industries in (:ind)
This does not work - it throws an error and says it does not know about the Industries property. I guess because Industries, being a "property" in programming terms, is actually a "relationship" in terms of DBMS.
What is the simplest and most efficient way to write this query in HQL?
Thanks!
I'm not sure you can avoid this extra join given the mapping strategy you have used.
You could avoid it by using an intermediary class but this would mean you would need a class structure like this:
public class Industry {
//Other stuff
public virtual List<LocationIndustry> LocationIndustries {get; set:;}
}
public class LocationIndustry {
public virtual Location Location {get; set;}
public virtual Industry Industry {get; set;}
}
public class Location {
//normal stuff
public virtual IList<LocationIndustry> LocationIndustries {get; set;}
}
Then you can query on the LocationIndustry class and avoid the join to Location.

nHibernate collections and alias criteria

I have a simple test object model in which there are schools, and a school has a collection of students.
I would like to retrieve a school and all its students who are above a certain age.
I carry out the following query, which obtains a given school and the children which are above a certain age:
public School GetSchoolAndStudentsWithDOBAbove(int schoolid, DateTime dob)
{
var school = this.Session.CreateCriteria(typeof(School))
.CreateAlias("Students", "students")
.Add(Expression.And(Expression.Eq("SchoolId", schoolid), Expression.Gt("students.DOB", dob)))
.UniqueResult<School>();
return school;
}
This all works fine and I can see the query going to the database and returning the expected number of rows.
However, when I carry out either of the following, it gives me the total number of students in the given school (regardless of the preceding request) by running another query:
foreach (Student st in s.Students)
{
Console.WriteLine(st.FirstName);
}
Assert.AreEqual(s.Students.Count, 3);
Can anyone explain why?
You made your query on the School class and you restricted your results on it, not on the mapped related objects.
Now there are many ways to do this.
You can make a static filter as IanL said, however its not really flexible.
You can just iterate the collection like mxmissile but that is ugly and slow (especially considering lazy loading considerations)
I would provide 2 different solutions:
In the first you maintain the query you have and you fire a dynamic filter on the collection (maintaining a lazy-loaded collection) and doing a round-trip to the database:
var school = GetSchoolAndStudentsWithDOBAbove(5, dob);
IQuery qDob = nhSession.CreateFilter(school.Students, "where DOB > :dob").SetDateTime("dob", dob);
IList<Student> dobedSchoolStudents = qDob.List<Student>();
In the second solution just fetch both the school and the students in one shot:
object result = nhSession.CreateQuery(
"select ss, st from School ss, Student st
where ss.Id = st.School.Id and ss.Id = :schId and st.DOB > :dob")
.SetInt32("schId", 5).SetDateTime("dob", dob).List();
ss is a School object and st is a Student collection.
And this can definitely be done using the criteria query you use now (using Projections)
Unfortunately s.Students will not contain your "queried" results. You will have to create a separate query for Students to reach your goal.
foreach(var st in s.Students.Where(x => x.DOB > dob))
Console.WriteLine(st.FirstName);
Warning: That will still make second trip to the db depending on your mapping, and it will still retrieve all students.
I'm not sure but you could possibly use Projections to do all this in one query, but I am by no means an expert on that.
You do have the option of filtering data. If it there is a single instance of the query mxmissle option would be the better choice.
Nhibernate Filter Documentation
Filters do have there uses, but depending on the version you are using there can be issues where filtered collections are not cached correctly.