问题
I'm trying to maintain state across multiple calls by using an EXTENDED_PERSISTENT_CONTEXT. My understanding is that managed entities will not detach between calls however I keep getting errors related to detached entities in calls after I have previously thrown validation errors. The state is being maintained in a stateful session bean:
@Named(SessionFacadeBean.SEAM_NAME)
@SessionScoped
@Stateful
@LocalBean
@AccessTimeout(value = 10, unit = TimeUnit.SECONDS)
public class SessionFacadeBean implements Serializable
{
public static final String SEAM_NAME = "sessionCacheBean";
@PersistenceContext(unitName = GlobalParameters.BACKEND_CODE_PERSISTENCE_CONTEXT_NAME, type = PersistenceContextType.EXTENDED)
private EntityManager em;
private ParentOne sessionData;
public synchronized ParentOne getSessionData() {
if(sessionData == null) {
sessionData = new ChildTwo();
}
return sessionData;
}
public boolean getLock() {
return true;
}
public void clearLock() {
}
// Other stuff I don’t ‘think’ is relevant.
}
The (simplified) state is being stored using hibernate. It consists of three classes (a parent, and two children, one of which contains a list of children):
@XmlRootElement(name = XMLConstants.COMPONENT_ELEMENT_NAME_IN_XML)
@XmlAccessorType(XmlAccessType.NONE)
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "Class", length = 50)
@Entity
public class ParentOne
{
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@XmlElement(name = "ID")
private Long iD;
@XmlElement(name = "name")
protected String friendlyName = "";
}
@XmlRootElement(name = XMLConstants.COMPONENT_ELEMENT_NAME_IN_XML)
@XmlAccessorType(XmlAccessType.NONE)
@Entity
public class ChildOne extends ParentOne
{
public ChildOne(String name, ParentOne child) {
super(name);
myChild = child;
}
@ManyToOne(cascade = CascadeType.ALL)
protected ParentOne myChild;
}
@XmlRootElement(name = XMLConstants.COMPONENT_ELEMENT_NAME_IN_XML)
@XmlAccessorType(XmlAccessType.NONE)
@Entity
public class ChildTwo extends ParentOne
{
public ChildTwo() {
super(“common”);
}
}
I’m accessing the stateful bean from a stateless bean like so:
@Stateless
@LocalBean
@Path("/")
public class MyService
{
@PersistenceContext(unitName = GlobalParameters.BACKEND_CODE_PERSISTENCE_CONTEXT_NAME)
private EntityManager em;
@Inject
private SessionFacadeBean sessionBean;
@POST
@Path("/create/item")
@ValidateRequest
public ComponentShortSummary addItem(@Form NewItemForm itemForm)
{
if(sessionBean.getLock()) {
try {
if(itemForm.getName().equals("INVALID") == true) {
throw new ConstraintViolationException("Failed", new HashSet<ConstraintViolation<?>>());
}
ChildOne child = new ChildOne(itemForm.getName(), sessionBean.getSessionData());
em.persist(child);
return null;
}
finally {
sessionBean.clearLock();
}
} else {
return null;
}
}
}
To reproduce the problem, I perform the following sequence:
- Call addItem with a valid name (this persists the item to the database).
- Call addItem with a name ‘INVALID’, this throws the constraint exception.
- Call addItem with a valid name (this results in a detached entity error on the line
em.persist(child)
.
What I don’t understand is how/why I’m ending up with detached entities. In the real code, I would be performing some request / state validation, before modifying the state (so there is no reason that I can see for the state to have been detached).
If I remove the call to sessionBean.getLock()
then the problem goes away (the objects persist correctly). The purpose of the lock methods is essentially to serialize access to the session state, however currently the getLock()
method is empty, it feels like the problem might be related to the fact that I’m calling into the stateful bean before throwing the exception.
Can anybody explain what’s going on that results in my entities becoming detached / if there is a way to avoid it (and ideally point me at any documentation that supports the explanation)?
Whilst there are probably ways that I can work around the current issue, performing validation before accessing the stateful bean at all, I’m concerned about the general case (where any exception is thrown after the stateful bean has been accessed in the call). Is there an accepted strategy for dealing with exceptions when I don’t want the entities from the extended persistent context to be detached?
回答1:
It looks like this is expected behaviour. Thanks to Scott Marlow's reference to the JPA spec, section 3.3.2.
Transaction Rollback For both transaction-scoped and extended persistence contexts, transaction rollback causes all pre-existing managed instances and removed instances[31] to become detached. The instances’ state will be the state of the instances at the point at which the transaction was rolled back. Transaction rollback typically causes the persistence context to be in an inconsistent state at the point of rollback. In particular, the state of version attributes and generated state (e.g., generated primary keys) may be inconsistent. Instances that were formerly managed by the persistence context (including new instances that were made persistent in that transaction) may therefore not be reusable in the same manner as other detached objects—for example, they may fail when passed to the merge operation.[32]
So, entities that are involved in the active transaction are detached when the transaction is rolled back and by calling out to the sessionBean I am involving it in the transaction.
One way around this appears to be to decorate acceptable exceptions with the @AppicationException
annotation. This marks the exception as non-fatal and prevents the transaction from being rolled back. This approach is described in some detail by David Blevin.
来源:https://stackoverflow.com/questions/12675654/unexpected-detached-entities-with-extended-persistent-context