MyBatis Insert a complex object which has associations - sql

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

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.

JPA Criteria 2 embebbed JOINs and condition between them

I'm gonna adapt my problem to a simple example. Lets say I have 2 entities: Employee and EmployeeCompany (the class that defines the relation of a Employee with a Company).
I need to retrieve the Employee entities that are associated with 2 EmployeeCompany entities, and these 2 EmployeeCompany entities must share the same value for a specific attribute (let's call this attribute "X"). EmployeeCompany entities have also embebbed identifiers, so the entities definition would look something like this:
Employee:
Long idEmployee;
List<EmployeeCompany> employeeCompanies;
EmployeeCompany:
// Embebbed ID
EmployeeCompanyId id;
Long X;
EmployeeCompanyId:
Long idEmployee;
Long idCompany;
This is what I'm currently doing to make the 2 JOINs with the EmployeeCompany tables starting from the Employee entity:
Root<T> root = criteria.from(Employee.class);
Join<Object, Object> joinFirstEmployeeCompanyId = root.join("employeeCompanies").join("id");
Predicate finalPredicate = builder.equal(joinFirstEmployeeCompanyId.get("idCompany"), idCompany1);
Join<Object, Object> joinSecondEmployeeCompanyId = root.join("employeeCompanies").join("id");
finalPredicate = builder.and(finalPredicate, builder.equal(joinSecondEmployeeCompanyId.get("idCompany"), idCompany2));
My problem is, I don't know how I can specify now that between these 2 joins, they must meet the X attribute condition (both EmployeeCompany entities must have the same value for the X attribute).
In case it helps, this is what the SQL query would look like:
SELECT * FROM Employee e
INNER JOIN EmployeeCompany ec1 ON ec1.idemployee = e.idemployee AND ec1.idcompany = :idCompany1
INNER JOIN EmployeeCompany ec2 ON ec2.idemployee = e.idemployee AND ec2.idcompany = :idCompany2
WHERE c1.X = c2.X
I solved my issue this way:
Root<T> root = criteria.from(Employee.class);
Join<Object, Object> joinFirstEmployeeCompany = root.join("employeeCompanies");
joinFirstEmployeeCompany.on(builder.equal(joinFirstEmployeeCompany.get("id").get("idCompany"), idCompany1));
Join<Object, Object> joinSecondEmployeeCompany = root.join("employeeCompanies");
joinSecondEmployeeCompany.on(builder.equal(joinSecondEmployeeCompany.get("id").get("idCompany"), idCompany2));
Predicate finalPredicate = builder.equal(joinFirstEmployeeCompany.get("X"), joinSecondEmployeeCompany.get("X"));

Subqueries for each row

Using these tables:
*student {'s_id','s_name,'...} , class {'c_id','c_name',...} student2class {'s_id','c_id'}, grades {'s_id','c_id','grade'}*
Is it possible to perform a query (nested query?) put class name as subtitle and then all students (of that class) and grades, next class name as subtitle ...
The result I need is:
Maths
John .... C
Anna .... B
[...]
Biology
Anna .... C
Jack .... A
[...]
For each row from class I'll have a subquery fetching all data related with this class
No need of any sub-query. You can get your data this way:
SELECT c_name, s_name_, grade
FROM student, class, grades
WHERE student.s_id = grades.s_id and class.c_id = grades.c_id
ORDER BY c_name
The presentation of results depends on your system/tools, as others already said. This is a link to the solution for Microsoft Access:
http://office.microsoft.com/en-001/access-help/create-a-grouped-or-summary-report-HA001159160.aspx
The solution should be implemented in your client side code and not in the database. From database you should just get a simple table formatted data (subject, student, grade)
Then convert the above recordset to the format you want:
For an example in C# you could convert the recordset into lookup
var Lookup = DataSet.Tables[0].Rows.ToLookup(x => x["subjectColumn"]);
now you can loop through the lookup and format your result
foreach (var grade in Lookup)
{
subject = grade.Key;
...
}

Selecting a collection does not result in exception in JPA 2

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.

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.