javax.persistence.TransactionRequiredException in small facelet application

后端 未结 3 1450
佛祖请我去吃肉
佛祖请我去吃肉 2020-12-03 04:07

I\'m trying to persist some values to a MySql database from a small facelet application but keep getting this error. I had this same application with a JPS page and a servle

相关标签:
3条回答
  • 2020-12-03 04:27

    You're abusing a CDI managed bean as a business service. It has no clues of transaction management. You'd need to manually manage transactions. As this is usually a pain and you're apparently using Glassfish, which is a fullworthy Java EE container supporting EJBs, you'd rather like to use a fullworthy EJB for this. When using EntityManager inside an EJB, the container will fully transparently manage DB transactions. One EJB method call counts as a single transaction (i.e. when you fire multiple DB queries and one of them fails, then everything will be automatically rolled back).

    Overall, you seem to be mixing the responsibilities of the model, controller and service. Do not make your entity a managed bean as well. You should further absolutely also not perform business logic in a Javabean getter method (e.g. getBooks()). When referenced in an iterating component, it's invoked during every iteration round. So imagine that you've 100 records, then the DB will be hit 100 times. This is plain inefficient.

    Here's how it should all look like:

    Model (the entity):

    @Entity
    @Table(name = "BOOKS")
    public class Book implements Serializable {
        // ...
    }
    

    Controller (the backing bean):

    @Named
    @RequestScoped
    public class BookController {
    
        private Book book;
        private List<Book> books;
    
        @EJB
        private BookService service;
    
        @PostConstruct
        public void init() {
            book = new Book();
            books = service.list();
        }
    
        public void add() {
            service.save(book);
            init();
        }
    
        public Book getBook() { 
            return book;
        }
    
        public List<Book> getBooks() {
            return books;
        }
    
    }
    

    Service (the EJB):

    @Stateless
    public class BookService {
    
        @PersistenceContext
        private EntityManager em;
    
        public List<Book> list() {
            return em.createQuery("FROM Book", Book.class).getResultList();
        }
    
        public Book find(Integer id) {
            return em.find(Book.class, id);
        }
    
        public Integer save(Book book) {
            em.persist(book);
            return book.getId();
        }
    
        public void update(Book book) {
            em.merge(book);
        }
    
        public void delete(Book book) {
            em.remove(em.contains(book) ? book : em.merge(book));
        }
    
    }
    

    View (the Facelet; simplified):

    <h:inputText id="title" value="#{bookController.book.title}"/>
    <h:inputText id="author" value="#{bookController.book.author}"/>
    <h:inputText id="price" value="#{bookController.book.price}"/>
    <h:commandButton value="Add" action="#{bookController.add}" />
    ...
    <h:dataTable value="#{bookController.books}" var="book">
        <h:column><f:facet name="header">ID</f:facet>#{book.id}</h:column>
        <h:column><f:facet name="header">Title</f:facet>#{book.title}</h:column>
        <h:column><f:facet name="header">Author</f:facet>#{book.author}</h:column>
        <h:column><f:facet name="header">Price</f:facet>#{book.price}</h:column>
    </h:dataTable>
    

    (your edit and delete buttons didn't make any sense, so I removed them, you might want to put them inside the data table)

    See also:

    • Recommended JSF 2.0 CRUD frameworks
    • Why JSF calls getters multiple times
    0 讨论(0)
  • 2020-12-03 04:34

    The javadoc for EntityManager#persist(Object) says

    Throws: TransactionRequiredException - if invoked on a container-managed entity manager of type PersistenceContextType.TRANSACTION and there is no transaction

    You need to call EntityManager.html#getTransaction() and begin a transaction before you call persist (and some of the other methods). Don't forget to also commit or rollback the transaction when you are done.

    0 讨论(0)
  • 2020-12-03 04:40

    Just add @Transactional annotation on your method, e.g.

    @Transactional // <-------------
    public long setSessionState(StateEnum state, String hash) {
    
        QSession s = QSession.session;
        JPAUpdateClause upd = new JPAUpdateClause(em, s);
        upd.set(s.state, state).where(s.hash.eq(hash));
        return upd.execute();
    }
    
    0 讨论(0)
提交回复
热议问题