Trying to understand more about Hibernate,I wrote some code which creates some entities and saves them in db and then tries to delete one of the entities.
The mapping file for entity Customer has id generator set to native.I am using postgresql as db.
...
<class name="Customer" table="CUSTOMER">
<id column="CUSTOMER_ID" name="customer_id" type="java.lang.Long">
<generator class="native"/>
</id>
...
I came across hibernate.NonUniqueObjectException.
org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: [org.me.hibernatestore.Customer#129]
Full stack trace here
I fired up the eclipse debugger and found that the object involved has same address in all the involved methods ..
The relevant part of the code is
public class Main {
CustomerDao custdao;
Customer mark;
public void storeDemo(){
custdao = DaoFactory.getCustomerDao();
createCustomers();
updateEntities();
deleteCustomer(mark);
}
private void createCustomers() {
mark = new Customer();
mark.setName("mark");
mark.setEmailAddress("mark#home");
mark.setAddress("121,3rd avenue");
mark.setCity("San Diego");
mark.setState("CA");
mark.setCountry("U.S.A");
}
private void updateEntities() {
Transaction tx = null;
Session session = HibernateUtil.getCurrentSession();
try{
tx = session.beginTransaction();
custdao.saveOrUpdateCustomer(mark);
tx.commit();
}catch(RuntimeException e){
tx.rollback();
throw e;
}
}
private void deleteCustomer(Customer cust){
Transaction tx = null;
Session session = HibernateUtil.getCurrentSession();
try{
tx = session.beginTransaction();
String custName = cust.getName();
custdao.deleteCustomer(cust);
tx.commit();
}catch(RuntimeException e){
tx.rollback();
throw e;
}
}
public static void main(String[] args) {
new Main().storeDemo();
}
}
With the help of debugger I found the address of object 'mark'
Main.createCustomers(): mark-> Customer#2bc3f5
CustomerDaoImpl.saveOrUpdateCustomer(Customer customer):customer-> Customer#2bc3f5
BaseDaoImpl.saveOrUpdate(T obj):obj-> Customer#2bc3f5
Main.deleteCustomer(Customer customer):customer-> Customer#2bc3f5
CustomerDaoImpl.deleteCustomer(Customer customer):customer-> Customer#2bc3f5
BaseDaoImpl.delete(T obj):obj-> Customer#2bc3f5
Experimenting further,I modified the code and through dao.findById() got a different object with same id and used that in deleteCustomer().This time the code worked without throwing any exception
public class Main {
CustomerDao custdao;
Customer mark;
public void storeDemo(){
custdao = DaoFactory.getCustomerDao();
createCustomers();
updateEntities();
Long mark_id = mark.getCustomer_id();
Customer mark2 = getCustomer(mark_id);
deleteCustomer(mark2);
}
private Customer getCustomer(Long id){
Transaction tx = null;
Customer cust = null;
Session session = HibernateUtil.getCurrentSession();
try{
tx = session.beginTransaction();
return custdao.findCustomerById(id);
}catch(RuntimeException e){
throw e;
}
}
...
}
Can someone explain this behaviour?My understanding about the 'a different object with the same identifier value' part of the error message is fuzzy ..The object as shown in debugger in the first case has same memory address everywhere in the code.Then how can it be a different object?
sincerely
Jim
This exception usually occurs when dealing with detached objects. In order to avoid that, you have to get the object and delete it in the same session or reattach it to the session and then delete it.
Hope this helps!
Related
i've come across a problem in these days, which would be simple for other languages, like php, but the project I'm doing is in Spring MVC.
The question is: In Spring MVC, how can i delete an entity with two attributes ids coming from this entity?
Example: "Delete from Entity Where id1 =: id1 and id2 =: id2" (This is the query that i want)
Thanks for the attention.
What i was trying ...
public boolean remover(int idUsuario, int idCategoria) {
EntityManagerFactory factory = Persistence.createEntityManagerFactory(PERSISTENCE_UNIT);
EntityManager manager = factory.createEntityManager();
String hqlStr = "delete from UsuarioEscolheCategoria where idUsuario = :idUsuario and idCategoria = :idCategoria";
Query query = null;
try {
query = manager.createQuery(hqlStr);
query.setParameter("idUsuario", idUsuario);
query.setParameter("idCategoria", idCategoria);
query.executeUpdate();
manager.close();
factory.close();
return true;
}catch(Exception e) {
return false;
}
}
If i take the exception, it gives me:
String hqlStr = "delete from UsuarioEscolheCategoria where usuario.idUsuario = :idUsuario and categoria.idCategoria = :idCategoria";
The important part is usuario.idUsuario and categoria.idCategoria. That way you're making a reference to the attribute type Usuario, which is on your model class.
you prblem is the session factory, check how you have created it, here a simple usefull example:
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
#Autowired
private SessionFactory sessionFactory;
protected Session getSession() {
return sessionFactory.getCurrentSession();
}
public void deleteById(Integer id) {
Query query = getSession().createSQLQuery("delete from TABLE where id = :t_id");
query.setInteger("t_id", id);
query.executeUpdate();
}
public static int add_Book(String title, String auth_name, String publisher, String genre) {
Transaction tx = session.beginTransaction();
Query query = session.createQuery
("INSERT INTO Book(title,auth_name,publisher,genre)"
+ "SELECT "+title+", "+auth_name+", "+publisher+", "+genre);
tx.commit();
return query.executeUpdate();
and this error
Exception in thread "AWT-EventQueue-0"
java.lang.IllegalStateException: No data type for node:
org.hibernate.hql.internal.ast.tree.IdentNode +-[IDENT] IdentNode:
'q' {originalText=q}...
'q' is JTextField data
insert 'q' to 'title'
... Help would BE appreciated..
Just do it
public static void addBook(String title, String authName, String publisher, String genre) {
session.save(new Book(title, authName, publisher, genre));
}
Obviously, you should wrap that method with session/transaction control code.
And, please, always use Java Naming Convention!
auth_name, add_Book are incorrect names — use authName, addBook instead.
I think the answer of #v.ladynev is good but you can also try like this :
public static void addBook(String title, String authName, String publisher, String genre) {
Session session = HibernateUtil.getSessionFactory().openSession();
Transaction tx = null;
// create book Object
Book book = new Book();
book.setTitle(title);
book.setAuthName(authName);
book.setPublisher(publisher);
book.setGenre(genre);
try {
tx = session.beginTransaction();
// Save the book to database
session.save(book);
tx.commit();
}catch(Exception e){
if (tx!=null) {
tx.rollback();}
e.printStackTrace();
}finally{
// close your session
session.close();
}
}
Use at least the Bean class to code "cleanly". We use the query only in special cases, otherwise we must at most avoid it in the code or use at least bundle resources.
Anything else checks that in your Book bean class you havesetAuthName and not setAuth_Name.
Tag is an entity and I remove tags with this method:
public static <T> boolean deleteById(Class<? extends BaseEntity> clazz, Long id) {
Session session = HibernateUtil.getSessionFactory().openSession();
try {
session.beginTransaction();
T e = get(clazz, id);
if (e != null) {
session.delete(e);
session.getTransaction().commit();
return true;
} else {
return false;
}
} finally {
session.close();
}
}
Next thing, I read the list with Tags again with this method:
public static List<Tag> listTags() {
Session session = HibernateUtil.getSessionFactory().openSession();
Query q = session.createQuery("FROM Tag tag");
List<Tag> tags = (List<Tag>) q.list();
session.close();
return tags;
}
The problem is that when deleting and reselecting all Tags the removed Tag is in the list although not in the database. when I run listTags() a second time, by clicking a link the object is removed and I get the correct list.
Does anyone know why?
Try to add flush() and use commit() like below:
...
session.delete(e);
session.flush();
session.beginTransaction().commit();
...
For more info go to session.getTransaction() vs session.beginTransaction()
I had a similar problem with Hibernate while migration from MySQL to MariaDB, were multiple transactions were being open and not closed...
the solution was removing the opening of the transactions as they were not required anyway....
I am currently working on a web application which is basically a portfolio site for different vendors.
I was working on a thread which copies the details of a vendor and puts it against a new vendor, pretty straightforward.
The thread is intended to work fine but when selecting a particular Catalog object (this catalog object contains a Velocity template), the execution stops and it goes nowhere. Invoking the thread once again just hangs the whole application.
Here is my code.
public class CopySiteThread extends Thread {
public CopySiteThread(ComponentDTO componentDTO, long vendorid, int admin_id) {
/**Application specific business logic not exposed **/
}
public void run() {
/** Application based Business Logic Not Exposed **/
//Copy Catalog first
List<Catalog> catalog = catalogDAO.getCatalog(vendorid);
System.out.println(catalog);
List<Catalog> newCat = new ArrayList<Catalog>();
HashMap<String, Integer> catIdMapList = new HashMap<String, Integer>();
Iterator<Catalog> catIterator = catalog.iterator();
while (catIterator.hasNext()) {
Catalog cat = catIterator.next();
System.out.println(cat);
int catId = catalogDAO.addTemplate(admin_id, cat.getHtml(), cat.getName(), cat.getNickname(), cat.getTemplategroup(), vendor.getVendorid());
catIdMapList.put(cat.getName(), catId);
cat = null;
}
}
}
And the thread is invoked like this.
CopySiteThread thread = new CopySiteThread(componentDTO, baseVendor, admin_id);
thread.start();
After a certain number of iterations, it gets stuck on line Catalog cat = catIterator.next();
This issue is rather strange because I've developed many applications like this without any problem.
Any help appreciated.
The actual problem was in the addCatalog method in CatalogDAO
Session session = sf.openSession();
Transaction tx = null;
Integer templateID = null;
Date date = new Date();
try {
tx = session.beginTransaction();
Catalog catalog = new Catalog();
//Business Logic
templateID = (Integer) session.save(catalog);
} catch (HibernateException ex) {
if (tx != null) tx.rolback();
} finally {
session.close();
}
return templateID;
Fixed by adding a finally clause and closing all sessions.
I got a really weird problem here and I absolutely cannot understand why this is happening.
The problem looks like this:
I got a class called "SmampiAccount" which holds a list of email accounts. The mapping file looks like this (shortened):
<hibernate-mapping>
<class name="com.smampi.web.model.account.SmampiAccount" table="SMAMPIACCOUNT">
<id name="id" type="long" access="field">
<column name="SMAMPI_ACCOUNT_ID" />
<generator class="native" />
</id>
<bag name="mailAccounts" table="MAILACCOUNTS" lazy="false" inverse="true">
<key column="SMAMPI_ACCOUNT_ID"></key>
<one-to-many class="com.smampi.web.model.mail.account.MailAccount"/>
</bag>
</class>
</hibernate-mapping>
I get instances of this class through this method:
public SmampiAccount loadSmampiAccount(long id) throws FailedDatabaseOperationException {
SmampiAccount smampiAccount = null;
Session session = null;
Transaction transaction = null;
try {
session = getSession();
transaction = session.beginTransaction();
smampiAccount = (SmampiAccount) session.load(com.smampi.web.model.account.SmampiAccount.class, id);
List<MailAccount> mailAccounts = smampiAccount.getMailAccounts();
doSomething(mailAccounts);
transaction.commit();
} catch (Exception e) {
rollback(transaction);
closeSession();
throw new FailedDatabaseOperationException(e);
} finally {
closeSession();
}
return smampiAccount;
}
private Session getSession() {
if (_session == null) {
_session = getSessionFactory().openSession();
}
if (_session.isOpen() == false) {
_session = getSessionFactory().openSession();
}
return _session;
}
This works fine as it is.
Now, I wanted to add a new property to the mapping file in order to save a reference to a default email account:
<many-to-one name="defaultMailAccount" column="DEFAULT_MAIL_ACCOUNT_ID" />
Now, I get an exception in the method public SmampiAccount loadSmampiAccount(long id) in this line:
List<MailAccount> mailAccounts = smampiAccount.getMailAccounts();
Stacktrace:
org.hibernate.SessionException: Session is closed!
at org.hibernate.impl.AbstractSessionImpl.errorIfClosed(AbstractSessionImpl.java:72)
at org.hibernate.impl.SessionImpl.getPersistenceContext(SessionImpl.java:1954)
at org.hibernate.event.def.DefaultPostLoadEventListener.onPostLoad(DefaultPostLoadEventListener.java:49)
at org.hibernate.engine.TwoPhaseLoad.initializeEntity(TwoPhaseLoad.java:250)
at org.hibernate.loader.Loader.initializeEntitiesAndCollections(Loader.java:982)
at org.hibernate.loader.Loader.doQuery(Loader.java:857)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:274)
at org.hibernate.loader.Loader.loadEntity(Loader.java:2037)
at org.hibernate.loader.entity.AbstractEntityLoader.load(AbstractEntityLoader.java:86)
at org.hibernate.loader.entity.AbstractEntityLoader.load(AbstractEntityLoader.java:76)
at org.hibernate.persister.entity.AbstractEntityPersister.load(AbstractEntityPersister.java:3293)
at org.hibernate.event.def.DefaultLoadEventListener.loadFromDatasource(DefaultLoadEventListener.java:496)
at org.hibernate.event.def.DefaultLoadEventListener.doLoad(DefaultLoadEventListener.java:477)
at org.hibernate.event.def.DefaultLoadEventListener.load(DefaultLoadEventListener.java:227)
at org.hibernate.event.def.DefaultLoadEventListener.onLoad(DefaultLoadEventListener.java:147)
at org.hibernate.impl.SessionImpl.fireLoad(SessionImpl.java:1090)
at org.hibernate.impl.SessionImpl.immediateLoad(SessionImpl.java:1026)
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:176)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:215)
at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:190)
at com.smampi.web.model.account.SmampiAccount_$$_javassist_19.getMailAccounts(SmampiAccount_$$_javassist_19.java)
How is this possible?
The session gets not closed manually and .commit() isn't called yet (which would normally close the session).
It's also not possible that another method is interfering here because I create a new hibernate session for each method call which is dedicated just for this one method.
Edit
I added some debug info on the session open status:
session = getSession();
System.err.println(session.isOpen());
transaction = session.beginTransaction(); // 1 (true)
System.err.println(session.isOpen()); // 2 (true)
smampiAccount = (SmampiAccount) session.load(com.smampi.web.model.account.SmampiAccount.class, id);
System.err.println(session.isOpen()); // 3 (true)
List<MailAccount> mailAccounts = smampiAccount.getMailAccounts(); // Throws exception that session is closed
doSomething(mailAccounts);
System.err.println(session.isOpen()); // 4 (not called)
transaction.commit();
This gives me:
true
true
true
org.hibernate.SessionException: Session is closed!
I am the biggest idiot in the world.
In the setter of defaultMailAccount, I had this:
public void setDefaultMailAccount(MailAccount defaultMailAccount) {
this.defaultMailAccount = defaultMailAccount;
try {
databasecontroller.update(this);
} catch (FailedDatabaseOperationException e) {
handleException(e, false, null, null);
}
}
The call to databasecontroller.update(this) caused a cascade whenever Hibernate tried to load a persisted version from the database and that again caused the session to close.
Moving the call of databasecontroller.update(..) to outside the method fixed the issue.
Sorry to everyone for taking up your time and thanks for the help!