I have a Join looking like:
Join<RepairEntity, RepairAssignEntity> joinRepairAssignEntities = root.join(RepairEntity_.repairAssignEntities, LEFT);
when it converted to SQL it looks like
left outer join
repair_assign repairassi1_
on repairenti0_.id=repairassi1_.repair_id
I want it to be like:
left outer join
repair_assign repairassi1_
on repairenti0_.PARENT_ID=repairassi1_.repair_id
If I add condition to that join:
Predicate equal = cb.equal(root.get(RepairEntity_.parentId), repairEntityPath.get(RepairEntity_.id));
joinRepairAssignEntities = joinRepairAssignEntities.on(equal);
it makes it
left outer join
repair_assign repairassi1_
on repairenti0_.id=repairassi1_.repair_id
and (
repairenti0_.parent_id=repairassi1_.repair_id
)
How do I get rid of
on repairenti0_.id=repairassi1_.repair_id
?
A priori it seems that the entities are not well related, if you want to join by the parent field it should be something like this:
Entity RepairEntity
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="PARENT_ID")
RepairAssignEntity repairAssignEntity;
Entity RepairAssignEntity
#OneToMany(mappedBy="repairAssignEntity", fetch=FetchType.LAZY)
List<RepairEntity> RepairEntities;
if not, you would have to restructure the query to adapt it to the JPA entity modeling.
Regarding to get rid of the condition "on repairenti0_.id = repairassi1_.repair_id" you cannot because it is the implicit one of the relationship defined in the entity, but if you modify the modeling as I indicate, only the condition "repairenti0_.parent_id = repairassi1_ will appear. .repair_id "
Related
i have the following entities
class ProductUnitOfMessure
#ManyToOne
#JoinColumn(name="PRODUCT_ID")
private Product product;
#ManyToOne
#JoinColumn(name="VARIANT_ID")
private Variant variant;
class Product
#OneToMany(mappedBy = "product", fetch = FetchType.LAZY)
List<Variant> variants;
class Variant
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "PRODUCT_ID")
private Product product;
Now when i run the query
select p from ProductUnitOfMeasures p where p.variant.sku=?1 or p.product.sku =?1
(sku of type String).
it return empty list, although there are some as per the below ProductUnitOfMessure table
ProductUnitOfMeasures
when i only run select p from ProductUnitOfMeasures p where p.product.sku =?1
i get some values, why this is happening even though i use OR not AND ?
database is oracle
JPA forces inner joins to be used for references when specifying '.' on a relationship by default. Inner joins automatically exclude nulls from the results - which might be inline with what you might want if you are calling ProductUnitOfMeasures.getVariant().getSku() == youValue as you wouldn't want to deal with NPEs. If you want nulls included in the results, database have what is known as outer join semantics, which allow rows with nulls to be included and still used for the rest of the filter.
Something like:
"select p from ProductUnitOfMeasures p left join p.variant variant left join p.product product where variant.sku = ?1 or product.sku = ?1"
Will allow ProductUnitOfMeasures with either null variant or null product references to still be considered, allowing your ProductUnitOfMeasures with a null variant but with a product.sku value matching your filter to still be returned.
I have 3 entities, infact many more joined together for brevity i'm skipping those and i'm using open jpa 2.2.2 and oracle 11g. Any thoughts what's going wrong here?
Entity SystemRules{
#OneToMany(mappedBy = "systemRule", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private List<ServiceActionMap> serviceActionMap;
}
Entity ServiceActionMap{
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "SYSTEM_RULE_ID")
private SystemRules systemRule;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "RFS_TYPE_ID", nullable = true)
private RfsTypeMap rfsType;
}
Entity RfsTypeMap{
#Id
#Column(name="RFS_TYPE_ID" ,nullable=false)
private BigDecimal rfsTypeId;
#Column(name="RFS_NAME")
private String rfsName;
}
Now, I am trying to order the result by RfsTypeMap.rfsName, i'm creating query using criteria builder in following manner
CriteriaQuery<SystemRules> query = cb.createQuery(SystemRules.class);
Root<SystemRules> root= query.from(SystemRules.class);
root.fetch(SystemRules_.serviceActionMap).fetch(ServiceActionMap_.rfsType, JoinType.LEFT);
My order by clause is like this
cb.desc(cb.upper(systemRules.get("serviceActionMap").get("rfsType").get("rfsName").as(String.class)));
Generate JPQL looks like, where i expect a left outer join clause between ServiceActionMap and RfsTypeMap but it's missing. Same gets translated in SQL and i miss those records which are having ServiceActionMap.rfsType as null value.
SELECT DISTINCT s FROM SystemRules s INNER JOIN FETCH s.serviceActionMap INNER JOIN FETCH s.serviceActionMap INNER JOIN FETCH s.ruleProperty INNER JOIN FETCH s.ruleProperty where ... ORDER BY UPPER(s.serviceActionMap.rfsType.rfsName)
I tried going over several answers here but no success, tried explicitly putting a where clause for ServiceActionMap.rfsType is null as suggested on few answers but it's getting ignored, since join happens before where evaluation. Somewhere this question openJPA outer join on optional many-to-one when have order by clause matches my scenario but not able to generate suggested JPQL through criteria API.
I found one related bug on apache jira https://issues.apache.org/jira/browse/OPENJPA-2318. But, not sure that's the case.
I see that every join is repeated twice and even the join alias is always referring to SystemRules. It might be that orderby has caused the repeating inner joins and we may need to explicitly use Join object to refer to extended column.
CriteriaQuery<SystemRules> query = cb.createQuery(SystemRules.class);
Root<SystemRules> root = query.from(SystemRules.class);
Join<SystemRules, ServiceActionMap> join1 = root.join(SystemRules_.serviceActionMap, JoinType.INNER);
Join<ServiceActionMap, RfsTypeMap> join2 = join1.join(ServiceActionMap_.rfsType, JoinType.LEFT);
query.orderBy(cb.desc(cb.upper(join2.get(RfsTypeMap_.rfsName))));
Say I want to get all rows of the MyEntity that have an id lower than 10. This entity contains a list of Another entity. I would like this list to be fetched only by a subset of the listAnother. This subset containing only Another where the user contained in it is a specific one.
Basically in SQL it would be like this :
SELECT * FROM myentity m
LEFT JOIN another a
ON m.idTable=a.my_entity
AND a.user = "test1"
WHERE m.idTable < 10;
I didn't manage however to translate this query to jpql.
My entities being like this :
#Entity
public class MyEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int idTable;
#OneToMany(mappedBy = "myEntity")
private List<Another> listAnother;
}
#Entity
public class Another implements Serializable {
#Id
private int idAnother;
// bi-directional many-to-one association to Thethread
#ManyToOne(fetch = FetchType.LAZY)
private MyEntity myEntity;
#ManyToOne(fetch = FetchType.LAZY)
private User user;
}
#Entity
public class User implements Serializable {
#Id
private String username;
}
In jpa I could do this :
SELECT m FROM MyEntity where m.idTable < 10;
And then for each entity I get from this list call this:
SELECT a FROM Another Where a.user.username=:'test' AND a.myEntity=:entity;
However I would like to do it all at once in one query. Can I do this with criteria ? I didn't take the time to learn it but if it's possible then I will.
JPQL and Critaria API are equal in terms of what you can express with them. What is possible with JPQL is possible with Criteria and vice versa.
With JPQL, you can simply combine your 2 queries into one in this way:
SELECT a FROM Another a Where a.user.username=:test AND a.myEntity.idTable < 10
You can use dot notation (.) to join multiple entities in the query, provided that the relationship is X-to-one. If you have X-to-many relationship, you need to use JPQL JOIN, which is not very complicated. Example with (LEFT) JOIN:
SELECT m FROM MyEntity m LEFT JOIN m.listAnother a Where a.user.username=:test AND m.idTable < 10
The result is of course not equal - in first case you will get list of Another entities and you can get MyEntity by a.myEntity, in the second case you will get list of MyEntity, which all have at least one Another entity with given user
In hibernate you can use Filters and FilterJoinTable. Here you can read how to do that. Similar problem was solved here.
You need to extend the logic which you applied to check the username (a.user.username=:'test') which was for many-to-one relation between anything and user by taking it one level up to myEntity and then using it for one-to-many relation as well -
SELECT m FROM MyEntity where m.idTable < 10 and (m.listAnother.user.username=:'test')
The join condition "m.listAnother.myEntity=:entity" wouldn't be needed now as in our query we have started from the specific myEntity and then moved down to listAnother.user.username .
I don't have the table definitions to try this query myself, exact SQL may require some tweaks - but logically it should work like the way I showed above, i.e. just the way you joined Another with User, the same way you can join MyEntity with Another by just traversing down the child listAnother.
If you have this entity:
#Entity
public class A {
#ManyToOne
#JoinColumn(name = "bField", nullable = true)
private B myBObject;
}
And I have a generic generator of Criteria who will do that:
Root<A> root = criteria.from(A.class);
root.get("myBObject").get("aFieldInB");
The problem is the following: the generated sql will contains a CROSS JOIN between A and B.
But I would like that the generated sql will contains a LEFT JOIN between A and B.
How can I do that?
You must use a join(). In general it is better to always use a join() for relationships.
See,
http://en.wikibooks.org/wiki/Java_Persistence/Criteria#JoinType
I make a query:
String query = "SELECT DISTINCT a FROM A a FETCH ALL PROPERTIES " +
"JOIN a.Bs AS b " +
"JOIN b.Cs AS c WHERE c = :c";
Query q = DAO.getSession().createQuery(query);
q.setParameter("c", c);
return q.list();
Even though I've said FETCH ALL PROPERTIES on a, when I access all the collections that A has, they still need to be loaded, thus aren't eagerly loaded. They have been defined as lazy loading, and that is the default behaviour I want, but this is the exception: I would like them loaded right now. I've tried swapping JOIN for LEFT OUTER JOIN to provoke Hibernate into loading them, and I've tried setting q.setFetchMode("a", FetchMode.EAGER), but it doesn't exist for Query.
The list of As is quite long, and they have quite a few collections, so making this an n+1 query thing is very slow (about ten seconds, as opposed to doing it in a single query which would be sub-second speed). I'd far prefer one query and loading all that's necessary in that one go. Any suggestions on how I can do that?
PS, Little bonus question: If I replace the "JOIN b.Cs AS c WHERE c = :c";
line with "WHERE :c IN b.Cs";, I get an SQL exception:
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '))' at line 1
The double paranthesis it's referring to is "and ('151000000-0000' in (.))" where 151000000-0000 is the primary key of c. Any idea why I get this error when I do it this way compared to not getting it when I do it the with joining b.Cs in?
UPDATE, as requested, here is the way I use for mapping. B and C are very similarly designed:
#Entity
#Table(name = "tblA")
public class A {
#Id
String AId;
#Column(name = "shortName", length = 12, nullable = false)
String shortName;
#OneToMany(fetch=FetchType.LAZY, mappedBy="theA")
private Set<B> Bs;
#OneToMany(fetch=FetchType.LAZY, mappedBy="theA")
private Set<D> Ds;
#OneToMany(fetch=FetchType.LAZY, mappedBy="theA")
private Set<E> Es;
#OneToMany(fetch=FetchType.LAZY, mappedBy="theA")
private Set<F> Fs;
}
theA in B, D, E and F is defined like this:
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name = "AId", nullable = true)
#ForeignKey(name="FK_KategoriID")
private A theA;
Cheers
Nik
fetch all properties is not what you want; it's used for telling Hibernate that you want it to fetch single-valued lazy-loaded properties. Details are here.
You need to specify join fetch in your query instead:
SELECT DISTINCT a FROM A a
LEFT JOIN FETCH a.Bs AS b
LEFT JOIN FETCH b.Cs AS c
WHERE c = :c
As far as bonus question goes, WHERE :c IN b.Cs is illegal syntax. Depending on how your C is mapped, you may want to look at elements() function instead.
FETCH ALL PROPERTIES only works for lazy properties (where a property is a String, Integer, ...) not one-to-may associations (i.e. collections)
see A Short Primer On Fetching Strategies