Stripes更新对象时创建新对象:如何复用分离脏态对象?
Great question—this is a common gotcha when combining Stripes' default parameter binding with DataNucleus JDO's detached state management on GAE. The short answer: yes, you can absolutely reuse the detached clean object you fetched, modify it, and persist it in a detached dirty state. Here’s how to make it work:
1. Override Stripes' Default Object Population
Stripes defaults to creating a new transient instance of your entity class to bind request parameters to. To fix this, you need to tell Stripes to use the existing detached object you loaded from the database instead.
Option A: Manual Parameter Binding (Simple & Straightforward)
In your ActionBean, first fetch the detached object in your edit handler, then manually map request parameters to this instance in your save handler:
public class ProductAction implements ActionBean { private ActionBeanContext context; private Product product; // Our detached object private Long productId; @DefaultHandler public Resolution edit() { // Fetch the entity from GAE datastore and detach it PersistenceManager pm = PMF.get().getPersistenceManager(); try { Product persistedProduct = pm.getObjectById(Product.class, productId); product = pm.detachCopy(persistedProduct); // Clean detached state } finally { pm.close(); } return new ForwardResolution("/product-edit.jsp"); } public Resolution save() { // Manually bind request params to our existing detached product HttpServletRequest request = context.getRequest(); product.setName(request.getParameter("name")); product.setPrice(Double.parseDouble(request.getParameter("price"))); product.setDescription(request.getParameter("description")); // Persist the dirty detached object PersistenceManager pm = PMF.get().getPersistenceManager(); try { pm.makePersistent(product); // DataNucleus detects changes and runs UPDATE } finally { pm.close(); } return new RedirectResolution(ProductAction.class, "list"); } // Getters and setters for context, productId, product }
Option B: Custom Population Strategy (Scalable)
If you don’t want to manually bind every field, create a custom PopulationStrategy to reuse existing instances in your ActionBean:
public class ReuseDetachedEntityStrategy extends DefaultPopulationStrategy { @Override public Object getInstance(ActionBean bean, Class<?> type, String name) { // Check if the ActionBean has an existing instance of the entity if (type == Product.class && bean instanceof ProductAction) { ProductAction action = (ProductAction) bean; if (action.getProduct() != null) { return action.getProduct(); // Return the pre-loaded detached object } } // Fall back to default behavior for other cases return super.getInstance(bean, type, name); } }
Then annotate your ActionBean to use this strategy:
@PopulationStrategy(ReuseDetachedEntityStrategy.class) public class ProductAction implements ActionBean { // ... rest of your code, including loading the detached product in edit() }
Now Stripes will bind request parameters directly to your existing detached object instead of creating a new transient one.
2. Ensure DataNucleus Tracks Detached Dirty State
When you fetch and detach the object using pm.detachCopy(), DataNucleus automatically retains the original field values to track changes. As long as you modify the same detached instance, calling pm.makePersistent() will trigger an UPDATE (not INSERT) because DataNucleus recognizes it as a detached dirty object, not a transient one.
For complex entities with relationships, you might need to adjust the fetch plan when detaching to include related objects:
pm.getFetchPlan().setGroup("detach-group"); // Define a fetch group in your JDO metadata product = pm.detachCopy(persistedProduct);
3. GAE-Specific Checks
Google App Engine’s DataNucleus implementation fully supports detached dirty state management, so you don’t need any extra configuration here—just ensure you’re using the official GAE JDO dependencies and that your persistence.xml is correctly set up for GAE.
内容的提问来源于stack exchange,提问作者Sumanta

