How to replace a #JoinColumn with a hardcoded value? - java

For more context, please see my other question.
I want to join my Employee entity class to Code entity class but the Code PK is composite (DOMAIN, CODE) because it lists many different code domains, yet the codes that go in the Employee class/table are all from within a single domain, eliminating the need to have a domain field in the Employee class/table (because it would always be the same).
Can I join Employee to Code by using the CODE field in the Employee table and a hardcoded value (e.g. EMP_TYPE) instead of a redundant column?
If my Employee class/table did indeed have that redundant column, I would join it like this:
#JoinColumns({
#JoinColumn(name = "EMP_TYPE_CODE", referencedColumnName = "CODE"),
#JoinColumn(name = "DOMAIN", referencedColumnName = "DOMAIN)})
#ManyToOne(optional = false)
private Code sharedStatute;
but I REALLY don't want to have that extra column in the DB and in the class because it will always be the same.
What I am trying to accomplish would be equivalent to the following in SQL:
SELECT e.emp_id, e.first_name, e.last_name, c.description as emp_type
FROM Employee e JOIN Code c
ON e.emp_type_code = c.code
WHERE c.domain = 'EMP_TYPE'
as opposed to adding a field domain in the Employee table and populating EVERY SINGLE RECORD with the same value ('EMP_TYPE') and then doing:
SELECT e.emp_id, e.first_name, e.last_name, c.description as emp_type
FROM Employee e JOIN Code c
ON e.emp_type_code = c.code
AND e.domain = c.domain
The former is more efficient because it saves me from having to have a redundant field. So what I am trying to do is the same thing but in JPA.
Some of you may say something to the effect of "why not have a separate lookup table for each code" but I think that's a terrible idea and causes cluttering of DB tables and corresponding application entities. It is much better to have a single code lookup table partitioned by code type (or domain).

This should work?
#JoinColumn(name = "EMP_TYPE_CODE", referencedColumnName = "CODE")
#Where(clause = "domain = 'EMP_TYPE'")
#ManyToOne(optional = false)
private Code sharedStatute;

Note: Apparantly this solution is OpenJPA only.
There is a way to do this with javax.persistence annotations only:
#JoinColumns({
#JoinColumn(name = "EMP_TYPE_CODE", referencedColumnName = "CODE"),
#JoinColumn(name = "CODE_TABLE_NAME.DOMAIN", referencedColumnName = "'EMP_TYPE'")})
#ManyToOne(optional = false)
private Code sharedStatute;
Note the single quotes in the referencedColumnName, this is the String value you are looking for.
There is a downside however, when you have multiple Code objects in your entity, the join on DOMAIN will be done only once in jpa, giving bad results. At the moment I circumvent this by making those field load lazily, but that's not ideal.
More information here.

Related

Duplicate items in a list attribute of JPA and Hibernate entity

The problem which i am trying to solve is avoid duplicate items inside a list attribute in hibernate.
Consider the below domain.
public class Account
{
#OneToMany(fetch = FetchType.LAZY)
#JoinTable(name = "FI_COMPANY_ACCOUNT", joinColumns = #JoinColumn(name = "ACCOUNT_ID", referencedColumnName = "ID"), inverseJoinColumns = #JoinColumn(name = "COMPANY_ID", referencedColumnName = "ID"))
private List<Company> companies;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "account", cascade = CascadeType.ALL, orphanRemoval = true)
private List<AccountDesc> accountDescList;
}
public class Company {}
public class AccountDesc
{
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "PARENT_ID", referencedColumnName = "ID")
private Account account;
}
I use a Criteria API to fetch Account. In the query i perform fetch using left join for companies and inner join for accountDescList attribute. This help me to get both attributes in first select, and which avoid further selects.
Root<Account> root = criteriaQuery.from(Account.class);
root.fetch("companies", JoinType.LEFT);
root.fetch("accountDescList");
I know the root entity (here Account) can be repeated in the results. I can solve the issue using multiple ways like,
http://in.relation.to/2016/08/04/introducing-distinct-pass-through-query-hint/
https://howtoprogramwithjava.com/how-to-fix-duplicate-data-from-hibernate-queries/
But issue i face is the attribute companies inside the Account has also duplicate entities. This happen if we have more than one entry for accountDescList.
To solve the issue of duplicates in the attribute companies, I feel only solution is to use Set. Could you please clarify on the below questions.
Is there a way other than using Set (for the attribute companies), to solve this issue.
Even if i use can i instruct hibernate to use OrderedSetType (which uses LinkedHashSet). So that i can retain the order of the items as it returned from database. Unfortunately I do not have a attribute to use in OrderBy. I need the whatever default order returned by database.
Thanks in advance.
But the issue I face is the attribute companies inside the Account has also duplicate entities.
That shouldn't happen unless you have duplicate Company entities assigned to the same account.
Using DISTINCT in the Criteria API query will remove root duplicates. However, in your case, it's not worth using JOIN FETCH on both #OneToMany relations since this will cause a Cartesian Product.
You should fetch at most one collection at a time, and maybe use #Subselect fetching for the second collection.
I think that it is much better use Set because a set doesn't allow elements duplicated, also you can overwrite equals method of Company and put it on what fields will be validated when two elements are equals.
The other way would be in setCompanies(List companies) method you can make something logic before this.companies = companies.stream().distinct().collect(Collectors.toList()); or
this.companies = new ArrayList<>(new HashSet(companies)) ;

Load all lazy collection at once with hibernate/spring

I have a problem that loading my lazy collections produces a lot of SQL-Statements and I wonder if there is no more efficient way of loading the data.
Situation:
Parent has a lazy collection of Child called children. It is actually a Many-To-Many relation.
I load a list of Parents with a CrudRepository and I need to get all child_ids for each Parent. So every time I access the children collection i executes a new SQL-Statement.
If i load 200 Parents there are 201 queries executes (1 for the list of Parents and 1 for each Parent's children).
Any idea how i can load the data with just one query?
EDIT 1
Parent/Child is probably a bad naming here. In fact i have a Many-To-Many relation.
Here is some code:
#Entity
public class Tour {
#Id
#GeneratedValue(generator = "system-uuid")
#GenericGenerator(name="system-uuid",
strategy = "uuid2")
#Column(length = 60)
private String id;
#ManyToMany
#JoinTable(
name="parent_images",
joinColumns = #JoinColumn(name="tour_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name="image_id", referencedColumnName = "id"),
foreignKey = #ForeignKey(name = "FK_TOUR_IMAGE_TOUR"),
inverseForeignKey = #ForeignKey(name = "FK_TOUR_IMAGE_IMAGE")
)
private List<Image> images = new ArrayList<>();
}
#Entity
public class Image {
#Id
#GeneratedValue(generator = "system-uuid")
#GenericGenerator(name="system-uuid",
strategy = "uuid2")
#Column(length = 40)
private String id;
//....
}
// Code to fetch:
#Autowired
TourRepository repo;
List<Tour> tours = repo.findBy(....);
List<String> imageIds = new ArrayList<>();
for(Tour tour : tours){
imageIds.addAll(tour.getImages().stream().map(b -> b.getId()).collect(Collectors.toList()));
}
As another answer suggested, JOIN FETCH is usually the way to solve similar problem. What happens internally for join-fetch is that the generated SQL will contains columns of the join-fetched entities.
However, you shouldn't blindly treat join-fetch being the panacea.
One common case is you want to retrieve entities with 2 One-To-Many relationships. For example, you have User, and each User may have multiple Address and Phone
If you naively do a from User user join fetch user.phones join fetch users.addresses, Hibernate will either report problem in your query, or generate a inefficient query which contains Cartesian product of addresses and phones.
In the above case, one solution is to break it into multiple queries:
from User user join fetch user.phones where .... followed by from User user join fetch user.addresses where .....
Keep in mind: less number of SQL does not always means better performance. In some situation, breaking up queries may improve performance.
That's the whole idea behind lazy collections :)
Meaning, a lazy collection will only be queried if the getter for that collection is called, what you're saying is that you load all entities and something (code, framework, whatever) calls the getChildren (assumption) for that entity; This will produce those queries.
Now, if this is always happening, then first of all, there's no point in having a lazy collection, set them as EAGER. - EDIT: as said in the comments, EAGER is rarely the solution, in this case in particular it definitely does not seem like it, the join is though :)
Either way, for your case that won't help, what you want is to load all data at once I assume, for that, when you do the query you have to make the join explicit, example with JPQL:
SELECT p FROM Parent p LEFT JOIN FETCH p.children

N + 1 when ID is string (JpaRepository)

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.

ManyToOne relation in Hibernate, not in Database

I have an entity named 'Department' and another entity named 'student'. I know the department will have many students and there shoulld be relation between these two tables in database. But in my project, the DB tables are already there and there is no relation (foreign key) between department and student tables.
In entity class, student.java , there is a relation written as,
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = 'DeptId', nullable = false, insertable = false, updatable = false)
Department department
I am confused about this existing code.
When I wrote a test, I am fetching the department from DB by deptId and set the student entity as,
student.setDepartment(department);
This doesn't populate the DB column 'DEPTID' in student table.
Since there's no student collection in Department, I cannot set the student as,
department.addStudents(student);
I am not sure whether we can insist a #ManyToOne relation without a relation between the tables in DB.
Please let me know how I can fix this issue so that the 'DEPTID' column in student table is populated with the correct data.
Thanks in advance,
BS
you r having
#JoinColumn(name = 'DeptId', nullable = false, insertable = false, updatable = false)
instead why dont you try
#JoinColumn(name = 'DeptId', nullable = false)
Hibernate wont check whether the mapping constraints that you are putting are valid at db level. It just assumes it is valid and executes queries based on that assumption.
Hi sorry for responding to your question so late but I think the reply could equally help another person. Now you said the tables existed already in the database, if they haven't yet got some data then I suggest you drop them, activate your Table Generation Strategy in your persistence.xml file to Create, in that case, it will recreate those tables with your desired relationship columns. Do not also forget to use the #OneToMany annotation on the Department.java class to indicate its capabilities of reception of many students. It is used together with the #ManyToOne

How do I create an index on join tables using Hibernate annotations?

I have a relationship as follows using Hibernate annotations, this is what I tried:
public class Job{
...
#OneToMany(cascade = CascadeType.ALL)
#JoinTable(name = "jobs_resource_locations")
#ForeignKey(name = "job_inputs_fk")
#Index(name="job_inputs_fk")
private List<FileSystemLocation> inputs;
This sort of thing works nicely on ManyToOne like so:
#ManyToOne
#JoinColumn(name = "service_call_id", referencedColumnName = "id")
#ForeignKey(name = "job_service_call_fk")
#Index(name = "job_service_call_fk")
private ServiceCall serviceCall;
I wanted to ensure that the foreign key gets indexed on PostgreSQL and that the schema looks similar on MySQL, hence the #ForeignKey and #Index with the same name (MySQL always creates an index with the same name as the FK).
I cannot create the index on the inverse side because FileSystemLocation is unaware of the relationship. Hence the JoinTable.
The former example fails since Hibernate finds no column in Job to index:
org.hibernate.MappingException: Unable to find logical column name from physical name null in table jobs
Does anyone know how to create indices on JoinTable foreign keys using Hibernate?
It's not exactly the answer you would like to receive, but this is the expected behavior. In other words: this is not supported. See the following JIRA for more details:
https://hibernate.atlassian.net/browse/HHH-4263

Categories