Java entity manager does not load changes when I manually update row - java

I have a simple service injected by guice. It uses Hibernate, EntityManager.
class BrillantPaulaServiceImpl implements BrillantPaulaService {
#Inject
EntityManager em;
#Override
public Status EnqueueStatusCheck(Integer statusId) {
Status status = em.find(Status.class, statusId);
EntityTransaction transaction = em.getTransaction();
try {
//..... do some work
} finally {
if (transaction.isActive()) {
transaction.rollback();
}
}
}
return status;
}
When I manually update row from pgsql, the "Status status = em.find(Status.class, statusId);" does not view changes. It returns old entity. What is the possible issue?

What em.find(...) does is that it first checks persistence context and because there is cached entity it returns it instead of getting it from database. Here is some quote from here:
Find by primary key, using the specified properties. Search for an
entity of the specified class and primary key. If the entity instance
is contained in the persistence context, it is returned from there. If
a vendor-specific property or hint is not recognized, it is silently
ignored.
In the case that cache is used, JPA will get the entities from there. It will track changes to those entities only if they are modified via JPA. If you update the underlying data yourself, either directly or via some other external system, JPA will not be aware of those changes.

Related

How to force commit transaction of EntityManager

I have the following case in a web app:
#Stateless
#LocalBean
public class AccountBean {
#PersistenceContext(unitName = "foreign-context")
private EntityManager fem;
#PersistenceContext(unitName = "own-context")
private EntityManager oem;
public void doCreate() {
Account account = createAccount();
SubAccount subAccount = createSubAccount(account);
}
private Account createAccount() {
Account account = new Account("This is a sample");
oem.persist(account);
oem.flush();
return oem;
}
private SubAccount createSubAccount(Account account) {
SubAccount subAccount = new SubAccount(account.getId()); // This field is only set after Account entity is persisted
fem.persist(subAccount);
fem.flush();
return subAccount;
}
}
The problem as I see it is that account.getId() returns the default value 0 (as is int) causing SQL exception when attempting to save SubAccount due to table constraints. The ID field on account is supposed to be set after the Account is persisted however I suspect that due to the uncommitted transactions the result is not persisted, therefore the ID field is not updated in code. I have tested both of the methods individually and they seem to work fine but when combined the issue arises.
I have to use two separate EntityManager(s) due to business requirements having the model objects in different project dependencies each with it's own descriptor.
I have tried creating a new container managed transaction using #TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) on createAccount() but to no avail.
The code runs on a Wildfly Server using Hibernate 5.2.4 if that's relevant. No other framework is used (except JPA)
How can I alleviate this issue?
Do not completely understand your code, method persist should return you object with id and you can use it.

JPA flushing to Database before #PreUpdate is called

I am trying to capture the entity data in the database before the save is executed, for the purpose of creating a shadow copy.
I have implemented the following EntityListener in my Spring application:
public class CmsListener {
public CmsListener() {
}
#PreUpdate
private void createShadow(CmsModel entity) {
EntityManager em = BeanUtility.getBean(EntityManager.class);
CmsModel p = em.find(entity.getClass(), entity.getId());
System.out.println(entity);
}
}
The entity does indeed contain the entity object that is to be saved, and then I inject the EntityManager using another tool, which works fine - but for some reason, the entity has already been saved to the database. The output of CmsModel p = em.find(...) results in identical data which is in entity.
Why is JPA/hibernate persisting the changes before #PreUpdate is called? How can I prevent that?
I would assume this is because em.find doesn't actually query the database but fetches the object from cache, so it actually fetches the same object entity refers to (with changes already applied).
You could check your database log for the query that fetches the data for entity.id to verify this is indeed the case or you could add a breakpoint in createShadow() and have a look at the database entry for entity at the time the function is called to see for yourself if the changes are already applied to the database at that time.
To actually solve your problem and get your shadow copy you could fetch the object directly from database via native query.
Here is an untested example of what this could look like:
public CmsModel fetchCmsModelDirectly(){
Query q = em.createNativeQuery("SELECT cm.id,cm.value_a,cm.value_b FROM CmsModel cm", CmsModel.class);
try{
return q.getSingleResult();
}catch(NoResultException e){
return null;
}
}
Do you check if the entity is really updated to database? My suspect is that the change is only updated to the persistence context (cache). And when the entity is query back at the listener, the one from the cache is returned. So they are identical.
This is the default behavior of most of the ORM (JPA in this case) to speed up the data lookup. The ORM framework will take care of the synchronizing between the persistence context and the database. Usually when the transaction is committed.

Using Both Query Api And Criteria Api in Hibernate Leads Problems

When I update a row with query API and then retrieve the data with criteria API in the same transaction, I get old value, not the updated value. Why is it like that and how can I solve the problem? I need to get the updated value.
#Service
#Transactional
public class ExampleServiceImpl implements ExampleService {
#Autowired
ExampleRepository exampleRepository;
#Transactional
public void example() {
ExampleEntity entity = (ExampleEntity) sessionFactory.getCurrentSession().createCriteria(ExampleEntity.class).add(Restrictions.eq("id", 190001L)).uniqueResult();
exampleRepository.updateState(190001L, State.CLOSED);
ExampleEntity updatedEntity = (ExampleEntity)sessionFactory.getCurrentSession().createCriteria(ExampleEntity.class).add(Restrictions.eq("id", 190001L)).uniqueResult();
assertEquals(State.CLOSED, updatedEntity.getState());
}
}
#Repository
public class ExampleRepositoryImpl implements ExampleRepository {
public void updateState(Long id, State state) {
String updateScript = "update exampleEntity set state= '%s', " +
"VERSION = VERSION + 1 " +
"where ID = %s;";
updateScript = String.format(updateScript, state, id);
Query sqlQuery = sessionFactory.getCurrentSession().createSQLQuery(updateScript);
sqlQuery.executeUpdate();
}
}
Note: If I delete the first line and don't retrieve entity at the beginning everything works as I expected.
You are mixing native SQL and hibernate. Basically, when you first retrieve the entity, it gets stored in your session EntityManager. You then use plain SQL to update the row in the database, but as far as hibernate is concerned, the entity has not been dirtied because it isn't clever enough to understand how plain SQL relates to the object model. When you retrieve it the second time, it simply gives you the original entity it already has cached in the EntityManager rather than querying the database.
The solution is to simply manually force evict the entity from the EntityManager after the update as follows:
sessionFactory.getCurrentSession().evict(entity);
Or you could simply update the entity you fetched and persist it (best solution IMHO, no superfluous DAO method, and best abstraction away from the database):
ExampleEntity entity = (ExampleEntity) sessionFactory.getCurrentSession().createCriteria(ExampleEntity.class).add(Restrictions.eq("id", 190001L)).uniqueResult();
entity.setState(State.CLOSED);
entity.setVersion(e.getVersion() + 1);
sessionFactory.getCurrentSession().update(entity);
Basically... whichever option you choose, don't mix plain SQL and hibernate queries in the same transaction. Once hibernate has an object loaded, it will return that same entity from its cache until it knows for a fact that it is dirty. It is not clever enough to know that an entity is dirty when plain SQL was used to dirty it. If you have no choice and must use SQL (and this should never be the case in a well designed hibernate model), then call evict to tell hibernate the entity is dirty.
Your transaction is still not committed when you get result - this is the reason you get "old" value.

Bulk inserting existing data: Preventing JPA to do a select before every insert

I'm working on a Spring Boot application that uses JPA (Hibernate) for the persistence layer.
I'm currently implementing a migration functionality. We basically dump all the existing entities of the system into an XML file. This export includes ids of the entities as well.
The problem I'm having is located on the other side, reimporting the existing data. In this step the XML gets transformed to a Java object again and persisted to the database.
When trying to save the entity, I'm using the merge method of the EntityManager class, which works: everything is saved successfully.
However when I turn on the query logging of Hibernate I see that before every insert query, a select query is executed to see if an entity with that id already exists. This is because the entity already has an id that I provided.
I understand this behavior and it actually makes sense. I'm sure however that the ids will not exist so the select does not make sense for my case. I'm saving thousands of records so that means thousands of select queries on large tables which is slowing down the importing process drastically.
My question: Is there a way to turn this "checking if an entity exists before inserting" off?
Additional information:
When I use entityManager.persist() instead of merge, I get this exception:
org.hibernate.PersistentObjectException: detached entity passed to
persist
To be able to use a supplied/provided id I use this id generator:
#Id
#GeneratedValue(generator = "use-id-or-generate")
#GenericGenerator(name = "use-id-or-generate", strategy = "be.stackoverflowexample.core.domain.UseIdOrGenerate")
#JsonIgnore
private String id;
The generator itself:
public class UseIdOrGenerate extends UUIDGenerator {
private String entityName;
#Override
public void configure(Type type, Properties params, ServiceRegistry serviceRegistry) throws MappingException {
entityName = params.getProperty(ENTITY_NAME);
super.configure(type, params, serviceRegistry);
}
#Override
public Serializable generate(SessionImplementor session, Object object)
{
Serializable id = session
.getEntityPersister(entityName, object)
.getIdentifier(object, session);
if (id == null) {
return super.generate(session, object);
} else {
return id;
}
}
}
If you are certain that you will never be updating any existing entry on the database and all the entities should be always freshly inserted, then I would go for the persist operation instead of a merge.
Per update
In that case (id field being set-up as autogenerated) the only way would be to remove the generation annotations from the id field and leave the configuration as:
#Id
#JsonIgnore
private String id;
So basically setting the id up for always being assigned manually. Then the persistence provider will consider your entity as transient even when the id is present.. meaning the persist would work and no extra selects would be generated.
I'm not sure I got whether you fill or not the ID. In the case you fill it on the application side, check the answer here. I copied it below:
Here is the code of Spring SimpleJpaRepository you are using by using Spring Data repository:
#Transactional
public <S extends T> S save(S entity) {
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
It does the following:
By default Spring Data JPA inspects the identifier property of the given entity. If the identifier property is null, then the entity will be assumed as new, otherwise as not new.
Link to Spring Data documentation
And so if one of your entity has an ID field not null, Spring will make Hibernate do an update (and so a SELECT before).
You can override this behavior by the 2 ways listed in the same documentation. An easy way is it to make your Entity implement Persistable (instead of Serializable), which will make you implement the method "isNew".

JPA, removing an entity which has found by different manager

Assume we have a simple entity bean, like above
#Entity
public class Schemes implements serializable{
...
#Id private long id;
...
}
I find a record using find method and it works perfect, the problem is I cannot manipulate it(remove) by another EntityManager later, for example I find it with a method, and later I want to remove it, what is the problem?! if I find it with same manager again I would remove it, but if object has found by another manager I cannot.
#ManagedBean #SessionScopped class JSFBean {
private Schemes s;
public JSFBean(){
....
EntityManager em;//.....
s=em.find(Schemes.class,0x10L);//okay!
....
}
public void remove(){//later
....
EntityManager em;//.....
em.getTransaction().begin();
em.remove(s);//Error! some weird error, it throws IllegalArgumentException!
em.getTransaction().commit();
....
}
}
many thanks.
You are probably getting a java.lang.IllegalArgumentException: Removing a detached instance.
The two EMs do not share a persistence context and for the second EM, your object is considered detached. Trying to remove a detached object will result in an IllegalArgumentException.
You can refetch the entity before the removal:
Schemes originalS = em.find(Schemes.class, s.getId());
em.remove(originalS);
EDIT You can also delete the entity without fetching it first by using parametrized bulk queries:
DELETE FROM Schemes s WHERE s.id = :id
Be aware that bulk queries can cause problems on their own. First, they bypass the persistence context, meaning that whatever you do with a bulk query will not be reflected by the objects in the persistence context. This is less an issue for delete queries than for update queries. Secondly, if you have defined any cascading rules on your entites - they will be ignored by a bulk query.

Categories

Resources