In my springboot application I have these 3 entities :
#Entity
public class Process {
#Id
private Long Id;
#ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.ALL})
#JoinColumn(name = "input_id")
private Input input;
...
}
#Entity
public class Input{
#Id
private Long Id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "template_id")
private Template template;
...
}
#Entity
public class Template{
#Id
private Long Id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "template_id")
private Template template;
private String name;
...
}
In summary, Process has an FK to Input and Input has an FK to Template.
I would like to filter the processes whose template have a certain name. Here is the SQL I would to perform something like that:
select
*
from
process p
left outer join
input i
on p.input_id=i.id
left outer join
template t
on i.template_id=t.id
where
t.name='templateName'
Here is what I currently have in my Process entity to access the template :
#ManyToOne
#JoinTable(name = "Input",
joinColumns = {#JoinColumn(table = "Input", referencedColumnName = "id")},
inverseJoinColumns = {
#JoinColumn(name = "template_id", referencedColumnName = "id", table = "Template")})
private Template template;
Here is my ProcessRepository class, where I now have access to the desired find method :
#Repository
public interface ProcessRepository extends PagingAndSortingRepository<Process, Long> {
...
List<Process> findByTemplateNameEquals(String templateName);
...
}
When I execute the findByTemplateNameEquals method, I retrieve the process and one template. But the result I got was not the one expected.
I enabled the sql logging and here is the query really performed (I hide the columns, it is not important here):
select
...
from
process process0_
left outer join
input process0_1_
on process0_.id=process0_1_.id
left outer join
template template1_
on process0_1_.template_id=template1_.id
where
template1_.name=?
There is one problem with the join between Process and Input. It executes
from
process process0_
left outer join
input process0_1_
on process0_.id=process0_1_.id
instead of
from
process process0_
left outer join
input process0_1_
on process0_.input_id=process0_1_.id
I don't understand why it use the PK of Process instead of the FK to Input.
I tried several things to solve this :
Adding name="input_id" in the joinColumns = {#JoinColumn(... but instead of replacing the FK, it replaces the PK of input => failure during execution
replacing the referencedColumnName by "input_id" in the joinColumns = {#JoinColumn(... but it failed at launching.
Configuring a #ForeignKey(name = "input_id") at several places (directly in the #JoinTable, in the #JoinColumn and even in the #JoinColumn of the Input input attribute ) but there was no change.
I also remarked that the joinColumns = {#JoinColumn(table = "Input", referencedColumnName = "id")} was not necessary, because I have the same behaviour if I remove it.
Could someone help me on this ?
Many thanks in advance
I think the declaration of the template field at the Process level is probably unnecessary because you already have the relationship with Input, and certainly error prone.
In any case, if necessary, I would define the field with something like that instead:
#ManyToOne
#JoinTable(
name = "Input",
joinColumns = { #JoinColumn(name = "input_id")},
inverseJoinColumns = {#JoinColumn(name = "template_id")}
)
private Template template;
Pease, verify the code.
Having said that, if you only need to obtain the repositories associated with a certain template by name you can try to navigate through the object hierarchy in your find method. Please, try:
#Repository
public interface ProcessRepository extends PagingAndSortingRepository<Process, Long> {
...
List<Process> findByInput_Template_Name(final String templateName);
...
}
Related
I have a many to many relationship. Task2Group.
I'm trying to update some base info of a task using the save() method of spring-boot-jpa.
But when I do not set the groupSet of the task I try to update, and save it, the old task2group relationship will be removed.
I know reason but I just wanna konw how to update the task entity without geting the GroupSet and then set it.
Here are the Task entity related to group
#ManyToMany(cascade = {CascadeType.REFRESH})
#JoinTable(name = "task_2_group", joinColumns = #JoinColumn(name = "task_id"),
inverseJoinColumns = #JoinColumn(name = "group_id"))
private Set<Group> groupSet;
and here are the group
#ManyToMany(mappedBy = "groupSet")
private Set<Task> taskSet;
I got two entities FoaParamEmploye and FoaParamPosition (tables FOA_PARAM_EMPLOYE and FOA_PARAM_POSITION) with ManyToMany annotation (I didn't put here all attributes) :
#Entity
#Table(name = "FOA_PARAM_EMPLOYE")
#NamedQuery(name = "FoaParamEmploye.findAll", query = "SELECT f FROM FoaParamEmploye f")
public class FoaParamEmploye implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
private FoaParamEmployePK id;
#ManyToMany
#JoinTable(
name = "FOA_PARAM_EMPLOYE_POSITION",
joinColumns = { #JoinColumn(name = "ID_EMPLOYE"),
#JoinColumn(name = "COD_ENTREP") }
)
private List<FoaParamPosition> foaParamPositions;
}
And the second one :
#Entity
#Table(name="FOA_PARAM_POSITION")
#NamedQuery(name="FoaParamPosition.findAll", query="SELECT f FROM FoaParamPosition f")
public class FoaParamPosition implements Serializable {
#EmbeddedId
private FoaParamPositionPK id;
#ManyToMany
#JoinTable(
name = "FOA_PARAM_EMPLOYE_POSITION",
joinColumns = { #JoinColumn(name = "ID_POSITION"),
#JoinColumn(name = "COD_ENTREP") }
)
private List<FoaParamEmploye> foaParamEmployes;
}
Association table is FOA_PARAM_EMPLOYE_POSITION with COD_ENTREP, ID_POSITION and ID_EMPLOYE fields.
PK are :
#Embeddable
public class FoaParamEmployePK implements Serializable {
//default serial version id, required for serializable classes.
private static final long serialVersionUID = 1L;
#Column(name="COD_ENTREP")
private String codEntrep;
#Column(name="ID_EMPLOYE")
private long idEmploye;
}
#Embeddable
public class FoaParamPositionPK implements Serializable {
//default serial version id, required for serializable classes.
private static final long serialVersionUID = 1L;
#Column(name="COD_ENTREP")
private String codEntrep;
#Column(name="ID_POSITION")
private long idPosition;
}
I try to get all FoaParamPosition for a FoaParamEmploye. I wrote this JPQL query :
Query q = entityMgr.createQuery(
"SELECT position FROM FoaParamPosition position
INNER JOIN position.foaParamEmployes employes
where
employes.id.idEmploye = :idEmploye AND
employes.id.codEntrep =:codEntrep")
.setParameter("idEmploye", pIdEmploye)
.setParameter("codEntrep", "ENT");
I got an exception :
ORA-00904: "FOAPARAMEM1_"."FOAPARAMEMPLOYES_ID_EMPLOYE" : non valid identifier
As you can see, the generate SQL has this attribute but I can't understand why :
select
foaparampo0_.COD_ENTREP as COD1_2337_,
foaparampo0_.ID_POSITION as ID2_2337_,
foaparampo0_.ACTEUR_MAJ_OCCUR as ACTEUR3_2337_,
foaparampo0_.CD_PROFIL_AFFECTATION as CD4_2337_,
foaparampo0_.CD_TYPE_PROFIL_POSITION as CD5_2337_,
foaparampo0_.DATE_HEURE_MAJ_OCCUR as DATE6_2337_,
foaparampo0_.DT_FIN_ACTIVITE_POSITION as DT7_2337_,
foaparampo0_.HIERARCHIE_POSITION as HIERARCHIE8_2337_,
foaparampo0_.ID_DIVISION_AGENCE as ID9_2337_,
foaparampo0_.ID_EMPLOYE_PRINCIPAL as ID10_2337_,
foaparampo0_.ID_REF_EXT_POSITION_PARENTE as ID11_2337_,
foaparampo0_.ID_REF_EXTERNE_POSITION as ID12_2337_,
foaparampo0_.NIVEAU_AGENCE as NIVEAU13_2337_,
foaparampo0_.REF_EXT_POSITION as REF14_2337_,
foaparampo0_.xqcif as xqcif2337_
from
FOA_PARAM_POSITION foaparampo0_,
FOA_PARAM_EMPLOYE_POSITION foaparamem1_,
FOA_PARAM_EMPLOYE foaparamem2_
where
foaparampo0_.COD_ENTREP=foaparamem1_.ID_POSITION
and foaparampo0_.ID_POSITION=foaparamem1_.COD_ENTREP
and foaparamem1_.foaParamEmployes_COD_ENTREP=foaparamem2_.COD_ENTREP
and foaparamem1_.foaParamEmployes_ID_EMPLOYE=foaparamem2_.ID_EMPLOYE
and foaparamem2_.ID_EMPLOYE=?
and foaparamem2_.COD_ENTREP=?
I would recommend you to map your classes with some tool for example Netbeans -> generate JPA entities.
One of your side must be owning side (JoinColumn) and one must be inverse side (mappedBy).
If you don't wanna use any mapping tool, see this:
http://alextretyakov.blogspot.cz/2013/07/jpa-many-to-many-mappings.html
I'm not sure but I think if first you get an FoaParamEmploye object through below JPA query and after that use getFoaParamPosition method of this object to retrieve a list of positions your problem will get solved (when using this method in many-to-many association, the JPA will automatically retrieve the list):
Select distinct f from FoaParamEmployes f
where f.employes.idEmploye = :idEmploye
I don't see a problem in your mapping. The error you're getting complains explicitly about the invalid column name. In Oracle, the quoted names such as "FOAPARAMPO0_"."FOAPARAMPOSITIONS_ID_POSITION" are case sensitive, while the generated columns are foaParamPositions_COD_ENTREP and foaParamPositions_ID_POSITION and the case mismatch is causing your error.
A quick check would be to uppercase your foaParamEmployes and foaParamPositions and if I'm right the mentioned issue should be resolved. If so you can work-out a more elegant way to control the generated names with mappings.
I've noticed your other question in the comments referring to trouble with the inverseJoinColumns. That's why I'm mentioning upper-casing your properties. But in fact, you can handle it with mappings like
#ManyToMany
#JoinTable(
name = "FOA_PARAM_EMPLOYE_POSITION",
joinColumns = { #JoinColumn(name = "ID_EMPLOYE"),
#JoinColumn(name = "COD_ENTREP") }
,
inverseJoinColumns = {#JoinColumn(name = "FOAPARAMPOSITION_COD_ENTREP"), #JoinColumn(name = "FOAPARAMPOSITION_ID_POSITION")}
)
private List<FoaParamPosition> foaParamPosition;
and
#ManyToMany
#JoinTable(
name = "FOA_PARAM_EMPLOYE_POSITION",
joinColumns = { #JoinColumn(name = "ID_POSITION"),
#JoinColumn(name = "COD_ENTREP") }
,
inverseJoinColumns = {#JoinColumn(name = "FOAPARAMEMPLOYE_COD_ENTREP"), #JoinColumn(name = "FOAPARAMEMPLOYE_ID_EMPLOYE")}
)
private List<FoaParamEmploye> foaParamEmploye;
which solves the issue in your other question I believe
I have three tables with entities in hibernate. DB - MySQL. I need to get fields from entity "Item" where ModelsMm.id has some value. At first I tried to do separate queries, it was huge amount of requests in sum. So, i tried to do complex query, but it became a very long run.
I think there is a simpler way, but I do not know what.
My query and entities.
List<Item> itemIds = session.createQuery("select it from Item it where :id in elements(it.mmPrice.modelsMm)");
#Entity (name = "MODELS_MM")
public class ModelsMm {
#Id
private int Id;
#ManyToMany(cascade=CascadeType.ALL, fetch = FetchType.LAZY)
#JoinTable(name="parth_mm", joinColumns={#JoinColumn(name="MODEL_ID", referencedColumnName="ID")}, inverseJoinColumns={#JoinColumn(name="PART_ID", referencedColumnName="ID")})
private List<MmPrice> mmPrices;
#Entity (name = "MM_PRICE")
public class MmPrice {
#Id
private int id;
private String article;
#OneToOne(optional = true, fetch = FetchType.LAZY)
#JoinColumn(name = "article", referencedColumnName = "article",insertable = false, updatable = false)
private Item item;
#ManyToMany
#JoinTable(name="parth_mm", joinColumns={#JoinColumn(name="PART_ID", referencedColumnName="ID")}, inverseJoinColumns={#JoinColumn(name="MODEL_ID", referencedColumnName="ID")})
private List<ModelsMm> modelsMm;
#Entity
#Table(name="SHOP_ITEMS")
public class Item implements Serializable {
#Id
private int id;
private String article;
#OneToOne(optional = true, fetch = FetchType.LAZY)
#JoinColumn(name = "article", referencedColumnName = "article",insertable = false, updatable = false)
private MmPrice mmPrice;
In console i have that query
Hibernate: select item0_.ID as ID0_, item0_.ARTICLE as ARTICLE0_, item0_.article as article0_ from SHOP_ITEMS item0_ cross join MM_PRICE mmprice1_ where item0_.article=mmprice1_.article and (? in (select modelsmm2_.MODEL_ID from parth_mm modelsmm2_ where mmprice1_.ID=modelsmm2_.PART_ID))
Thanks.
First, you'll have to fix your mapping. In a bidirectional association, one side MUST be the inverse side, and thus use the mappedBy attribute. For example, if you choose ModelsMm to be the inverse side, then its mmPrices attribute should be declared as
#ManyToMany(mappedBy = "modelsMm")
private List<MmPrice> mmPrices;
You should also forget about CascadeType.ALL on ManyToMany associations: it makes no sense. You don't want to delete all the courses of a student when you delete a student, since the course is also followed by several other students.
Now, regarding your query, it's not very clear what you want to do. If you want to select all the items which have a price which have at least one model whose ID is in a collection of IDs, then you simply need the following query:
select distinct i from Item i
join i.mmPrice p
join p.modelsMm m
where m.id in :modelIds
Side note: please fix your naming. This inconsistent and unnecessary usage of mm as a prefix or suffix makes the code unreadable. Name your class Price, the fields of type Price price, and the collections of prices prices. Just as you would do in English: an Item has a price, and a price has models.
I have two entity classes 'User' and 'Document'. Each user has an inbox and an outbox which are in fact two List and each Document may reside in multiple inbox's and outbox's of users. Here are my classes:
#Entity
public class User {
#Id
private Long id;
#ManyToMany(mappedBy = "userinbox", cascade=CascadeType.ALL)
private List<Document> inbox = new ArrayList<Document>();
#ManyToMany(mappedBy = "useroutbox", cascade=CascadeType.ALL)
private List<Document> outbox = new ArrayList<Document>();
}
#Entity
public class Document {
#Id
private Long id;
#ManyToMany(cascade=CascadeType.ALL)
private List<User> userinbox = new ArrayList<User>();
#ManyToMany(cascade=CascadeType.ALL)
private List<User> useroutbox = new ArrayList<User>();
}
When I run the programm and try to assign a document to a user's inbox (and vice-versa), I get the following error:
Error Code: 1364
Call: INSERT INTO DOCUMENT_USER (userinbox_ID, inbox_ID) VALUES (?, ?)
bind => [2 parameters bound]
Internal Exception: java.sql.SQLException: Field 'useroutbox_ID' doesn't have a default value
Query: DataModifyQuery(name="userinbox" sql="INSERT INTO DOCUMENT_USER (userinbox_ID, inbox_ID) VALUES (?, ?)")
The generated association table looks like this:
DOCUMENT_USER
useroutbox_ID | outbox_ID |userinbox_ID | inbox_ID
How would I assign default values for such a many-to-many relation? Would it be better to make two association tables -> one for inbox-relation, another for outbox-relation? How would I accomplish that ? Other solutions to this problem ?
Any help highly appreciated - many thanks in advance!
I think that the better option is to have two separate tables, one per relation. Because you actually have two relations between two different entities, and not one relation with four different entities.
So, you should add a #JoinTable annotation to each of your attributes in the Document side since the User side those relations are mapped to a property. Something like the following:
#Entity
public class Document {
#Id
private Long id;
#ManyToMany(cascade=CascadeType.ALL)
#JoinTable(name = "document_inbox", joinColumns = #JoinColumn(name = "userinbox_id"),
inverseJoinColumns = #JoinColumn(name = "inbox_id"))
private List<User> userinbox = new ArrayList<User>();
#ManyToMany(cascade=CascadeType.ALL)
#JoinTable(name = "document_outbox", joinColumns = #JoinColumn(name = "useroutbox_id"),
inverseJoinColumns = #JoinColumn(name = "outbox_id"))
private List<User> useroutbox = new ArrayList<User>();
}
Leave the other entity as it is now. Hope this helps.
Simplifying, in my database I have tables:
Car (pk="id_car")
CarAddon (pk="id_car_fk,id_addon_fk",
`FK_car_addon_addon` FOREIGN KEY (`id_addon_fk`) REFERENCES `addon` (`id_addon`)
`FK_car_addon_car` FOREIGN KEY (`id_car_fk`) REFERENCES `car` (`id_car`)
Addon (pk="id_addon")
Shortly: I have cars, many cars can has many addons (like ABS etc).
There are tables with cars, addons, and one table which is logical connection.
Overall, entities work fine. I have no problems with persist data, when I want persist single object. I don't have problems, when I want FETCH data, ie. Car->getAddon();
But, when I'm going to persisting a collection, nothing happens. No exceptions were thrown, there were no new data in database.
//DBManager is a singleton to create an EntityManager
EntityManager em = DBManager.getManager().createEntityManager();
em.getTransaction().begin();
Addon addon1 = new Addon();
addon1.setName("czesc1");
em.persist(addon1);
Addon addon2 = new Addon();
addon2.setName("czesc2");
em.persist(addon2);
car.setAddonCollection(new ArrayList<Addon>());
car.getAddonCollection().add(addon1);
car.getAddonCollection().add(addon2);
em.persist(car);
em.getTransaction().commit();
In this case, addons were stored in Addon table, car in Car table. There are no new data in CarAddon table though object car has good data (there is addon collection in debbuger).
When I changed em.persist(car) to em.merge(car) I got an exception:
"SEVERE: Persistence error in /admin/AddAuction : java.lang.IllegalStateException: During synchronization a new object was found through a relationship that was not marked cascade PERSIST: model.entity.Car[ idCar=0 ]."
Simple version of my classess:
#Entity
#Table(name = "addon")
#XmlRootElement
#NamedQueries({...})
public class Addon implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#NotNull
#Column(name = "id_addon")
private Integer idAddon;
#Size(max = 100)
#Column(name = "name")
private String name;
#JoinTable(name = "car_addon",
joinColumns = {
#JoinColumn(name = "id_addon_fk", referencedColumnName = "id_addon")},
inverseJoinColumns = {
#JoinColumn(name = "id_car_fk", referencedColumnName = "id_car")})
#ManyToMany
private List<Car> carCollection;
#XmlTransient
public List<Car> getCarCollection() {
return carCollection;
}
public void setCarCollection(List<Car> carCollection) {
this.carCollection = carCollection;
}
}
#Entity
#Table(name = "car")
#XmlRootElement
#NamedQueries({...)
public class Car implements Serializable {
#ManyToMany(mappedBy = "carCollection", fetch= FetchType.EAGER, cascade=CascadeType.ALL)
private List<Addon> addonCollection;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#NotNull
#Column(name = "id_car")
private Integer idCar;
#XmlTransient
public List<Addon> getAddonCollection() {
return addonCollection;
}
public void setAddonCollection(List<Addon> addonCollection) {
this.addonCollection = addonCollection;
}
}
How can I fix it?
ps1. I have:
cascade=CascadeType.ALL przy #ManyToMany private List<Car> carCollection
but this dos not solve my problem.
ps2. I am using Netbeans 7, EclipseLink and MySQL (not Hibernate - I have problem with it)
I have one theory that always seems to trip people up with many-to-many collections. The problem is that in memory, the associations are made in two places. Both in the car's addons list and in the addon's cars list. In the database, there isn't such a duplication.
The way JPA providers get around this is through the mappedBy attribute. Since you have mappedBy on the car's addons list this means that the relationship is actually controlled by the addon's cars list (confusing I know).
Try adding the following:
addon1.setCarCollection(new ArrayList<Car>());
addon1.getCarCollection().add(car);
addon2.setCarCollection(new ArrayList<Car>());
addon2.getCarCollection().add(car);
before you persist the car.
Generally speaking, I would avoid many-to-many associations. What you really have is an intermediate link table, with a one-to-many and a many-to-one. As soon as you add anything of interest to that link table (e.g. datestamp for when the association was made), poof, you are no longer working with a pure many-to-many. Add in the confusion around the "owner" of the association, and you're just making things a lot harder than they should be.
could you try add
#JoinTable(name = "car_addon",
joinColumns = {
#JoinColumn(name = "id_addon_fk", referencedColumnName = "id_addon")},
inverseJoinColumns = {
#JoinColumn(name = "id_car_fk", referencedColumnName = "id_car")})
to both side
just reverse the joinColumns and inverseJoinColumns
Try adding (fetch = FetchType.EAGER) to your ManyToMany annotation