0

It seems like a pretty basic question to me, so probably I'm either lacking the right search terms or I'm completely missing something about the way how managed entites work, but nevertheless I was unable to find out how to do this: Writing new attribute values of a managed entity to the database in a transactional way, meaning I want to set a bunch of values to an entity bean and have them persisted all at once and without other threads seeing a „dirty“ intermediate state of the bean or interrupting the writing process.

This is the entity class:

@Entity
public class MyEntityClass
{
    ...
    private String status;
    private String value;
    ...
    public void setStatus(String status)
    {
    ...
    public void setValue(String vlaue)
    {
    ...
}

I'm using it here:

import javax.ejb.Stateless;
import javax.ejb.TransactionManagement;
import javax.ejb.TransactionManagementType;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

@Stateless
@TransactionManagement(TransactionManagementType.CONTAINER)
public class MyService
{
    @PersistenceContext
    protected EntityManager entityManager;
    ...
    MyEntityClass entity = entityManager.find(entityClass, id);

    //Now I'd like to set some attributes in a transactional way
    entity.setStatus(newStatus);

    //what if the new status got persisted and another thread reads from the database now
    entity.setValue(newValue);

    //flushing, just to make sure that at least from here on the database is in a consistent state
    entityManager.flush();
}

I need to ensure that no other thread can see the entity in a „half-written“ state, i.e. with the new status already persisted but the old value still present in the database. I tried using a lock:

...
MyEntityClass entity = entityManager.find(entityClass, id);
entityManager.lock(entity, LockModeType.WRITE);
entity.setStatus(newStatus);
entity.SetValue(newValue);
entityManager.flush();
entityManager.lock(entity, LockTypeMode.NONE);
...

But this throws:

java.lang.IllegalArgumentException: entity not in the persistence context because the transaction used for reading the entity ends after the entityManager.find() is completed and therefore also the persistence context is gone, as I learned from this answer.

Also, I read here that I cannot manually create transactions with an EntityManager that uses container-managed transactions. But isn't there any way now to manually ensure that the entity's attributes are persisted together (or not at all)? Or is this somehow already done automatically?

Community
  • 1
  • 1
Joe7
  • 508
  • 1
  • 4
  • 17
  • 1
    As long as your transaction isolation level is `TRANSACTION_READ_COMMITTED`, there is no chance that an entity can be read in the middle of a transaction. – Keppil Aug 31 '15 at 11:07
  • 1
    You can read more [here](https://docs.oracle.com/javase/tutorial/jdbc/basics/transactions.html). – Keppil Aug 31 '15 at 11:09
  • 1
    "the transaction used for reading the entity ends after the entityManager.find()" - This doesn't sound right. Are you sure your method is running in a transaction? Did you use the [`@Transactional`](http://docs.oracle.com/javaee/7/api/javax/transaction/Transactional.html) or [`@TransactionAttribute`](https://docs.oracle.com/javaee/6/tutorial/doc/bncij.html#bnciu) annotations? – JimmyB Aug 31 '15 at 11:37
  • @HannoBinder: I got this information from the first link my question, which I found after googling for the IllegalArgumentException that I mentioned. I'm not using the `@Transactional` or `@TransactionAttribute` annotations. Should I? If I got Szarpul's answer right, I don't need them. – Joe7 Aug 31 '15 at 12:05
  • 1
    @Keppil: Help a rookie out: The transaction isolation level is a setting of my RDMS, so if I know that the default transaction isolation level of my RDBMS (MySQL) is REPEATABLE_READ or READ_COMMITTED, I don't need to do anything (e.g. on the Java side), correct? – Joe7 Aug 31 '15 at 12:11
  • 1
    Btw, what does your `persistence.xml` look like? You may have to change JPA's transaction handling there, see e.g. http://stackoverflow.com/questions/17331024/persistence-xml-different-transaction-type-attributes. – JimmyB Aug 31 '15 at 12:14
  • 1
    @Joe7: Yes, that is correct. – Keppil Aug 31 '15 at 12:37
  • @HannoBinder: 1. You were right, the method wasn't running in a transaction - see Szarpul's answer and our discussion. 2. Thanks for pointing out the transaction handling issue. My `persistence-unit` does not define any `transaction-type` explicitly, but injecting an `EntityManager` via `@PersistenceContext` works fine, so I assume the application is defaulting to "JPA" and I'm good. – Joe7 Aug 31 '15 at 15:56

1 Answers1

1

Since you run in EJB container and your transaction is managed by the container, every business method of your EJB bean is invoked inside a transaction, so as far as your isolation level of the transaction is no set to Read uncommitted, changes that where made in the method will be visible when the transaction commits, that is when the method finishes.

UPDATE:

As you don't use business methods it looks like your code is running without any transaction. In this case, every invocation of EnitytMangers method will create a new PersistenceContext. This is why you are getting exception. Entity that you get form find method is detached when next method of EntityManager is called (as new PersistenceContext is created).

Szarpul
  • 1,531
  • 11
  • 21
  • 1
    Just to be sure I got this right: By "every business method of your EJB bean" you mean any method of the stateless session bean (MyService in my case), right? – Joe7 Aug 31 '15 at 11:56
  • 1
    Not any, business methods that you will have to implement from business interface. Here you have an example : https://docs.oracle.com/javaee/6/tutorial/doc/bnbod.html – Szarpul Aug 31 '15 at 12:55
  • 1
    Thanks for the link and the update. Again, just to be sure I got things right: If I extract an interface from the session bean, and this interface stays the only one that the session bean implements (or I explicitly annotate it with @Local or @Remote), then all methods mentioned in this interface will be considered business methods. This in turn means that each method call happens in a transaction. Correct? – Joe7 Aug 31 '15 at 14:40
  • 1
    I don't quite understand. What you have to have is: business interface(remote or local), stateless bean which implements this interface- than all implemented methods are business methods. After that you get your bean either by dependency injection or JNDI lookup. How to inject ejb bean- you have to google it. – Szarpul Aug 31 '15 at 14:52
  • Yup, that's what I meant. :-) And a business method is automatically invoked inside a transaction, as you answered earlier. – Joe7 Aug 31 '15 at 14:59
  • 1
    Yes, to be more specific: "Typically, the container begins a transaction immediately before an enterprise bean method starts and commits the transaction just before the method exits". - as the spec. says: https://docs.oracle.com/javaee/6/tutorial/doc/bncij.html – Szarpul Aug 31 '15 at 15:03
  • 1
    OK, I extracted an interface from my stateless session bean now. Just as a little test, I left the `entityManager.lock()` lines in the method on the first test. Indeed the `IllegalArgumentException` didn't happen again, from which I conclude that the transaction was still open after the `entityManager.find(entityClass, id)` call, as expected. – Joe7 Aug 31 '15 at 15:44