JPA: check whether an entity object has been persisted or not - java

Is there a general method that can
if(entity is persisted before){
entity = entity.merge();
}else{
entity.persist();
}
So the method contain above logic is safe everywhere?

If you need to know is object already in persistence context you should use contains method of EntityManager.
Only EntityManager can tell you is entity persisted or not, entity does not have such information.
Here you can check javadoc for contains method.
if (!em.contains(entity)) {
em.persist(entity);
} else {
em.merge(entity);
}

To check if entity object has been persisted or not by the current PersistenceContext you can use the EntityManager method contains(Object entity)

Maybe it's too late, but here are my findings!
If you have an entity with a generate value, you can use it to check if the entity is already in DB, assuming you are not modifying this value manually.
#Entity
public class MyEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Integer id;
// getter...
}
public class Main {
public static void main() {
MyEntity myEntity1 = new MyEntity();
MyEntity myEntity2 = em.find(MyEntity.class, 4);
em.detach(myEntity2); // or em.close()
// other stuff and modifications
// begin transaction
persistEntity(myEntity1); // will use persist()
persistEntity(myEntity2); // will use merge()
// commit transaction
}
// This will manage correctly entities in different state
public void persistEntity(MyEtity entity) {
if (myEntity.getId() != null) em.merge(entity);
else em.persist(entity);
}
}
Using em.contains(entity) will fail in this scenario:
public static void main(){
MyEntity myEntity = em.find(MyEntity.class, 5);
em.detach(myEntity); // or em.close()
// We are going to execute persist() because the entity is detached
if (!em.contains(myEntity))
// This call will produce an exception org.hibernate.PersistentObjectException
em.persist(myEntity);
else
em.merge(myEntity);
}
There are a performance reasons to try to achieve what OP is trying to do. You surely can use em.merge() instead of em.persist(), but not without a cost.
A call to em.merge() is trying to retrieve an existing entity from DB with a SELECT query and update it. So if the entity was never persisted, this will waste some CPU cycles. On the other side em.persist() will only produce one INSERT query.

Related

Why is just this one field of a Hibernate controlled object/entity not being created or updated?

I have an object "Chemical" that is updated according to entries in an HTML page. The data is returned to the Java code correctly, yet this one field is not updating or being created in the database. The chemical entity is defined as follows:
#Entity
#NamedQuery(name="Chemical.findAll", query="SELECT c FROM Chemical c")
public class Chemical implements Serializable {
#Id
#Column(name="chemical_id")
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int chemicalId;
...
private string formula; <--- THE FIELD THAT WILL NOT UPDATE/BE CREATED
....
public void setFormula(String formula) {
this.formula = formula;
}
public String getFormula() {
return this.formula;
}
.....
The Java code that actually saves/updates the database:
public void saveOrUpdate(final T data) throws CPDPersistenceException {
final EntityManager em = getEntityManager();
try {
final EntityTransaction transaction = em.getTransaction();
transaction.begin();
em.merge(data);
transaction.commit();
} catch (final PersistenceException e) {
throw new PersistenceException(e);
}
}
After the code is executed, other fields have changed in the database if changes have been made. However, "formula" is not changed and remains a NULL field. Can anybody see why?
There is I think a difference between UPDATE and CREATE.
Try to replace em.merge(data) with em.persist(data)
Does it save the content the first time you create a new row with persist ?
What is the length of your string definition in the database ?
Check the database logs whenever the HQL is run from your code, do the log complain at one point during the persist ?
It might be necessary to first test if the row exists before you can run a merge.

Delete not saved object doesnt invoke exception

I'm writing tests for my Dao Spring application. I found out that when I delete not saved items no exception is invoked as I'd expect, I've got no idea why.
Model:
#Entity
public class Ingredient {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String condition;
private int quantity;
public Ingredient() {
}
}
The Dao implementation:
#Override
public void delete(Object o) throws DaoException {
try {
Session session = mSessionFactory.openSession();
session.beginTransaction();
session.delete(o);
session.getTransaction().commit();
session.close();
} catch (Exception ex) {
throw new DaoException(ex, String.format("Problem deleting %s object (delete method).", o));
}
}
And my test, expecting DaoException:
#Test
public void testDeleteNotSavedThrowsDaoException() throws Exception {
Ingredient ingredient = new Ingredient("Not saved ingredient","", 1);
ingredientDao.delete(ingredient);
}
Hibernate's Javadoc for Session#delete(Object) states:
Remove a persistent instance from the datastore. The argument may be an instance associated with the receiving Session or a transient instance with an identifier associated with existing persistent state.
So it's not an error to pass in a transient entity (as you do). Also, the Session#delete method does not declare any exceptions, so it's not defined what happens when you pass in an entity with an ID that does not exist in the DB. As you can see - nothing happens - you requested the entity not to exist in the DB, it's not there to start with, so no reason to throw an exception (according to Hibernate, at least).
Compare this to the basic SQL DELETE FROM X WHERE ID = Y - this does not check if a record with ID=Y exists, it will succeed either way (updating 0 or 1 rows).
UPDATE after realizing the passed in transient entity has null ID.
I've dug into the sources of Hibernate 5.2.2 Session and it seems that if the passed in entity has no ID, no DELETE query is even performed on that entity's table.
See DefaultDeleteEventListener#onDelete(DeleteEvent, Set):
if (ForeignKeys.isTransient( persister.getEntityName(), entity, null, source ) ) {
// yes, your entity is transient according to ForeignKeys.isTransient
deleteTransientEntity( source, entity, event.isCascadeDeleteEnabled(), persister, transientEntities );
return;
}
Now
protected void deleteTransientEntity(
EventSource session,
Object entity,
boolean cascadeDeleteEnabled,
EntityPersister persister,
Set transientEntities) {
LOG.handlingTransientEntity(); // Only log it
if ( transientEntities.contains( entity ) ) {
LOG.trace( "Already handled transient entity; skipping" );
return;
}
transientEntities.add( entity );
// Cascade deletion to related entities
cascadeBeforeDelete( session, persister, entity, null, transientEntities );
cascadeAfterDelete( session, persister, entity, transientEntities );
}
this will just print "HHH000114: Handling transient entity in delete processing" in the logs and do nothing with the entity (however, it will cascade the deletion to the related entities if there are any - not your case).
So again - it's OK to pass in a transient entity without an ID - it will simply not run a DELETE on the DB.
And that was an answer, Adam, there was no exception, because id of my new, not saved item was null. When I set id to value which not persist in DB exception was thrown.

Spring JPA - Best way to update multiple fields

I'm new to using JPA and trying to transition my code from JdbcTemplate to JPA. Originally I updated a subset of my columns by taking in a map of the columns with their values and created the SQL Update string myself and executed it using a DAO. I was wondering what would be the best way to do something similar using JPA?
EDIT:
How would I transform this code from my DAO to something equivalent in JPA?
public void updateFields(String userId, Map<String, String> fields) {
StringBuilder sb = new StringBuilder();
for (Entry<String, String> entry : fields.entrySet()) {
sb.append(entry.getKey());
sb.append("='");
sb.append(StringEscapeUtils.escapeEcmaScript(entry.getValue()));
sb.append("', ");
}
String str = sb.toString();
if (str.length() > 2) {
str = str.substring(0, str.length() - 2); // remove ", "
String sql = "UPDATE users_table SET " + str + " WHERE user_id=?";
jdbcTemplate.update(sql, new Object[] { userId },
new int[] { Types.VARCHAR });
}
}
You have to read more about JPA for sure :)
Once entity is in Persistence Context it is tracked by JPA provider till the end of persistence context life or until EntityManager#detach() method is called. When transaction finishes (commit) - the state of managed entities in persistence context is synchronized with database and all changes are made.
If your entity is new, you can simply put it in the persistece context by invoking EntityManager#persist() method.
In your case (update of existing entity), you have to get a row from database and somehow change it to entity. It can be done in many ways, but the simpliest is to call EntityManager#find() method which will return managed entity. Returned object will be also put to current persistence context, so if there is an active transaction, you can change whatever property you like (not the primary key) and just finish transaction by invoking commit (or if this is container managed transaction just finish method).
update
After your comment I can see your point. I think you should redesign your app to fit JPA standards and capabilities. Anyway - if you already have a map of pairs <Attribute_name, Attrbute_value>, you can make use of something called Metamodel. Simple usage is shown below. This is naive implementation and works good only with basic attributes, you should take care of relationships etc. (access to more informations about attributes can be done via methods attr.getJavaType() or attr.getPersistentAttributeType())
Metamodel meta = entityManager.getMetamodel();
EntityType<User> user_ = meta.entity(User.class);
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaUpdate<User> update = cb.createCriteriaUpdate(User.class);
Root e = update.from(User.class);
for( Attribute<? super User, ?> attr : user_.getAttributes() ) {
if (map.containsKey(attr.getName())) {
update.set(attr, map.get(attr));
}
}
update.where(cb.equal(e.get("id"), idOfUser));
entityManager.createQuery(update).executeUpdate();
Please note that Update Criteria Queries are available in JPA since 2.1 version.
Here you can find more informations about metamodel generation.
Alternatively to metamodel you can just use java reflection mechanisms.
JPA handles the update. Retrieve a dataset as entity using the entitymanager, change the value and call persist. This will store the changed data in your db.
In case you are using Hibernate(as JPA provider), here's an example
Entity
#Entity
#Table(name="PERSON")
public class Person {
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
#Column(name="NAME", nullable=false)
private String name;
other fields....
}
DAO
public interface PersonDao {
Person findById(int id);
void persist(Person person);
...
}
DaoImpl
#Repository("personDao")
public class PersonDaoImpl extends AnAbstractClassWithSessionFactory implements PersonDao {
public Person findById(int id) {
return (Person) getSession().get(Person.class, id);
}
public void persist(Person person){
getSession().persist(person);
}
}
Service
#Service("personService")
#Transactional
public class PersonServiceImpl implements PersonService {
#Autowired
PersonDao personDao;
#Override
public void createAndPersist(SomeSourceObject object) {
//create Person object and populates with the source object
Person person = new Person();
person.name = object.name;
...
personDao.persist(person);
}
#Override
public Person findById(int id) {
return personDao.findById(id);
}
public void doSomethingWithPerson(Person person) {
person.setName(person.getName()+" HELLO ");
//here since we are in transaction, no need to explicitly call update/merge
//it will be updated in db as soon as the methods completed successfully
//OR
//changes will be undone if transaction failed/rolledback
}
}
JPA documentation are indeed good resource for details.
From design point of view, if you have web interfacing, i tends to say include one more service delegate layer(PersonDelegateService e.g.) which maps the actual data received from UI to person entity (and viceversa, for display, to populate the view object from person entity) and delegate to service for actual person entity processing.

Multiple calls to entities "setter" methods by EclipseLink

Can somebody explain this behaviour?
Given an Entity MyEntity below, the following code
EntityManagerFactory emf = Persistence.createEntityManagerFactory("greetingPU");
EntityManager em = emf.createEntityManager();
MyEntity e = new MyEntity();
e.setMessage1("hello"); e.setMessage2("world");
em.getTransaction().begin();
em.persist(e);
System.out.println("-- Before commit --");
em.getTransaction().commit();
System.out.println("-- After commit --");
results in an output indicating multiple calls to the "setter" methods of MyEntity by the EclipseLinks EntityManager or its associates. Is this behaviour to be expected? Possibly for some internal performance or structural reasons? Do other JPA implementations show the same behaviour?
-- Before commit --
setId
setId
setMessage1
setMessage2
setId
setMessage1
setMessage2
-- After commit --
There seem to be two different kinds of reassignments. First, an initial set of the Id. Second, two consecutive settings of the whole Entity.
Debugging shows that all calls of a given "setter" have the same object as their parameter.
#Entity
public class MyEntity {
private Long id;
private String message1;
private String message2;
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE)
public Long getId(){ return id; }
public void setId(Long i) {
System.out.println("setId");
id = i;
}
public String getMessage1() { return message1; }
public void setMessage1(String m) {
message1 = m;
System.out.println("setMessage1");
}
public String getMessage2() { return message2; }
public void setMessage2(String m) {
message2 = m;
System.out.println("setMessage2");
}
}
Are you using weaving? http://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Advanced_JPA_Development/Performance/Weaving
EclipseLink must call setId once to set the generated ID in the managed entity instance. It will also create an instance and set its values for the shared cache, explaining another setId and set values calls. If you are not using weaving, because the EntityManager still exists, EclipseLink will also create a backup instance to use to compare for future changes - any changes to the managed entity after the transaction commits still need tracked.
If this isn't desirable, weaving allows attribute change tracking to be used instead so that backup copies aren't needed to track changes. You can also turn off the shared cache, but unless you are running into performance or stale data issues, this is not recommended.

openjpa, update, error 'PK has non-default value'

I wonder if anyone has come across this error and can explain what's happening:
<openjpa-2.1.1-SNAPSHOT-r422266:1087028 nonfatal user error>
org.apache.openjpa.persistence.InvalidStateException:
Primary key field com.qbe.config.bean.QBEPropertyHistory.id of com.qbe.config.bean.QBEPropertyHistory#1c710ab has non-default value.
The instance life cycle is in PNewProvisionalState state and hence an
existing non-default value for the identity field is not permitted.
You either need to remove the #GeneratedValue annotation or modify the
code to remove the initializer processing.
I have two objects, Property and PropertyHistory. Property has OneToMany List of PropertyHistory:
#OneToMany(fetch = FetchType.LAZY, cascade=CascadeType.MERGE, orphanRemoval=false)
#JoinColumn(name="PROPERTY_NAME")
#OrderBy("updatedTime DESC")
private List<QBEPropertyHistory> history = new ArrayList<QBEPropertyHistory>();
And Property object is loaded and saved like this:
public T find(Object id) {
T t = null;
synchronized(this) {
EntityManager em = getEm();
t = em.find(type, id);
//em.close(); //If this is uncommented, fetch=LAZY doesn't work. And fetch=EAGER is too slow.
}
return t;
}
public T update(T t) {
synchronized(this) {
EntityManager em = getEm();
em.getTransaction().begin();
t = em.merge(t);
em.getTransaction().commit();
em.close();
return t;
}
}
In the service layer I load a property using find(id) method, instantiate a new PropertyHistory, add it into property prop.getHistory().add(propHist) then call update(prop) and get the above error.
The error disappears if I close EntityManager in find() but that breaks lazy loading and prop.getHistory() always returns null. If I set fetch=EAGER it becomes unacceptably slow as there are 10s of 1000s of records and I need to select thousands of property objects at a time and history is not needed 99.99% of the time.
I can't remove the #GeneratedValue as the error text suggests because it is generated (DB2, autoincrement). Now I wonder how would i "modify the code to remove the initializer processing" ?
Thanks!
The problem is that you are trying to share an Entity across persistence contexts(EntityManager). You could change your methods to take an EntityManager instance and use the same EM for the find and update operations.

Categories