Search code examples
javaspringhibernatetransactionmanager

I'm getting an TransactionRequiredException in my Spring+Hibernate server applet


I've been building a Spring MVC application that uses SpringDataJPA and hibernate, and leverages MySQL. From some point on I am getting an TransactionRequiredException on my main web-page (named 'main'), which I wasn't able to get rid of. Can you show me where my configuration / code went wrong?

appconfig-data.xml

<bean id="dataSource"
    class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName"
        value="${jdbc.driverClassName}" />
    <property name="url" value="${jdbc.url}" />
    <property name="username" value="${jdbc.username}" />
    <property name="password" value="${jdbc.password}" />
</bean>

<!-- Configure the entity manager factory bean -->
<bean id="entityManagerFactory"
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="packagesToScan" value="com.ajwt.entities" />
    <property name="jpaVendorAdapter">
        <bean
            class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
    </property>
    <property name="jpaProperties">
        <props>
            <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
            <prop key="hibernate.show_sql">true</prop>
        </props>
    </property>
</bean>

<!-- Configure the transaction manager bean -->
<bean id="transactionManager"
    class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory"
        ref="entityManagerFactory" />
    <property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven
    transaction-manager="transactionManager" />

<!-- Configure Spring Data JPA and set the base package of the repository 
    interfaces -->

<!-- Configure Spring Data JPA and set the base package of the repository 
    interfaces -->

<jpa:repositories base-package="com.ajwt.repositories" />

<bean id="sessionFactory"
    class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="packagesToScan" value="com.ajwt.entities" />
    <property name="hibernateProperties">
        <props>
            <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
            <prop key="hibernate.show_sql">true</prop>
        </props>
    </property>
</bean>

Stack trace

Hibernate: select projects0_.id as id1_2_, projects0_.description as
descript2_2_, projects0_.duedate as duedate3_2_, projects0_.grade as
grade4_2_, projects0_.pname as pname5_2_, projects0_.sid as sid6_2_,
projects0_.state as state7_2_ from ajwt.projects projects0_
אפר׳ 17, 2019 3:14:24 לפנה״צ org.apache.catalina.core.StandardWrapperValve
invoke
SEVERE: Servlet.service() for servlet [dispatcher] in context with path [/ajwt]
threw exception [Request processing failed; nested exception is
javax.persistence.TransactionRequiredException: no transaction is in
progress] with root cause
javax.persistence.TransactionRequiredException: no transaction is in
progress

Controller

@RequestMapping(value = "/", method = RequestMethod.GET)
public String defaultPage(Model model) {
return "redirect:/main";
}

@RequestMapping(value = { "/main" }, method = RequestMethod.GET)
public String welcome(Model model) {
model.addAttribute("addProject", new Projects());
model.addAttribute("projectList", projectService.getAllProjects());
model.addAttribute("availableProjectList",
projectService.getAvailableProjects());
model.addAttribute("scholarList", userService.getScholars());
model.addAttribute("technologiesList",
techService.getAllTechnologies());

return "main";
}

@RequestMapping(value = "/main", method = RequestMethod.POST)
public String addProject(@ModelAttribute("addProject") Projects project,
BindingResult bindingResult, ModelMap model) {
projectService.save(project);
return "redirect:/main";
}

Projects Service

@Service("projectService")
public class ProjectServiceImpl implements ProjectService {
    @Autowired
    private ProjectRepository projectRepository;
    @Autowired
    private SessionFactory sessionFactory;

    public void save(Projects project) {
        projectRepository.save(project);
    }
    @Transactional
    @Override
    public List<Projects> getAllProjects() {
        TypedQuery<Projects> query = sessionFactory.getCurrentSession().createQuery("from Projects");
        return query.getResultList();
    }
    @Transactional
    @Override
    public List<Projects> getAvailableProjects() {
        TypedQuery<Projects> query = sessionFactory.getCurrentSession()
                .createQuery("from Projects where state = 'PROJECT_AVAIL'");
        return query.getResultList();
    }
    @Transactional
    @Override
    public List<Projects> getMyProjects(int id) {
        TypedQuery<Projects> query = sessionFactory.getCurrentSession().createQuery("from Projects");
        return query.getResultList();
    }
    @Transactional
    @Override
    public void addProject(Projects project) {
        projectRepository.save(project);
    }
}

Technologies Service

@Service("technologiesService")
public class TechServiceImpl implements TechService {
    @Autowired
    private ProjectRepository projectRepository;
    @Autowired
    private SessionFactory sessionFactory;
    @Override
    @Transactional
    public List<Technologies> getAllTechnologies() {
        TypedQuery<Technologies> query = sessionFactory.getCurrentSession().createQuery("select tech from Technologies");
        return query.getResultList();
    }
}

Technologies

package com.ajwt.entities;
// Generated 17 באפר׳ 2019, 2:45:49 by Hibernate Tools 4.3.5.Final

import java.util.HashSet;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import static javax.persistence.GenerationType.IDENTITY;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;

/**
 * Technologies generated by hbm2java
 */
@Entity
@Table(name = "technologies", catalog = "ajwt")
public class Technologies implements java.io.Serializable {

    private Integer id;
    private String tech;
    private Set<Projects> projectses = new HashSet<Projects>(0);

    public Technologies() {
    }

    public Technologies(String tech) {
        this.tech = tech;
    }

    public Technologies(String tech, Set<Projects> projectses) {
        this.tech = tech;
        this.projectses = projectses;
    }

    @Id
    @GeneratedValue(strategy = IDENTITY)

    @Column(name = "id", unique = true, nullable = false)
    public Integer getId() {
        return this.id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    @Column(name = "tech", nullable = false, length = 45)
    public String getTech() {
        return this.tech;
    }

    public void setTech(String tech) {
        this.tech = tech;
    }

    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(name = "project_technologies", catalog = "ajwt", joinColumns = {
            @JoinColumn(name = "tid", nullable = false, updatable = false) }, inverseJoinColumns = {
                    @JoinColumn(name = "pid", nullable = false, updatable = false) })
    public Set<Projects> getProjectses() {
        return this.projectses;
    }

    public void setProjectses(Set<Projects> projectses) {
        this.projectses = projectses;
    }

}

main.jsp

<%@taglib uri="http://www.springframework.org/tags" prefix="spring"%>
<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<c:set var="contextPath" value="${pageContext.request.contextPath}" />
<!DOCTYPE html>

<html>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
<meta name="description" content="">
<meta name="author" content="Edward Romanenco">
<head>
<title>Administrator Console</title>
<link rel="stylesheet"
    href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
    integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"
    crossorigin="anonymous">
<link href="${contextPath}/resources/css/common.css" rel="stylesheet">
</head>
<body>
    <h2>Project Management Screen</h2>
    <c:if test="${pageContext.request.userPrincipal.name != null}">
        <form id="logoutForm" method="POST" action="${contextPath}/logout">
            <input type="hidden" name="${_csrf.parameterName}"
                value="${_csrf.token}" />
        </form>
        <h6>
            Welcome ${pageContext.request.userPrincipal.name}! | <a
                onclick="document.forms['logoutForm'].submit()">Logout</a>
        </h6>
    </c:if>
    <div>
        <h3>My Projects</h3>
        <c:choose>
            <c:when test="${!empty projectList}">
                <table class="data">
                    <tr>
                        <th>Project Name</th>
                        <th>Due Date</th>
                    </tr>
                    <c:forEach items="${projectList}" var="project">
                        <tr>
                            <td>${project.pname}</td>
                            <td><c:choose>
                                    <c:when test="${project.duedate}!=null">${project.duedate}</c:when>
                                    <c:otherwise>No Due Date Yet</c:otherwise>
                                </c:choose></td>
                            <!--<td><a href="edit/${project.id}">Edit</a></td>
                            <td><a href="delete/${project.id}">Delete</a></td>-->
                        </tr>
                    </c:forEach>
                </table>
            </c:when>
            <c:otherwise>
            You don't participate in any project, look over the available projects section to start!
            </c:otherwise>
        </c:choose>
    </div>
    <div>
        <h3>Available Projects</h3>
        <c:choose>
            <c:when test="${!empty availableProjectList}">
                <table class="data">
                    <tr>
                        <th>Project Name</th>
                        <th>Due Date</th>
                    </tr>
                    <c:forEach items="${availableProjectList}" var="project">
                        <tr>
                            <td>${project.pname}</td>
                            <td>${project.duedate}</td>
                            <!--  <td><a href="join/${project.id}">join</a></td>-->
                        </tr>
                    </c:forEach>
                </table>
            </c:when>
            <c:otherwise>
    There are no available projects currently, make sure you come back soon as those are added daily!
  </c:otherwise>
        </c:choose>
    </div>
    <h3>Suggest Projects</h3>
    <div>
        <form:form method="POST" modelAttribute="addProject">
            <table>
                <spring:bind path="pname">
                    <tr>
                        <td><form:label path="pname">Project Name</form:label></td>
                        <td><form:input path="pname" /></td>
                    </tr>
                </spring:bind>
                <spring:bind path="description">
                    <tr>
                        <td><form:label path="description">Description</form:label></td>
                        <td><form:textarea path="description" rows="5" cols="25" /></td>
                    </tr>
                </spring:bind>
                <spring:bind path="sid">
                    <tr>
                        <td><form:label path="sid">Scholar:</form:label></td>
                        <td><form:select id="projectSelect" name="userId" path="sid">
                                <c:forEach var="theUser" items="${scholarList}">
                                    <form:option value="${scholarList.id}">
                                        <c:out value="${scholarList.fname} ${scholarList.lname}" />
                                    </form:option>
                                </c:forEach>
                            </form:select></td>
                    </tr>
                </spring:bind>
                <spring:bind path="technologieses">
                    <tr>
                        <td><form:label path="technologieses">Technologies:</form:label></td>
                        <td><form:checkboxes items="${technologiesList}"
                                path="technologieses" /></td>
                    </tr>
                </spring:bind>
                <tr>
                    <td colspan="2"><input type="submit" value="Submit" /></td>
                </tr>
            </table>
        </form:form>
    </div>
    <script
        src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <script
        src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
        integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"
        crossorigin="anonymous"></script>
</body>
</html>

Solution

  • The problem is that you both have configured an EntityManagerFactory and a SessionFactory. You have now 2 seperate instances managing your dependencies. The transaction manager you configured is for JPA and not for plain Hibernate.

    Instead of using plain Hibernate, use JPA. Remove the definition of the SessionFactory from your configuration and instead of autowiring the SessionFactory use the EntityManager provided by JPA.

    @Service("technologiesService")
    public class TechServiceImpl implements TechService {
    
        @Autowired
        private ProjectRepository projectRepository;
    
        @PersistenceContext
        private EntityManager entityManager;
    
        @Override
        @Transactional
        public List<Technologies> getAllTechnologies() {
            return entityManager.createQuery("SELECT t FROM Technologies t").getResultList();
        }
    }
    

    Spring will take care of injecting the current bound EntityManager.