Get just added Object with EntityManagerFactory - java

I'm developing a java app with MySql database, JPA objects and EntityManagerFactory with EclipseLink to manage the database. Everything works Ok but I have an issue.
One of my JPA objects is like this
public class JPAObject1{
#Id
#GeneratedValue
private int id;
#OneToMany(//things here)
List<JPAObject2> list1;
...
}
So the id field will be autogenerated by the EntityManagerFactory when I store it in the database. Asumming em type EntityManager and object type JPAObject1:
em.getTransaction().begin();
em.persist(object);
em.getTransaction().commit();
//house work closing things
The JPAObject1 is added correctly, I can see all fields in my database. As field id is the key to do the find operation, my question is:
Is there a way to get the last added object on the EntityManager on just the moment it is added?
Because I have others objects that use the JPAObject1 id field as a foreign key and I need that field when just the object is added to the database to link the others, but the only way I know to get it is getting all the JPAObjects and getting the last one in the Collection. So, with a few Objects it won't be a problem but if one process insert on database and another do the same before process 1 does the findAll to get the last added, there will be a coherence error....
I think I've explained it well.
Thanks a lot!

you can use this code
Obejct en = new Obejct ();
en.setxxx("My name");
em.persist(en);
em.flush();
System.out.println(en.getId());
the id genrated after flush

Note that the datas saved to database is a set, not list. So they don't have the order or anything like that, and you can't get the last one you've added. If you want to, pls add a column like date, time..., and the query will be like:
" SELECT * FROM Table ORDER BY dateColumn DESC LIMIT 1"

Related

ID of java objects not synchronizing with database

The only link I found that's close to what I am experiencing is this one :
How do you synchronize the id of a java object to its associated db row?
and there's not much of a solution in it.
My problem is that my Java objects aren't updated after being added to my database despite the .commit()
em.getTransaction().begin();
System.out.println(eleve.getID());
em.persist(eleve);
em.getTransaction().commit();
System.out.println(eleve.getID());
which refers to this class
public class Eleve {
private String _nom;
private String _prenom;
private float _ptsMerite;
#Id
private int _IDEleve;
and yields this output :
0
0
I think I've done everything properly when it comes to the persistence since it does create the object in the database (mySQL) with correct ID's which I've set to be autoincrement.
I am using javax.persistence for everything (annotations and such).
Did you try to add the #GeneratedValue annotation at your ID field?
There are four possible strategies you can choose from:
GenerationType.AUTO: The JPA provider will choose an appropriate strategy for the underlying database.
GenerationType.IDENTITY: Relies on a auto-increment column in your database.
GenerationType.SEQUENCE: Relies on a database sequence
GenerationType.TABLE: Uses a generator table in the database.
More info: https://www.baeldung.com/jpa-strategies-when-set-primary-key
If you ever change to a more powerful framework it is likely that this manages your transactions (CMT) so you can't (or don't want) commit everytime you want to access the ID for a new entity. In these cases you can use EntityManager#flush to synchronize Entity Manager with database.

HibernateException when updating a collection configured with delete orphan : can't save the parent object

I work on a Java project and I have to write a new module in order to copy some data from one database to another (same tables).
I have an entity Contrat containing several fields and the following field :
#OneToMany(mappedBy = "contrat", fetch = FetchType.LAZY)
#Fetch(FetchMode.SUBSELECT)
#Cascade( { org.hibernate.annotations.CascadeType.ALL, org.hibernate.annotations.CascadeType.DELETE_ORPHAN })
#BatchSize(size = 50)
private Set<MonElement> elements = new HashSet<MonElement>();
I must read some "Contrat" objects from a database and write them in another database.
I hesitate between 2 solutions :
use jdbc to query the first database and get the objects and then write those objects into the second database (paying attention to the order and the different keys). It will be long.
as the project currently uses Hibernate and contains all hibernate mapping classes, I was thinking about opening a first session to the first database, reading the hibernate Contrat object, setting the ids to null in the children elements and writing the object to the destination database with a second session. It should be quicker.
I wrote a test class for the second use case and the process fails with the following exception :
org.hibernate.HibernateException: Don't change the reference to a
collection with cascade="all-delete-orphan"
I think the reference must change when I set the ids to null, but I am not sure : I don't understand how changing a field of a Collection member can change the Collection reference
Note that if I remove DELETE_ORPHAN from the configuration, everything works, all the objects and their dependencies are written in the database.
So I would like to use the hibernate solution which is faster but I have to keep the DELETE_ORPHAN feature because the application currently uses this feature to ensure that every MonElement removed from the elements Set will be deleted in the database.
I don't need this feature but cannot remove it.
Also, I need to set the MonElement ids to null in order to generate new ones because their id in the first database may exist in the target database.
Here is the code I wrote which works well when I remove the DELETE_ORPHAN option.
SessionFactory sessionFactory = new AnnotationConfiguration().configure("/hibernate.cfg.src.xml").buildSessionFactory();
Session session = sessionFactory.openSession();
// search the Contrat object
Criteria crit = session.createCriteria(Contrat.class);
CriteriaUtil.addEqualCriteria(crit, "column", "65465454");
Contrat contrat = (Contrat)crit.list().get(0);
session.close();
SessionFactory sessionFactoryDest = new AnnotationConfiguration().configure("/hibernate.cfg.dest.xml").buildSessionFactory();
Session sessionDest = sessionFactoryDest.openSession();
Transaction transaction = sessionDest.beginTransaction();
// setting id to null, also for the elements in the elements Set
contrat.setId(null);
for (MonElement element:contrat.getElements()) {
element.setId(null);
}
// writing the object in the database
sessionDest.save(contrat);
transaction.commit();
sessionDest.flush();
sessionDest.close();
This is way faster than managing myself the queries and the primary / foreign keys and dependencies between objects.
Does anyone have an idea to get rid of this exception ?
Or maybe I should change the state of the Set.
In fact I'm not trying to delete any element of this Set, I just want them to be considered as new objects.
If I don't find a solution, I will do something dirty : duplicate all hibernate entity objects in my new project and remove the DELETE_ORPHAN parameter in the newly created Contrat.
So the application will continue using its mapping and my new project will use my specific mapping. But I want to avoid that.
Thanks
A correct solution has been written by crizzis as a comment to my question.
I quote him :
I'd try wrapping the contrat.elements in a new collection (contrat.setElements(new HashSet<>(contrat.getElements())) before trying to persist the contract with the new session
It works well.

How can I update a row based on id in hibernate

I am new in hibernate. I am using SesssionFactory to get the session of the transaction and one way which I found after searching is used for setting few fields using set parameter i.e
Query query = getCurrentSession().createSQLQuery(
"UPDATE table_name set field1=:f1 where ID=:id");
query.setParameter("f1", f1);
query.setParameter("id", id);
but I want to update the whole row. I have already set the values in the entity class but is there a way to pass the values of the whole entity class to the database based on the id the id is the primary key for the table which I want to update.
you already have all data present in the hibernate entity object? Then just call the session directly:
getCurrentSession().save(myEntity);
to create a new object, or
getCurrentSession().update(myEntity);
to update an existing row.
If not sure, you can use:
getCurrentSession().saveOrUpdate(myEntity);
Take a look at Session#update (or saveOrUpdate). This will allow you to persist a complete, mapped, object to the database.
To be as OO as you can, you can get entity by session.get(entityClass, id);
And then after modifying object by setters/getters, you can save it back to the DB using update method :session.update(entity);

Does JPA JPQL select query reads from database or from persistence context?

I have Document entity and some managed document object for doc with id=1.
Document managedDoc = entityManager.find(Document .class, 1);
managedDoc.setName("changedName");
As I know, managed doc state changed in persistent context (futher PC) after calling setter but nothing changed in database. Somewhere in my code I do the following:
Query query = entityManager.createQuery("from Document");
List<Document> list = query.getResultList();
return list;
When I perform select-all query as shown above, is document with id=1 taken from DB or from PC? From DB means select will not see new name because new name still in PC.
Actually, my problem is in updating via merge() and flush() and futher retrieving all objects - currently my select-all query doesn't see new values of some fields. Looks like merge+flush is OK, but JPA Query reads not from DB but from PC. But even if I'm right, both PC and DB contains new value of the name, why my select-all doesn't see it?
Moreover, select all sometimes returns correct/updated values, sometimes not
UPDATE
Clarification:
I put some object to PC via entityManager.find(Document .class, 1);
I create new detached instance with some name property setted. Id and other props gotten from managed instance. For example,
managedDoc = getFromSomeDataStructure();
Document nonManaged = new Document(managedDoc.getId()); nonManaged.setName("newName");
I update DB via em.merge(nonManaged);flush();
I saw my changes in DB when check it in Workbench.
I'm pressing F5 (and even CTRL+F5) button which performs select-all JPQL query and on each odd button press==select-all query I see non-actual old value, on each even button press==select-all query I see correct value.
It will be taken from the Persistent Context, as long as it has them their. To be more correct: as long as you have an entity in a managed state (i.e in the Persistence Context), it will not be overrriden. Of course, in the context when the same EntityManager instance is used.
If you want to refetch the value from DB, you have different possibilities:
Use another EntityManager, in a different transaction (important!).
Use EntityManager.detach() or if you want to clear the entire persistence context, use EntityManager.clear()
Use EntityManager.refresh() to throw out all changes made to an entity instance.
Let me try to clarify with a couple of examples an maybe this answer your question or with luck, helps to make the question clearer.
Scenario #1: Two Different Reads
Department department = em.find(Department.class, 1);
department.setName("Jedi Masters");
TypedQuery<Department> typedQuery = em.createQuery("SELECT d FROM Department d", Department.class);
List<Department> departments = typedQuery.getResultList();
for(Department found : departments){
if(found.getId().equals(1)){
assert found == department;
assert found.getName().equals(department.getName());
}
}
In this first scenario you can expect the department and found to be exact same instance and therefore have the exact same values. Both assertions above pass.
Scenario #2: Merging Detached Entity
//detached entity
Department department = new Department();
department.setId(1);
department.setName("Jedi Masters");
em.merge(department);
TypedQuery<Department> typedQuery = em.createQuery("SELECT d FROM Department d", Department.class);
List<Department> departments = typedQuery.getResultList();
for(Department found : departments){
if(found.getId().equals(1)){
assert found != department);
assert found.getName().equals(department.getName());
}
}
At least with Hibernate, the behavior in this case is slightly different. The two objects are not the same instance. They are different instances, but they still should have the same contents.
So, depending on your implementation on how you are comparing them you might get unexpected results, above all if you do not implemented a right equals/hashCode protocol for detached cases like this.
As answered here, I should call refresh() for each item in result list. But only refreshing didn't work for me. After setting READ COMMITED in persistence.xml by writing
<property name="hibernate.connection.isolation" value="2" />
everything worked perfectly.
P.S Don't forget to mark select method as #Transactional because refresh() doesn't work without this annotation.

Hibernate returns only one result (After changing table names)

I am using hibernate retrieve results from my MySQL database into my Java project. Recently, I had a lot of redundant data and had to manually clean up the database by copying the required data into new tables and then renaming the newly created table to old table.
But, now querying the database with hibernate gives only one row as the result. I have manually checked the database and there are several different rows in the database. My query to Hibernate is something like this:
Criteria c = session.createCriteria(UserDto.class);
c.setMaxResults(100);
List<UserDto> users = c.list();
users contains 100 elements but all are the same.
The mapping of userDto is here.
Any idea what is happening here?
If your UserDto class has ToMany relations, then this is quite possible that outer join on them results in many records which all contain one and the same user data. You should use
session.createCriteria(UserDto.class).setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
Double-check your mapping of the UserDto class to the database.
My guess is that you don't have it mapped to the table that you think you do.
As Sebastien mentioned, setting hibernate.show.sql to true should make this obvious.
Did you deleted the old tables? And in the configuration file what is the value for "hibernate.hbm2ddl.auto"?
I think the reason is these records have same id, so Hibernate treat them as the same record. You can check it.
I had same problem. In my case, the problem detected when I created a table in MySQL manually and I tried to read data from that table using hibernate and a dto class. After checking my dto class fields and database table, I figured out that there is a difference between table column named "id" and the class field which named dbId. The code was something like this:
#Id
#GeneratedValue
#Column(name="db_id", unique = true)
private long dbId;
So I edited the name and changed the code:
#Id
#GeneratedValue
#Column(name="id", unique = true)
private long dbId;
Which "id" was the correct name of databse table column and the problem has been solved.

Categories

Resources