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