Search code examples
mysqlspringspring-boothibernate

Spring Boot Error: no transaction is in progress


I'm getting this error when I'm trying to save ChatUser object from /showSignUpForm request . The object details are stored successfully in the DB but it gives "jakarta.persistence.TransactionRequiredException: no transaction is in progress" error on on /processSignUpForm

This is my controller class

@Controller
public class ChatUserController {

    @Autowired
    private ChatUserService chatUserService;
    
    @GetMapping("/showLoginForm")
    public String showLoginForm(Model theModel){
        return "login-form";
    }
    
    @GetMapping("/showSignUpForm")
    public String showSignUpForm(Model theModel) {
        theModel.addAttribute("chatUser", new ChatUser());
        return "sign-up-form";
    }
    
    @PostMapping("/processSignUpForm")
    @Transactional
    public String processSignUpForm(@ModelAttribute("chatUser") ChatUser chatUser) {
        
        System.out.println(chatUser.toString());
        chatUserService.saveChatUser(chatUser);
        return "chat-page";
    }

}

This my DAO interface

public interface ChatUserDAO {
    
    public List<ChatUser> getchatUsers();

    public void saveChatUser(ChatUser theChatUser);
    
    public ChatUser getChatUser(int theId);

    public void deleteChatUser(int theId);

}

DAO Implementation:

@Repository
public class ChatUserDaoImpl implements ChatUserDAO{
    
    @Autowired
    private SessionFactory sessionFactory;

    @Override
    public List<ChatUser> getchatUsers() {
        Session currentSession = sessionFactory.getCurrentSession();
        Query<ChatUser> theQuery = currentSession.createQuery("from ChatUser", ChatUser.class);
        List<ChatUser> chatUsers = theQuery.getResultList();
        return chatUsers;
    }

    @SuppressWarnings("deprecation")
    @Override
    public void saveChatUser(ChatUser theChatUser) {
        Session currentSession = sessionFactory.getCurrentSession();
        currentSession.saveOrUpdate(theChatUser);   
    }
}

This is my ChatUser Entity

@Entity
@Table(name="chat_user")
public class ChatUser {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "user_id")
    private int user_id;
    
    @Column(name="user_name")
    private String user_name;
    
    @Column(name="user_email")
    private String user_email;
    
    @Column(name="user_password")
    private String user_password;
    
    
    @ManyToMany(fetch=FetchType.LAZY, cascade = {CascadeType.PERSIST,CascadeType.MERGE,CascadeType.DETACH,CascadeType.REFRESH })
    @JoinTable(
            name="group_user",
            joinColumns=@JoinColumn(name="user_id"),
            inverseJoinColumns=@JoinColumn(name="group_id")
            )
    private List<ChatGroup> chatGroups;

    public ChatUser() {
        
    }

    public ChatUser(String user_name, String user_email, String user_password) {
        this.user_name = user_name;
        this.user_email = user_email;
        this.user_password = user_password;
    }
}

This is my Service Class Implementation

@Service
public class ChatUserServiceImpl implements ChatUserService{

    @Autowired
    private ChatUserDAO chatUserDao;
    
    @Override
    @Transactional
    public List<ChatUser> getChatUsers() {
        return chatUserDao.getchatUsers();
    }

    @Override
    @Transactional
    public void saveChatUser(ChatUser theChatUser) {
        chatUserDao.saveChatUser(theChatUser);
    }
    
    @Override
    @Transactional
    public ChatUser getChatUser(int theId) {
        return chatUserDao.getChatUser(theId);
    }

    @Override
    @Transactional
    public void deleteChatUser(int theId) {
        chatUserDao.deleteChatUser(theId);
    }

}

Please help. Thankyou.

though the object is getting saved, but error still persists.

Error

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1">
  <display-name>spring-mvc-crud-demo</display-name>

  <absolute-ordering />

  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>

  <servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>/WEB-INF/spring-mvc-crud-demo-servlet.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  
  <servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>

spring-mvc-crud-demo-servlet.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc.xsd
        http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!-- Add support for component scanning -->
    <context:component-scan base-package="com.springprojects.realtimechatapp" />

    <!-- Add support for conversion, formatting and validation support -->
    <mvc:annotation-driven/>

    <!-- Define Spring MVC view resolver -->
    <bean
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/" />
        <property name="suffix" value=".jsp" />
    </bean>

    <!-- Step 1: Define Database DataSource / connection pool -->
    <bean id="myDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
          destroy-method="close">
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver" />
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/chat_db?useSSL=false&amp;serverTimezone=UTC" />
        <property name="user" value="root" />
        <property name="password" value="mysql" /> 

        <!-- these are connection pool properties for C3P0 -->
        <property name="minPoolSize" value="5" />
        <property name="maxPoolSize" value="20" />
        <property name="maxIdleTime" value="30000" />
    </bean>  
    
    <!-- Step 2: Setup Hibernate session factory -->
    <bean id="sessionFactory"
        class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
        <property name="dataSource" ref="myDataSource" />
        <property name="packagesToScan" value="com.springprojects.realtimechatapp" />
        <property name="hibernateProperties">
           <props>
              <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
              <prop key="hibernate.show_sql">true</prop>
           </props>
        </property>
   </bean>    

    <!-- Step 3: Setup Hibernate transaction manager -->
    <bean id="myTransactionManager"
            class="org.springframework.orm.hibernate5.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>
    
    <!-- Step 4: Enable configuration of transactional behavior based on annotations -->
    <tx:annotation-driven transaction-manager="myTransactionManager" />

    
    <!-- Add support for reading web resources: css, images, js, etc. -->
    <mvc:resources location="/resources/" mapping="/resources/**"></mvc:resources>
    
    
</beans>

Main Application class

package com.springprojects.realtimechatapp;

import java.awt.Desktop;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@SpringBootApplication
@EnableTransactionManagement
public class RealtimechatappApplication {

    public static void main(String[] args) {

        SpringApplication.run(RealtimechatappApplication.class, args);
        if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
            try {
                Desktop.getDesktop().browse(new URI("http://localhost:8080/"));
            } catch (IOException | URISyntaxException e) {
                e.printStackTrace();
            }
        }
    }

}

application.properties

server.port=8080
spring.application.name=realtimechatapp
spring.datasource.url=jdbc:mysql://localhost:3306/chat_db
spring.datasource.username=root
spring.datasource.password=mysql
spring.jpa.hibernate.ddl-auto=update

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp


logging.level.org.hibernate.type.descriptor.sql=trace
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.allow_update_outside_transaction=true

spring.jpa.open-in-view=true
spring.jpa.properties.hibernate.current_session_context_class=org.springframework.orm.hibernate5.SpringSessionContext

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.0.6</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.springprojects</groupId>
    <artifactId>realtimechatapp</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>war</packaging>
    <name>realtimechatapp</name>
    <description>Chat Application using Spring Boot</description>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
            <version>10.1.18</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <image>
                        <builder>paketobuildpacks/builder-jammy-base:latest</builder>
                    </image>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>


Solution

  • I am not sure of the cause of this problem, but it seems related to the use of sessionFactory.getCurrentSession() and it does go away if you don't implement ChatUserDao explicitly and instead lean on JPA's auto-generated implementations. For ChatUserDao, declare the following

    
    import org.springframework.data.jpa.repository.JpaRepository;
    
    public interface ChatUserDao extends JpaRepository<ChatUser, Integer> {
    }
    

    Remove ChatUserDaoImpl.

    In ChatUserService, replace the calls to the custom methods in ChatUserDao with the standard methods on JpaRepository:

    @Service
    public class ChatUserServiceImpl implements ChatUserService {
    
        @Autowired
        private ChatUserDao chatUserDao;
    
        @Override
        @Transactional
        public List<ChatUser> getChatUsers() {
            return chatUserDao.findAll();
        }
    
        @Override
        @Transactional
        public void saveChatUser(ChatUser theChatUser) {
            chatUserDao.save(theChatUser);
        }
    
        @Override
        @Transactional
        public ChatUser getChatUser(int theId) {
            return chatUserDao.getById(theId);
        }
    
        @Override
        @Transactional
        public void deleteChatUser(int theId) {
            chatUserDao.deleteById(theId);
        }
    
    }
    

    The addition of the property spring.jpa.properties.hibernate.allow_update_outside_transaction=true is probably not quite what you want as you may eventually want transactional updates.