Performing join given complex entity relationships in hibernate - java

I'm having difficulty establishing an association between two entities in Hibernate. The relationship is complicated.
We have a table (lets call it Translations) that contains translations used within our program whose primary key is the base text and the language code. This means there are several lines with the same base text but with differing language codes.
Translations:
BaseText
LanguageCode
I have a second table (lets call it Usage) which indicates, for each distinct BaseText in Translations table, where it is used in the program. The same text can be used in multiple places within the program, so there are potentially several lines with the same text but with differing programs.
Usage:
Text
Program
The one field that ties them both together is Text in Usage and BaseText in Translations. If I wanted to write a query that selects translations pertaining to a specific Program (as indicated in Usage table), I would probably write something like:
select * from Translations inner join Usage on Translations.BaseText = Usage.Text where Usage.Program = ?
However, I'm using hibernate and JPA to do this. I see many examples for creating a Parent-Child association between two entities, however this relationship is clearly a bit dysfunctional. This would probably be better represented with a middle table and a many-to-many relationship, however to make things more complicated, this is a legacy program using a legacy database, and thus I can't really add any tables.
The closest question that I could find related to my problem I found here, however it deals with a much cleaner Parent-Child type relationship. If I use only the one JoinColumn, it complains that the whole primary key is not satisfied.
Using annotations, how should I structure my entities so that I can optionally query Translation entities by Usage.program (if at least one line exists in Usage with a given program, I would like to retrieve its corresponding Translation line)?
This is what I have currently:
#Entity
public class Translation
#Id
private String baseText;
#Id
private String languageCode;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="baseText", referencedColumnName="text", nullable=false, insertable = false, updatable = false)
private Usage usage;
// .. getters and setters
}
#Entity
public class Usage {
#Id
private String text;
#Id
private String program;
// .. getters and setters
// OneToMany counterpart added, but I removed it because it seemed to
// make Usage the "owner" entity of the relationship, which isn't my case
}
I appreciate any help you can provide.

Related

Best way to query a view with different complex where clauses for multiple reports using JPA

I am trying to come up with a nice way of implementing multiple report pages based on the same base entity.
Imagine I want to have multiple reports based on some complex properties of a car - basically the same SELECT and the same FROM but with different complex WHERE clauses.
Report examples:
R1: list of all cars of brand A that did inspection on period B to C, with piece D installed, used in country E.
R2: list of all cars manufactured this year
...
R20: list of ...
On top of this, I want to have a filtering component that will help look for a specific case. We can assume that the filtering component is the same for all these reports, since the fields/columns will be (almost) the same for all reports.
The most straightforward solution is to create 20 views with all the statements, create 20 entities to map these DB views, and create repositories for all of them. But I guess this can be achieved in a smarter way.
My initial idea was to create a base entity CarReportBaseEntity that holds around 50 fields/columns.
#Entity
#Table(name = "CAR_REPORT_VIEW")
public abstract class CarReportBaseEntity<T extends CarReportBaseEntity<T>> implements Serializable, FilteredEntity<T> {
#Id
#Column(insertable = false, updatable = false)
private Long id;
#Column(insertable = false, updatable = false)
private Long manufacturedYear;
etc...
}
Then for each different report, I would just create an entity like:
#Entity
#Where(clause = "manufacturedYear = 2020")
class ManufacturedIn2020ReportEntity extends CarReportBaseEntity<ManufacturedIn2020ReportEntity> {
}
and similarly for other reports:
#Entity
#Where(clause = "some complex where clause")
class SomeOtherReportEntity extends CarReportBaseEntity<SomeOtherReportEntity> {
}
This would allow me to quickly create new reports by just adding one class and by tweaking the #Where annotation, including all the functionalities shared by the FilteredEntity. However this doesnt work since it creates a #Inheritance(strategy = InheritanceType.SINGLE_TABLE) by default, which then breaks the whole application since I don't have any DTYPE column specified. I don't want to need to specify anything, I just want to create classes for the same Entity but with different complex where clauses.
What could I do to solve this in an elegant way?
You need only:
the entity class that you already have CarReportBaseEntity
a factory that creates 20 CriteriaQueries containing the jpa criateria for each of your reports
a bunch of (service) methods that get a CriteriaQuery parameter and add further filtering (eg. whereCarBrandStartsWith(CriteriaQuery q, String prefix))
To get a report you get the CriteriaQuery object for that report and just get that result list. If you need to further filter those results you'd pass the criteria object to the service method you want
Edit 1:
we would really benefit from multiple classes since then we could extend the FilterEntity with specific columns/order for each report case
Your criteria queries don't have to always return CarReportBaseEntity. For each report you can define whatever select clause you want (CriteriaQuery::multiselect) and wrap the result in whatever dto you want (CriteriaBuilder::createQuery(resultType)). The advantage I see in this approach is that you can keep all your query logic in one place, in your factory, instead of splitting it between 20 classes and inside one of many annotations.

Is there a way of using a lookup table as a converter in JPA?

Imagine that I have a simple entity as follows:
#Entity
#Table(name = "PERSON")
public class Person {
#Id
#Column(name = "NAME")
private String name;
#Column(name = "GENDER")
private String gender;
}
And two tables, the actual table holding the information and a lookup table.
TABLE PERSON (
NAME VARCHAR2 NOT NULL,
GENDER INT NOT NULL);
TABLE GENDER_LOOKUP (
GENDER_ID INT NOT NULL,
GENDER_NAME VARCHAR2 NOTNULL);
I want to save the information from my entity into the table, so that the String field gender is automatically converted to the corresponding gender int, using the lookup table as a reference. I thought of two approaches, but I was wondering if there was a more efficient way.
Create an enum and use ordinal enum to persist. I would rather avoid this because I'd like to have only one "source of truth" for the information and for various business reasons, it has to be a lookup table.
Use the #Converter annotation and write a custom converter. I think that this would require me to query the table to pull out the relevant row, so it would mean that I would have to make a JPA call to the database every time something was converted.
I'm currently planning to use 2, but I was wondering if there was any way to do it within the database itself, since I assume using JPA to do all of these operations has a higher cost than if I did everything in the database. Essentially attempt to persist a String gender, and then the database would look at the lookup table and translate it to the correct Id and save it.
I'm specifically using openJpa but hopefully this isn't implementation specific.
Since you seriously considered using enum, it means that GENDER_LOOKUP is static, i.e. the content doesn't change while the program is running.
Because of that, you should use option 2, but have the converter cache/load all the records from GENDER_LOOKUP on the first lookup. That way, you still only have one "source of truth", without the cost of hitting the database on every lookup.
If you need to add a new gender1, you'll just have to restart the app to refresh the cache.
1) These days, who know what new genders will be needed.

Should a large relationship be modeled in an entity in terms of readability and performance

I have an entity that has a relation to a very large table ( > 100k Rows ). Now I am asking myself if I should express that relation really in my channel entity because would never call getProducts directly to load all products into memory. I would rather go through a product repositiory and query for a very specific subset for that channel.
I have only put it there so it's more readable from a client perspective.
#Entity
#Table(name = "Channel")
public class Channel {
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "product_id")
private List<Product> products;
public void setProducts(List<Product> products) {
this.products= products;
}
public List<Product> getProducts() {
return this.products;
}
}
Don't put that in a entity definition at all. You see what is often missed is that name #OneToMany is very misleading. As there is no possibility to limit the number of items you are loading. It can practically be used only when Many means Few :). The Hibernate Guru explains it better then me
Of course the join is executed lazily meaning that as long as it is not used it will do no harm. But why tempt faith. Writing query in a repo gives you ability for pagination and you can narrow the results to the one you will truly need.
The other side of the equation is more useful. So if you have on the child entity annotation #ManyToOne leave it there. It is good if you update parent from context of a child.
In short : no.
I think what you are asking may be opinion-based, but here is what I tend to do : remove all "non-navigable" relationships.
If you are using some generator to get JPA entities from a database model, the generator will create only bi-directional relationships. You need to remove all the relationships you will not used through the ORM mechanisms (for readability, maintainability and performance (in case someone would have the idea to remove the "lazy" indication or to call a getter on the collection in a transaction)).
For example, a User will be linked to a Language (many-to-one), but you don't want the (one-to-many) relationship between Language and User, even if at some point you will need to know in a report screen how many user speak which language.

JPA throwing "multiple assignments to same column" during save operation

I have a model class that references another model class and seem to be encountering an issue where the #OneToOne annotation fixes one problem but causes another. Removing it causes the inverse.
JPA throws "multiple assignments to same column" when trying to save changes to model. The generated SQL has duplicate columns and I'm not sure why.
Here's a preview of what the classes look like:
The parent class references look like this:
public class Appliance {
public Integer locationId;
#Valid
#OneToOne
public Location location;
}
The child Location class has an id field and a few other text fields -- very simple:
public class Location {
public Integer id;
public String name;
}
When I attempt to perform a save operation, does anyone know why JPA is creating an insert statement for the Appliance table that contains two fields named "location_id"?
I need to annotate the reference to the child class with #OneToOne if I want to be able to retrieve data from the corresponding database table to display on screen. However, If I remove #OneToOne, the save works fine, but it obviously won't load the Location data into the child object when I query the db.
Thanks in advance!
It appears you did not define an #InheritanceType on the parent Class. Since you did not, the default is to combine the the parent and the child class into the same Table in the Single Table Strategy.
Since both entities are going into the same table, I think that #OneToOne is trying to write the id twice - regardless of which side it is on.
If you want the parent to be persisted in its own table, look at InheritanceType.JOINED.
Or consider re-factoring so that you are not persisting the parent separately as JOINED is not considered a safe option with some JPA providers.
See official Oracle Documentation below.
http://docs.oracle.com/javaee/7/tutorial/doc/persistence-intro002.htm#BNBQR
37.2.4.1 The Single Table per Class Hierarchy Strategy
With this strategy, which corresponds to the default InheritanceType.SINGLE_TABLE, all classes in the hierarchy are mapped to a single table in the database. This table has a discriminator column containing a value that identifies the subclass to which the instance represented by the row belongs.
In OpenJPA, according to the docs (http://openjpa.apache.org/builds/1.0.1/apache-openjpa-1.0.1/docs/manual/jpa_overview_mapping_field.html), section 8.4, the foreign key column in a one-to-one mapping:
Defaults to the relation field name, plus an underscore, plus the name
of the referenced primary key column.
And the JPA API seems to concur with this (http://docs.oracle.com/javaee/6/api/javax/persistence/JoinColumn.html)
I believe this means that in a one-to-one mapping, the default column name for properties in a dependent class is parentClassFieldName_dependentClassFieldName (or location_id in your case). If that's the case, the location_id column you are defining in your Appliance class is conflicting with the location_id default column name which would be generated for your Location class.
You should be able to correct this by using the #Column(name="someColumnName") annotation and the #JoinColumn annotation on your #OneToOne relationship to force the column name to be something unique.
Ok gang, I figured it out.
Here's what the new code looks like, followed by a brief explanation...
Parent Class:
public class Appliance {
public Integer locationId;
#Valid
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name="location_id", referencedColumnName="id")
public Location location;
}
Child Class:
public class Location {
public Integer id;
public String name;
}
The first part of the puzzle was the explicit addition of "cascade = CascadeType.ALL" in the parent class. This resolved the initial "multiple assignments to same column" by allowing the child object to be persisted.
However, I encountered an issue during update operations which is due to some sort of conflict between EBean and JPA whereby it triggers a save() operation on nested child objects rather than a cascading update() operation. I got around this by issuing an explicit update on the child object and then setting it to null before the parent update operation occurred. It's sort of a hack, but it seems like all these persistence frameworks solve one set of problems but cause others -- I guess that's why I've been old school and always rolled my own persistence code until now.

How can I retrieve the foreign key from a JPA ManyToOne mapping without hitting the target table?

I have the following two annotated classes that I use to build a graph:
#Entity
#Table(name = "Edge")
public class Edge
{
/* some code omitted for brevity */
#ManyToOne
#JoinColumn(name = "ixNodeFrom", nullable = false)
private Node _nodFrom;
#ManyToOne
#JoinColumn(name = "ixNodeTo", nullable = false)
private Node _nodTo;
/* some code omitted for brevity */
}
#Entity
#Table(name = "Node")
public class Node
{
/* some code omitted for brevity */
#OneToMany(mappedBy = "_nodTo")
private Set<Edge> _rgInbound;
#OneToMany(mappedBy = "_nodFrom")
private Set<Edge> _rgOutbound;
/* some code omitted for brevity */
}
Now, when I build the graph, I issue two queries to fetch all rows from either table and set up the child / parent references, for which I need the ids stored in the Edge table.
Because I have defined the relation between the two tables in JPA, accessing the edge object to get the two nodes' ids triggers two SQL statements per edge, when the JPA provider lazily * loads the associated nodes. Since I already have the node objects, and the ids have already been loaded from the edge table, I want to skip those queries, as they take an awfully long time for larger graphs.
I tried adding these lines to the Edge class, but then my JPA provider wants me to make one mapping read-only, and I can't seem to find a way how to do that:
#Column(name = "ixNodeTo")
private long _ixNodeTo;
#Column(name = "ixNodeFrom")
private long _ixNodeFrom;
I'm using Eclipselink and MySQL, if it matters.
**The default behaviour for #ManyToOne actually is eager loading, see Pascal's answer*
I got three good answers that were equally helpful, and by now none percolated to the top by public vote, so I'm merging them together here for a single comprehensive answer:
a) Change the query
You can load the whole graph at once by changing the query, thereby giving the JPA provider a chance to realize that it already has everything in memory and doesn't need to go back to the DB:
List<Node> nodes = em.createQuery(
"SELECT DISTINCT n FROM Node n LEFT JOIN FETCH n._rgOutbound")
.getResultList();
(via axtavt)
b) Use read-only fields for the FKs
Loading the FKs into their own fields, as described in the question, will also work if, as the JPA provider is demanding, the fields are declared to be readonly, which is done like this:
#Column(name = "ixNodeTo", insertable = false, updatable = false)
(via bravocharlie)
c) Use property access
If you are using property access instead of field access, the JPA provider also gets a chance to realize it already has the FK and doesn't need to fetch the referenced object. In short, property access means that you put the JPA annotations on the getter, thereby "promising" the JPA provider that your getter won't go and access the rest of the object. More details in this question. This will work for Hibernate, and for Eclipselink, it will work (assumed in the original answer, experimentally confirmed by me) with weaving enabled. (via Pascal Thivent)
Additionally, as Pascal points out in his answer, #ManyToOne, contrary to my original post, is not lazy-loading, but eager-loading by default, and changing that will require weaving as well.
Have you tried
#Column(name = "ixNodeTo", insertable = false, updatable = false)
How can I retrieve the foreign key from a JPA ManyToOne mapping without hitting the target table?
In theory, a JPA provider should be able to not trigger a query when calling
someEdge.getNodeFrom().getId()
as it already has the id (as FK).
I'm 100% sure Hibernate can (assuming you're using property access). In the case of EclipseLink, I don't know (if it does, it will probably requires weaving).
Because I have defined the relation between the two tables in JPA, accessing the edge object to get the two nodes' ids triggers two SQL statements per edge, when the JPA provider lazily loads the associated nodes. Since I already have the node objects, and the ids have already been loaded from the edge table, I want to skip those queries, as they take an awfully long time for larger graphs.
Note that #ManyToOne uses an EAGER strategy by default. If you want to make it LAZY, you have to decalre it explicitly (but again, this will require weaving of your classes with EclipseLink).
I think you should try to optimize your query rather than change the mapping. For example, the following query fetches the whole graph at once (tested in Hibernate):
List<Node> nodes = em.createQuery(
"SELECT DISTINCT n FROM Node n LEFT JOIN FETCH n._rgOutbound")
.getResultList();
How about using getReference()?
For example:
Node fkNode = em.getReference(edge.getNodeFrom()); // [1]
fkNode.getId()
[1] This will not trigger a SQL query to retrieve the nodeFrom

Categories

Resources