JPA ManyToMany persist - java

I have a NOTIFICATION table who contains one ManyToMany association :
#Entity
#Table(name="NOTIFICATION")
#NamedQuery(name="Notification.findAll", query="SELECT f FROM Notification f")
public class Notification {
/** SOME COLUMN DEFINITION NOT IMPORTANT FOR MY CASE
COD, DATE, ID_THEME, ID_TYP, IC_ARCH, ID_CLIENT, INFOS, NAME, TITRE_NOT, ID_NOT
**/
#ManyToMany
#JoinTable(
name="PJ_PAR_NOTIF"
, joinColumns={
#JoinColumn(name="ID_NOTIF")
}
, inverseJoinColumns={
#JoinColumn(name="ID_PJ_GEN")
}
)
private List<PiecesJointesGen> piecesJointesGens;
}
So, I have an association table called PJ_PAR_NOTIF.
I try to persist a Notification entity. Here is piecesJointesGens initialisation, from a Value Object :
#PersistenceContext(unitName="pu/middle")
private EntityManager entityMgr;
FoaNotification lFoaNotification = new FoaNotification();
for(PieceJointeGenVO lPJGenVO : pNotificationVO.getPiecesJointes()){
PiecesJointesGen lPiecesJointesGen = new PiecesJointesGen();
lPiecesJointesGen.setLienPjGen(lPJGenVO.getLienPieceJointeGen());
lPiecesJointesGen.setIdPjGen(lPJGenVO.getIdPieceJointeGen());
lNotification.getFoaPiecesJointesGens().add(lFoaPiecesJointesGen);
}
entityMgr.persist(pNotification);
The persist doesn't work. JPA generate a first insert for my Notification object, that is ok :
insert
into
NOTIFICATION
(COD, DATE, ID_THEME, ID_TYP, IC_ARCH, ID_CLIENT, INFOS, NAME, TITRE_NOT, ID_NOT)
values
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
Then, JPA try to insert values in my association table, but piecesJointesGen doesn't exists for the moment :
insert
into
PJ_PAR_NOTIF
(ID_NOTIF, ID_PJ_GEN)
values
(?, ?)
So, I have this error :
GRAVE: EJB Exception: : java.lang.IllegalStateException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.entities.PiecesJointesGen
Is there a way to tell JPA to insert piecesJointesGen before the PJ_PAR_NOTIF insert ?

Modify piecesJointesGens mapping to #ManyToMany(cascade = CascadeType.PERSIST).

Related

JPA Inserting an entity if not exists in concurrent operation

I have a simple use case:
I want to insert an entity that doesn't exist.
Here is the entity class, it has combined unique columns:
#Entity
#Table(uniqueConstraints = {
#UniqueConstraint(columnNames = {
"firstName", "lastName"
})
})
public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String firstName;
private String lastName;
And here is the simple insertion logic:
Optional<Customer> customer = customerRepository.findByFirstNameAndLastName("John", "Doe");
if (!customer.isPresent()) {
customerRepository.save(new Customer("John", "Doe"));
}
when I call this via concurrent threads, I get this ConstraintViolationException and that's obvious
insert into customer (first_name, last_name, id) values (?, ?, ?) [23505-200]]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement] with root cause
org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException: Unique index or primary key violation: "PUBLIC.UKKIYY7M3FWM4VO5NIL9IBP5846_INDEX_5 ON PUBLIC.CUSTOMER(FIRST_NAME, LAST_NAME) VALUES 8"; SQL statement:
insert into customer (first_name, last_name, id) values (?, ?, ?) [23505-200]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:459) ~[h2-1.4.200.jar:1.4.200]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:429) ~[h2-1.4.200.jar:1.4.200]
I can catch this exception, but ConstraintViolationException can occur in any scenario other than the unique index.
I also tried JPA's Optimistic locking but it seems to work only in the update and delete operations but not in insertion.
I don't want native query (like ON DUPLICATE KEY in mysql, ON CONFLICT in PostgresSQL) because I want to make operations to be handled in JPA itself.
Also, I cannot make it threadsafe because the application will be running on multiple JVMs.
Is there any way somehow I can handle this, like locking on insert or atleast update on same keys?

Can't fetch join lazy initialized collections

I am unable to perform a simple fetchjoin because of MultipleBagFetchException.
#Entity
public class Person {
#OneToMany(mappedBy="person",fetch=FetchType.LAZY)
private List<Auto> autos;
}
#Entity
public class Auto {
#ManyToOne
#JoinColumn(name = "person_id", nullable = false)
private Person person;
#OneToMany(mappedBy="auto",fetch=FetchType.LAZY)
private List<Tool> tools;
}
#Entity
#Table(name="tool")
public class Tool {
#ManyToOne
#JoinColumn(name = "auto_id", nullable = false)
private Auto auto;
}
As you can see all of my associactions uses default fetchtype.
#Query("SELECT p FROM Person p JOIN FETCH p.autos a JOIN FETCH a.tools")
List<Person>findAll();
result:
Caused by: org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags: [com.example.entities.Person.autos, com.example.entities.Auto.tools]
I have read about this exceptions, but in those cases the reason for this exception was the usage of EAGER fetch type for collections. What about this? This the most simple Entity relation.
And on the top of that lets suppose we are not allowed to touch the Entities.
How to solve this only on the query side?
There is one way to avoid n+1 queries without touching the entities, only changing the query for findAll. We can write a wrapper function which will first load persons with autos and them fetch all tools in a single select.
PersonRepository
#Query("SELECT distinct p FROM Person p JOIN FETCH p.autos a")
List<Person> findAll();
Wrapper code
List<Person> persons = personRepository.findAll();
Session session = (Session) entityManager.getDelegate();
List<Auto> autos = new ArrayList<>();
for (Person person : persons) {
if(!CollectionUtils.isEmpty(person.getAutos())) {
autos.addAll(person.getAutos());
}
}
try{
autos = session.createQuery("select distinct a from Auto a Join fetch a.tools " +
" where a in :autos", Auto.class)
.setParameter("autos", autos)
.setHint(QueryHints.PASS_DISTINCT_THROUGH, false)
.getResultList();
} catch (Exception ex) {
ex.printStackTrace();
}
The first query will be:
SELECT DISTINCT
person0_.id AS id1_6_0_,
autos1_.id AS id1_0_1_,
person0_.name AS name2_6_0_,
autos1_.name AS name2_0_1_,
autos1_.person_id AS person_i3_0_1_,
autos1_.person_id AS person_i3_0_0__,
autos1_.id AS id1_0_0__
FROM
Person person0_
INNER JOIN
Auto autos1_
ON
person0_.id=autos1_.person_id
The second query generated will be :
SELECT
auto0_.id AS id1_0_0_,
tools1_.id AS id1_8_1_,
auto0_.name AS name2_0_0_,
auto0_.person_id AS person_i3_0_0_,
tools1_.auto_id AS auto_id3_8_1_,
tools1_.name AS name2_8_1_,
tools1_.auto_id AS auto_id3_8_0__,
tools1_.id AS id1_8_0__
FROM
Auto auto0_
INNER JOIN
Tool tools1_
ON
auto0_.id=tools1_.auto_id
WHERE
auto0_.id IN (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
Other than this I believe our options are limited, we will have to change Tool entity FetchMode or add BatchSize for default FetchMode.SELECT in order to get Tools in a separate query.
#OneToMany(mappedBy = "auto", fetch = FetchType.LAZY)
#Fetch(FetchMode.SUBSELECT)
private List<Tool> tools;
The query will be
SELECT
tools0_.auto_id AS auto_id3_8_1_
, tools0_.id AS id1_8_1_
, tools0_.id AS id1_8_0_
, tools0_.auto_id AS auto_id3_8_0_
, tools0_.name AS name2_8_0_
FROM
Tool tools0_
WHERE
tools0_.auto_id IN
(
SELECT
autos1_.id
FROM
Person person0_
INNER JOIN
Auto autos1_
ON
person0_.id=autos1_.person_id
)

JPA doesn't save result of select with #Transactional annotation

I can't save result of select into database using JPA in Spring Boot application. The code that I use is below:
#Override
#Transactional
public void fetchAndSave() {
List<TestData> all = testDataRepository.findAllRecords();
testDataRepository.saveAll(all);
// let suppose I will save another data here that's why I need #Transactional for roll-back in case of exception
}
#Repository
public interface TestDataRepository extends JpaRepository<TestData, Long> {
#Query(value = "select raw_values.identificator AS id, raw_values.name as value from test.raw_values", nativeQuery = true)
List<TestData> findAllRecords();
}
When I call fetchAndSave with a property spring.jpa.show-sql=true I see in logs only select:
Hibernate: select raw_values.identificator AS id, raw_values.name as value from test.raw_values
In a case I don't use #Transactional I can see more requests to database in logs and values are saved:
Hibernate: select raw_values.identificator AS id, raw_values.name as value from test.raw_values
Hibernate: select testdata0_.id as id1_0_0_, testdata0_.value as value2_0_0_ from test.test_data testdata0_ where testdata0_.id=?
Hibernate: select testdata0_.id as id1_0_0_, testdata0_.value as value2_0_0_ from test.test_data testdata0_ where testdata0_.id=?
Hibernate: select testdata0_.id as id1_0_0_, testdata0_.value as value2_0_0_ from test.test_data testdata0_ where testdata0_.id=?
Hibernate: insert into test.test_data (value, id) values (?, ?)
Hibernate: insert into test.test_data (value, id) values (?, ?)
Hibernate: insert into test.test_data (value, id) values (?, ?)
I have a pretty simple table in database, DDL looks like:
create table test_data
(
id serial not null
constraint test_data_pk
primary key,
value varchar(256)
);
-- There are 3 records in table raw_values
create table table_name
(
identificator integer not null
constraint table_name_pk
primary key,
name varchar(256)
);
Can you help me to identify the reason of such behavior? I expect records to be saved into database when I use #Transactional.
The short answer for "why it does not save" is: because they are already saved.
The longer answer is Hibernate sees that these IDs has already present in DB, and it does not save them.
If you want to inset another three entities to DB, just create duplicates for these objects, with id=null and save them:
List<TestData> all = testDataRepository.findAllRecords();
List<TestData> copies = all.stream()
.map(testData -> new TestData(...)) //copy all the fields EXCEPT ID
.collect(toList());
testDataRepository.saveAll(copies);

Hibernate putting restrictions on collection elements conflicts with fetch mode

I am using Hibernate 4.3.8 as ORM tool for our MySql database. I have a class to be mapped which is annotated as follows:
#Entity
#DynamicUpdate
#Table(name = "myclass")
public class MyClass {
#Id
#Column(name = "myClassId")
private String id;
#Column(name = "status")
private String status;
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name = "myclass_children", joinColumns = #JoinColumn(name = "myClassId"))
#Column(name = "child")
#Fetch(FetchMode.JOIN)
#BatchSize(size = 100)
#Cascade(value = CascadeType.ALL)
private Set<String> children;
}
To perform read queries via Hibernate, I am asked to use Criteria API. I should mention at the beginning that using HQL or SQL are not options.
Using the following code performs exactly what I want to do: Performs a second select query to retrieve collection elements and returns exactly 20 MyClass objects.
public List<MyClass> listEntities() {
Session session = sessionFactory.openSession();
try {
Criteria criteria = session.createCriteria(MyClass.class);
criteria.setFetchMode("children", FetchMode.SELECT);
criteria.add(Restrictions.eq("status", "open"));
criteria.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
criteria.setMaxResults(20);
}
}
Here are the queries generated:
select
this.myClassId as myClassId_1_0_0,
this.status as status_2_0_0
from
myclass this
where
status = ?
limit ?
select
children0.myClassId as myClassId1_0_0,
children0.child as child2_0_0
from
myclass_children as children0_
where
children0_.myClassId in (
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
)
However, when I try to put a restriction on collection elements, hibernate performs a single join query. When number of rows (not distinct root entities) in the result set of this single query reaches to the limit, Hibernate returns the existing MyClass objects as result. If each MyClass objects as 2 children, 10 MyClass objects are returned.
public List<MyClass> listEntities() {
Session session = sessionFactory.openSession();
try {
Criteria criteria = session.createCriteria(MyClass.class);
criteria.setFetchMode("children", FetchMode.SELECT);
criteria.createCriteria("children", "ch", JoinType.LEFT_OUTER_JOIN);
criteria.add(Restrictions.eq("status", "open"));
criteria.add(Restrictions.in("ch.elements", Arrays.asList("child1", "child2"));
criteria.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
criteria.setMaxResults(20);
}
}
Here is the generated query:
select
this.id as id1_0_0,
this.status as status2_0_0,
ch.child as child1_0_2
from
myclass this
left outer join
myclass_children ch1_
on this.myClassId = ch1_.myClassId
where
this.status = ? limit ?
What can I do to obtain 20 MyClass objects while adding restrictions on collection elements? Any suggestions & answers are welcome!
NOTE: #Fetch(FetchMode.JOIN) annotation is used for other code base (like selecting by id, etc.). It should does not have any effect on my question since I am setting FetchMode.SELECT for criteria object separately.

Spring + Hibernate : a different object with the same identifier value was already associated with the session

In my application, which uses Spring and Hibernate, I parse a CSV file and populate the db by calling handleRow() every time a record is read from the CSV file.
My domain model:
'Family' has many 'SubFamily'
'SubFamily' has many 'Locus'
a 'Locus' belongs to a 'Species'
Family <-> SubFamily <-> Locus are all bi-directional mappings.
Code:
public void handleRow(Family dummyFamily, SubFamily dummySubFamily, Locus dummyLocus) {
//Service method which access DAO layers
CommonService serv = ctx.getCommonService();
boolean newFamily=false;
Family family=serv.getFamilyByFamilyId(dummyFamily.getFamilyId());
if(family==null){
newFamily=true;
family=new Family();
family.setFamilyId(dummyFamily.getFamilyId());
family.setFamilyIPRId(dummyFamily.getFamilyIPRId());
family.setFamilyName(dummyFamily.getFamilyName());
family.setFamilyPattern(dummyFamily.getFamilyPattern());
family.setRifID(dummyFamily.getRifID());
}
SubFamily subFamily = family.getSubFamilyBySubFamilyId( dummySubFamily.getSubFamilyId() );
if(subFamily==null){
subFamily=new SubFamily();
subFamily.setRifID(dummySubFamily.getRifID());
subFamily.setSubFamilyId(dummySubFamily.getSubFamilyId());
subFamily.setSubFamilyIPRId(dummySubFamily.getSubFamilyIPRId());
subFamily.setSubFamilyName(dummySubFamily.getSubFamilyName());
subFamily.setSubFamilyPattern(dummySubFamily.getSubFamilyPattern());
family.addSubFamily(subFamily);
}
//use the save reference, to update from GFF handler
Locus locus = dummyLocus;
subFamily.addLocus(locus);
assignSpecies(serv,locus);
//Persist object
if(newFamily){
serv.createFamily(family);
} else {
serv.updateFamily(family);
}
}
a Species is assigned to a Locus using following method, which simply accesses the DAO layer:
private void assignSpecies (CommonService serv, Locus locus) {
String locusId = locus.getLocusId();
String speciesId = CommonUtils.getLocusSpecies(locusId, ctx.getSpeciesList()).getSpeciesId();
//Simply get Species object from DAO
Species sp = serv.getSpeciesBySpeciesId(speciesId);
locus.setSpecies(sp);
}
Hibernate gives following error:
[INFO] Starting scheduled refresh cache with period [5000ms]
Hibernate: insert into species (species_id, name) values (?, ?)
Hibernate: insert into species (species_id, name) values (?, ?)
Hibernate: insert into species (species_id, name) values (?, ?)
############################ROW#####################1
SubFamiyID#######RIF0005913
Hibernate: select this_.id as id1_0_, this_.family_id as family2_1_0_, this_.rif_iD as rif3_1_0_, this_.family_name as family4_1_0_, this_.family_ipr_id as family5_1_0_, this_.family_pattern as family6_1_0_ from family this_ where this_.family_id=?
Creating NEW SubFamiyID#######RIF0005913
Hibernate: select this_.id as id3_0_, this_.species_id as species2_3_0_, this_.name as name3_0_ from species this_ where this_.species_id=?
Hibernate: insert into family (family_id, rif_iD, family_name, family_ipr_id, family_pattern) values (?, ?, ?, ?, ?)
Hibernate: insert into subfamily (sub_family_id, rif_iD, sub_family_name, sub_family_ipr_id, sub_family_pattern, family_id, sub_family_index) values (?, ?, ?, ?, ?, ?, ?)
Hibernate: insert into locus (locus_id, refTrans_id, function, species_id, sub_family_id, sub_family_index) values (?, ?, ?, ?, ?, ?)
Hibernate: update species set species_id=?, name=? where id=?
Hibernate: update subfamily set family_id=?, sub_family_index=? where id=?
Hibernate: update locus set sub_family_id=?, sub_family_index=? where id=?
############################ROW#####################2
SubFamiyID#######RIF0005913
Hibernate: select this_.id as id1_0_, this_.family_id as family2_1_0_, this_.rif_iD as rif3_1_0_, this_.family_name as family4_1_0_, this_.family_ipr_id as family5_1_0_, this_.family_pattern as family6_1_0_ from family this_ where this_.family_id=?
Hibernate: select subfamilie0_.family_id as family7_1_, subfamilie0_.id as id1_, subfamilie0_.sub_family_index as sub8_1_, subfamilie0_.id as id0_0_, subfamilie0_.sub_family_id as sub2_0_0_, subfamilie0_.rif_iD as rif3_0_0_, subfamilie0_.sub_family_name as sub4_0_0_, subfamilie0_.sub_family_ipr_id as sub5_0_0_, subfamilie0_.sub_family_pattern as sub6_0_0_, subfamilie0_.family_id as family7_0_0_ from subfamily subfamilie0_ where subfamilie0_.family_id=?
Hibernate: select locuslist0_.sub_family_id as sub5_1_, locuslist0_.id as id1_, locuslist0_.sub_family_index as sub7_1_, locuslist0_.id as id2_0_, locuslist0_.locus_id as locus2_2_0_, locuslist0_.refTrans_id as refTrans3_2_0_, locuslist0_.function as function2_0_, locuslist0_.sub_family_id as sub5_2_0_, locuslist0_.species_id as species6_2_0_ from locus locuslist0_ where locuslist0_.sub_family_id=?
Hibernate: select species0_.id as id3_0_, species0_.species_id as species2_3_0_, species0_.name as name3_0_ from species species0_ where species0_.id=?
Hibernate: select this_.id as id1_0_, this_.family_id as family2_1_0_, this_.rif_iD as rif3_1_0_, this_.family_name as family4_1_0_, this_.family_ipr_id as family5_1_0_, this_.family_pattern as family6_1_0_ from family this_ where this_.family_id=?
Hibernate: select this_.id as id3_0_, this_.species_id as species2_3_0_, this_.name as name3_0_ from species this_ where this_.species_id=?
Exception in thread "main" [INFO] Closing Compass [compass]
org.springframework.orm.hibernate3.HibernateSystemException: a different object with the same identifier value was already associated with the session: [com.bigg.nihonbare.common.domain.Species#1]; nested exception is org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: [com.bigg.nihonbare.common.domain.Species#1]
Caused by: org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: [com.bigg.nihonbare.common.domain.Species#1]
at org.hibernate.engine.StatefulPersistenceContext.checkUniqueness(StatefulPersistenceContext.java:590)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.performUpdate(DefaultSaveOrUpdateEventListener.java:284)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsDetached(DefaultSaveOrUpdateEventListener.java:223)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:89)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:70)
at org.hibernate.impl.SessionImpl.fireSaveOrUpdate(SessionImpl.java:507)
at org.hibernate.impl.SessionImpl.saveOrUpdate(SessionImpl.java:499)
at org.hibernate.engine.CascadingAction$5.cascade(CascadingAction.java:218)
at org.hibernate.engine.Cascade.cascadeToOne(Cascade.java:268)
Any tips?
Use merge(). The exception means that the current session is already aware of the entity you are passing. If not, check how you have overridden hashCode() and equals() - it should return different values for different entities.
You can also encounter this problem if you are doing a delete() or update(). The problem is likely to occur if you build the hibernate-mapped pojo yourself, perhaps from a DTO. This pojo now has the same identifier as one that is already in the Session, and that causes the problem.
You now have two options. Either do what #Bozho said and first merge() the object. That takes care of updating. For deleting, take the object returned by merge() and delete it.
The other option is to first query the Session using the id of the object and then delete or update it.
I have seen this when an Entity does not have a GeneratedValue annotation for its ID column:
#GeneratedValue(strategy = GenerationType.AUTO)
I resolved so:
On delete method:
this.getHibernateTemplate().clear();
this.getHibernateTemplate().delete(obj);
// Esta línea realiza el "commit" del comando
this.getHibernateTemplate().flush();
On update method:
this.getHibernateTemplate().merge(obj);
// Esta línea realiza el "commit" del comando
this.getHibernateTemplate().flush();
If you are updating an object evict() it from session after the saveOrUpdate() call, also check your hashCode implementation of the object.
You may have created two instances of Session
Session session = factory.openSession();
If you have opened one session in one function and executing another function with creating another session, then this problem occurs.
This happened to me because part of my compound key was null. Ex:
#Id
#Column(name = "id")
private String id;
#JoinColumn(name = "id")
private Username username;
Username happened to be null which led to "duplicate" null primary keys, even though the id was different.

Categories

Resources