JDO handling unique constraint violations - java

I have what I think is a common scenario in JDO. I have a simple persistent class, say
#PersistenceCapable
public class Person {
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.INCREMENT)
private long id;
#Persistent
#Unique
private String name
//constructor, equals, hashcode, etc...
}
Then I want to do a simple add, but throw a custom exception if the add fails because the unique constraint was violated
public void addPerson(String name) throws NotUniqueException {
PersistenceManager pm = getPM();
Transaction tx = pm.currentTransaction();
try {
tx.begin();
Person toAdd = new Person(name);
pm.makePersistent(toAdd);
// how do I throw a NotUniqueException if the transaction fails due to
// "name" not being unique?
tx.commit();
} finally {
if (tx.isActive()) {
tx.rollback();
}
pm.close();
}
}
How do I detect this error condition in JDO? I'm fine with having to do a query first to check whether it will succeed, as long as the check and add operation are atomic. I'm just new to JDO transactions and am not sure what guarantees I get if I do the query and checking in the transaction.
According to this answer, with JPA I would have to dig through the exception chain for the vender-specific cause exception and use that information. Is that true of JDO?

JDODataStoreException with nested datastore-specific exception, as per the JDO spec

Related

JPA-Repository save: Proceed saving after insert Constraint violation

i'm using JPA repository to save simple data objects to the database. To avoid duplicates i created a unique constraint on multiple fields. If now a duplicate according to the unique fields/constraint should be saved i want to catch the exception, log the object and the application should proceed and saves the next object. But here i always get this exception: "org.hibernate.AssertionFailure: null id in de.test.PeopleDBO entry (don't flush the Session after an exception occurs)".
In general i understand what hibernate is doing, but how i can revert the session or start a new session to proceed with saving of the next data objects. Please have a look to the code below:
PeopleDBO.java
#Entity
#Data
#Table(
name = "PEOPLE",
uniqueConstraints = {#UniqueConstraint(columnNames = {"firstname", "lastname"}})
public class PeopleDBO {
public PeopleDBO(String firstname, String lastname) {
this.firstname = firstname;
this.lastname = lastname;
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String firstname;
private String lastname;
}
The Test:
public void should_save_people_and_ignore_constraint_violation(){
final List<PeopleDBO> peopleList = Arrays.asList(
new PeopleDBO("Georg","Smith"),
new PeopleDBO("Georg","Smith"),
new PeopleDBO("Paul","Smith")
);
peopleList.forEach(p -> {
try {
peopleRepository.save(p);
} catch (DataIntegrityViolationException e) {
log.error("Could not save due to constraint violation: {}",p);
}
}
Assertions.assertThat(peopleRepository.count()).isEqualTo(2);
}
The problem is, that with saving of the second people the unique constraint gets violated. The error log happens, and with the next call of peopleRepository.save() the mentioned exception above is thrown:
"org.hibernate.AssertionFailure: null id in de.test.PeopleDBO entry (don't flush the Session after an exception occurs)"
How i can avoid this behaviour? How i can clean the session or start a new session?
Thanks a lot in advance
d.
--------- Edit / new idea ------
I just tried some things and have seen that i could implement a PeopleRepositoryImpl, like this:
#Service
public class PeopleRepositoryImpl {
final private PeopleRepository peopleRepository;
public PeopleRepositoryImpl(PeopleRepository peopleRepository) {
this.peopleRepository = peopleRepository;
}
#Transactional
public PeopleDBO save(PeopleDBO people){
return peopleRepository.save(people);
}
}
This is working pretty fine in my tests. ... what do you think?
One single transaction
The reason is that all inserts occur in one transaction. As this transaction is atomic, it either succeeds entirely or fails, there is nothing in-between.
The most clean solution is to check if a People exists before trying to insert it:
public interface PeopleRespository {
boolean existsByLastnameAndFirstname(String lastname, String firstname);
}
and then:
if (!peopleRepository.existsByLastnameAndFirstname(p.getLastname, p.getFirstname)) {
peopleRepository.save(p);
}
One transaction per people
An alternative is indeed to start a new transaction for each person. But I am not sure it will be more efficient, because there is an extra cost to create transaction.

In Hibernate why does saveOrUpdate give an exception when object already exists in database

Previously, when I was adding a entity to database with Hibernate I used to check that it hadn't already been added. But in an effort to improve performance I forgot this check and just tried to add without checking, as I was using saveOrUpdate() it was my understanding that if Hibernate found it was already added it would just update with and changes made by my save.
But instead it fails with
18/08/2018 21.58.34:BST:Errors:addError:SEVERE: Adding Error:Database Error:Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.jthink.songlayer.MusicBrainzReleaseWrapper#95f6f584-407f-4b26-9572-bb8c6e9c580a]
java.lang.Exception
at com.jthink.songkong.analyse.general.Errors.addError(Errors.java:28)
at com.jthink.songkong.exception.ExceptionHandling.handleHibernateException(ExceptionHandling.java:209)
at com.jthink.songkong.db.ReleaseCache.addToDatabase(ReleaseCache.java:394)
at com.jthink.songkong.db.ReleaseCache.add(ReleaseCache.java:65)
#Entity
public class MusicBrainzReleaseWrapper
{
#Id
private String guid;
#Version
private int version;
#org.hibernate.annotations.Index(name = "IDX__MUSICBRAINZ_RELEASE_WRAPPER_NAME")
#Column(length = 1000)
private String name;
#Lob
#Column(length = 512000)
private String xmldata;
public String getGuid()
{
return guid;
}
public void setGuid(String guid)
{
this.guid = guid;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getXmldata()
{
return xmldata;
}
public void setXmldata(String xmldata)
{
this.xmldata = xmldata;
}
}
private static boolean addToDatabase(Release release)
{
Session session = null;
try
{
session = HibernateUtil.beginTransaction();
//Marshall to String
StringWriter sw = new StringWriter();
Marshaller m = jc.createMarshaller();
m.marshal(release, sw);
sw.flush();
MusicBrainzReleaseWrapper wrapper = new MusicBrainzReleaseWrapper();
wrapper.setGuid(release.getId());
wrapper.setName(release.getTitle().toLowerCase(Locale.UK));
wrapper.setXmldata(sw.toString());
session.saveOrUpdate(wrapper);
session.getTransaction().commit();
MainWindow.logger.info("Added to db release:" + release.getId() + ":" + release.getTitle());
return true;
}
catch (ConstraintViolationException ce)
{
MainWindow.logger.warning("Release already exists in db:"+release.getId()+":"+release.getTitle());
return true;
}
catch(GenericJDBCException jde)
{
MainWindow.logger.log(Level.SEVERE, "Failed:" +jde.getMessage());
ExceptionHandling.handleDatabaseException(jde);
}
catch(HibernateException he)
{
MainWindow.logger.log(Level.SEVERE, "Failed:" +he.getMessage());
ExceptionHandling.handleHibernateException(he);
}
catch(Exception e)
{
MainWindow.logger.log(Level.WARNING,"Failed AddReleaseToDatabase:"+release.getId()+ ':' +e.getMessage(),e);
throw new RuntimeException(e);
}
finally
{
HibernateUtil.closeSession(session);
}
return false;
}
Used to check first before call to addToDatabase
if(ReleaseCache.get(release.getId())==null)
{
addToDatabase(release)
}
Hiberante object has 3 states for an Entity. They are:
- Transient Or New
- Detached (Objects are fetched from DB and hibernate session is closed)
- Persistent (Object are fetched from DB and hibernate session is open)
In saveOrUpdate method, it either save the transient object or update the detached/ persistent object.
In your code, you are trying to create Transient/New object and setting the old id in it. That's the reason you are getting above error. The correct way to fetch the object first using id and then update it.
The problem you are hitting is directly related to the Optimistic locking you have enabled through the #Version annotation on the MusicBrainzReleaseWrapper. saveOrUpdate really can either add or update an entity but this is only if the entity version is the same as the one of the detached object you are trying to add or merge.
In your particular example your detached object has a version previous to the last version in the database therefore the operation can not be executed on a stale data.
UPDATE:
MusicBrainzReleaseWrapper wrapper = session.get(release.getId()):
//the wrapper is managed object
if (wrapper == null) {
//initilize wrapper with the values from release
.......
session.save(wrapper)
}
else {
// do not set ID here. ID is aready present!!!
// never manuay set the version field here
wrapper.setName(release.getTitle().toLowerCase(Locale.UK));
wrapper.setXmldata(sw.toString());
session.saveOrUpdate(wrapper);
//In case you don't need update logic at all
// remove the #Version field from the entity
// and do othing in the else clause , or throw exception
// or log error or anything you see fit
}
No. saveOrUpdate method is used either to persist or merge an entity with the current session. It doesn't do what you expect. Either save or update entity is application's specific logic. Hibernate doesn't do any application's specific logic.
Session.merge() can directly save a previously unknown instance, but note it won't necessarily avoid the extra select against the database.
#Pavan is right about the entity being transient or detached in Hibernate (or JPA) terminology. Both of these states mean that Hibernate has not yet got a reference to this instance of the entity in its session (in the StatefulPersistenceContext), but detached clearly means it is known to the database.
merge() instructs Hibernate to stop and check for a detached instance. The first check is for the #Id value in the session, but if it's not already there, it must hit the database.
saveOrUpdate() instructs Hibernate that the caller knows it is safe to only check the StatefulPersistenceContext for the #Id. If it's not there, the entity is assumed to be transient (i.e. new), and Hibernate will proceed to the insert operation.
saveOrUpdate() is good for instances (with or without an #Id value) that are known to the session already.
In your case clearly Hibernate is unaware of the detached instance, so you would need to use merge(). But that also means Hibernate has to check the database for the instance it hasn't seen before - if the entity has an #Id value.
To come back to the original intent in your question, update without select is harder ...
For an update, Hibernate likes to know the prior state of the entity. This makes sense if it's using dynamic updates (so not updating all columns), but otherwise you would think it could go straight for the update. The only option I know of for this is a direct update query (via HQL or JPQL), but this is hardly convenient if you have an entity instance. Maybe someone else knows how to do this.

JPA many-to-many relationship, keeping a list of ids

I have 2 POJO classes in Java: Phrase and Tag, in a many-to-many relationship:
Phrase.java
#Entity
#EntityListeners(value={PhraseListener.class})
public class Phrase {
#Id
#GeneratedValue
#Column(name="id")
private Long phraseId;
#Column(nullable=false)
private String text;
#ManyToMany(cascade=CascadeType.ALL)
#JoinTable(name="phrase_has_tag",
joinColumns={#JoinColumn(name="phrase_id",referencedColumnName="id")},
inverseJoinColumns={#JoinColumn(name="tag_uname",referencedColumnName="uname")})
private Collection<Tag> tagObjects;
#Transient
private Set<String> tags;
public Phrase() {
tagObjects = new ArrayList<Tag>();
tags = new HashSet<String>();
}
// getters and setters
// …
public void addTagObject(Tag t) {
if (!getTagObjects().contains(t)) {
getTagObjects().add(t);
}
if (!t.getPhrases().contains(this)) {
t.getPhrases().add(this);
}
}
public void addTag(String tagName) {
if (!getTags().contains(tagName)) {
getTags().add(tagName);
}
}
Tag.java
#Entity
public class Tag {
#Id
#Column(name="uname")
private String uniqueName;
private String description;
#ManyToMany(mappedBy="tagObjects")
private Collection<Phrase> phrases;
public Tag() {
phrases = new ArrayList<Phrase>();
}
// getters and setters
// …
The primary key for the tag entity is its name. I want to keep in Phrase.java a Set of tag names "synchronized" with the tagObjects field of the many-to-many relationship, and viceversa. For doing this, I add a listener to Phrase.java:
public class PhraseListener {
#PostLoad
public void postLoad(Phrase p) {
System.out.println("In post load");
for (Tag tag : p.getTagObjects()) {
p.addTag(tag.getUniqueName());
}
}
#PrePersist
public void prePersist(Phrase p) {
System.out.println("In pre persist");
EntityManagerFactory emf = Persistence.createEntityManagerFactory("TestJPA");
EntityManager em = emf.createEntityManager();
for (String tagName : p.getTags()) {
Tag t = em.find(Tag.class, tagName);
if (t == null) t = new Tag(tagName);
p.addTagObject(t);
}
}
}
which after loading, it creates the set of tag names from the tag objects and before persisting it reads the set of tag names, and fetch or create tag objects.
My problem is that if I try to create multiple phrases which share tags, JPA instead of only creating the relationship (insert into the join table) it also create tag objects which violate primary key constraint.
transaction.begin();
Phrase p = new Phrase("Never ask what sort of computer a guy drives. If he's a Mac user, he'll tell you. If not, why embarrass him?", "Tom Clancy");
p.addTag("apple");
p.addTag("macintosh");
em.persist(p);
transaction.commit();
transaction.begin();
p = new Phrase("It's better to be a pirate than to join the Navy.", "Steve Jobs");
p.addTag("apple");
em.persist(p);
transaction.commit();
Exception in thread "main" javax.persistence.RollbackException: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.5.0.v20130507-3faac2b): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: java.sql.SQLException: [SQLITE_CONSTRAINT] Abort due to constraint violation (column uname is not unique)
Error Code: 0
Call: INSERT INTO TAG (uname, DESCRIPTION) VALUES (?, ?)
bind => [apple, null]
You haven't shown your addTag method in Phrase, but I assume that you have somewhere in there an expression new Tag() and it would look somehow similar to this:
public void addTag(String tagName) {
Tag tag = new Tag();
tag.setUniqueName(tagName);
tag.getPhrases().add(this);
this.tagObjects.add(tag);
}
In this case the method addTag will create new object of type Tag everytime the method is called, which will result in different entries in the relational table, cause Hibernate persists the whole objects, not only particular fields of theirs, regardless if these fields are primary keys or not.
After calling the method addTag two times, you will create two different objects and Hibernate could not know if those two objects relate to the same entry in the DB or not. This means that even though they have the same uniqueName, they could have a different description.
Imagine the following scenario:
transaction.begin();
Phrase p = new Phrase("Never ask what sort of computer a guy drives. If he's a Mac user, he'll tell you. If not, why embarrass him?", "Tom Clancy");
Tag t = new Tag();
t.setUniqueName("apple");
t.setDescription("This is an example apple");
p.getTagObjects().add(t);
em.persist(p);
transaction.commit();
transaction.begin();
p = new Phrase("It's better to be a pirate than to join the Navy.", "Steve Jobs");
t = new Tag();
t.setUniqueName("apple");
t.setDescription("Another description of the apple");
em.persist(p);
transaction.commit();
With this example the difference should be more obvious and should illustrate why it is impossible to Hibernate to know when are you referring to the same entry in the DB with two or more different objects.
As a solution I would suggest you to change the method addTag so that it has the following signature public void addTag(Tag tag) {... and keep track of the existing tags somewhere centralized or you can try out em.merge(p); instead of em.persist(p);

How to persist a JPA autogenerated value before commit?

Hi I am beginner on the JPA world, I have a question on the auto-generated id. We are using OpenJPA, My application requires that one operation which creates bunch of related objects must be inside a single transaction which will be part of global transaction (XA). I am struggling in get the auto-generated id and use it to set values in other object. Here is the snapshot:
#ENTITY
#Table(name="TDepart")
class Department{
private long id;
#GeneratedValue(strategy= GenerationType.TABLE)
public long getId();
}
//And some classes like
class Professor {
void setDepartmentId(long id);
}
Now I have a business operation:
void doSomething()
{
Department depart = new Department();
handleProfessors (depart);
handleStudent (depart);
//and someother rountines need to refer department
}
//sample code which will getId
void handleProfessors(Department depart)
{
Professor p = new Professor ();
p.setDepartmentId(depart.getId);
}
So the Department.getId() will be called several times. The doSomething() will be in a single managed transaction, but the GeneratedValue will use an unmanaged tx. Now may problem is: whenever the getId is called, it will return a new value, and when the department is final persisted, the id is the latest number, so all other objects refer to an non-exists department. Is there anyway to handle this so that the id is (kindof) persist?
I have a loose requirement solution, which will create an dummy department first and persist it, so the ID is not change. The code is similar to this:
void doSomething()
{
Department depart = createEmptyDepartment(); // always new tx so department is created;
try {
reallyDoSomehing(); // tx required so it is part of global tx
}
catch (SomeException e) {
removeEmptyDepartment(depart);
}
Now I do not know how I can set the tx for removeEmptyDepartment(), if is required it will use the global request so it will be rollback as well. If it is new tx it will cause a deadlock since reallyDoSomething() will lock the db row.
Please, give me some ideas on how to solve it.
Thanks,
Howard.
I don't fully understand your issue, but I'm thinking that rather than setting the departmentId in your professor class, you should be setting the Department instead
i.e.
void setDepartmentId(long id);
change to
void setDepartment(Department d);
The id components should be handled automatically by the entity manager

AppEngine datastore: "Object with id ... is managed by a different Object Manager"

I'm using the Google AppEngine, with Java. When I use some datastore features, I'm getting an error message:
Object with id "edvaltt.Teacher#64064b" is managed by a different Object Manager
I don't know what this means or how to fix it or where to look for documentation on this error. Can anyone help me? The code I'm using is:
#PersistenceCapable(identityType = IdentityType.APPLICATION)
public class School {
#PrimaryKey
#Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private String shortname;
#Persistent
private String fullname;
#Persistent
#Order(extensions = #Extension(vendorName="datanucleus", key="list-ordering", value="code asc"))
private List<Teacher> Teachers;
...
public Teacher FindOrCreateTeacher(String code)
{
// Can we find the teacher without any database code?
Teacher newTeacher = FindTeacher(code);
if (newTeacher != null)
return newTeacher;
// Create the teacher:
PersistenceManager pm = PMF.get().getPersistenceManager();
Transaction tx = pm.currentTransaction();
try {
tx.begin();
for (Teacher teacher : Teachers) {
if (teacher.getCode() == code) {
tx.rollback();
return teacher;
}
}
newTeacher = new Teacher(code);
Teachers.add(newTeacher);
pm.makePersistent(newTeacher);
pm.makePersistent(Teachers);
tx.commit();
} finally {
tx.commit();
}
return newTeacher;
}
I believe that "private List<Teacher> Teachers;" refers to an "owned, one to many" relationship.
A persistent object can only be "managed" by one PersistenceManager. In DataNucleus this is backed internally by an "ObjectManager". The message says that you are trying to associate an object managed by one PM with a different PM. You can easily debug that by printing out the PM for each (persistent) object
JDOHelper.getPersistenceManager(obj);
Since you don't define where the message comes from, not much more can be said. The DataNucleus log entries would tell you way way more than that.
Closing the PM is always an essential thing to do (unless you want resource leaks)
As illustrated in this ticket, shouldn't you close the pm (PersistenceManager)?
} finally {
tx.commit();
pm.close();
}
DataNucleus,
Thank you for the pm.close(); tip.
I was making a query with one em
em = EMF.get().createEntityManager();
and making a commit with another one without closing the first one.

Categories

Resources