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!
Related
I've read some related questions but they are not exactly the same problem as mine.
I'm using JPA + Hibernate + Spring and I want to do something that I'm not sure if it is possible just with config.
I have my domain classes with a more or less complicated relation. There are many elements that are related with one element (like if it was a tree many elements are sons of one element).
Something like:
#Entity
class Foo {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#ManyToOne
#JoinColumn(name = "PARENT_ID")
private Foo parentNode;
...
}
Which will get a table like:
Foo id parent_id
1
2 1
3 1
When I delete row with id = 1 I want to delete rows with id = 2 and id = 3 (it may be recursive, elements with parent_id = 2 and parent_id = 3 would be deleted as well).
For some restrictions I only can have the relation in son's side with the parent_id reference.
My question is: is it possible to do this with JPA or Hibernate configuration or do I need to do some recursive function to delete all children and all parents?
I've tried with:
#OneToMany(name = "PARENT_ID", cascade = CascadeType.REMOVE)
And I've read that maybe using Hibernate annotations.
If anyone can give me some clue I'm lost at this point.
Edit 1
Would it be possible to do like:
#Entity
class Foo {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#ManyToOne
#JoinColumn(name="PARENT_ID")
private Foo parentNode;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "parentNode", cascade = CascadeType.REMOVE, orphanRemoval = true)
private Set<Foo> childs = new LinkedHashSet<Foo>();
...
}
Keeping the table as is, with the fk to the parent?
I've tried this but I keep getting the same error, fk restriction violated.
Edit 2
Finally solved with:
#Entity
class Foo {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#ManyToOne
#JoinColumn(name = "PARENT_ID")
private Foo parentNode;
#OneToMany(mappedBy = "parentNode", cascade = CascadeType.REMOVE)
private Set<Foo> childs = new LinkedHashSet<Foo>();
...
}
This #OneToMany is needed even if we do the mapping in our BBDD by refering just the parent id.
Now when we delete a Foo with childs, it's childs will be deleted as well.
Thanks for your time and good advices!
Look at orphanRemoval option:
#OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true)
Here is complete explication about CascadeType.REMOVE and orphanRemoval.
Good luck!
Relationships in JPA are always unidirectional, unless you associate the parent with the child in both directions. Cascading REMOVE operations from the parent to the child will require a relation from the parent to the child (not just the opposite).
So here you need to change unidirectional relationship to bi-directional.
for more details refer this link.
I have the following entities:
DummyA:
#Entity
#Table(name = "dummy_a")
#Data
public class DummyA implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "dummy_b_name", referencedColumnName = "name", updatable = false, insertable = false)
private DummyB dummyB;
}
DummyB:
#Entity
#Table(name = "dummy_b")
#Data
public class DummyB implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "entity_id")
private Integer id;
#Column(name = "name")
private String name;
}
As it currently stands, any attempt to fetch DummyA objects results in additional queries to fetch DummyB objects as well. This causes unacceptable extra delay due to N+1 queries and also breaks Page objects returned by repository.findAll(specification, pageable), causing incorrect total page counts and element counts to be returned (in my case repository extends JpaRepository). Is there a way to do it such that DummyB objects are lazily loaded or, if that's not possible, so that they're all eagerly loaded in a single query?
Limitations:
I'm fairly new to JPA and Hibernate and have been learning how to use them. I've come across the following in a project I'm working on. I don't have the liberty to include new dependencies and my project currently does not allow hibernate bytecode enhancement through #LazyToOne(LazyToOneOption.NO_PROXY).
Things I've tried so far and did not work / did not work as expected:
#ManyToOne(optinoal = false, fetch = FetchType.LAZY)
Tried to see if accessing the dummyB field in dummyA is what caused the N+1 queries by removing dummyB's setter and getter. Still had N+1 queries.
Using #EntityGraph on findAll.
Tried implementing PersistentAttributeInterceptable and using PersistentAttributeInterceptor to solve the problem.
Links to resources I've looked up so far:
#ManyToOne(fetch = FetchType.LAZY) doesn't work on non-primary key referenced column
N+1 query problem with JPA and Hibernate
Hibernate lazy loading for reverse one to one workaround - how does this work?
PersistentAttributeInterceptable
JPA Entity Graph
Any help is greatly appreciated.
I've come back with an answer in case anyone is curious. It turns out that some entries had an invalid "magic" value in the column used by DummyA as the foreign key to associate it with DummyB, causing Hibernate to execute separate queries for those null values in order to check if the association is truly not found (see this doc and this answer from a related question). I mistook those queries as N+1. The project also had an interceptor that extended Hibernate's EmptyInterceptor in order to modify queries that produced pages, resulting in incorrect counts if secondary queries were executed.
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.
#Entity
#Table(name = "MATCHES")
public class Match implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "MATCH_ID")
private Long id;
#ManyToMany(mappedBy = "matches", cascade = CascadeType.ALL)
private Set<Team> teams = new HashSet<Team>();
}
#Entity
#Table(name = "Teams")
public class Team implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "TEAM_ID")
private long id;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "TEAM_MATCH", joinColumns = { #JoinColumn(name = "TEAM_ID") }, inverseJoinColumns = {
#JoinColumn(name = "MATCH_ID") })
private Set<Match> matches = new HashSet<Match>();
}
I got those classes, now I want to get all the matches and let's say, print names of both teams.
public List getAllMatches() {
Session session = HibernateUtil.getSession();
Transaction t = session.beginTransaction();
Criteria criteria = session.createCriteria(Match.class, "match");
criteria.createAlias("match.teams", "mt", JoinType.LEFT_OUTER_JOIN);
List result = criteria.list();
t.commit();
session.close();
return result;
}
But when I invoke that method, result has size 2 when I got only 1 match in my table. Both of those matches in result have 2 teams, which is correct. I have no idea why this happends. What I want is to have one Match object with two Team objects in 'teams' set, but I have two of those Match objects. They are fine, but there are two of them. I'm completely new to this and have no idea how to fix those criterias. I tried deleting 'FetchType.LAZY' from #ManyToMany in Team but it doesn't work. Team also has properties like Players/Trainer etc. which are in their own tables, but I don't want to dig that deep yet, baby steps. I wonder tho if doing such queries is a good idea, should I just return Matches and then if I want to get Teams, get them in another session?
Edit: I added criteria.setResultTransformer(DistinctRootEntityResultTransformer.INSTANCE); and it works, is that how I was suppose to fix that or this is for something completely different and I just got lucky?
I think the duplication is a result of your createAlias call, which besides having this side effect is redundant in the first place.
By calling createAlias with those arguments, you are telling Hibernate to not just return all matches, but to first cross index the MATCHES table with the TEAM_MATCH table and return a result for each matching pair of rows. You get one result for a row in the matches table paired with the many-to-many mapping to the first team, and another result for the same row in the matches table paired with the many-to-many mapping to the second team.
I'm guessing your intent with that line was to tell Hibernate to fetch the association. This is not necessary, Hibernate will fetch associated objects on its own automatically when needed.
Simply delete the criteria.createAlias call, and you should get the result you expected - with one caveat. Because the association is using lazy fetching, Hibernate won't load it until you access it, and if that comes after the session is closed you will get a LazyInitializationException. In general I would suggest you prefer solving this by having the session opened and closed at a higher level of abstraction - getting all matches is presumably part of some larger task, and in most cases you should really use one session for the duration of the entire task unless there are substantial delays (such as waiting for user input) involved. Changing that would likely require significant redesign of your code, however; the quick solution is to simply loop over the result list and call Hibernate.initialize() on the teams collection in each Match. Or you could just change the fetch type to eager, if the performance cost of always loading the association whether or not you need it is acceptable.
I've read some related questions but they are not exactly the same problem as mine.
I'm using JPA + Hibernate + Spring and I want to do something that I'm not sure if it is possible just with config.
I have my domain classes with a more or less complicated relation. There are many elements that are related with one element (like if it was a tree many elements are sons of one element).
Something like:
#Entity
class Foo {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#ManyToOne
#JoinColumn(name = "PARENT_ID")
private Foo parentNode;
...
}
Which will get a table like:
Foo id parent_id
1
2 1
3 1
When I delete row with id = 1 I want to delete rows with id = 2 and id = 3 (it may be recursive, elements with parent_id = 2 and parent_id = 3 would be deleted as well).
For some restrictions I only can have the relation in son's side with the parent_id reference.
My question is: is it possible to do this with JPA or Hibernate configuration or do I need to do some recursive function to delete all children and all parents?
I've tried with:
#OneToMany(name = "PARENT_ID", cascade = CascadeType.REMOVE)
And I've read that maybe using Hibernate annotations.
If anyone can give me some clue I'm lost at this point.
Edit 1
Would it be possible to do like:
#Entity
class Foo {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#ManyToOne
#JoinColumn(name="PARENT_ID")
private Foo parentNode;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "parentNode", cascade = CascadeType.REMOVE, orphanRemoval = true)
private Set<Foo> childs = new LinkedHashSet<Foo>();
...
}
Keeping the table as is, with the fk to the parent?
I've tried this but I keep getting the same error, fk restriction violated.
Edit 2
Finally solved with:
#Entity
class Foo {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#ManyToOne
#JoinColumn(name = "PARENT_ID")
private Foo parentNode;
#OneToMany(mappedBy = "parentNode", cascade = CascadeType.REMOVE)
private Set<Foo> childs = new LinkedHashSet<Foo>();
...
}
This #OneToMany is needed even if we do the mapping in our BBDD by refering just the parent id.
Now when we delete a Foo with childs, it's childs will be deleted as well.
Thanks for your time and good advices!
Look at orphanRemoval option:
#OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true)
Here is complete explication about CascadeType.REMOVE and orphanRemoval.
Good luck!
Relationships in JPA are always unidirectional, unless you associate the parent with the child in both directions. Cascading REMOVE operations from the parent to the child will require a relation from the parent to the child (not just the opposite).
So here you need to change unidirectional relationship to bi-directional.
for more details refer this link.