Spring Annotation Configuration with Hibernate: Insert Example (2026)
Last Updated: | Author: Deep Singh
For years, Spring projects relied on XML files to wire everything together. If you've ever maintained a Spring app with a 500-line applicationContext.xml, you know the pain. Since Spring 3.0, though, you can configure the entire application using Java annotations — the same power, without the XML sprawl.
In this tutorial, you'll build a small Spring MVC + Hibernate application that inserts a user into a MySQL database. Everything is configured with annotations. There are no XML files in this project — every bean, every scan, and every view resolver is defined in Java.
Project Structure
Here's the layout you'll be building:
src/main/java
com.deepsingh44
config/AppConfig.java
controller/Reception.java
controller/PageController.java
controller/LoginController.java
dao/UserDao.java
dao/UserDaoImpl.java
model/User.java
src/main/webapp
WEB-INF/views/
index.jsp
register.jsp
head.jsp
If you want to skip ahead and just run the project, the full source is available on GitHub.
Maven Dependencies (pom.xml)
Add the following dependencies inside the <dependencies> block of your pom.xml.
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.5</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.3.5</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.2.17.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.4.1.Final</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.12.1.GA</version>
</dependency>
</dependencies>
Heads-up on versions: The versions above match the original tutorial from 2021 and still work if you're on Java 8 or 11 with Servlet 4.0. If you're starting a new project in 2026, consider upgrading to Spring 6, Hibernate 6, and the Jakarta namespace (jakarta.* instead of javax.*), or better yet, use Spring Boot which removes most of this manual configuration entirely.
AppConfig.java — Spring Configuration
This class replaces applicationContext.xml. It defines the data source, Hibernate session factory, transaction manager, and view resolver — all as beans.
package com.deepsingh44.config;
import java.util.Properties;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.hibernate5.HibernateTransactionManager;
import org.springframework.orm.hibernate5.LocalSessionFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import com.deepsingh44.model.User;
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = { "com.deepsingh44" })
@EnableTransactionManagement
public class AppConfig {
@Bean
public InternalResourceViewResolver internalResourceViewResolver() {
return new InternalResourceViewResolver("/WEB-INF/views/", ".jsp");
}
@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/rahishdb?useSSL=false");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
@Bean
public LocalSessionFactoryBean sessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(dataSource());
sessionFactory.setAnnotatedClasses(User.class);
sessionFactory.setHibernateProperties(hibernateProperties());
return sessionFactory;
}
private Properties hibernateProperties() {
Properties properties = new Properties();
properties.put("hibernate.show_sql", "true");
properties.put("hibernate.format_sql", "true");
properties.put("hibernate.hbm2ddl.auto", "update");
properties.put("hibernate.dialect", "org.hibernate.dialect.MySQL8Dialect");
return properties;
}
@Bean
public HibernateTransactionManager transactionManager() {
HibernateTransactionManager txManager = new HibernateTransactionManager();
txManager.setSessionFactory(sessionFactory().getObject());
return txManager;
}
}
What each annotation does:
@Configuration— marks this class as a source of bean definitions.@EnableWebMvc— activates Spring MVC infrastructure (handler mappings, message converters, etc.).@ComponentScan— tells Spring to scan the package for@Controller,@Service,@Repository, and@Componentclasses.@EnableTransactionManagement— enables annotation-based transaction handling (needed for@Transactional).@Bean— declares a method whose return value should be registered as a Spring bean.
Fixed in this version: The original tutorial used com.mysql.jdbc.Driver, which is deprecated and triggers warnings on MySQL Connector 8. I've switched to com.mysql.cj.jdbc.Driver. I also removed the System.out.println debugging lines — they clutter your logs.
Reception.java — The Front Controller
Extending AbstractAnnotationConfigDispatcherServletInitializer is the modern replacement for a web.xml file. Spring uses the Servlet 3.0 SPI to discover this class automatically.
package com.deepsingh44.controller;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
import com.deepsingh44.config.AppConfig;
public class Reception
extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { AppConfig.class };
}
@Override
protected Class<?>[] getServletConfigClasses() {
return null;
}
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
When the servlet container starts, it finds this class, reads the AppConfig, and wires up the DispatcherServlet at the root URL. No XML required.
PageController and LoginController
Two controllers handle the routes. PageController serves the views, and LoginController handles the form submission.
package com.deepsingh44.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class PageController {
@GetMapping("/")
public String mainPage() {
return "index";
}
@GetMapping("/register")
public String register() {
return "register";
}
}
package com.deepsingh44.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import com.deepsingh44.dao.UserDao;
import com.deepsingh44.model.User;
@Controller
public class LoginController {
@Autowired
private UserDao userDao;
@PostMapping("/signup")
public String register(User user) {
userDao.insert(user);
return "redirect:/";
}
}
Note: The original had return "index"; after a successful insert. I changed it to return "redirect:/"; — this follows the Post/Redirect/Get pattern and prevents the browser from resubmitting the form if the user refreshes the page. It's a small change with a big UX payoff.
Also worth mentioning: the original code returned "index" which would show the index.jsp page directly, but the URL would still say /signup. A redirect keeps the URL and content in sync.
UserDao and UserDaoImpl
The DAO layer is split between an interface and its implementation. This keeps the controller decoupled from the persistence details.
package com.deepsingh44.dao;
import com.deepsingh44.model.User;
public interface UserDao {
int insert(User user);
}
package com.deepsingh44.dao;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import com.deepsingh44.model.User;
@Repository
public class UserDaoImpl implements UserDao {
@Autowired
private SessionFactory sessionFactory;
@Transactional
@Override
public int insert(User user) {
sessionFactory.getCurrentSession().save(user);
return 1;
}
}
Key points:
@Repositorymarks this as a Spring-managed bean and enables exception translation (Hibernate exceptions become Spring's DataAccessException).@Autowiredinjects the session factory that was defined in AppConfig.@Transactionalwraps the method in a transaction. Without it, Hibernate would throw a "no session" error because it doesn't know when to open and close the session.
User.java — The Entity
The User class maps to a users table in MySQL. Each field becomes a column.
package com.deepsingh44.model;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import org.hibernate.validator.constraints.Length;
@Entity
@Table(name = "users")
public class User {
@Length(max = 35)
private String name;
@Id
private String email;
private String pass;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPass() {
return pass;
}
public void setPass(String pass) {
this.pass = pass;
}
}
Points to note:
@Entitytells Hibernate to map this class to a table.@Table(name = "users")specifies the exact table name (since "user" is a reserved word in MySQL).@Idmarks the primary key. Here it's the email — meaning you can't have two users with the same email.@Length(max = 35)is a Hibernate Validator constraint that caps the name at 35 characters.
With hibernate.hbm2ddl.auto=update, Hibernate will automatically create the users table on first run if it doesn't already exist.
JSP Views
Three JSP files make up the front-end: a shared header, an index page, and a registration form.
head.jsp
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<head>
<title>ABC Project</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet"
href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
</head>
<nav class="navbar navbar-expand-md bg-dark navbar-dark">
<a class="navbar-brand" href="#">MyApp</a>
<button class="navbar-toggler" type="button" data-toggle="collapse"
data-target="#collapsibleNavbar">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="collapsibleNavbar">
<ul class="navbar-nav">
<c:choose>
<c:when test="${sessionScope.user != null}">
<li class="nav-item"><a class="nav-link" href="#">Profile</a></li>
<li class="nav-item"><a class="nav-link" href="#">Logout</a></li>
</c:when>
<c:otherwise>
<li class="nav-item"><a class="nav-link" href="register">Register</a></li>
<li class="nav-item"><a class="nav-link" href="#">Login</a></li>
</c:otherwise>
</c:choose>
</ul>
</div>
</nav>
index.jsp
<%@ include file="head.jsp" %>
register.jsp
<%@ include file="head.jsp" %>
<div class="container">
<form action="signup" method="post">
<div class="form-group">
<label for="name">Name:</label>
<input type="text" class="form-control"
placeholder="Enter name" name="name">
</div>
<div class="form-group">
<label for="pwd">Password:</label>
<input type="password" class="form-control"
placeholder="Enter password" name="pass">
</div>
<div class="form-group">
<label for="email">Email address:</label>
<input type="email" class="form-control"
placeholder="Enter email" name="email">
</div>
<div align="center">
<button type="submit" class="btn btn-primary"
style="width: 60%;">Submit</button>
</div>
</form>
</div>
Important: The name attributes in the form (name, pass, email) must match the field names in the User class exactly. That's how Spring binds the form data to the entity.
How the Pieces Fit Together
Here's what happens when a user submits the registration form:
- Browser POSTs to
/signupwith the form data. - DispatcherServlet (registered by
Reception) looks for a matching handler method. - LoginController.register() matches. Spring binds the request parameters to a new
Userobject based on field names. - UserDaoImpl.insert() is called. The
@Transactionalannotation opens a Hibernate session and transaction. - Hibernate translates
session.save(user)into anINSERT INTO users (...) VALUES (...)SQL statement. - MySQL stores the row. Hibernate commits the transaction.
- LoginController returns
"redirect:/", which tells Spring to send an HTTP 302 to the browser. - Browser requests
/, which resolves toindex.jspvia the view resolver.
Nothing here relies on XML. The servlet initializer discovers the config class, the config class declares the beans, and the annotations on the components tie it all together.
Common Pitfalls
- Forgetting
@EnableTransactionManagement. Without it,@Transactionaldoes nothing and you'll get a "no session found for current thread" error. - Using
com.mysql.jdbc.Driver. That's the deprecated driver. Usecom.mysql.cj.jdbc.Driverfor MySQL Connector 8.x. - Wrong JSP path.
InternalResourceViewResolver("/WEB-INF/views/", ".jsp")means"index"resolves to/WEB-INF/views/index.jsp. Double-check the folder name and extension. - Mismatched form field names. If your JSP field is
name="userName"but your entity hasprivate String name;, the binding silently fails and the field stays null. - Missing
@Repositoryon the DAO. Without it, Spring won't create a bean, and@Autowiredin the controller will fail at startup. - Reserved words as table names.
useris reserved in MySQL — that's why we useusers. Watch for other reserved words likeorder,group, andtable. - Forgetting to configure the servlet container. If you're running on Tomcat, make sure you're deploying a WAR and that Tomcat is pointed at the right
web.xml-less setup (Servlet 3.0+ container required). - Assuming this pattern is still the default in 2026. For new Spring projects, use Spring Boot. It auto-configures the data source, transaction manager, and view resolver for you. This tutorial is valuable for understanding what Boot does under the hood, but Boot is what you'd reach for today.
Wrapping Up
You now have a fully functional Spring MVC + Hibernate application configured entirely with Java annotations. No XML files, no applicationContext.xml, no web.xml — just classes and annotations.
From here, you can extend this into a full CRUD app by adding update, delete, and list operations. You can also add form validation using Spring's @Valid annotation, or switch from JSP to Thymeleaf for a more modern template engine.
And if you're starting a new project in 2026, take a look at Spring Boot. Everything you've built here — the data source, the session factory, the transaction manager, the view resolver — Boot sets up automatically from a few properties in application.properties. It's worth understanding how the pieces fit together first, then letting Boot save you the boilerplate.
No comments:
Post a Comment