8

I am getting a ERROR: duplicate key value violates unique constraint "users_pkey" Detail: Key (userid)=(2701) already exists. whenever i use the persist model to update my user entity.

In the code sample below: SetLoginAttempts takes in a user entity that has been queried and when i start the transaction i simply set one of the entity's fields and call persist(), then commit the transaction.

/**
* @param user
* @param attemptNumber
*/
@Transactional
public void setLoginAttempts(Users user, int attemptNumber){         
    user.setLoginAttempts(attemptNumber);
    System.out.println(user);
}

Here is how i reference and grab the entity manager:

eFactory = Persistence.createEntityManagerFactory("persistenceUnit");
eManager = eFactory.createEntityManager();

When looking at the stack trace, i noticed that the commit actually injects an insert

Call: INSERT INTO USERS (userID, EMAIL, ISLOCKED, LOGINATTEMPTS, passwordHash, passwordSalt, USERNAME, version) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
bind => [2701, admin@d.com, false, 1, $shiro1$SHA-256$500000$6mqzZ/d/3BLQuJqLh1dDhQ==$NKW7Z++o/JTvf884aDWhP3Uhpyb5fTPMrm4joWnw7nI=, [B@1a8e3115, admin, 1]

What is the proper way to reference an entity manager in Spring roo, update a field and commit the changes?

Edit

I added the @Transactional to the method and the stack trace shows that this is creating the entity manager instance:

2012-03-14 23:49:15,503 ["http-bio-8080"-exec-18] DEBUG org.springframework.orm.jpa.JpaTransactionManager - Creating new transaction with name [org.bixin.dugsi.service.UserService.setLoginAttempts]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; ''
2012-03-14 23:49:15,503 ["http-bio-8080"-exec-18] DEBUG org.springframework.orm.jpa.JpaTransactionManager - Opened new EntityManager [org.eclipse.persistence.internal.jpa.EntityManagerImpl@46b9979b] for JPA transaction
[EL Finer]: 2012-03-14 23:49:15.503--ServerSession(2128384958)--Thread(Thread["http-bio-8080"-exec-18,5,main])--client acquired: 1116759395
[EL Finer]: 2012-03-14 23:49:15.503--ClientSession(1116759395)--Thread(Thread["http-bio-8080"-exec-18,5,main])--acquire unit of work: 368076985
2012-03-14 23:49:15,503 ["http-bio-8080"-exec-18] DEBUG org.springframework.orm.jpa.JpaTransactionManager - Not exposing JPA transaction [org.eclipse.persistence.internal.jpa.EntityManagerImpl@46b9979b] as JDBC transaction because JpaDialect [org.springframework.orm.jpa.DefaultJpaDialect@c595bcd] does not support JDBC Connection retrieval
Email: admin@d.com, Id: 2701, IsLocked: false, LoginAttempts: 2, Password: $shiro1$SHA-256$500000$6mqzZ/d/3BLQuJqLh1dDhQ==$NKW7Z++o/JTvf884aDWhP3Uhpyb5fTPMrm4joWnw7nI=, PasswordSalt: [B@1a8e3115, Roles: 0, Username: admin, Version: null
2012-03-14 23:49:15,503 ["http-bio-8080"-exec-18] DEBUG org.springframework.orm.jpa.JpaTransactionManager - Initiating transaction commit
2012-03-14 23:49:15,503 ["http-bio-8080"-exec-18] DEBUG org.springframework.orm.jpa.JpaTransactionManager - Committing JPA transaction on EntityManager [org.eclipse.persistence.internal.jpa.EntityManagerImpl@46b9979b]
[EL Finer]: 2012-03-14 23:49:15.503--UnitOfWork(368076985)--Thread(Thread["http-bio-8080"-exec-18,5,main])--begin unit of work commit
[EL Finer]: 2012-03-14 23:49:15.503--UnitOfWork(368076985)--Thread(Thread["http-bio-8080"-exec-18,5,main])--end unit of work commit
[EL Finer]: 2012-03-14 23:49:15.504--UnitOfWork(368076985)--Thread(Thread["http-bio-8080"-exec-18,5,main])--resume unit of work
2012-03-14 23:49:15,504 ["http-bio-8080"-exec-18] DEBUG org.springframework.orm.jpa.JpaTransactionManager - Closing JPA EntityManager [org.eclipse.persistence.internal.jpa.EntityManagerImpl@46b9979b] after transaction
2012-03-14 23:49:15,504 ["http-bio-8080"-exec-18] DEBUG org.springframework.orm.jpa.EntityManagerFactoryUtils - Closing JPA EntityManager
[EL Finer]: 2012-03-14 23:49:15.504--UnitOfWork(368076985)--Thread(Thread["http-bio-8080"-exec-18,5,main])--release unit of work

But still no updates to the DB even after refresh, why isnt the transaction closing and updating the DB?

Vojtech Ruzicka
  • 16,384
  • 15
  • 63
  • 66
Warz
  • 7,386
  • 14
  • 68
  • 120

3 Answers3

21

JPA exposes two methods... persist() and merge()

Persist: Persist is responsible for inserting new rows to DB and then associating the Entity with state in JPA session.

Merge: Merge takes existing Entity and updates the DB row. It also updates the state of the entity in JPA session.

I think The user already exists in your database table. In order to update the login count, you can use the merge() method on EntityManager.

Ameya
  • 627
  • 4
  • 6
  • I just tried replacing persist() with merge() and it seems as nothing happened. Looking at the stack trace again and i get this "Initiating a transaction roll back" for some reason? how do i prevent it from rolling back so i can see the changes in the DB – Warz Mar 15 '12 at 04:24
  • Write following inside class: ` @PersistenceContext(unitName = "myEntityManager") private EntityManager entityMgr; public void setLoginAttempts(Users user, int attemptNumber){ user.setLoginAttempts(attemptNumber); entityMgr.merge(user); } public User getUser(Object userId) { return entityMgr.find(User.class, userId); } ` – Ameya Mar 15 '12 at 05:34
2

If neither of the mentioned works, try (by adjusting since it is relevant to my code):

            Query query = entityManager.createQuery("UPDATE User x SET x.activated = 1 "+ "WHERE username=:usernameParam ");
            query.setParameter("usernameParam", ""+user.username);
            query.executeUpdate();
3xCh1_23
  • 1,491
  • 1
  • 20
  • 39
0

EntityManager.persist() is used to create a new entity bean.

Creating a new entity bean involves inserting a new row in the database.

You use EntityManager.merge() to update an entity bean that already exists.

Calling EntityManager.merge() updates the database to reflect changes made to a detached entity bean.

If your entity bean is not detached, there's no need to invoke merge().

If your user bean is not detached, you can simply modify its properties by calling methods like setLoginAttempts().

The EntityManager and container will update the database automatically (when the transaction is committed).

jahroy
  • 22,322
  • 9
  • 59
  • 108
  • If i simply call setLoginAttempts() without acquiring an EntityManager , calling persist() or merge() the logs show (with the print statement ) that the field is updated, However refreshing the DB or shutting down the app does not reflect the changes made? – Warz Mar 15 '12 at 04:29
  • I'd recommend letting the container handle the transaction (in stead of doing it yourself). Assuming you're using EJB 3, you can simply annotate the method with the appropriate TransactionAttribute and the container will do the dirty work. Actually, the default TransactionAttributeType is REQUIRED, which will automatically wrap the whole method in an Transaction. – jahroy Mar 15 '12 at 04:38
  • I got the User in which i am calling the method on from a query ( a finder method in spring roo to be particular), and then i want to set the attempts on this user. From what i am understanding, since i read the user from a query and still using this user, i dont need to create or get an entity manager instance and simply can call the set method on that entity? – Warz Mar 15 '12 at 04:42