JPA 2: Order by not working in ManyToMany with extra fields - java

I have a ManyToMany which I have mapped like this question.
Please note, I have removed boilerplate for simplicity
#Entity
class Person {
#OneToMany(mappedBy = "person")
#OrderBy("sort")
private List<PersonAddress> adresses = new ArrayList<>();
}
#Entity
class PersonAdress {
#EmbeddedId
private PersonAdressId id;
#Column
private int sort;
#ManyToOne
private Person person;
#ManyToOne
private Address address;
}
#Entity
class Address {
#OneToMany(mappedBy = "address")
#OrderBy("sort")
private List<PersonAddress> persons = new ArrayList<>();
}
#Embeddable
public class PersonAdressId implements Serializable {
#Column(name = "person_id")
private long personId;
#Column(name = "address_id")
private long addressId;
}
I am trying to get all the adresses for person, and order by the sort attribute.
But for some reason I get exception or I don't get it sorted.
I have tried the following:
"select p from Person p where p.id=pid join fetch p.address a order by a.sort"
I have also tried:
Person person = entityManager.find(Person.class, personId);
person.getAddress() //<-- This should use the #OrderBy, but I don't get it ordered nor does it print out order by in the output
Can anyone spot why its not working?

Did you try following query:
Select p From Person p Left Join p.adresses a Where p.id = :pid Order By a.sort
See the HQL reference for more information.

To start with, your mapping is false : you want a many-to-many association between Person and Address ? and that's why you have that PersonAddress entity in the middle ?
In class Person, it should be :
#OneToMany(mappedBy = "person")
private List<PersonAddress> personAdresses = new ArrayList<>();
In class Address, it should be :
#OneToMany(mappedBy = "address")
private List<PersonAddress> personAddresses = new ArrayList<>();
Only then, your sort will work, as it is a member of PersonAddress entity

Related

Criteria query filtering an entity's OneToMany field's ManyToOne property

I would like to get help with Criteria Query.
I want to get those students who has course in a specific room like 'math lab 1'.
Basically the java entity classes: ( + getters setters)
#Entity
public class Student {
private Long id;
private String name;
#OneToMany(mappedBy = "student")
private List<Course> courses = new ArrayList();
}
#Entity
public class Course {
private Long id;
private String name;
#ManyToOne
private Student student;
#ManyToOne
private Room room;
}
#Entity
public class Room {
private Long id;
private String name;
}
I would do a query like this:
SELECT DISCTINCT(s) FROM Student s
INNER JOIN Course c ON c.student = s
INNER JOIN Room r ON c.room = r
WHERE r.name = ?

Using EntityGraph with SuperClasses and Embedded Attributes

I would like to use the EntityGraph Feature because of the known n+1 Problem. I have the following Entities structure:
#Entity
#Table(name = "customer")
public class Customer extends Person {
#Column(name = "foo")
public String foo;
#Column(name = "bar")
public String bar;
}
#Entity
#Table(name = "person")
public class Person {
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "car.id")
public Car car;
#Embedded
public Key key;
}
#Entity
#Table(name = "car")
public class Car {
#Column(name = "a")
public String a;
#Column(name = "b")
public String b;
}
#Embeddable
public class Key
{
#Column(name = "key_id")
public Long keyId;
#Column(name = "key_color")
public String keyColor;
}
Now I want to use a NamedEntityGraph. As far as I understand with "#NamedEntityGraph(name = "getCustomer", includeAllAttributes=true)" it should work but it doesnt.
The NamedEntityGraph call with
em.createQuery(criteriaQuery).setHint("javax.persistence.fetchgraph", em.getEntityGraph("getCustomer")).getResultList()
returns the amount of Customers in the database but all Attributes including car and the Embedded Attribute key is always null.
Do I have to use subgraphs? I tried to declare the NamedEntityGraph on Customer class also on Person class. It makes no difference.
EDIT:
After struggling a long time with this problem, i tried to break down it to the lowest level with these two entities
#Entity
#Table(name = "publication")
#NamedEntityGraph(name = "graph.Publication.articles",
attributeNodes = #NamedAttributeNode("articles"))
public class Publication {
#Id
private String publicationId;
private String name;
private String category;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "publicationId")
private List<Article> articles;
#Entity
#Table(name = "article")
public class Article {
#Id
private String articleId;
private String title;
private String publicationId;
}
If i create a query i can see further more than one query in the postgres log.
EntityGraph<?> entityGraph = em.getEntityGraph("graph.Publication.articles");
List resultList = em.createQuery("SELECT x FROM Publication x").setHint("javax.persistence.fetchgraph", entityGraph).getResultList();
Different queries for all publications
SELECT ARTICLEID, publicationId, TITLE FROM article WHERE (publicationId = $1) parameters: $1 = 'publication_1'
SELECT ARTICLEID, publicationId, TITLE FROM article WHERE (publicationId = $1) parameters: $1 = 'publication_2'
But I would only have expected one query with a join here.
Finally I found a solution for my problem. I refer to the edited part of my question.
I found this page which describes very well how to use batch query hints to improve performance.
http://java-persistence-performance.blogspot.com/2010/08/batch-fetching-optimizing-object-graph.html?m=1
For my example I don't need the entitygraph anymore. The query should created like this
List resultList = em.createQuery("SELECT x FROM Publication x").setHint("eclipselink.batch", "x.articles").getResultList();

Refresh one-to-many relationship after child was added

So I have two entities:
Person.java
#Entity
#Table(name = "persons")
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#OneToMany(mappedBy="person", cascade = CascadeType.REMOVE)
#JsonIgnoreProperties("person")
private Set<Address> addresses;
//getters and setters
}
Address.java
#Entity
#Table(name = "addresses")
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#NotNull
#ManyToOne
#JoinColumn(name = "person_id", nullable = false)
#JsonIgnoreProperties("addresses")
private Person person;
//getters and setters
}
And later on in my code I have a personDb object (already saved in database) and then I add Address:
Address address = new Address();
address.setPerson(personDb);
address = addressRepository.save(address);
and now I have address object with person object attached to it but my personDb still doesn't have any addresses attached to it. Even if I try to get it from database once more:
personRepository.findOne(personDb.getId());
I have null where should be set of addresses. I also tried changing my annotation in Person class to something like this:
#OneToMany(mappedBy="person", fetch = FetchType.EAGER, cascade = CascadeType.REMOVE)
or changing CascadeType to ALL but nothing helps. What can I do to load addresses to my personDb object after they were added to database?
This is not best solution, but try to add also new address to personDB.
Address address = new Address();
address.setPerson(personDb);
personDB.addAddress(address);
personRepo.save(personDB)
Make sure the person is persisted.
For that make an integration test for it. If you are using Spring, I also suggest you use in-memory db for your tests.
#Transactional
#Test
public void testFindOneAddress(){
// persist the person
Person person = new Person();
...
personRepository.save(person);
// persist the address
Address address = new Address();
address.setPerson(person);
addressRepository.save(address);
// find the persisted person and addresses
Person queryResult= personRepository.findOne(person.getId());
assertNotNull(queryResult);
assertNotNull(queryResult.getAddresses());
assertEquals(1, queryResult.getAddresses().size());
assertEquals(address.getId(), queryResult.getAddresses().get(0).getId());
}
Also make sure you person id column is called "person_id"
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "person_id")
private Integer id;
take it from there

Hibernate HQL. ManyToMany in subquery

I have following entities:
#Entity
public class Company {
....
#OneToMany
private List<Product> products = new Arraylist<>();
....
}
#Entity
public class Product {
....
#Column(name="product_key")
private String productKey; // same value as in ProductCategory
....
}
#Entity
public class ProductCategory{
....
#Column(name="product_key")
private String productKey // same value as in Product
#ManyToMany
#JoinTable (...)
private List<Category> categories = new ArrayList<>();
....
}
I want to write query which will return companies with their corresponding categories:
Company - List<Category>
I.e. I want aggregate categories of each Product company have.
Currently I end up with this HQL query, but it doesn't work:
SELECT DISTINCT c,
(SELECT pc.categories
FROM ProductCategories pc
LEFT JOIN c.products products
WHERE pc.productKey IN products.productKey)
FROM Company c
I tried to add virtual List<Categories> field to Company entity using #JoinFormula, but without success (#JoinFormula can not be used with List types, only with single values)
I think Your DB design is making the solution hard.
Since company & product has a one to many relationship, the owner of the relation should be product. Therefore you could keep a reference to the company inside each product.
If I were you, I would go with a design like this.
Company Class
#Entity
public class Company {
#OneToMany(mappedBy="company", cascade = CascadeType.ALL)
private List<Product> products = new ArrayList<Product>();
#Id
private int companyId;
}
Product Class
#Entity
public class Product {
#Id
#Column(name = "product_key")
private String productKey; // same value as in ProductCategory
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "company", nullable = false)
private Company company;
#ManyToMany(cascade = CascadeType.ALL, mappedBy="products")
private List<ProductCategory> categories = new ArrayList<ProductCategory>();
}
ProductCategory Class
#Entity
public class ProductCategory {
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name="ProductWiseCategory", joinColumns=#JoinColumn(name="cat_name"),
inverseJoinColumns=#JoinColumn(name="product_key") )
private List<Product> products = new ArrayList<Product>();

Why does this one-to-many mapping fails when using mappedBy?

#Entity
public class Person implements Serializable{
#Id
#GeneratedValue
private int id;
private String name;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "person")
private List<Car> cars;
//getters and setters
}
#Entity
public class Car implements Serializable{
#Id
#GeneratedValue
private int id;
private String name;
#ManyToOne
#JoinColumn(name = "person_id")
private Person person;
// getters and setters
}
And.. I use it thus..
Person per = new Person();
per.setName("some");
Car car1 = new Car();
car1.setName("Ford");
Car car2 = new Car();
car2.setName("WagonR");
//s.save(car1);
//s.save(car2);
per.setCars(new ArrayList<Car>());
per.getCars().add(car1);
per.getCars().add(car2);
s.save(per);
Now.. the table Car has a column person_id but its showing null for both the cars.. what am I doing wrong here ? The table Person is correctly being filled. If I remove the "mappedBy" from the Person table.. and instead include the #JoinColumn here... then it works fine.
With your mapping, the owner of the relation is the Car and not the Person ... That's why when saving the Person, the Car is not saved. If you remove the mappedBy then the owner of the relation becomes the Person and you get your expected behavior!
Try calling car.setPerson(per).

Categories