Spring/JPA: Entity referenced by a view as a #ManyToOne association - java

Currently, my database is organized in a way that I have the following relationships(in a simplified manner):
#Entity
class A {
/*... class A columns */
#Id #NotNull
private Long id;
}
#Entity
#Immutable
#Table(name = "b_view")
class B {
/* ... same columns as class A, but no setters */
#Id #NotNull
private Long id;
}
The B entity is actually defined by a VIEW, which is written in this manner(assuming Postgres):
CREATE VIEW b_view AS
SELECT a.* FROM a WHERE EXISTS
(SELECT 1 FROM filter_table ft WHERE a.id = ft.b_id);
The idea here is that B references all elements of A that are present on filter_table. filter_table is another view that isn't really important, but it's the result of joining the A table with another, unrelated table, through a non-trivial comparison of substrings. These views are done so that I don't need to duplicate and control which elements of A also show up in B.
All of these are completely fine. JpaRepository is working great for B(obviously without saving the data, as B is Immutable) and it's all good.
However, at one point we have an entity that has a relationship with B objects:
#Entity
class SortOfRelatedEntity {
/** ... other columns of SortOfRelatedEntity */
#ManyToOne(fetch = FetchType.EAGER, targetEntity = Fornecedor.class)
#JoinColumn(name = "b_id", foreignKey = #ForeignKey(foreignKeyDefinition = "references a(id)"))
private B b;
}
For obvious reasons, I can't make this foreign key reference "b", since B is a view. However, I do want the query for searching this attribute to be defined by the b_view table, and having the foreign key defined by the underlying table(as written above) would be also nice in order to guarantee DB integrity.
However, when applying the above snippet, my sort-of-related-entity table doesn't create a foreign key as I would have expected. For the record, I'm using Hibernate 5.2.16 atm.
What am I doing wrong? Is this even possible? Is there something else I should do that I'm not aware of?

Oh FFS
I realized my mistake now. This:
#JoinColumn(name = "b_id", foreignKey = #ForeignKey(foreignKeyDefinition = "references a(id)"))
Should have been this:
#JoinColumn(name = "b_id", foreignKey = #ForeignKey(foreignKeyDefinition = "foreign key(b_id) references a(id)"))
Notice that the foreignKeyDefinition must include foreign key(), not just the references part.
Hopefully this helps someone in the future.

Related

Mapping multiple tables to one List Hibernate

I've been searching over the web to find out a solution for this. It seems nobody has the answer... I start thinking i'm in wrong way adressing the problem.
Let's see if i can explain easy.
Im developing a contract maintenance. (table: contrat_mercan). For the contract, we will select a category (table: categoria), each category has qualities (table: calidad) in relation 1 - N (relationship table categoria_calidad).
This qualities must have a value for each contract where the category is selected, so I created a table to cover this relationship: contrato_categoria_calidad.
#Entity
#Table(name = "contrato_categoria_calidad")
public class ContratoCategoriaCalidad implements Serializable{
// Constants --------------------------------------------------------
private static final long serialVersionUID = -1821053251702048097L;
// Fields -----------------------------------------------------------
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "CCC_ID")
private int id;
#Column(name = "CONTRAT_MERCAN_ID")
private int contratoId;
#Column(name = "CATEGORIA_ID")
private int categoriaId;
#Column(name = "CALIDAD_ID")
private int calidadId;
#Column(name = "VALOR")
private double valor;
.... getters/ setters
In this table I wanted to avoid having an Id, three fields are marked as FK in database and first attempts where with #JoinColumn in the three fields. But it does not worked for hibernate.
Anyway, now ContratoCategoriaCalidad is behaving okay as independent entity. But I will need to implement all maintenance, updates, deletes for each case manually... :(
What I really want, (and I think is a better practice) is a cascade when I saveOrUpdate the contract as the other entities do, but I don't find the way to make a List in contrat_mercan table.
This is working perfect for other relationships in same table:
#OneToOne
#JoinColumn(name="CONDICION")
private Condicion condicion;
#OneToMany (cascade = {CascadeType.ALL})
#JoinTable(
name="contrato_mercan_condicion",
joinColumns = #JoinColumn( name="CONTRATO_MERCAN_ID")
,inverseJoinColumns = #JoinColumn( name="CONDICION_ID")
)
private List<Condicion> condiciones;
But all my attempts to map this failed, what i want, is to have in my Java entity contrat_mercan a field like this:
private List<ContratoCategoriaCalidad> relacionContratoCategoriaCalidad;
not a real column in database, just representation of the relationship.
I found solutions to join multiple fields of the same table, here, and here, but not to make a relationship with 3 tables...
Any idea? Im doing something wrong? Maybe i must use intermediate table categoria_calidad to perform this?
Thanks!!
If you want to access a list of related ContratoCategoriaCalidad objects from Contrato entity you need to declare a relationship between those two entities using proper annotations.
In ContratoCategoriaCalidad class change field to:
#ManyToOne
#JoinColumn(name = "CONTRATO_ID")
private Contrato contrato;
In Contrato class add field:
#OneToMany(mappedBy = "contrato")
private List<ContratoCategoriaCalidad> relacionContratoCategoriaCalidad;
If you want to enable cascade updates and removals consider adding cascade = CascadeType.ALL and orphanRemoval = true attributes to #OneToMany annotation.
Hope this helps!

Implementing hierarchical data structures with JPA (fixed depth)

I have a hierarchical data structure with a fixed depth of 4. For a better understanding, let's assume the following (just an example):
The "root" level is called countries
Each country contains an arbitrary amount of states
Each state countains an arbitrary amount of counties
Each county contains an arbitrary amount of cities
So there are always 1-N relationships between the levels.
A very important usecase (given the id of a country) is to load the whole "content" of a country at once with the smallest possible impact on the performance of the database.
In a first naive approach, I created 4 entitiy classes in Java where the entity "Country" contains a list of the type "State", the entity "State" contains a list of the type "County" and so on...
But what JPA creates afterwards are of course not 4 tables, but 7 (4 for the entities + 3 for the connection between the levels due to 1-N). I don't know if this is a good solution since there is a lot of joining going on under the hood.
I also tried to map the subtypes to their parent types (a city belongs to one county, a county belongs to one state, a state belongs to one country). This results in 4 tables, but makes it more difficult to retrieve all data at once from the application's point of view. If I'm not wrong, I would need 4 different requests instead of one.
How could I solve this problem? Is there a way to combine a simple table layout (with four tables, not seven) with easy to use entity classes (a parent type should know its children)?
If not, how would you realize this?
I'm using JPA with Hibernate and PostgreSQL.
You can avoid the 3 extra mapping tables by using the #JoinColumn annotation rather than the #JoinTable annotation that I suspect you are using.
So for example:
COUNTRY
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy="country")
private List<State> stateList;
STATE
#ManyToOne
#JoinColumn(name="country_id")
private Country country
The database tables would be as follows:
Country
country_id => primary key
State
state_id => primary key
country_id => foreign key
This way the mapping tables between all the 4 entities can be avoided.
You can achieve this pretty easily using JPQL:
SELECT DISTINCT country
FROM Country country
JOIN FETCH country.states states
JOIN FETCH states.counties counties
JOIN FETCH counties.cities cities
WHERE country.id = :countryId
Using fetchType = FetchType.EAGER on #OneToMany/#ManyToOne(believe that one is already EAGER by default) will achieve similar results.
It's very simple use bidirectional mapping. Go through that link
How to delete Child or Parent objects from Relationship?
Make some changes like below
Country Entity:
------
#OneToMany(mappedBy="Country ",cascade = CascadeType.ALL)
private List<States > states;
#OneToMany(mappedBy="Country ",cascade = CascadeType.ALL)
private List<Counties> counties;
#OneToMany(mappedBy="Country ",cascade = CascadeType.ALL)
private List<Cities> cities;
-------
setters & getters
States Entity:
-----
#ManyToOne(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
#JoinColumn(name="countryId")
private Country country ;
-----
Counties Entity:
--------
#ManyToOne(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
#JoinColumn(name="countryId")
private Country country ;
-------
Cities Entity:
#ManyToOne(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
#JoinColumn(name="countryId")
private Country country ;
---------
After compilation of all entity's do your insertion . Only 4 will create and read your data by using Country object id.
You already have the solution: four table is the way to go, with bidirectional relationships (use the mappedBy property in the not-owning side of every relationship). If the relationships are EAGER-fetched, than all entities are automatically loaded. If you want to use LAZY fetching, you could try a named query in order to load the entity with all relationships loaded:
SELECT DISTINCT c FROM Country c LEFT JOIN FETCH c.states s LEFT JOIN FETCH s.counties co...
Did you try to declare the fetch type of the relations explicitely to eager with your second approach (default is lazy, that's why you have to do four queries).
E.g.
#OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
#JoinColumn ...
private ...;
see here: http://www.concretepage.com/hibernate/fetch_hibernate_annotation
Here is how your entities will look like:(You can use EAGER Loading instead of LAZY as well if you want)
Entity: Country
#Id
private Integer id;
#OneToMany(orphanRemoval=true fetch=FetchType.LAZY)
#JoinColumn(name="COUNTRY_ID")
private List<State> stateList;
Entity: State
This table has COUNTRY_ID that is Foreign Key to Country
#Id
private Integer id;
#OneToMany(orphanRemoval=true fetch=FetchType.LAZY)
#JoinColumn(name="STATE_ID")
private List<County> countyList;
#Column(name="COUNTRY_ID")
private Integer countryId;
Entity: County
This table has STATE_ID that is Foreign Key to State
#Id
private Integer id;
#OneToMany(orphanRemoval=true fetch=FetchType.LAZY)
#JoinColumn(name="COUNTY_ID")
private List<City> cityList;
#Column(name="STATE_ID")
private Integer stateId;
Entity: City
This table has COUNTY_ID that is Foreign Key to County
#Id
private Integer id;
#Column(name="COUNTY_ID")
private Integer countyId;
Your JPQL will be:
Select o from Country o where o.id=10
This will pick The Country Entity along with all the mappings like below.
Country
Holding List of States
Each States Holding List of Counties
Each Counties Holding LIst of Cities
For a requirement like yours, I would suggest to have a tree-like structure to maintain the hierarchical location data. It is relatively easy to implement & maintain and is more scalable & extensible.
In order to implement tree you need to have 2 tables LOCATION_NODE (Location ID, Location Name, Location Type[country, state, county, city]) & LOCATION_REL (Relation ID, Parent ID, Child ID). Below is the basic implementation of the tree idea.
public class LocationRel<T> {
private LocationNode<T> root;
public LocationRel(T rootData) {
root = new LocationNode<T>();
root.data = rootData;
root.children = new ArrayList<LocationNode<T>>();
}
public static class LocationNode<T> {
private T data;
private LocationNode<T> parent;
private List<LocationNode<T>> children;
}
}
This is the basic building block for a tree. You may need to add methods for add to, removing from, traversing, and constructors. But, once implemented, you have the freedom to add any new location type, change the hierarchy, add node, delete node etc with your hierarchical data.
Think out of the box.
Shishir
If you need the performance, I would suggest to de-normalize your tables and create 4 entities with following attributes (columns):
Country: id, name
State: id, countryId, name
County: id, countryId, stateId, name
City: id, countryId, stateId, countyId, name
(mapping is obvious)
Then you will be able to build a simple SQL queries.
If you need performance, prefer named queries as they are compiled at initialization time.
E.g. select all cities by country: "SELECT id, name FROM city WHERE country_id=?"
You may even not declare a references between entities using #ManyToOne, but just declare a simple #Columns. API call will, most likely, accept IDs (countryId, stateId), so you'll be better to pass that IDs as parameters to DAO. Most likely, you have a locations tables filled in once by sql script and the data should not be modified. Create foreign keys to guarantee data integrity.
And do you really need a tree-like structure in memory? If so, create it by hand, it is not very complex.
Searching Online, I found a couple of Links on JPQL which I think might help.
Link 1
Link 2
Anyways,
JPQL is one of the best ways to achieve this, try out this Query
SELECT DISTINCT country FROM Country country JOIN FETCH country.states states JOIN FETCH states.counties counties JOIN FETCH counties.cities cities WHERE country.id = :countryId
A solution that is useful, if you have relations that point to their parent only is the following:
With records:
#Entity
public class Country
{
#Id
private Long id;
}
#Entity
public class State
{
#Id
private Long id;
#ManyToOne(optional = false)
#JoinColumn(name = "country_id", referencedColumnName = "id", nullable = false)
Country country;
}
#Entity
public class County
{
#Id
private Long id;
#ManyToOne(optional = false)
#JoinColumn(name = "state_id", referencedColumnName = "id", nullable = false)
State state;
}
#Entity
public class City
{
#Id
private Long id;
#ManyToOne(optional = false)
#JoinColumn(name = "county_id", referencedColumnName = "id", nullable = false)
County county;
}
You can get all cities of a country with:
public interface CityRepository extends JpaRepository<City, Long>
{
List<City> findByCounty(County county); // county is a direct field of City
#Query("SELECT c FROM City c WHERE c.county.state.country = ?1")
List<City> findByCountry(Country country);
}

Hibernate Exception: Missing Column (column exists)

Okay, so within the database we have a table called distributionCompanies, created like so:
CREATE TABLE `distributionCompanies` (
`distributionCompanyID` INT(11) NOT NULL,
`distributionCompanyName` VARCHAR(255) NOT NULL,
PRIMARY KEY (distributionCompanyID)
);
I'm trying to map this table to a class using Hibernate:
#Entity
#Table(name = "distributionCompanies")
public class DistributionCompany implements DatabaseObject {
#Id
#GeneratedValue
#Column(name = "distributionCompanyID", length = 11, unique = true, nullable = false)
private int distributionCompanyID;
....
However, when running, I hit this issue:
Initial SessionFactory creation failedorg.hibernate.HibernateException: Missing column: distributionCompanyID_distributionCompanyID in database2.distributionCompanies
This isn't the only table in the database, and I've managed to map other classes successfully using the same method, so I'm a little stumped as to why this is causing an issue.
Thank you for your time,
Samuel Smith
EDIT: In response to Xavi's comment, I temporarily removed another mapping for the column, and the error went away, so the bad-egg probably lays in the following code:
#ManyToOne(targetEntity = DistributionCompany.class)
#JoinTable(name = "distributionCompanies", joinColumns = { #JoinColumn(name = "distributionCompanyID", nullable = false) })
private int distributionCompanyID;
Hibernate is looking for a column named distributionCompanyID_distributionCompanyID in your distributionCompanies table.
This is probably due to a ToOne association mapping towards this table without #JoinColum.
From Hibernate Documentation:
The #JoinColumn attribute is optional, the default value(s) is like in one to one, the concatenation of the name of the relationship in the owner side, _ (underscore), and the name of the primary key column in the owned side. In this example company_id because the property name is company and the column id of Company is id.
If you've got a #ManyToOne or #OneToOne association mapping in another entity, this would explain why Hibernate is looking for such a column.
EDIT Seeing the association mapping you posted, it looks like it should be:
#ManyToOne(targetEntity = DistributionCompany.class)
#JoinColumn(name = "distributionCompanyID")
private DistributionCompany distributionCompany;
The #JoinTable annotation is used to specify a join table (that means an intermediate table used to model many-to-many associations). And the point of mapping an association would be to dispose of the mapped object instance (in this case a DistributionCompany, not just a distributionCompanyId).

Hibernate Criteria Join problem

I have a 2 classes that share a UUID and are uni-directionally mapped. I use the UUID to group related rows, and this group shares many details (this is just an example):
#Entity #Table
class Something {
#Id #Column("something_id")
private Long id;
private String uuid = UUID.randomUUID().toString();
#OneToMany
#JoinColumn("uuid")
private List<Detail> details = new LinkedList<Detail>();
}
#Entity #Table
class Detail {
#Id #Column("detail_id")
private Long id;
private String value;
private String uuid;
}
I'm attempting to use Criteria:
Criteria c = getSession().createCriteria(Something.class).createAlias("details", "detail").add(Restrictions.eq("detail.value", someValue));
This is all fine and dandy, but I'm not getting results because of the join:
inner join DETAIL d1_ on this_.SOMETHING_ID=d1_.UUID
Is it possible to specify:
inner join DETAIL d1 on this_.UUID=d1.UUID
I would have expected the join to use the #JoinColumn annotaiton to find the column to join on. I see that I can specify a join type, but I don't see a way to specify the actual column.
I would have expected the join to use the #JoinColumn annotation to find the column to join on. I see that I can specify a join type, but I don't see a way to specify the actual column.
The join is using the JoinColumn annotation since it's joining on d1_.UUID. However, because you didn't specify the referencedColumnName element, the foreign key is assumed to refer to the primary key of the referenced table (this_.SOMETHING_ID), hence the obtained result.
In other words, try this:
#OneToMany
#JoinColumn(name="uuid", referencedColumnName="uuid")
private List<Detail> details = new LinkedList<Detail>();
I'm not sure to understand the benefit but let's say it's another story.

Hibernate - #ElementCollection - Strange delete/insert behavior

#Entity
public class Person {
#ElementCollection
#CollectionTable(name = "PERSON_LOCATIONS", joinColumns = #JoinColumn(name = "PERSON_ID"))
private List<Location> locations;
[...]
}
#Embeddable
public class Location {
[...]
}
Given the following class structure, when I try to add a new location to the list of Person's Locations, it always results in the following SQL queries:
DELETE FROM PERSON_LOCATIONS WHERE PERSON_ID = :idOfPerson
And
A lotsa' inserts into the PERSON_LOCATIONS table
Hibernate (3.5.x / JPA 2) deletes all associated records for the given Person and re-inserts all previous records, plus the new one.
I had the idea that the equals/hashcode method on Location would solve the problem, but it didn't change anything.
Any hints are appreciated!
The problem is somehow explained in the page about ElementCollection of the JPA wikibook:
Primary keys in CollectionTable
The JPA 2.0 specification does not
provide a way to define the Id in the
Embeddable. However, to delete or
update a element of the
ElementCollection mapping, some unique
key is normally required. Otherwise,
on every update the JPA provider would
need to delete everything from the
CollectionTable for the Entity, and
then insert the values back. So, the
JPA provider will most likely assume
that the combination of all of the
fields in the Embeddable are unique,
in combination with the foreign key
(JoinColunm(s)). This however could be
inefficient, or just not feasible if
the Embeddable is big, or complex.
And this is exactly (the part in bold) what happens here (Hibernate doesn't generate a primary key for the collection table and has no way to detect what element of the collection changed and will delete the old content from the table to insert the new content).
However, if you define an #OrderColumn (to specify a column used to maintain the persistent order of a list - which would make sense since you're using a List), Hibernate will create a primary key (made of the order column and the join column) and will be able to update the collection table without deleting the whole content.
Something like this (if you want to use the default column name):
#Entity
public class Person {
...
#ElementCollection
#CollectionTable(name = "PERSON_LOCATIONS", joinColumns = #JoinColumn(name = "PERSON_ID"))
#OrderColumn
private List<Location> locations;
...
}
References
JPA 2.0 Specification
Section 11.1.12 "ElementCollection Annotation"
Section 11.1.39 "OrderColumn Annotation"
JPA Wikibook
Java Persistence/ElementCollection
In addition to Pascal's answer, you have to also set at least one column as NOT NULL:
#Embeddable
public class Location {
#Column(name = "path", nullable = false)
private String path;
#Column(name = "parent", nullable = false)
private String parent;
public Location() {
}
public Location(String path, String parent) {
this.path = path;
this.parent= parent;
}
public String getPath() {
return path;
}
public String getParent() {
return parent;
}
}
This requirement is documented in AbstractPersistentCollection:
Workaround for situations like HHH-7072. If the collection element is a component that consists entirely
of nullable properties, we currently have to forcefully recreate the entire collection. See the use
of hasNotNullableColumns in the AbstractCollectionPersister constructor for more info. In order to delete
row-by-row, that would require SQL like "WHERE ( COL = ? OR ( COL is null AND ? is null ) )", rather than
the current "WHERE COL = ?" (fails for null for most DBs). Note that
the param would have to be bound twice. Until we eventually add "parameter bind points" concepts to the
AST in ORM 5+, handling this type of condition is either extremely difficult or impossible. Forcing
recreation isn't ideal, but not really any other option in ORM 4.
We discovered that entities we were defining as our ElementCollection types did not have an equals or hashcode method defined and had nullable fields. We provided those (via #lombok for what it's worth) on the entity type and it allowed hibernate (v 5.2.14) to identify that the collection was or was not dirty.
Additionally, this error manifested for us because we were within a service method that was marked with the annotation #Transaction(readonly = true). Since hibernate would attempt to clear the related element collection and insert it all over again, the transaction would fail when being flushed and things were breaking with this very difficult to trace message:
HHH000346: Error during managed flush [Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1]
Here is an example of our entity model that had the error
#Entity
public class Entity1 {
#ElementCollection #Default private Set<Entity2> relatedEntity2s = Sets.newHashSet();
}
public class Entity2 {
private UUID someUUID;
}
Changing it to this
#Entity
public class Entity1 {
#ElementCollection #Default private Set<Entity2> relatedEntity2s = Sets.newHashSet();
}
#EqualsAndHashCode
public class Entity2 {
#Column(nullable = false)
private UUID someUUID;
}
Fixed our issue. Good luck.
I had the same issue but wanted to map a list of enums: List<EnumType>.
I got it working like this:
#ElementCollection
#CollectionTable(
name = "enum_table",
joinColumns = #JoinColumn(name = "some_id")
)
#OrderColumn
#Enumerated(EnumType.STRING)
private List<EnumType> enumTypeList = new ArrayList<>();
public void setEnumList(List<EnumType> newEnumList) {
this.enumTypeList.clear();
this.enumTypeList.addAll(newEnumList);
}
The issue with me was that the List object was always replaced using the default setter and therefore hibernate treated it as a completely "new" object although the enums did not change.

Categories

Resources