Spring MVC: Tutorial

This is a step-by-step instruction on how to create a Spring MVC web application. In order to follow it, you only need a Maven and JDK 18+. Starting from simple hello world, you will add more and more modules.

If you want to have more straight-forward experience following this instruction, you can use Powershell to create files and folders. To install Powershell, run winget install --id Microsoft.Powershell --source winget and launch it in your project's folder by typing pwsh in explorers address bar. In this tutorial Powershell is optional and you can ignore provided pwsh commands. You can create files and folders as you wish. If you want to execute Powershell command from CMD, use this: pwsh -c "your_pwsh_command".

Regarding data access there are different concepts: JDBC, ORM, JPA, Spring Data Access[1] and different Spring modules: spring-jdbc, spring-orm, spring-data-jdbc[2], spring-data-jpa[3].

Spring MVC

edit

1. Create pom.xml:

pom.xml
<project>
	<modelVersion>4.0.0</modelVersion>
	<groupId>mygroup</groupId>
	<artifactId>SpringTinyWeb</artifactId>
	<version>0.1</version>
	<packaging>war</packaging>
	
	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<maven.compiler.source>18</maven.compiler.source>
		<maven.compiler.target>18</maven.compiler.target>

		<spring.version>6.0.0</spring.version>
		<jetty-maven-plugin.version>11.0.12</jetty-maven-plugin.version>
	</properties>
	
	<dependencies>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-webmvc</artifactId>
			<version>${spring.version}</version>
		</dependency>
	</dependencies>

	<build>
		<pluginManagement>
			<plugins>
				<plugin>
					<groupId>org.eclipse.jetty</groupId>
					<artifactId>jetty-maven-plugin</artifactId>
					<version>${jetty-maven-plugin.version}</version>
				</plugin>
			</plugins>
		</pluginManagement>
	</build>
	
</project>
  • Copy from above and execute gcb > pom.xml

2. Create src\main\webapp\WEB-INF\web.xml:

<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        https://jakarta.ee/xml/ns/jakartaee
        https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
    version="5.0">
	<servlet>
		<servlet-name>dispatcher</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>
	<servlet-mapping>
		<servlet-name>dispatcher</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>
</web-app>
  • Copy from above and execute Get-Clipboard > ( New-Item src\main\webapp\WEB-INF\web.xml -force )
  • Namespaces can differ if you're using Spring < 6[4][5].

3. Create src\main\webapp\WEB-INF\dispatcher-servlet.xml:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
	xmlns:context="http://www.springframework.org/schema/context"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        https://www.springframework.org/schema/mvc/spring-mvc.xsd
		http://www.springframework.org/schema/context
    	http://www.springframework.org/schema/context/spring-context.xsd">
	<context:component-scan base-package="springtinyweb" />
	<mvc:annotation-driven/>
</beans>
  • gcb > src\main\webapp\WEB-INF\dispatcher-servlet.xml

4. Create src\main\java\springtinyweb\controller\Greeting.java:

package springtinyweb.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class Greeting {
	@GetMapping("/raw")
	@ResponseBody
	public String greetRaw() {
		return "hello there! UTF-8 не работает 😕";
	}
}
  • gcb > ( ni src\main\java\springtinyweb\controller\Greeting.java -force )

5. Run your app: mvn clean jetty:run

6. Visit localhost:8080/raw in your browser, you'll see:

hello there! UTF-8 ?? ???????? ?

Encoding doesn't work, but this problem will go away when you'll choose a template engine for your View layer - proceed to #JSP or #Thymeleaf section.

To stop server, press CTRL + C. Similar steps can be found in defferent sources[6].

1. Add ViewResolver bean to dispatcher-servlet.xml/<beans>[7]:

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
	<property name="prefix" value="/WEB-INF/jsp/" />
	<property name="suffix" value=".jsp" />
	<!-- <property name="viewNames" value="*.jsp" /> -->
</bean>
  • If you'll uncomment viewNames property, this ViewResolver will be called only if view name you are returning from controller method ends with .jsp. If this is what you need, comment out suffix property.

2. Add this method to Greeting.java:

import org.springframework.ui.ModelMap;
<...>
@GetMapping("/jsp")
public String greetJsp(ModelMap model) {
	model.put("msg", "jsp сообщение 🪂");
	return "greeting";
}

3. Create src\main\webapp\WEB-INF\jsp\greeting.jsp page:

<html>
	<head>
		<title>Welcome</title>
	</head>
	<body>
		<p>🚀 This is a complete ${msg}.</p>
	</body>
</html>
  • gcb > ( ni src\main\webapp\WEB-INF\jsp\greeting.jsp -force )

4. Restart server and visit localhost:8080/jsp, you'll see:

?Ÿš€ This is complete jsp сообщение ?.

To fix encoding issue you can add <%@page pageEncoding="UTF-8" %> as the first line of your .jsp file or you can change encoding globally by adding following lines to web.xml:

<jsp-config>
    <jsp-property-group>
        <url-pattern>*.jsp</url-pattern>
        <page-encoding>UTF-8</page-encoding>
    </jsp-property-group>
</jsp-config>

Thymeleaf

edit

Similar guides can be found in different sources[8][9][10].

1. In your pom.xml add this dependency:

<dependency>
	<groupId>org.thymeleaf</groupId>
	<artifactId>thymeleaf-spring6</artifactId>
	<version>3.1.0.RELEASE</version>
</dependency>

2. Add this beans to your dispatcher-servlet.xml[11]:

<bean id="templateResolver"
	class="org.thymeleaf.spring6.templateresolver.SpringResourceTemplateResolver">
	<property name="prefix" value="/WEB-INF/thy/" />
	<property name="suffix" value=".html" />
	<property name="cacheable" value="false" />
</bean>
    
<bean id="templateEngine"
	class="org.thymeleaf.spring6.SpringTemplateEngine">
	<property name="templateResolver" ref="templateResolver" />
</bean>

<bean class="org.thymeleaf.spring6.view.ThymeleafViewResolver">
	<property name="templateEngine" ref="templateEngine" />
	<property name="characterEncoding" value="UTF-8"/>
	<!-- <property name="viewNames" value="*.html" /> -->
</bean>
  • Comment out cacheable property or set it to true, and you will have to restart server every time you change your templates.
  • Uncomment viewNames property and Spring will use Thymeleaf only if view name you're returning in your controller ends with .html.

3. Create src\main\webapp\WEB-INF\thy\hello.html:

<html xmlns:th="http://www.thymeleaf.org">
	<head>
		<title>Welcome</title>
	</head>
	<body>
		🦊The quick brown fox <label th:text="${msg}"></label>
	</body>
</html>
  • gcb > ( ni src\main\webapp\WEB-INF\thy\hello.html -force )

4. Add this method to Greeting.java:

import org.springframework.ui.ModelMap;
<...>
@GetMapping("/thy")
public String greetThy(ModelMap model) {
	model.put("msg", "прыгает через ленивую собаку🐶");
	return "hello.html";
}

5. mvn clean jetty:run and visit localhost:8080/thy.

6. (Optional) List of links

For your convenience you can add to your template a list of all links avaiable in your application:

6.1 In your hello.html/<html>/<body> add this:

<ul>
	<li th:each="endPoint :${endPoints}">
		<a th:href="@{${endPoint}}" th:text="${endPoint}" />
	</li>
</ul>

6.2 In your Greeteng.java add this:

import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.beans.factory.annotation.Autowired;
<...>
@Autowired
private RequestMappingHandlerMapping requestMappingHandlerMapping;

@ModelAttribute
public String getEndPoints(ModelMap model) {
	var endPoints = 
	requestMappingHandlerMapping
		.getHandlerMethods()
		.keySet().stream()
		.flatMap(o -> o.getDirectPaths().stream())
		.toList();
	model.put("endPoints", endPoints);
	return "hello.html";
}

1. In your dispatcher-servler.xml/<beans> add this element:

<mvc:resources mapping="/resources/**" location="/resources/" />

2. Create src\main\webapp\resources\css\styles.css:

body {
	background-color: LightGrey;
}
  • gcb > ( ni src\main\webapp\resources\css\styles.css -force )

3. Thymeleaf: to your hello.html/<html>/<head> add this element:

<link rel="stylesheet" th:href="@{/resources/css/styles.css}" />

3. JSP: to your greeting.jsp/<html>/<head> add this element:

<link rel="stylesheet" href="${pageContext.request.contextPath}/resources/css/styles.css" />

4. mvn clean jetty:run and visit your page.

Spring Security

edit

This section consists of four subsections, but you will need to follow only two of them - one way of configuration and one template engine.

Spring Security can be configured via Java code or XML files. First two steps are common for both flows, but then you should choose only one flow - XML or JAVA - and follow it only. XML flow is almost never used and probably should be ignored, but it's provided here as demonstration.

1. Add this property and dependencies to your pom.xml[12][13]:

<spring-security.version>6.0.0</spring-security.version>
<...>
<dependencies>
	<dependency>
		<groupId>org.springframework.security</groupId>
		<artifactId>spring-security-web</artifactId>
		<version>${spring-security.version}</version>
	</dependency>
	<dependency>
		<groupId>org.springframework.security</groupId>
		<artifactId>spring-security-config</artifactId>
		<version>${spring-security.version}</version>
	</dependency>
</dependencies>

2. Add Spring Security filter chain to your web.xml/<web-app>:

<filter>
	<filter-name>springSecurityFilterChain</filter-name>
	<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
	<filter-name>springSecurityFilterChain</filter-name>
	<url-pattern>/*</url-pattern>
</filter-mapping>

Java configuration

edit

3. Create a src\main\java\springtinyweb\security\SecurityConfiguration.java[14][15]:

package springtinyweb.security;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

import static org.springframework.security.config.Customizer.withDefaults;

@Configuration
@EnableWebSecurity
public class SecurityConfiguration {

	@Bean
	public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
		http
			.authorizeHttpRequests((authorize) -> authorize
					.anyRequest().authenticated()
					// .requestMatchers("/jsp/**").hasRole("USER")
					// .anyRequest().permitAll()
			)
			.httpBasic(withDefaults())
			.formLogin(withDefaults());
		return http.build();
	}

	@Bean
	public UserDetailsService userDetailsService() {
		UserDetails user = User.withDefaultPasswordEncoder()
				.username("user")
				.password("1234")
				.roles("USER")
				.build();
		return new InMemoryUserDetailsManager(user);
	}
}
  • gcb > ( ni src\main\java\springtinyweb\security\SecurityConfiguration.java -force )
  • This is a new API. You can find example of defining both of this security beans from Spring itself: [16][17];
  • Different way of java configuration can be found in book Mastering Spring[18], through overriding methods in WebSecurityConfigurerAdapter, which has been removed from API in Spring 6.

XML configuration

edit

3. To your web.xml/<web-app> add ContextLoaderListener:

<listener>
	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

4. Create src\main\webapp\WEB-INF\applicationContext.xml[19][20]:

<b:beans xmlns="http://www.springframework.org/schema/security"
		 xmlns:b="http://www.springframework.org/schema/beans"
		 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		 xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
						http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd">
	<http auto-config="true">
		<intercept-url pattern="/**" access="authenticated"/>
	</http>

	<user-service>
		<user name="user" password="{noop}password" authorities="ROLE_USER" />
	</user-service>
</b:beans>

Now your application is protected and you can learn how to interact with Spring Security from View layer.

Spring Security + Thymeleaf

edit

At this point you should already have Spring Security and Thymeleaf both working, this section will show you how to call security layer from Thymeleaf templates.

1. Add this dependency to your pom.xml:

<dependency>
	<groupId>org.thymeleaf.extras</groupId>
	<artifactId>thymeleaf-extras-springsecurity6</artifactId>
	<version>3.1.0.RELEASE</version>
</dependency>

2. In your dispatcher-servlet.xml to the bean with the id="templateEngine" add this property:

<property name="additionalDialects">
	<set>
		<bean class="org.thymeleaf.extras.springsecurity6.dialect.SpringSecurityDialect"/>
	</set>
</property>

3. In your hello.html/<html>/<body> add this line:

<p>Your name is <label th:text="${#authentication.name}" />, if I'm not mistaken. 🔑</p>

4. Visit this page, you will see:

Your name is user, if I'm not mistaken. 🔑


Spring Security + JSP

edit

At this point you should already have Spring Security and JSP both working, this section will show you how to call security layer from view layer.

1. Add this dependency to your pom.xml:

<dependency>
	<groupId>org.springframework.security</groupId>
	<artifactId>spring-security-taglibs</artifactId>
	<version>${spring-security.version}</version>
</dependency>

2. Add this line as the first line in greeting.jsp:

<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>

and this line to the <html>/<body>:

<p>🔑 Glad to see you, <sec:authentication property="principal.username" />.</p>
<p>There are multiple ways to obtain username, <sec:authentication property="name" />.</p>
  • First line will result in exception being thrown if you will open this page not being logged in, while second line wouldn't.

3. Visit this page, you will see:

🔑 Glad to see you, user.
There are multiple ways to obtain username, user.

Logging

edit

1. In your pom.xml add this dependency higher than thymeleaf:

<dependency>
	<groupId>org.apache.logging.log4j</groupId>
	<artifactId>log4j-slf4j-impl</artifactId>
	<version>2.19.0</version>
</dependency>

2. Create src\main\resources\log4j2.xml:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Appenders>
		<Console name="LogToConsoleFull" target="SYSTEM_OUT">
            <PatternLayout pattern="%highlight{[%level] %logger.%method()}{trace=bright cyan} %msg%n"
				disableAnsi="false" charset="866"/>
        </Console>
		<Console name="LogToConsole" target="SYSTEM_OUT">
            <PatternLayout pattern="%highlight{[%-20.-40maxLen{%level] %logger{1}.%method()}{}}{trace=bright cyan} %msg%n"
				disableAnsi="false" charset="866"/>
        </Console>
		<Console name="LogToConsoleBrief" target="SYSTEM_OUT">
            <PatternLayout pattern="%highlight{[%level] %logger{1.}}{trace=bright cyan} %msg%n"
				disableAnsi="false" charset="866"/>
        </Console>
		<Console name="LogToConsoleTiny" target="SYSTEM_OUT">
            <PatternLayout pattern="%highlight{[%level]}{trace=bright cyan} %msg%n"
				disableAnsi="false" charset="866"/>
        </Console>
    </Appenders>
    <Loggers>
        <Logger name="springtinyweb" level="trace" additivity="false">
            <AppenderRef ref="LogToConsoleTiny"/>
        </Logger>
        <Root level="info">
            <AppenderRef ref="LogToConsoleTiny"/>
        </Root>
    </Loggers>
</Configuration>
  • gcb > ( ni src\main\resources\log4j2.xml -force )

3. To any of your classes, add following imports and a class variable, and you can write logs[21][22]:

import org.slf4j.*;
<...>
private final Logger log = LoggerFactory.getLogger(getClass());
<...>
log.trace("something happened");


Internationalization

edit

1. To src\main\webapp\WEB-INF\dispatcher-servlet.xml/<beans> add this elements[23]:

<bean id="messageSource"
	class="org.springframework.context.support.ResourceBundleMessageSource">
	<property name="defaultEncoding" value="UTF-8"/>
	<property name="basename" value="messages"/>
</bean>

<bean id="localeResolver"
	class="org.springframework.web.servlet.i18n.SessionLocaleResolver">
	<property name="defaultLocale" value="en" />
</bean>
<mvc:interceptors>
	<bean id="localeChangeInterceptor"
		class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
		<property name="paramName" value="lang" />
	</bean>
</mvc:interceptors>

2. Create src\main\resources\messages_en.properties:

welcome=You are welcome!

3. Create src\main\resources\messages_ru.properties:

welcome=Добро пожаловать!

4. In your Thymeleaf template *.html/<html>/<body> add this line:

<h2 th:text="#{welcome}">Welcome</h2>

4. In your JSP add this lines[24]:

<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<...>
<h2><spring:message code="welcome" /></h2>

5. Visit this page and change language of message by appending ?lang=ru or ?lang=en parameters to URL.

Aspect Oriented Programming

edit

Consult book Spring Start Here, Chapter 6.

1. Add this dependency to your pom.xml:

<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-aspects</artifactId>
	<version>${spring.version}</version>
</dependency>

2. Add these namespace and element to your dispatcher-servlet.xml:

<beans
	xmlns:aop="http://www.springframework.org/schema/aop"
	xsi:schemaLocation="
		http://www.springframework.org/schema/aop 
		https://www.springframework.org/schema/aop/spring-aop.xsd
">
<...>
<aop:aspectj-autoproxy />

3. Create .\src\main\java\springtinyweb\aop\LoggingAspect.java:

package springtinyweb.aop;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint; 
import org.springframework.stereotype.Component;
import org.slf4j.LoggerFactory;
import org.slf4j.Logger;

@Aspect
@Component
public class LoggingAspect {

	private final Logger log = LoggerFactory.getLogger(getClass());

	@Around("execution(* *.service.*.*(..))")
	public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
		log.info("Before method call");
		var obj = joinPoint.proceed();
		log.info("After method call");
		return obj;
	}
}

JDBC Template

edit

H2 DataSource with spring-jdbc

edit

1. To your pom.xml add this dependencies:

<dependency>
	<groupId>org.springframework</groupId>
	<artifactId>spring-jdbc</artifactId>
	<version>${spring.version}</version>
</dependency>
<dependency>
	<groupId>com.h2database</groupId>
	<artifactId>h2</artifactId>
	<version>2.1.214</version>
</dependency>

2. To your dispatcher-servlet.xml add this namespace and a bean[25]:

<beans
	xmlns:jdbc="http://www.springframework.org/schema/jdbc"
	xsi:schemaLocation="
		http://www.springframework.org/schema/jdbc
		http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
">
<...>
<jdbc:embedded-database id="dataSource" type="H2">
	<jdbc:script location="classpath:schema.sql"/>
</jdbc:embedded-database>

3. Create src\main\resources\schema.sql:

create table if not exists Message (
	id int auto_increment unique not null,
	message varchar(255) not null
);

4. If you will start your application you will see a log:

Starting embedded database: url='jdbc:h2:mem:dataSource;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false', username='sa'

Now you have a working dataSource bean.

JdbcTemplate Usage

edit

At this point you should have a working dataSource bean.

1. Create src\main\java\springtinyweb\model\Message.java:

package springtinyweb.model;

public record Message(Long id, String message) {}

2. Create src\main\java\springtinyweb\service\MessageService.java:

package springtinyweb.service;

import springtinyweb.model.Message;
import springtinyweb.repository.MessageJdbcDaoImpl;
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;

@Component
public class MessageService {
	
	@Autowired
	private MessageJdbcDaoImpl messageJdbcDao;
	
	public List<Message> findAll() {
		return messageJdbcDao.findAll();
	}
}

3. Create src\main\java\springtinyweb\repository\MessageJdbcDaoImpl.java:

package springtinyweb.repository;

import org.springframework.stereotype.Repository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import springtinyweb.model.Message;
import javax.sql.DataSource;
import java.util.List;

@Repository
public class MessageJdbcDaoImpl {
	
	JdbcTemplate jdbcTemplate;
	
	private final RowMapper<Message> messageRowMapper = (resultSet, rowNum) -> {
		return 
			new Message(resultSet.getLong("id"), resultSet.getString("message"));
	};
	
	@Autowired
	public MessageJdbcDaoImpl(DataSource dataSource) {
		jdbcTemplate = new JdbcTemplate(dataSource);
	}

	public List<Message> findAll() {
		String query = "select * from Message";
		
		List<Message> result = 
			jdbcTemplate.query(query, messageRowMapper);
		
		return result;
	}
}
  • You can find an example of how to use JdbcTemplate in an external source: Using JdbcTemplate to work with persisted data[26].

4. In your controller\Greeting.java add this import, class variable and a method:

import springtinyweb.service.MessageService;
<...>
@Autowired
private MessageService messageService;
<...>
@GetMapping("/all")
public String findAll(ModelMap model) {
	model.put("msg", messageService.findAll());
	return "hello.html";
}

5. Visit https://localhost:8080/all, you will see contents of your Message table.

Spring Data Repository

edit

Spring Data Repositories require a working dataSource bean, if you don't have it - follow this subsection: #H2 DataSource with spring-jdbc.

1. To your pom.xml add this property and dependency:

<spring-data-jdbc.version>3.0.0</spring-data-jdbc.version>
<dependency>
	<groupId>org.springframework.data</groupId>
	<artifactId>spring-data-jdbc</artifactId>
	<version>${spring-data-jdbc.version}</version>
</dependency>

2. Create src\main\java\springtinyweb\config\AdditionalConfig.java[27]:

package springtinyweb.config;

import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jdbc.repository.config.AbstractJdbcConfiguration;
import org.springframework.jdbc.core.namedparam.*;
import org.springframework.context.annotation.Bean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

@Configuration
@EnableJdbcRepositories(basePackages="springtinyweb")
public class AdditionalConfig extends AbstractJdbcConfiguration {

	@Autowired
	javax.sql.DataSource dataSource;

    @Bean
    NamedParameterJdbcOperations operations() {
        return new NamedParameterJdbcTemplate(dataSource);
    }

    @Bean
    PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dataSource);
    }
}
  • This is where you enable discovering of Spring Repositories. You don't need this class if you've already configured Hibernate as in #Hibernate Configuration section.

3. Create src\main\java\springtinyweb\model\Message.java:

package springtinyweb.model;

import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Table;
// import javax.persistence.*;

// @Entity
@Table
public class Message {
	@Id
	// @GeneratedValue(strategy=GenerationType.IDENTITY)
	private Long id;
	private String message;
	public Message() {}

	public Message(String message) {	
		this.message = message;
	}
	public Message(Long id, String message) {
		this.id = id;
		this.message = message;
	}
	
	public Long getId() {
		return id;
	}
	public void setId(Long id) {
		this.id = id;
	}
	
	public String getMessage() {
		return message;
	}
	public void setMessage(String message) {
		this.message = message;
	}
	
	@Override
	public String toString() {
		return String.format("Message[id=%s,txt=%s]",id,message);
	}
}
  • Some lines are commented out, because they are used in different flow: #Hibernate.

4. Create src\main\java\springtinyweb\repository\MessageRepository.java[28]:

package springtinyweb.repository;

import org.springframework.data.repository.CrudRepository;
import springtinyweb.model.Message;
import java.util.*;

public interface MessageRepository extends CrudRepository<Message, Long> {
	Optional<Message> findById(Long id);
	Message save(Message msg);
}

5. Create src\main\java\springtinyweb\service\MessageService.java:

package springtinyweb.service;

import springtinyweb.model.Message;
import springtinyweb.repository.MessageRepository;
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Optional;

@Component
public class MessageService {
	
	@Autowired
	private MessageRepository messageRepository;
	
	public Optional<Message> findById(Long id) {
		return
			messageRepository.findById(id);
	}
	
	public Message save(Message message) {
		return
			messageRepository.save(message);
	}
}

5. In your controller\Greeting.java add these imports, class variable and methods:

import springtinyweb.service.MessageService;
import org.springframework.web.bind.annotation.RequestParam;
<...>
@Autowired
private MessageService messageService;
<...>
@GetMapping("/find")
public String findMessage(@RequestParam Long id, ModelMap model) {
	var msgOpt = messageService.findById(id);
	if (msgOpt.isPresent())
		model.put("msg", msgOpt.get());
	else model.put("msg", "There is no message with id="+id);
	return "hello.html";
}

@GetMapping("/save")
public String saveMessage(@RequestParam String msg, ModelMap model) {
	Message msgNew =  messageService.save(new Message(msg));
	model.put("msg", "Message saved: "+msgNew);
	return "hello.html";
}

6. Create new messages: localhost:8080/save?msg=ilovetomato, or get message with id=1 by following localhost:8080/find?id=1.

Hibernate

edit

H2 DataSource

edit

Follow this section to obtain a dataSource bean.

1. To your pom.xml add this dependencies:

<dependency>
	<groupId>org.apache.commons</groupId>
	<artifactId>commons-dbcp2</artifactId>
	<version>2.9.0</version>
</dependency>
<dependency>
	<groupId>com.h2database</groupId>
	<artifactId>h2</artifactId>
	<version>2.1.214</version>
</dependency>

2. To your dispatcher-servlet.xml add this bean:

<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
	<property name="driverClassName" value="org.h2.Driver" />
	<property name="url" value="jdbc:h2:mem:dataSource;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false" />
	<property name="username" value="user" />
	<property name="password" value="1234" />
	<!-- <property name="connectionInitSqls" value="#{T(java.nio.file.Files).readString(T(org.springframework.util.ResourceUtils).getFile('classpath:schema.sql'))}"/> -->
</bean>
<!-- <jdbc:initialize-database data-source="dataSource">
	<jdbc:script location="classpath:schema.sql"/>
</jdbc:initialize-database> -->
  • As you can see, there is a one long ugly commented-out connectionInitSqls property. You don't need it with Hibernate, but if you will wonder how you can provide initializing SQL script to XML-defined dataSource bean - that's how you can do it. If you'll want something more readable and concise, uncomment jdbc:initialize-database element[29] and add spring-jdbc dependency and xmlns:jdbc namespace as in this section: #H2 DataSource with spring-jdbc.

Now you have a working dataSource bean, which you can use to interact with database.

Hibernate Configuration

edit

At this point you should have a working dataSource bean.

1. To your pom.xml add this property and dependency:

<hibernate-core.version>6.1.5.Final</hibernate-core.version>
<...>
<dependency>
	<groupId>org.hibernate</groupId>
	<artifactId>hibernate-core</artifactId>
	<version>${hibernate-core.version}</version>
</dependency>

2. To your dispatcher-servlet.xml add this namespace and elements:

<beans
	xmlns:jpa="http://www.springframework.org/schema/data/jpa"
	xsi:schemaLocation="
		http://www.springframework.org/schema/data/jpa
		https://www.springframework.org/schema/data/jpa/spring-jpa.xsd
">
<...>
<jpa:repositories base-package="springtinyweb" 
	entity-manager-factory-ref="sessionFactory"
	transaction-manager-ref="transactionManager"
	>
	<repository:include-filter type="regex" expression=".*"/>
</jpa:repositories>

<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
	<property name="dataSource" ref="dataSource"/>
	<property name="packagesToScan" value="springtinyweb"/>
	<property name="hibernateProperties">
		<value>
			hibernate.dialect=org.hibernate.dialect.H2Dialect
			hibernate.hbm2ddl.auto=create-drop
			hibernate.hbm2ddl.import_files=data.sql
		</value>
	</property>
</bean>

<bean id="transactionManager"
		class="org.springframework.orm.hibernate5.HibernateTransactionManager">
	<property name="sessionFactory" ref="sessionFactory"/>
</bean>

<tx:annotation-driven/>
  • Note <jpa:repositories ...> element- this is where you enable Spring Data Repository lookup, this is why you wouldn't need AdditionalConfig.java class in #Spring_Data_Repository section. If you are not going to use Spring Repositories you can remove this element and jpa: namespace.
  • Both Spring 5 and Spring 6 use Hibernate through org.springframework.orm.hibernate5 package, even if it's Hibernate 6.
  • If you're using Hibernate 6, you can remove hibernate.dialect line - Hibernate 6 will automatically resolve dialect from the JDBC DatabaseMetaData[30].

Now your Hibernate is configured, but it's not being used by your application. You can proceed to #Spring Data Repository section in which you should skip the optional step with creation of AdditionalConfig.java class. When you will create src\main\java\springtinyweb\model\Message.java comment out all org.springframework imports and @Table annotation. Also uncomment all javax.persistence imports, @Entity and @GeneratedValue line.


Spring Security JDBC

edit

At this point you should have a working DataSource bean. This is how you can use your database in Spring Security:

In src\main\java\springtinyweb\security\SecurityConfiguration.java remove userDetailsService() method and instead add this two methods[31]:

import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.beans.factory.annotation.Autowired;
import javax.sql.DataSource;
<...>
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth, DataSource dataSource) throws Exception {
	auth
		.jdbcAuthentication()
		.dataSource(dataSource)
		.passwordEncoder(passwordEncoder())
		.withDefaultSchema()
		.withUser("user")
		.password(passwordEncoder().encode("1234"))
		.roles("USER");
}

@Bean
public PasswordEncoder passwordEncoder() {
	return new BCryptPasswordEncoder();
}

REST

edit

1. Add this dependency to pom.xml:

<dependency>
	<groupId>com.fasterxml.jackson.core</groupId>
	<artifactId>jackson-databind</artifactId>
	<version>2.14.1</version>
</dependency>

2. Add this element to your dispatcher-servlet.xml:

<mvc:annotation-driven>
	<mvc:message-converters>
		<bean id="jacksonHttpMessageConverter" 
			class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
			<!--<property name="prettyPrint" value="true" />-->
		</bean>
	</mvc:message-converters>
</mvc:annotation-driven>

3. Create .\src\main\java\springtinyweb\rest\MainRest.java:

package springtinyweb.rest;

import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.GetMapping;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.HashMap;
import java.util.Map;

@RestController
public class MainRest {

	@GetMapping("/greeting0")
	Map<String,String> greet() {
		var map = new HashMap<String,String>();
		map.put("content","Οὐχὶ ταὐτὰ παρίσταταί 0 👾");
		return map;
	}
	@GetMapping(value="/greeting1", produces="text/plain;charset=UTF-8")
	String greet(ObjectMapper objectMapper) throws Exception {
		var map = new HashMap<String,String>();
		map.put("content","Οὐχὶ ταὐτὰ παρίσταταί 1 👾");
		return objectMapper.writeValueAsString(map);
	}
}
  • These are two ways which you can use to marshall an object to json, they produce different output for chars which are not in Basic Multilingual Plane, this is an issue of Jackson library[32].

4. Invoke these endpoints with curl or Powershell:

curl "http://localhost:8080/greeting0"
0..1 | % { Invoke-WebRequest -Uri "http://localhost:8080/greeting$($_)" } | select -ExpandProperty Content

Spring 5

edit

You can switch all of the above to Spring 5 and Hibernate 5.

1. In pom.xml use second group of dependency versions:

<spring.version>6.0.0</spring.version>
<spring-security.version>6.0.0</spring-security.version>
<spring-data-jpa.version>3.0.0</spring-data-jpa.version>
<spring-data-jdbc.version>3.0.0</spring-data-jdbc.version>
<hibernate-core.version>6.1.5.Final</hibernate-core.version>
<jetty-maven-plugin.version>11.0.12</jetty-maven-plugin.version>

<spring.version>5.3.24</spring.version>
<spring-security.version>5.8.0</spring-security.version>
<spring-data-jpa.version>2.7.6</spring-data-jpa.version>
<spring-data-jdbc.version>2.4.6</spring-data-jdbc.version>
<hibernate-core.version>5.6.14.Final</hibernate-core.version>
<jetty-maven-plugin.version>10.0.12</jetty-maven-plugin.version>
  • First group provided here for convenience, you will get it by following tutorial.

2. In pom.xml replace digit in thymeleaf-spring6 and thymeleaf-extras-springsecurity6 artifacts.

3. In dispatcher-servlet.xml replace digit in org.thymeleaf.spring6 and org.thymeleaf.extras.springsecurity6.

4. In all your entity classes (only Message.java in this tutorial) change your imports from jakarta.persistence to javax.persistence.

5. If you have

<dependency>
	<groupId>jakarta.persistence</groupId>
	<artifactId>jakarta.persistence-api</artifactId>
	<version>3.1.0</version>
</dependency>

replace it with

<dependency>
	<groupId>javax.persistence</groupId>
	<artifactId>javax.persistence-api</artifactId>
	<version>2.2</version>
</dependency>
(optional) spring-bom.xml

If you want to get rid of spring-data-*.version properties, for all org.springframework.* dependencies you can remove their <version> lines and add this properties and <project>/<dependencyManagement> element to pom.xml:

<spring-data-bom.version>2022.0.0</spring-data-bom.version>
<!-- <spring-data-bom.version>2021.2.6</spring-data-bom.version> -->
<...>
<dependencyManagement>
  <dependencies>
	<dependency>
	  <groupId>org.springframework</groupId>
	  <artifactId>spring-framework-bom</artifactId>
	  <version>${spring.version}</version>
	  <scope>import</scope>
	  <type>pom</type>
	</dependency>
	<dependency>
	  <groupId>org.springframework.security</groupId>
	  <artifactId>spring-security-bom</artifactId>
	  <version>${spring-security.version}</version>
	  <scope>import</scope>
	  <type>pom</type>
	</dependency>
	<dependency>
	  <groupId>org.springframework.data</groupId>
	  <artifactId>spring-data-bom</artifactId>
	  <version>${spring-data-bom.version}</version>
	  <scope>import</scope>
	  <type>pom</type>
	</dependency>
  </dependencies>
</dependencyManagement>

References

edit
  1. spring.io: Spring Reference - Data Access
  2. spring.io: Spring Data Jdbc Reference
  3. spring.io: Spring Data Jpa Reference
  4. spring.io: web.xml namespaces for Spring 5
  5. spring.io: web.xml namespaces for latest Spring
  6. Karanam, Ranga (2017). Mastering Spring. PACKT Publishing Limited. p. 55. 
  7. spring.io: Spring Reference - JSP
  8. thymeleaf.org: Thymeleaf + Spring official tutorial
  9. mkyong.com: Spring MVC HelloWorld
  10. baeldung.com: Thymeleaf tutorial
  11. thymeleaf.org: The SpringStandard Dialect
  12. spring.io: Getting Spring Security
  13. Karanam, Ranga (2017). Mastering Spring. PACKT Publishing Limited. p. 102. 
  14. github.com: SecurityConfiguration.java example
  15. baeldung.com: Upgrading the Deprecated WebSecurityConfigurerAdapter
  16. spring.io: Securing a Web Application
  17. spring.io: Hello Web Security Java Configuration
  18. Karanam, Ranga (2017). Mastering Spring. PACKT Publishing Limited. p. 103. 
  19. github.com: Spring Security XML config
  20. baeldung.com: Spring Security XML Hello World
  21. slf4j.org: Hello World
  22. spring.io: Documentation on logging
  23. Karanam, Ranga (2017). Mastering Spring. PACKT Publishing Limited. p. 94. 
  24. mkyong.com: Spring MVC i18n
  25. spring.io: Spring-JDBC H2 bean example
  26. Laurentiu Spilca (2021). Spring Start Here. Manning Publications Co.. p. 277. 
  27. spring.io: Spring Data Creating Repository Instances
  28. Craig Walls (2022). Spring in Action (Sixth ed.). Manning Publications Co.. p. 80. 
  29. spring.io: Initializing a Database by Using Spring XML
  30. vladmihalcea.com: The best way to do the Spring 6 migration
  31. sysout.ru: Настройка JDBC-аутентификации в Spring Security
  32. github.com: UTF8JsonGenerator writes supplementary characters as a surrogate pair