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 | edit source]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>
|
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>
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].
JSP
[edit | edit source]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 outsuffix
property.
- If you'll uncomment
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 | edit source]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
.
- Comment out
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 <ul>
<li th:each="endPoint :${endPoints}">
<a th:href="@{${endPoint}}" th:text="${endPoint}" />
</li>
</ul>
6.2 In your 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";
}
|
CSS
[edit | edit source]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 | edit source]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 | edit source]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 | edit source]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 | edit source]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 | edit source]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 | edit source]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 | edit source]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 | edit source]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 | edit source]H2 DataSource with spring-jdbc
[edit | edit source]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 | edit source]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 | edit source]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>
- If you have
spring-jdbc
dependency you can remove it (you've added it at #H2 DataSource with spring-jdbc step).
- If you have
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 | edit source]H2 DataSource
[edit | edit source]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 addspring-jdbc
dependency andxmlns:jdbc
namespace as in this section: #H2 DataSource with spring-jdbc.
- 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
Now you have a working dataSource bean, which you can use to interact with database.
Hibernate Configuration
[edit | edit source]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 needAdditionalConfig.java
class in #Spring_Data_Repository section. If you are not going to use Spring Repositories you can remove this element andjpa:
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].
- Note
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 | edit source]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 | edit source]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 | edit source]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-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 | edit source]- ↑ spring.io: Spring Reference - Data Access
- ↑ spring.io: Spring Data Jdbc Reference
- ↑ spring.io: Spring Data Jpa Reference
- ↑ spring.io: web.xml namespaces for Spring 5
- ↑ spring.io: web.xml namespaces for latest Spring
- ↑ Karanam, Ranga (2017). Mastering Spring. PACKT Publishing Limited. p. 55.
- ↑ spring.io: Spring Reference - JSP
- ↑ thymeleaf.org: Thymeleaf + Spring official tutorial
- ↑ mkyong.com: Spring MVC HelloWorld
- ↑ baeldung.com: Thymeleaf tutorial
- ↑ thymeleaf.org: The SpringStandard Dialect
- ↑ spring.io: Getting Spring Security
- ↑ Karanam, Ranga (2017). Mastering Spring. PACKT Publishing Limited. p. 102.
- ↑ github.com: SecurityConfiguration.java example
- ↑ baeldung.com: Upgrading the Deprecated WebSecurityConfigurerAdapter
- ↑ spring.io: Securing a Web Application
- ↑ spring.io: Hello Web Security Java Configuration
- ↑ Karanam, Ranga (2017). Mastering Spring. PACKT Publishing Limited. p. 103.
- ↑ github.com: Spring Security XML config
- ↑ baeldung.com: Spring Security XML Hello World
- ↑ slf4j.org: Hello World
- ↑ spring.io: Documentation on logging
- ↑ Karanam, Ranga (2017). Mastering Spring. PACKT Publishing Limited. p. 94.
- ↑ mkyong.com: Spring MVC i18n
- ↑ spring.io: Spring-JDBC H2 bean example
- ↑ Laurentiu Spilca (2021). Spring Start Here. Manning Publications Co.. p. 277.
- ↑ spring.io: Spring Data Creating Repository Instances
- ↑ Craig Walls (2022). Spring in Action (Sixth ed.). Manning Publications Co.. p. 80.
- ↑ spring.io: Initializing a Database by Using Spring XML
- ↑ vladmihalcea.com: The best way to do the Spring 6 migration
- ↑ sysout.ru: Настройка JDBC-аутентификации в Spring Security
- ↑ github.com: UTF8JsonGenerator writes supplementary characters as a surrogate pair