In my EclipseLink JPA system I want to access a custom query using a OneToMany. Unfortunately the database is not normalized.
Simplified my DB looks like this:
Company
ID Name
-----------
01 CompanyA
02 CompanyB
Person
ID Company_Id Name Occupation
------------------------------
01 01 Alice Management
02 01 Bob Accounting
03 01 Carl Accounting
The occupation is given as a natural key.
Very simplified I have the following code:
#Entity
class Company {
#Id
#Column private Integer id;
#Column private String name;
#OneToMany()
#JoinColumn(name="Company_Id")
private List<Person> persons;
}
#Entity
class Person {
#Id
#Column private Integer id;
#Column private Integer company_id;
#Column private String name;
#Column private String occupation;
}
So far it is running fine. Now I want to add a OneToMany relationship that that lists the occupations and gives me the possibility to get a list of persons that have that occupation.
I want to do:
for (Company c : myCollectionOfCompanyEntities) {
System.out.println(c.name);
for (OccupationsInCompany o : c.getOccupations()) {
System.out.println(o.name);
for (Person p : o.getPersons()) {
System.out.println(p.name);
}
}
}
How do I start to write that OccupationsInCompany class? If I simply create an entity class, JPA wants to read the data from a table.
I know how to get all the data by hand using custom queries. But is it possible to do this using OneToMany annotations?
You should be able to do this by using the #ElementCollection annotation.
#ElementCollection
#CollectionTable(name = "PEOPLE", joinColumns = {#JoinColumn(name="Company_Id")})
private List<Person> people;
Useful link at WikiBooks.
There are many ways to do this, but unless listing people by the occupation in a company is large part of the application, I don't know that it makes sense to add objects to your model that might affect any other use cases negatively - you will be forced to maintain this model when you may only want it sorted at one point in time.
Other options:
1) take your list of people and sort them by occupation in memory within the getOccupations method. This can then be stored in a transient occupation list so it doesn't need to be repeatedly done.
2)Query for it in one query. "Select c.name, p.occupation, p.name from company c join c.persons p order by c.name, p.occupation where..". This one query gives you everything without the need to traverse your object model. Depending on how often this is needed, you can add options to make either more efficient for your use cases.
Option 1 can be combined with JPA so that the organization list is populated upfront, such in a postLoad method. Any model changes will result in extra complexity and result in JPA doing the same work the model itself can handle just as well if not better.
Related
In a Hibernate application I'm working on a table with hierarchical data (Oracle Database).
For the sake of simplicity I'll put an example of a person with a father (assume the hierarchy is not fixed and can extend for more than 3 levels).
Every Person may own a couple of Cars.
#Entity
class Person
{
#Id
private String id;
#ManyToOne
#JoinColumn(name = "FATHER_ID")
private Person father;
}
#Entity
class Car
{
#Id
private String id;
#ManyToOne
private Person owner;
}
So far whenever we needed to fetch all cars for a person's descendants, we issued a native query fetching all descendants' ids and then a second hql query to fetch the cars themselves (using IN clause with the previously fetched ids as bind parameters).
I don't like it because it stains our code and causing us to issue more queries than we actually need.
Moreover, we are kind of abusing the IN clause, using a different number of bind parameters for many of our queries.
So I decided to try and encapsulate this hierarchical data using Hibernate.
I've tried using #JoinFormula to get the person's descendants as a data member like this:
#Entity
class Person
{
...
#JoinFormula(value = "SELECT p.id FROM Person p START WITH p.id = id CONNECT BY PRIOR p.id = father_id", referencedColumnName = "id")
#OneToMany
List<Person> descendants;
}
Unfortunately it doesn't work, hibernate throws an exception:
ClassCastException: org.hibernate.mapping.Formula cannot be cast to org.hibernate.mapping.Column
I have tried changing the relation to #ManyToMany, it fixes the ClassCastException, but then hibernate just ignores the formula and creates a new table.
Now I'm thinking of using an sql function to retrieve descendants ids and using it inside my hql.
However I'm not quite sure on how to do this...
Any help would be much appreciated!
I'm trying to learn JPA/Hibernate and I'm real green in this field. I tend to usually veer off and try things without knowing much about the API. So I decided to create a simple entity that retrieves information from multiple tables see this is easily implementable with JPA. The reason behind this is, if, hypothetically, the involving tables each has a few hundred columns and we only have a business need to retrieve a very few data, and we only need to focus on retrieval rather than inserts/updates/deletions, I would assume it is best to only retrieve the entire entity (specially if multiple rows need to be returned) then join them across other entities to derive a few columns.
I started up with two simple tables:
EMPLOYEES
EMPLOYEE_ID, EMAIL, DEPARTMENT_ID, MANAGER_ID, FIRST_NAME, etc...
DEPARTMENTS
DEPARTMENT_ID, DEPARTMENT_NAME, MANAGER_ID, etc...
I want my entity to retrieve only the following columns solely based on EMPLOYEES.EMPLOYEE_ID:
EMPLOYEES.EMPLOYEE_ID
EMPLOYEES.MANAGER_ID
EMPLOYEES.DEPARTMENT_ID
DEPARTMENT.DEPARTMENT_NAME
One thing to notice here is that EMPLOYEES.MANAGER_ID is a self-referencing foreign key to EMPLOYEES.EMPLOYEE_ID.
I might create the following...
#SecondaryTable(name="DEPARTMENTS",
pkJoinColumns=#PrimaryKeyJoinColumn(name="managerId",referencedColumnName="employeeId")
)
#Table(name="EMPLOYEES")
#Entity
public class EmployeesDepartment {
#Id
private String employeeId;
private String managerId;
private String email;
private int departmentId;
#Column(name="DEPARTMENT_NAME",table="DEPARTMENTS")
private String departmentDesc;
// Setters and getters
}
Obviously this doesn't give us the correct answer due to the fact that the join between the secondary table (DEPARTMENTS) occurs between its MANAGER_ID and EMPLOYEES.EMPLOYEE_ID, rather than DEPARTMENTS.MANAGER_ID = EMPLOYEES.MANAGER_ID.
I cannot replace referencedColumnName="employeeId" with referencedColumnName="managerId" as managerId of #Entity EmployeesDepartment is not a primary key of EMPLOYEES.
And I can't do the following:
#OneToOne
#JoinColumn(name="managerId",table="DEPARTMENTS",referencedColumnName="employeeId")
private String managerId;
My question is, how can I make my join to be on DEPARTMENTS.MANAGER_ID = EMPLOYEES.MANAGER_ID while the WHERE clause of the query is based on EMPLOYEES.EMPLOYEE.ID? In other word, how can I have the entity that is mapped to the following query:
SELECT
E.EMPLOYEE_ID,
E.MANAGER_ID,
E.DEPARTMENT_ID,
D.DEPARTMENT_NAME
FROM EMPLOYEES E LEFT OUTER JOIN DEPARTMENTS D ON E.MANAGER_ID = D.MANAGER_ID
WHERE E.EMPLOYEE_ID = ?
Or are there better solution with less side effects, e.g. order of updates of tables, loading, etc.?
I'm working on Spring Boot web application which uses Spring Data JPA for its persistance layer. When retrieving entities from repository I'm using Spring's Data JPA Sort object to sort them. It works when I'm sorting by retrieved entity property or by it's #OneToOne relationship object property, but I would like to use it to sort by one of the #OneToMany relationship object properties.
Let's explain with an example: suppose I have entity object Author which has one to many relationship with another entity Book. My entity classes in simplest form would look like this:
#Entity
public class Author {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#OneToMany(mappedBy = "author")
private List<Book> books;
<constructor, getters, setters etc.>
}
and
#Entity
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String title;
#ManyToOne
#JoinColumn(name = "author_id")
private Author author;
<constructor, getters, setters etc.>
}
now, when I'm retrieving authors with Spring's repository interface I'm passing Sort object to it like this one:
new Sort(Sort.Direction.ASC, "id")
which gives me results sorted by author id ascending. I would like to pass something like this:
new Sort(Sort.Direction.ASC, "books.title")
Let's say I have this data in the database (simplified table just to show example):
author | book_title
---------------------
Andrew | Letter C
Barbara | Letter A
Andrew | Letter B
Barbara | Letter D
The resulting list would be Barbara (her book "Letter A" is first after sorting by book title) then Andrew.
Passing new Sort(Sort.Direction.ASC, "books.title") right now results in "Barbara, Andrew, Andrew, Barbara" - which means there are duplicates on resulting list - I would like results to be distinct.
I do not want to use #OrderBy on collection in Author as I'm not interested in actual books order - only the authors.
I do not want to sort results with JPQL on repository level with #Query (it would be probably possible with some JPQL subquery and virtual field maybe), as I need it to be able to accept sortable fileds dynamically (it may be title now, but isbn number on other case and my API should be able to take one or the other).
It has to work with Spring Specification API which I'm using to filter results.
Is it possible?
I had the same question, but I found this answer:
#OneToMany
#OrderBy("value ASC") // sort by value ASC
private List<PropertyDefinition> propertyDefinitions;
Check the answer in this link:
Spring Data JPA sorting on nested collection
It solve my issue.
I have an entity with string id:
#Table
#Entity
public class Stock {
#Id
#Column(nullable = false, length = 64)
private String index;
#Column(nullable = false)
private Integer price;
}
And JpaRepository for it:
public interface StockRepository extends JpaRepository<Stock, String> {
}
When I call stockRepository::findAll, I have N + 1 problem:
logs are simplified
select s.index, s.price from stock s
select s.index, s.price from stock s where s.index = ?
The last line from the quote calls about 5K times (the size of the table). Also, when I update prices, I do next:
stockRepository.save(listOfStocksWithUpdatedPrices);
In logs I have N inserts.
I haven't seen similar behavior when id was numeric.
P.S. set id's type to numeric is not the best solution in my case.
UPDATE1:
I forgot to mention that there is also Trade class that has many-to-many relation with Stock:
#Table
#Entity
public class Trade {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column
#Enumerated(EnumType.STRING)
private TradeType type;
#Column
#Enumerated(EnumType.STRING)
private TradeState state;
#MapKey(name = "index")
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "trade_stock",
joinColumns = { #JoinColumn(name = "id", referencedColumnName = "id") },
inverseJoinColumns = { #JoinColumn(name = "stock_index", referencedColumnName = "index") })
private Map<String, Stock> stocks = new HashMap<>();
}
UPDATE2:
I added many-to-many relation for the Stock side:
#ManyToMany(cascade = CascadeType.ALL, mappedBy = "stocks") //lazy by default
Set<Trade> trades = new HashSet<>();
But now it left joins trades (but they're lazy), and all trade's collections (they are lazy too). However, generated Stock::toString method throws LazyInitializationException exception.
Related answer: JPA eager fetch does not join
You basically need to set #Fetch(FetchMode.JOIN), because fetch = FetchType.EAGER just specifies that the relationship will be loaded, not how.
Also what might help with your problem is
#BatchSize annotation, which specifies how many lazy collections will be loaded, when the first one is requested. For example, if you have 100 trades in memory (with stocks not initializes) #BatchSize(size=50) will make sure that only 2 queries will be used. Effectively changing n+1 to (n+1)/50.
https://docs.jboss.org/hibernate/orm/4.3/javadocs/org/hibernate/annotations/BatchSize.html
Regarding inserts, you may want to set
hibernate.jdbc.batch_size property and set order_inserts and order_updates to true as well.
https://vladmihalcea.com/how-to-batch-insert-and-update-statements-with-hibernate/
However, generated Stock::toString method throws
LazyInitializationException exception.
Okay, from this I am assuming you have generated toString() (and most likely equals() and hashcode() methods) using either Lombok or an IDE generator based on all fields of your class.
Do not override equals() hashcode() and toString() in this way in a JPA environment as it has the potential to (a) trigger the exception you have seen if toString() accesses a lazily loaded collection outside of a transaction and (b) trigger the loading of extremely large volumes of data when used within a transaction. Write a sensible to String that does not involve associations and implement equals() and hashcode() using (a) some business key if one is available, (b) the ID (being aware if possible issues with this approach or (c) do not override them at all.
So firstly, remove these generated methods and see if that improves things a bit.
With regards to the inserts, I do notice one thing that is often overlooked in JPA. I don't know what Database you use, but you have to be careful with
#GeneratedValue(strategy = GenerationType.AUTO)
For MySQL I think all JPA implementations map to an auto_incremented field, and once you know how JPA works, this has two implication.
Every insert will consist of two queries. First the insert and then a select query (LAST_INSERT_ID for MySQL) to get the generated primary key.
It also prevents any batch query optimization, because each query needs to be done in it's own insert.
If you insert a large number of objects, and you want good performance, I would recommend using table generated sequences, where you let JPA pre-allocate IDs in large chunks, this also allows the SQL driver do batch Insert into (...) VALUES(...) optimizations.
Another recommendation (not everyone agrees with me on this one). Personally I never use ManyToMany, I always decompose it into OneToMany and ManyToOne with the join table as a real entity. I like the added control it gives over cascading and fetch, and you avoid some of the ManyToMany traps that exist with bi-directional relations.
I'm using Spring 3.2 with Roo 1.2.3 to build a database-backed Java application via Hibernate. I have several bidirectional OneToMany/ManyToOne relationships among the tables in my database. When I set up the ManyToOne side of the relationship using #JoinColumn (via "field reference" in Roo), a new field whose type is the related entity (the "one" in ManyToOne) is created. However, once this is done, there seems to be no way to access the underlying column value on which the ManyToOne relationship is based. This is a problem when the underlying join column contains data needed by the application (i.e. when the join column contains product stock numbers).
Is there any way to set up my entity class so that the column on which its ManyToOne relationship is based remains accessible without traversing the new join property? How can I define an accessor method for the value of this column?
I've been looking online for an answer to this question for several days, but to no avail. Thanks in advance for your help.
just map the column a second time with insertable=false and updateable=false
To make it more concrete. It's possible to do a HQL-SELCT and restrict a ManyToOne relationship, without any join in the resulting SQL:
Instead of using a join in
session.createQuery("FROM Person person WHERE person.adress.id = 42")
we use can use the adress_idcolumn
session.createQuery("FROM Person person WHERE person.adressId = 42")
This works, if you specify an additional adressId field, which is only used as mapping info for Hibernate:
#Entity
#Access(AccessType.FIELD)
public class Person{
#Id
String id;
#JoinColumn(name = "adress_id")
#ManyToOne(fetch = FetchType.LAZY)
#Nullable
public Adress adress;
#Column(name = "adress_id", insertable = false, updatable = false)
private String adressId;
}
#Entity
#Access(FIELD)
public class Adress{
#Id
String id;
}
The AccessType.FIELD is not needed (But we can leave getters/setters in example). The FetchType.LAZY and #Nullable are also optional, but make it clear when it makes sense to use it. We are able to load Person entities which have a specific Address (we know the address id). But we don't need a join because it's not needed for the WHERE-clause and not for the initial fetch (the address can be fetched lazy).