Javax.persistence.TransactionRequiredException в малом приложении

Я пытаюсь сохранить некоторые значения в базе данных MySql из небольшого приложения facelet, но продолжаю получать эту ошибку. У меня было это же приложение со страницей JPS и сервлетом, и он работал нормально с примерно такой же логикой, это мой первый раз, когда я пытаюсь работать с facelets, поэтому это может быть просто глупо, но я бы посоветовал эту помощь.

Спасибо

Ошибка

javax.faces.el.EvaluationException: javax.persistence.TransactionRequiredException
    at javax.faces.component.MethodBindingMethodExpressionAdapter.invoke(MethodBindingMethodExpressionAdapter.java:102)
    at com.sun.faces.application.ActionListenerImpl.processAction(ActionListenerImpl.java:102)
    at javax.faces.component.UICommand.broadcast(UICommand.java:315)
    at javax.faces.component.UIViewRoot.broadcastEvents(UIViewRoot.java:794)
    at javax.faces.component.UIViewRoot.processApplication(UIViewRoot.java:1259)
    at com.sun.faces.lifecycle.InvokeApplicationPhase.execute(InvokeApplicationPhase.java:81)
    at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:101)
    at com.sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:118)
    at javax.faces.webapp.FacesServlet.service(FacesServlet.java:593)
    at org.apache.catalina.core.StandardWrapper.service(StandardWrapper.java:1550)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:281)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175)
    at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:655)
    at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:595)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:161)
    at org.apache.catalina.connector.CoyoteAdapter.doService(CoyoteAdapter.java:331)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:231)
    at com.sun.enterprise.v3.services.impl.ContainerMapper$AdapterCallable.call(ContainerMapper.java:317)
    at com.sun.enterprise.v3.services.impl.ContainerMapper.service(ContainerMapper.java:195)
    at com.sun.grizzly.http.ProcessorTask.invokeAdapter(ProcessorTask.java:860)
    at com.sun.grizzly.http.ProcessorTask.doProcess(ProcessorTask.java:757)
    at com.sun.grizzly.http.ProcessorTask.process(ProcessorTask.java:1056)
    at com.sun.grizzly.http.DefaultProtocolFilter.execute(DefaultProtocolFilter.java:229)
    at com.sun.grizzly.DefaultProtocolChain.executeProtocolFilter(DefaultProtocolChain.java:137)
    at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:104)
    at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:90)
    at com.sun.grizzly.http.HttpProtocolChain.execute(HttpProtocolChain.java:79)
    at com.sun.grizzly.ProtocolChainContextTask.doCall(ProtocolChainContextTask.java:54)
    at com.sun.grizzly.SelectionKeyContextTask.call(SelectionKeyContextTask.java:59)
    at com.sun.grizzly.ContextTask.run(ContextTask.java:71)
    at com.sun.grizzly.util.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:532)
    at com.sun.grizzly.util.AbstractThreadPool$Worker.run(AbstractThreadPool.java:513)
    at java.lang.Thread.run(Thread.java:722)
Caused by: javax.persistence.TransactionRequiredException
    at com.sun.enterprise.container.common.impl.EntityManagerWrapper.doTxRequiredCheck(EntityManagerWrapper.java:163)
    at com.sun.enterprise.container.common.impl.EntityManagerWrapper.doTransactionScopedTxCheck(EntityManagerWrapper.java:145)
    at com.sun.enterprise.container.common.impl.EntityManagerWrapper.persist(EntityManagerWrapper.java:263)
    at vecka19.controller.BookController.addBook(BookController.java:28)
    at vecka19.controller.BookController$Proxy$_$$_WeldClientProxy.addBook(BookController$Proxy$_$$_WeldClientProxy.java)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at javax.el.BeanELResolver.invokeMethod(BeanELResolver.java:779)
    at javax.el.BeanELResolver.invoke(BeanELResolver.java:528)
    at javax.el.CompositeELResolver.invoke(CompositeELResolver.java:257)
    at com.sun.el.parser.AstValue.invoke(AstValue.java:248)
    at com.sun.el.MethodExpressionImpl.invoke(MethodExpressionImpl.java:302)
    at org.jboss.weld.util.el.ForwardingMethodExpression.invoke(ForwardingMethodExpression.java:39)
    at org.jboss.weld.el.WeldMethodExpression.invoke(WeldMethodExpression.java:50)
    at com.sun.faces.facelets.el.TagMethodExpression.invoke(TagMethodExpression.java:105)
    at javax.faces.component.MethodBindingMethodExpressionAdapter.invoke(MethodBindingMethodExpressionAdapter.java:88)
    ... 32 more

index.xhtml

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
  xmlns:h="http://java.sun.com/jsf/html"
  xmlns:c="http://java.sun.com/jsp/jstl/core">
<h:head>
    <title>Vecka19</title>
</h:head>
<h:body>
    <section id="bookForm">
        <div>
            <h:form id="bookForm">
                <table>
                    <tr>
                        <td><h:outputLabel value="ID: " for="id"/></td>
                        <td><h:inputText id="id" value="#{book.bookId}"/></td>
                    </tr>
                    <tr>
                        <td><h:outputLabel value="TITLE: " for="title"/></td>
                        <td><h:inputText id="title" value="#{book.title}"/></td>
                    </tr>
                    <tr>
                        <td><h:outputLabel value="AUTHOR: " for="author"/></td>
                        <td><h:inputText id="author" value="#{book.author}"/></td>
                    </tr>
                    <tr>
                        <td><h:outputLabel value="PRICE: " for="price"/></td>
                        <td><h:inputText id="price" value="#{book.price}"/></td>
                    </tr>
                    <tr>
                        <td>
                            <h:commandButton value="Add" action="#{bookController.addBook()}" />
                            <h:commandButton value="Get" action="#{bookController.book}" />
                            <h:commandButton value="Edit" action="#{bookController.editBook()}" />
                            <h:commandButton value="Delete" action="#{bookController.deleteBook()}" />
                        </td>
                    </tr>
                </table>
            </h:form>
        </div>
    </section>
    <br />
    <section id="dbTable">
        <div>
            <table>
                <th>ID</th>
                <th>TITLE</th>
                <th>AUTHOR</th>
                <th>PRICE</th>
                <c:forEach items="${bookController.books}" var="book">
                    <tr>
                        <td>${book.bookId}</td>
                        <td>${book.title}</td>
                        <td>${book.author}</td>
                        <td>${book.price}</td>
                    </tr>
                </c:forEach>
            </table>
        </div>
    </section>
</h:body>

BookController.java

package vecka19.controller;

import java.util.List;
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.inject.Named;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import vecka19.model.Book;

@Named
@RequestScoped
public class BookController {
    @Inject Book book;

    @PersistenceContext
    private EntityManager em;

    public List getBooks() {
        return em.createNamedQuery("Book.findAll").getResultList();
    }

    public Book getBook() {
        return em.find(Book.class, book.getBookId());
    }

    public void addBook() {
        em.persist(book);
    }

    public void editBook() {
        em.merge(book);
    }

    public void deleteBook() {
        em.remove(getBook());
    }

}

Book.java

package vecka19.model;

import java.io.Serializable;
import javax.enterprise.context.RequestScoped;
import javax.inject.Named;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;

@Entity
@Named
@RequestScoped
@Table(name = "BOOKS")
@NamedQueries({@NamedQuery(name = "Book.findAll", query = "SELECT b FROM Book b")})
public class Book implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @NotNull
    @Column(name = "BOOK_ID")
    private Integer bookId;
    @Column(name = "TITLE")
    private String title;
    @Column(name = "AUTHOR")
    private String author;
    @Column(name = "PRICE")
    private Integer price;

    public Book() {
    }

    public Book(Integer bookId) {
        this.bookId = bookId;
    }

    public Integer getBookId() {
        return bookId;
    }

    public void setBookId(Integer bookId) {
        this.bookId = bookId;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public Integer getPrice() {
        return price;
    }

    public void setPrice(Integer price) {
        this.price = price;
    }
}

percistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
  <persistence-unit name="vecka19PU" transaction-type="JTA">
    <jta-data-source>jdbc/MySQLDataSource</jta-data-source>
    <exclude-unlisted-classes>false</exclude-unlisted-classes>
    <properties/>
  </persistence-unit>
</persistence>

Ответ 1

Вы злоупотребляете управляемым CDI bean как бизнес-услугу. У этого нет никаких ключей от управления транзакциями. Вам нужно будет вручную управлять транзакциями. Поскольку это, как правило, боль, и вы, по-видимому, используете Glassfish, который является полноправным контейнером Java EE, поддерживающим EJB, вы бы скорее хотели использовать для этого полноценный EJB. При использовании EntityManager внутри EJB контейнер полностью прозрачно управляет транзакциями БД. Один вызов метода EJB считается единственной транзакцией (т.е. Когда вы запускаете несколько запросов БД и один из них терпит неудачу, тогда все будет автоматически откат).

В целом, похоже, вы смешиваете обязанности модели, контроллера и сервиса. Не делайте свой объект управляемым bean. Кроме того, вы должны также не выполнять бизнес-логику в методе Javabean getter (например, getBooks()). При ссылке в итерационном компоненте он вызывается во время каждого раунда итерации. Итак, представьте, что у вас 100 записей, тогда БД будет ударить 100 раз. Это неэффективно.

Вот как все должно выглядеть:

Модель (объект):

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

Контроллер (поддержка 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;
    }

}

Сервис (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));
    }

}

Просмотр (упрощенная форма):

<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>

(ваши кнопки редактирования и удаления не имеют никакого смысла, поэтому я удалил их, вы можете поместить их в таблицу данных)

См. также:

Ответ 2

Просто добавьте аннотацию @Transactional к вашему методу, например.

@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();
}

Ответ 3

В javadoc для EntityManager#persist(Object) говорится

Броски:TransactionRequiredException - если вызывается на диспетчер объектов, управляемый контейнером типа PersistenceContextType.TRANSACTION и нет транзакции

Вам нужно позвонить EntityManager.html#getTransaction() и начать транзакцию перед тем, как вы назовете persist (и некоторые другие методы). Не забудьте также выполнить транзакцию commit или rollback, когда вы закончите.