Java EE运行时持久化新对象的正确实现方法
Hey there! Let's break down how to properly persist data after your Java EE app has started, while sticking to the MVC pattern and fixing your current issues.
First, a key rule to follow: Controllers (like your UploadController) should NOT handle persistence logic directly. In MVC, controllers exist to receive requests, validate input, and delegate business/persistence work to dedicated layers. Persistence should live in DAOs (Data Access Objects) or EJBs, with transactions managed by the Java EE container—this is the standard, maintainable approach.
Why Your Previous Solutions Failed
1. TransactionRequiredException in Solution 1
Your UploadController is likely a @RequestScoped or @ViewScoped bean (common for JSF controllers). The @PersistenceContext-injected EntityManager is transaction-scoped by default—meaning it only works when a valid transaction context exists. When you tried to call em.persist() directly in the controller, there was no active transaction, hence the exception.
Also, reusing the InitBean's persistence context is a bad idea: singletons have a completely different lifecycle than request-scoped controllers, and mixing scopes like this breaks Java EE's container-managed rules.
2. Static Method in Solution 2
Static methods exist outside the container-managed bean lifecycle. The EntityManager injection only works for instantiated beans managed by the container—so your static method would never get a valid EntityManager instance. This approach also breaks object-oriented design principles, so you were right to feel it's "un规范" (unconventional).
Step-by-Step Fix
1. Refactor Your ProductDAO to Use a Stateless EJB
Your existing ProductDAO uses @Model (equivalent to @RequestScoped + @Named), but @Stateless EJBs are the standard for persistence/business logic in Java EE. They automatically handle container-managed transactions (CMT), so you don't have to manually manage transactions.
Here's how to update your ProductDAO (completing the code you shared):
package at.technikum.mic16.prj.dao; import at.technikum.mic16.prj.entity.Product; import java.io.Serializable; import javax.ejb.Stateless; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; @Stateless // Critical: Container manages transactions and instance pooling public class ProductDAO implements Serializable { @PersistenceContext(unitName = "daisy-persunit") private EntityManager em; // Your existing find method public Product findByID(Long id) { return em.find(Product.class, id); } // Add method to persist new Product public void persistProduct(Product product) { em.persist(product); // Transaction is automatically created by the container } // Optional: Add method for updating existing Products public Product updateProduct(Product product) { return em.merge(product); } // Add your unfinished find all method here if needed }
@Stateless methods default to @TransactionAttribute(REQUIRED)—meaning the container starts a transaction if none exists, and commits it when the method completes (no exceptions = commit, exception = rollback).
2. Inject the ProductDAO into Your UploadController
Your controller should remain a request-scoped CDI bean (for MVC separation), and you'll inject the ProductDAO EJB to handle persistence. Example:
import at.technikum.mic16.prj.dao.ProductDAO; import at.technikum.mic16.prj.entity.Product; import javax.inject.Named; import javax.enterprise.context.RequestScoped; import javax.inject.Inject; @Named // Makes the bean accessible to JSF views @RequestScoped // Correct scope for a controller: one instance per HTTP request public class UploadController { @Inject // Container injects a valid ProductDAO instance from its pool private ProductDAO productDAO; // Method to handle your XML upload and parsed Product public String handleXmlUpload(Product parsedProduct) { // Optional: Add validation here (e.g., check for duplicate product names) productDAO.persistProduct(parsedProduct); // Persist with container-managed transaction return "success?faces-redirect=true"; // Redirect to success page } }
Now when handleXmlUpload is called, the container will automatically manage the transaction for productDAO.persistProduct(), so you won't get the TransactionRequiredException anymore.
3. Quick Scope Recap
- UploadController:
@RequestScopedis ideal—each request gets a fresh instance, which aligns with MVC controller responsibilities. - ProductDAO:
@Statelesslets the container manage instance pooling and transactions, so you don't have to worry about scope details. - EntityManager: Injected via
@PersistenceContextin a@Statelessbean, it's transaction-scoped—one instance per active transaction, managed entirely by the container.
Bonus Best Practices
- Keep controllers thin: Never put business/persistence logic in controllers—this keeps your code modular, testable, and aligned with MVC.
- Use Bean Validation: Add validation annotations to your
Productentity to ensure data integrity before persistence:@Column(unique = true, nullable = false) @NotNull(message = "Product name cannot be empty") private String name; @Column(nullable = false) @Min(value = 0, message = "Price cannot be negative") private float price; - Avoid manual transactions: Stick to container-managed transactions (CMT) unless you have a very specific need for bean-managed transactions (BMT)—CMT reduces code complexity and error risk.
内容的提问来源于stack exchange,提问作者SuperSpitter

