Mapping NativeQuery results when using inner join - java

I have the follow situation:
#Entity
#XmlRootElement
#Table(name = "TB_A")
public class A implements Serializable {
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "CD_B")
private B b;
}
#Entity
#XmlRootElement
#Table(name = "TB_B")
public class B implements Serializable {
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "CD_B")
#JsonIgnore
private Set<C> c = new HashSet<>();
}
#Entity
#XmlRootElement
#Table(name = "TB_C")
public class C implements Serializable {
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "CD_B")
private B b;
}
I need to run the follow code:
String sql = "SELECT * FROM TB_A a "
+ "INNER JOIN TB_B b ON ... "
+ "LEFT JOIN TB_C c ON ... ";
Query query = em.createNativeQuery(sql, A.class);
List<A> AList = query.getResultList();
for(A a : AList) {
List<c> CList = a.getB().getC();
}
Analising the executed queries, I notice that JPS is running a SELECT each time I access elements B and C.
How can I correctly map this nativeQuery to use the lazyLoad?
Obs: I MUST use NativeQuery, because in my WHERE clause I need to use an especific function from Oracle. So JPQL is not an option for me (If I could use it, my life would be much more easy!).

Short answer: you can't.
What this line will do:
Query query = em.createNativeQuery(sql, A.class);
Is execute your SQL, then throw away anything not related to A.class.
Then your for loop:
for(A a : AList) {
List<c> CList = a.getB().getC();
}
Fetches everything again.
Here you use JPA again, which doesn't know anything about the query you executed before.
In some cases, you can try to map to C.class instead.
Then you'll be able to avoid the loop entirely.
If in your case you need both A.class and C.class, you can look into ResultSetMapping instead: http://www.java2s.com/Tutorial/Java/0355__JPA/SqlResultSetMappingForHierarchicalEntity.htm

Related

Spring data CrudRepository findBy relationships

I want to select all Foo entities that have any Bars that have status new.
Here's what I tried:
#Entity
#Table(name = "FOO")
class Foo {
#Id
#Column(name = "ID")
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
#OneToMany
private Set<Bar> bars;
//...
}
public interface FooRepository extends CrudRepository<Foo, Long> {
#Query("select m from Foo f where f.bars.status = 'NEW' ")
public Page<Foo> findByBarStatus(Pageable pageable);
}
But I get :
org.hibernate.exception.SQLGrammarException: could not prepare statement
I also tried writing join statement instead:
select f from Foo f inner join f.bars b where b.status = 'NEW'
SELECT f FROM Foo AS f JOIN f.bars AS b WHERE b.status = 'NEW'
I think you're missing something like this
#OneToMany(mappedBy = "foo")
private Set<Bar> bars;
joining for one-to-many relationship can sometimes be tricky. (There is another answer doing it right, that you can refer to)
In this case, you may do it in reverse (assuming you have the relationship being bi-directional), by looking up from the Bar side:
select bar.foo from Bar bar where bar.status = 'NEW'

Hibernate join cannot cast exception many to many

I got a question regarding hibernate.
I have to classes with many to many relationship and try to make a select. The problem is I am getting an exception. Can you help?
#Entity(name = "pracownik")
#Inheritance(strategy = InheritanceType.JOINED)
#Proxy(lazy = false)
public class Pracownik extends Osoba {
#ManyToMany(fetch = FetchType.EAGER, cascade = { CascadeType.ALL })
#JoinTable(name = "grafikpracownika", joinColumns = { #JoinColumn(name = "idosoby") },
inverseJoinColumns = { #JoinColumn(name = "idgrafiku") })
private List<Grafik> grafiki = new ArrayList<>();
}
The second entity
#Entity (name = "grafik")
#Proxy(lazy = false)
public class Grafik {
#ManyToMany (mappedBy = "grafiki",cascade = { CascadeType.ALL })
private List <Pracownik> pracownik = new ArrayList<>();
}
The method I developed is:
public List<Pracownik> getWszyscyPracownicyGrafiku() {
List<Pracownik> pracownicy = new ArrayList<>();
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
Transaction transaction = session.beginTransaction();
Query query = session.createQuery("from pracownik as p join p.grafiki");
pracownicy = query.list();
transaction.commit();
return pracownicy;
}
And the exception is:
Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to model.Pracownik
Any idea what is wrong?
I also would like to add a "where" but it should be easy after I get rid of this exception.
I also tried with "normal" sql
SELECT * from pracownik p join grafikpracownika g on p.idosoby = g.idosoby where idgrafiku = 6
but what I am getting is:
org.hibernate.loader.custom.NonUniqueDiscoveredSqlAliasException: Encountered a duplicated sql alias [idosoby] during auto-discovery of a native-sql query
You're selecting from two tables without explicit select clause, therefore Hibernate produces a list of tuples (Pracownik, Grafik) as a result.
If you want only Pracowniks (i.e. join is needed to create condition on p.grafiki in where), use
select distinct p from pracownik as p join p.grafiki
If join is used to instruct Hibernate to fetch associated Grafiks, use join fetch:
from pracownik as p join fetch p.grafiki

Column index out of range when creating alias with criteria

Ok so I get this weird issue that I can't fix.
I have 3 entities ( i will write things that only matters imo)
#Data
#Entity // all # are in javax
#Table(name = "a", schema = "pl")
#SequenceGenerator(...)
public class A extends BaseEntity {
#OneToMany(mappedBy = "pk.a")
private Set<ABRel> Bs = new HashSet<ABRel>();
}
#Getter
#Setter
#Entity
#Table(name = "a_b_rel", schema = "pl")
public class ABRel implements IEntity {
#EmbeddedId
private OfferOrderProjectRelId pk;
public OfferOrderProjectRel(B b, A a) {
if (a == null || b == null) {
throw new IllegalArgumentException("B orA equals null");
}
B.addA(this); // this methods just adds ABRel to sets in A and B
A.addB(this);
pk = new ABRelId(b, a);
}
}
#Getter
#Setter
#Embeddable
#EqualsAndHashCode
public class OfferOrderProjectRelId implements Serializable {
#ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.REFRESH })
#JoinColumn(name = "b_id")
private B b;
#ManyToOne(cascade = { CascadeType.PERSIST, CascadeType.REFRESH })
#JoinColumn(name = "a_id")
private A a;
public ABRelId(B b, A a) {
setB(b);
setA(a);
}
}
#Data
#Entity (it has javax import)
#Table(name = "b", schema = "pl")
#SequenceGenerator(...)
public class B extends BaseEntity {
#OneToMany(mappedBy = "pk.b")
private Set<ABRel> As = new HashSet<ABRel>();
#NotBlank
#Length(max = 10000)
#Column(name = "type", length = 10000, nullable = false)
private String type;
}
ABRel and ABRelId have private contructor (ABRel() and ABRelId()) but not sure if it matters. Entities are working just fine, so I don't think somethink is wrong with them but meaby I am wrong.
So I'm tryin to add criteria by B.type for my filters. Criteria are made "in" (not sure how to say it :) ) A.class. So here's criteria that I'm tryin to add in my dao ( I can add this to criteria not detached one if someone ask):
DetachedCriteria idCriteria = DetachedCriteria.forClass(A.class, "a");
idCriteria.createAlias("Bs", "btype", JoinType.LEFT_OUTER_JOIN, Restrictions.eq("Bs.pk.B.type", "someType"));
Criteria criteria = getSession().createCriteria(A.class, "a");
criteria.add(Subqueries.propertyIn("id", idCriteria));
What I am tryin to achieve is to get all ABRels that have some specified B.type, then I will have to count it somehow, but this is not my issue atm. I have to use criteria, can't use any HQL. I also read that hibernate has some kind of bug with creating alias beetwen entity and its embedded so I can't make it too (probably thats why I am having to much trouble with it). So any ideas? I'm running out of option so any help would be great!
I almost forget, I'm getting this error
org.hibernate.HibernateException: Unknown entity: null at
org.hibernate.loader.criteria.CriteriaQueryTranslator.getPropertyMapping(CriteriaQueryTranslator.java:638)
at
org.hibernate.loader.criteria.CriteriaQueryTranslator.getType(CriteriaQueryTranslator.java:587)
at
org.hibernate.loader.criteria.CriteriaQueryTranslator.getTypeUsingProjection(CriteriaQueryTranslator.java:569)
at
org.hibernate.loader.criteria.CriteriaQueryTranslator.getTypedValue(CriteriaQueryTranslator.java:627)
at
org.hibernate.criterion.SimpleExpression.getTypedValues(SimpleExpression.java:100)
at
org.hibernate.loader.criteria.CriteriaQueryTranslator.getQueryParameters(CriteriaQueryTranslator.java:335)
at
org.hibernate.criterion.SubqueryExpression.createAndSetInnerQuery(SubqueryExpression.java:151)
at
org.hibernate.criterion.SubqueryExpression.toSqlString(SubqueryExpression.java:68)
UPDATE:
I have added sth like this
criteria.createAlias("As", "oorel", JoinType.LEFT_OUTER_JOIN);
criteria.createAlias("oorel.pk.b", "order", JoinType.LEFT_OUTER_JOIN, Restrictions.eq("type", "order"));
And now I'm getting new error(it's in my native language so i will try to translate it) its postgres and hibernate exception :
Column index out of range: 1, number of columns: 0
Sorry for my bad english and thank you in advance.
This is unfortunetly a hibernate bug which havent been fixed yet. This error appears because hibernate is not able to create alias via entity that contains composite key.

JPA multiple queries instead of one

I have two entities:
#Entity
#Table(name = "ACCOUNT")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class MyCloudAccount implements Serializable {
...
#OneToMany(mappedBy = "account", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<ServerInstance> servers = new HashSet<ServerInstance>();
...
}
#Entity
#Table(name = "SERVER_INSTANCE")
public class ServerInstance implements Serializable {
...
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ACCOUNT_ID")
private MyCloudAccount account;
...
}
I am getting all accounts by this code:
StringBuilder sql = new StringBuilder();
sql.append("SELECT e FROM ");
sql.append(persistentClass.getName());
sql.append(" e");
return entityManager.createQuery(sql.toString()).getResultList();
And this produces one query for the account and N queries for the servers instead of one with outer join. How to force JPA to make the query in optimal way?
I find it more convenient to use Java Persistence Query Language
you can do:
#NamedQueries{
#NamedQuery(name="myQuery" query="SELECT a FROM MyCloudAccount JOIN FETCH a.servers")
}
public class MyCloudAccount{
...
}
then you can do
TypedQuery<MyCloudAccount> query = em.createNamedQuery("MyCloudAccount.myQuery", MyCloudAccount.class);
List<MyCloudAccount> results = query.getResultList();
EDIT
You are actually already using JPQL. The key thing to your problem is using the JOIN FECTH command.

JPQL ManyToMany select

I have a Many To Many relationship between two entities called: Car and Dealership.
In native MySQL I have:
car (id and other values)
dealership (id and other values)
car_dealership (car_id and dealership_id)
And the query I want to make in JPQL is:
#Select List of cars in multiple dealerships
SELECT car_id FROM car_dealership WHERE dealership_id IN(1,2,3,56,78,999);
What is the proper way to make the JPQL equivalent?
My Java method signature is:
public List<Car> findByDealership(List<Dealership> dealerships);
I have tried
//TOTALLY WRONG QUERY CALL!!!
Query query = em.createQuery("SELECT c FROM Car c WHERE :dealer_ids IN c.dealerships");
List<Long> dealerIds = new ArrayList<Long>();
for(Dealership d : dealerships) {
dealerIds.add(d.getId());
}
query.setParameter(":dealer_ids", dealerIds);
List<Dealership> result = (List<Dealership>) query.getResultList();
return result;
}
Here is my JPA Annotations for such relationship in java:
#Entity
#Table(name = "car")
public class Car implements Serializable {
//Setup of values and whatnot....
#ManyToMany
#JoinTable(name = "car_dealership", joinColumns =
#JoinColumn(name = "car_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "dealership_id", referencedColumnName = "id"))
private List<Dealership> dealerships;
... other stuff (getters/setters)
}
#Entity
#Table(name = "property")
public class Dealership implements Serializable {
//Setting of values and whatnot
#ManyToMany(mappedBy = "dealerships")
private List<Car> cars;
.... other stuff(getters/setters)
}
EDIT
I also have tried:
Query query = em.createQuery("SELECT c FROM Car c INNER JOIN c.dealerships d WHERE d IN (:deals)");
query.setParameter("deals", dealerships);
Which threw the Error:
org.eclipse.persistence.exceptions.QueryException
Exception Description: Object comparisons can only use the equal() or notEqual() operators.
Other comparisons must be done through query keys or direct attribute level comparisons.
Expression: [
Relation operator [ IN ]
Query Key dealerships
Base stackoverflow.question.Car
Constant [
Parameter deals]]
select car from Car car
inner join car.dealerships dealership
where dealership in :dealerships
The parameter must be a collection of Dealership instances.
If you want to use a collection of dealership IDs, use
select car from Car car
inner join car.dealerships dealership
where dealership.id in :dealershipIds
Remamber that JPQL always use entities, mapped attributes and associations. Never table and column names.

Categories