After struggling for days attempting to get back collections that are linked to a table via a foreign key, I just realized that the tables I am linking to are actually LINKING tables to other tables with the actual data (chock one up for normalized tables).
I am still struggling to get collections out of ManyToOne annotated variables with references to foreign keys, but is there a way I can pull the data back from the table that actually contains the information? Has anyone run into an instance of this?
UPDATE: AS per request I will be posting some code instances... This would be my named query in the entity that I will be calling...
#NamedQuery(name="getQuickLaunchWithCollections", query = "SELECT q FROM QuickLaunch q LEFT JOIN FETCH q.quickLaunchDistlistCollection LEFT JOIN FETCH q.quickLaunchPermCollection LEFT JOIN FETCH q.quickLaunchProviderCollection")})
These would be the collections that I am looking to fill...
#OneToMany(mappedBy="quickLaunchId", fetch=FetchType.EAGER)
private List<QuickLaunchPerm> quickLaunchPermCollection;
#OneToMany(mappedBy="quickLaunchId", fetch=FetchType.EAGER)
private List<QuickLaunchProvider> quickLaunchProviderCollection;
#OneToMany(mappedBy="quickLaunchId", fetch=FetchType.EAGER)
private List<QuickLaunchDistlist> quickLaunchDistlistCollection;
As you can see, I have the fetch type set to eager. So technically, I should be getting some data back? But in actuality those are just linking tables the data that I actually want to pull back. I will need to figure out how to get that data back eventually.
This is how I am calling that named query...
listQL = emf.createNamedQuery("getQuickLaunchWithCollections").getResultList();
Alright, it appears as though LEFT JOIN FETCH
is causing my runtime to throw an expception of some kind. It is pretty unclear as to what it is. But I feel as though I am getting no where with that technique. I am going to try something slightly different.
I would suggest simplifying example, to face the problem, since you are going worldwide now.
Specifying mappedBy="quickLaunchId" attribute, you are saying, that QuickLaunchPerm entity has QuickLaunch as its property named "quickLaunchId". Is this true?
If it is not, then you need to define it in QuickLaunchPerm:
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "QUICK_LAUNCH_ID")
private QuickLaunch quickLaunchId;
//getters setters
Related
I am developing an API where I am confused as to what is the efficient way to handle join query.
I want to join 2 tables data and return the response. Either I can query the database with join query and fetch the result and then return the response OR I can fire two separate queries and then I would handle the join in the API on the fly and return the response. Which is the efficient and correct way ?
Databases are pretty much faster than querying and joining as class instances. Always do joins in the database and map them from the code. Also look for any lazy loading if possible. Cause in a situation like below:
#Entity
#Table(name = "USER")
public class UserLazy implements Serializable {
#Id
#GeneratedValue
#Column(name = "USER_ID")
private Long userId;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
private Set<OrderDetail> orderDetail = new HashSet();
// standard setters and getters
// also override equals and hashcode
}
you might not want order details when you want the initial results.
Usually it's more efficient to do the join in the database, but there are some corner cases, mostly due to the fact that application CPU time is cheaper than database CPU time. Here are a few examples that come to mind, with a query like "table A join table B":
B is a small table that rarely changes.
In this case it can be profitable to cache the contents of this table in the application, and not query it at all.
Rows in A are quite large, and many rows of B are selected for each row of A.
This will cause useless network traffic and load as rows from A are duplicated many times in each result row.
Rows in B are quite large, and there are few distinct b_id's in A
Same as above, except this time the same few rows from B are duplicated in the result set.
In the previous two examples, it could be useful to perform the query on table A, then gather a set of unique b_id's from the result, and SELECT FROM b WHERE b_id IN (list).
Data structure and ORMs
If each table contains a different object type, and they have a "belongs to" relationship (like category and product) and you use an ORM which will instantiate objects for each selected row, then perhaps you only want one instance of each category, and not one per selected product. In this case, you could select the products, gather a list of unique category_ids, and select the categories from there. The ORM may even do that for you behind the scene.
Complicated aggregates
Sometimes, you want some stuff, and some aggregates of other stuff related to the first stuff, but it just won't fit in a neat GROUP BY, or you may need several ones.
So basically, usually the join works better in the database, so that should be the default. If you do it in the application, then you should know why you're doing it, and decide it's a good reason. If it is, then fine. I gave a few reasons, based on performance, data model, and SQL constraints, these are only examples of course.
I am little confused, If i have two tables related then I will have a combined table in MySQL, Since we do not have a class in our project , how would my Typed Query look in order to fetch data from table that is create from Relationship( say One to Many).
eg:
#OneToMany(cascade = CascadeType.ALL)
#JoinTable(name = "CustomerBilling",joinColumns = #JoinColumn(name = "Customer_Id"),
inverseJoinColumns = #JoinColumn(name = "Billing_Id"))
private List<Billing> billing = new ArrayList<>();
with the above mentioned code i will have CustomerBilling table , So i would like to get all the records for a particular customer id. in my test (jUnit) file what Typed Query do i need to put ?
TypedQuery<Customer> a = em.createQuery("select b from CustomerBilling b where b.Customer_Id =?1", Customer.class);
This did not work Since CustomerBilling abstrate schema is not present.
Thanks
Prashanth
I am sure you have classes in your project as it is java based :)
Reading between the lines, you mean, that you have 3 tables in DB Customer, Billing and CustomerBilling, but only 2 entities in JPA the Customer and Billing as the CustomerBilling is there only to store the relation. (But your example of typed query tries to get Customer.class as the result so maybe I am wrong).
You don't reference jointable objects directly (unless they are mapped).
You can query by Customer, or by Billing but not by CustomerBilling. If there is no such entity defined in JPA, there is nothing that JPA can query about.
And you shouldn't try to do it, even for unit testing (BTW jpa tests are integration tests not unit tests).
The whole point of mapping the relationships with #OnteToMany and such is to hide, from the program, the actual DB structure. So you should access Billings of a customer, or customer for a billing, not the relation info.
Currently the relation is stored by join table, but it could be change it to join column and whole program logic would stay the same (and so should the tests).
You should test, that if you add bilings to customer and save them, you correctly retrieves them, that if you remove the biling from customer it disappears, but there is no reason to check the content of the join table.
Finally, if you really must, you can use native sql query to access CustomerBilling table
em.createNativeQuery
or
you can change your mappings, and introdude the CustomerBilling entity, and map your OneToMay on Customer to the CustomerBilling instead of Directly Billing.
I have the following Entities; Ticket contains a set of 0,N WorkOrder:
#Entity
public class Ticket {
...
#OneToMany(mappedBy="ticket", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<WorkOrder> workOrders = null;
...
}
#Entity
public class WorkOrder {
...
#ManyToOne
#JoinColumn(nullable = false)
private Ticket ticket;
}
I am loading Tickets and fetching the attributes. All of the 0,1 attributes present no problem. For workOrders, I used this answer to get the following code.
CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();
CriteriaQuery<Ticket> criteriaQuery = criteriaBuilder
.createQuery(Ticket.class);
Root<Ticket> rootTicket = criteriaQuery.from(Ticket.class);
ListAttribute<? super Ticket, WorkOrder> workOrders =
rootTicket.getModel().getList("workOrders", WorkOrder.class);
rootTicket.fetch(workOrders, JoinType.LEFT);
// WHERE logic
...
criteriaQuery.select(rootTicket);
TypedQuery<Ticket> query = this.entityManager.createQuery(criteriaQuery);
return query.getResultList();
The result is that, in a query that should return me 1 Ticket with 5 workOrders, I am retrieving the same Ticket 5 times.
If I just make the workOrders an Eager Fetch and delete the fetch code, it works as it should.
Can anyone help me? Thanks in advance.
UPDATE:
One explanation about why I am not just happy with JB Nizet's answer (even if in the end it works).
When I just make the relationship eager, JPA is examining exactly the same data that when I make it lazy and add the fetch clause to the Criteria / JPQL. The relationships between the various elements is also clear, as I define the ListAttribute for the Criteria query.
There is some reasonable explanaition for the reason that JPA does not return the same data in both cases?
UPDATE FOR BOUNTY: While JB Nizet's answer did solve the issue, I still find it meaningless that, given two operations with the same meaning ("Get Ticket and fetch all WorkOrder inside ticket.workOrders"), doing them by an eager loading needs no further changes while specifying a fetch requires a DISTINCT command
There is a difference between eager loading and fetch join. Eager loading doesn't mean that the data is loaded within the same query. It just means that it is loaded immediately, although by additional queries.
The criteria is always translated to an SQL query. If you specify joins, it will be join in SQL. By the nature of SQL, this multiplies the data of the root entity as well, which leads to the effect you got. (Note that you get the same instance multiple times, so the root entity is not multiplied in memory.)
There are several solutions to that:
use distinct(true)
Use the distinct root entity transformer (.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)).
When you don't need to filter by child properties, avoid the join
When you need to filter by child properties, filter by a subquery (DetachedCriteria).
Optimize the N+1 problem by using batch-size
Have you tried calling distinct(true) on the CriteriaQuery?
The JPA 2 specification, page 161, says:
The DISTINCT keyword is used to specify that duplicate values must be
eliminated from the query result.
If DISTINCT is not specified, duplicate values are not eliminated.
The javadoc also says:
Specify whether duplicate query results will be eliminated.A true
value will cause duplicates to be eliminated. A false value will cause
duplicates to be retained. If distinct has not been specified,
duplicate results must be retained.
The reason why you don't need the distinct when the association is eagerly loaded is probably just that the association is not loaded using a fetch join, but using an additional query.
I have a many-to-many relationship between 2 tables that I define with a join table.
//MyClassA
#ManyToMany
#JoinTable(name="A_TO_B",
joinColumns=#JoinColumn(name="A_UUID"),
inverseJoinColumns=#JoinColumn(name="B_UUID")
)
private List<MyClassB> classBs = new ArrayList<MyClassB>();
//MyClassB
#ManyToMany(mappedBy="classBs")
private List<MyClassA> classAs = new ArrayList<MyClassA>();
Due to company architecture restrictions, the join table must have a UUID column.
I have a DAO method called associateItems which should insert a row into the join column. In order to test everything, I do the following in a jUnit test.
myDao.associateItems(classAItem, classBItem);
classAItem = myDao.get(classAItem.getUuid());
classBItem = myDao.get(classBItem.getUuid());
assertEquals(1, classAItem.getClassBs().size());
assertEquals(1, classBItem.getClassAs().size());
This test fails on the last 2 lines.
In my DAO, I have tried a number of things. I tried adding the classAItem and classBItem to each others lists and merging them. But this causes an error because the join table object doesn't have a UUID. And I tried creating the join table object and persisting that. But then the last 2 lines of the test fail because it thinks the size of the lists are 0.
I have been sticking with the second solution (fail is better than error).
public void associateItems(MyClassA classAItem, MyClassB classBItem) {
//UUID populated in constructor
AToB association = new AToB(classAItem, classBItem);
entityManager.persist(association);
}
I know that the join table item is getting inserted into the database. I also manually entered a join table item and made sure the lists are populated when an association is found. So the test just has an issue with making the join table item and then associating the relevant lists.
I have tried messing with cascades, fetch types, and flushes. But I can't seem to find a way to make the tests pass.
Anyone know what I need to do to fix this?
This was actually a problem with the get method in the DAO. I added the following hint and test run without failure.
query.setHint(QueryHints.REFRESH, HintValues.TRUE);
I have a query in which I am attempting to get data out of a table. I have foreign keys pointing to other tables and I want that data back as well except I want it in collections of the entities that I will be returning...
try
{
System.out.println("testing 1..2..3");
listQL = emf.createNamedQuery("getQuickLaunch").getResultList();
System.out.println("What is the size of this list: number "+listQL.size());
qLaunchArr = listQL.toArray(new QuickLaunch[listQL.size()]);
}
That works at getting the initial table, but the other collections don't seem to be filling. At least in the test view window that I have... (I am using WID)...
Here are my JPA's...
#OneToMany(mappedBy="quickLaunchId", cascade=CascadeType.ALL)
private List<QuickLaunchPerm> quickLaunchPermCollection;
#OneToMany(mappedBy="quickLaunchId", cascade=CascadeType.ALL)//fetch=FetchType.EAGER)
private List<QuickLaunchProvider> quickLaunchProviderCollection;
#OneToMany(mappedBy="quickLaunchId")//, cascade=CascadeType.ALL)//fetch=FetchType.EAGER)
private List<QuickLaunchDistlist> quickLaunchDistlistCollection;
Would I need a named query that actually joins the tables that I am calling???
That is what I am thinking I will need.
Thanks.
UDPATE: Named query in question
#NamedQuery(name="getQuickLaunch", query = "SELECT q FROM QuickLaunch q")
So would i need to add an inner join (or left join) for every other collection that I want to pull back?