[JAVA] What I did in the migration from Spring Boot 1.4 series to 2.0 series

Introduction

Last time has upgraded from Spring Boot 1.5 to Spring Boot 2.0. This time, in another project, I will write about what I was addicted to when I raised it from 1.4 to 2.0.

The main difficulty this time was

--It took a long time to isolate whether the cause of the problem was the 1.4-> 1.5 part or the 1.5-> 2.0 part. --Thymeleaf has a big influence, so testing is difficult --Around serialization / deserialization in Spring Session --Countermeasures for production deployment

It is around.

Increase Spring Boot version

Updated version of spring-boot-starter-parent

Specify the Spring Boot version in pom.xml.

pom.xml


  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.3.RELEASE</version>
    <relativePath />
  </parent>

Removed Hikari PC dependencies

I was originally using HikariPC, so I will remove it from the dependencies.

pom.xml


    <dependency>
	<groupId>com.zaxxer</groupId>
	<artifactId>HikariCP</artifactId>
    </dependency>

Compile and crush errors

SpringBootServletInitializer not found

1.png

The package of SpringBootServletInitializer has changed, so reimport it.

Missing org.springframework.boot.context.embedded.

2.png

The package has changed to ʻorg.springframework.boot.web.servlet.`, so reimport it

DataSourceBuilder not found

3.png

The package has changed, so reimport it

Deprecation of WebMvcConfigurerAdapter

Change ʻextends WebMvcConfigurerAdapter to ʻimplements WebMvcConfigurer

Deprecate @EnableWebMvcSecurity

Change to @EnableWebSecurity.

https://docs.spring.io/spring-security/site/docs/current/reference/html/mvc.html

org.apache.velocity.app not found

4.png

Migrate from velocity to mustache.

pom.xml


-    <dependency>
-	<groupId>org.apache.velocity</groupId>
-	<artifactId>velocity</artifactId>
-    </dependency>
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-mustache</artifactId>
+    </dependency>

Replace velocity template file * .vm with mustache template file * .mustache.

According to the mustache template format

${hoge}
↓
{{hoge}}

Replace everything like this

You can replace them all at once with shell, but if you do it with IntelliJ refactoring, the calling code will also be found and told, so if the number is small, IntelliJ refactoring may be good.

org.json not found

5.png

Add the following dependency

pom.xml


	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-json</artifactId>
	</dependency>

Re-import with the following package org.springframework.boot.configurationprocessor.json.JSONObject

Error in SpringApplication.run

6.png

The argument of SpringApplication.run has been changed

Modified as follows

        public static void main(String[] args) {
-               Object[] objects = { HogeApplication.class, FugaService.class };
-               SpringApplication.run(objects, args);
+               final SpringApplication application = new SpringApplication(HogeApplication.class, FugaService.class);
+               application.run(args);
        }

In addition, inherit the following class

SpringBootServletInitializer

Correspondence around JPA method change

7.png

I will fix it silently

https://spring.io/blog/2017/06/20/a-preview-on-spring-data-kay#improved-naming-for-crud-repository-methods

AutoConfigureTestDatabase not found

8.png

import org.springframework.boot.test.autoconfigure.orm.jpa.AutoConfigureTestDatabase; From import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; Change package to

Thymeleaf migration

https://www.thymeleaf.org/doc/articles/thymeleaf3migration.html

Replace th: substitute by with th: replace

Mechanically like this

find src/main/resources -type f -name "*.html" -print | xargs sed -i  -e 's/th:substituteby/th:replace/g'

Removed type = "text / css" for reading css in link tag

Mechanically like this

find src/main/resources -type f -name "*.html" -print | xargs sed -i  -e 's@type=\"text/css\"@@g'

Delete inline = "text" / inline = "inline"

While looking at the contents, delete it.

Thymeleaf in Script tag is not expanded

Such a guy

<script>(window.dataLayer || (window.dataLayer = [])).push(<span th:remove="tag" th:utext="${hoge}"/>)</script>

Fix it like this

<script type="text/javascript" th:inline="javascript">/*<![CDATA[*/
     (window.dataLayer || (window.dataLayer = [])).push(/*[(${hoge})]*/)
 /*]]>*/</script>

Other

In Spring Boot, if @EnableWebMvc is attached, delete it

SessionScope

Error when @SessionScope cannot be referenced when executing SpringSecurity ʻonAuthenticationSuccess Error creating bean with name 'user': Scope 'session' is not active for the current thread;`

Create the following config file

WebRequestContextListener.java


package jp.hoge;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextListener;

import javax.servlet.annotation.WebListener;

@Configuration
@WebListener
public class WebRequestContextListener extends RequestContextListener {
}

Error in Hibernate SaveAndFlush method

java.sql.SQLSyntaxErrorException: Table 'hoge.hibernate_sequence' doesn't exist
	at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:536)
	at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:513)
	at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:115)
	at com.mysql.cj.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:1983)
	at com.mysql.cj.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1826)
	at com.mysql.cj.jdbc.PreparedStatement.executeQuery(PreparedStatement.java:1923)
	at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeQuery(ProxyPreparedStatement.java:52)

Change GenerationType

-    @GeneratedValue(strategy=GenerationType.AUTO)
+    @GeneratedValue(strategy=GenerationType.IDENTITY)

Hibernate error during execution

org.springframework.dao.InvalidDataAccessResourceUsageException: error performing isolated work; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: error performing
 isolated work
        at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:242)
        at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:225)
        at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:527)
        at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
        at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
        at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:153)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
        at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:135)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
        at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
        at org.springframework.data.repository.core.support.SurroundingTransactionDetectorMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.java:61)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
        at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
        at com.sun.proxy.$Proxy127.save(Unknown Source)
        at jp.hoge.service.FugaService.execute(FugaService.java:218)
        at jp.hoge.controller.FugaController.execute(FugaController.java:101)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)

Hibernate's Generator mappings have changed.

Corrected by adding the following settings to application.properties

application.properties


spring.jpa.hibernate.use-new-id-generator-mappings=false

After deploying to STG etc.

Profile loading error

<SpringProfile> in logback-spring.xml fails to load the profile correctly For executableJar, you can define it as -Dspring-boot.run.profiles = environment name.

In this project, we will deploy war on tomcat, so Define it in ʻapplication.properties like spring-boot.run.profiles = environment name`.

Actually, since the profile is divided when the war file is generated in pom.xml, it is defined as follows.

application.properties


spring-boot.run.profiles=${spring.profiles.active}

pom.xml


<profiles>
		<profile>
			<id>local</id>
			<properties>
				<spring.profiles.active>local</spring.profiles.active>
			</properties>
		</profile>
		<profile>
			<id>stg</id>
			<properties>
				<spring.profiles.active>stg</spring.profiles.active>
			</properties>
		</profile>
</profiles>

Profile at build time

mvn package -Pstg

With that feeling, it is embedded in ʻapplication.properties`.

After logging in via Spring Security, all member variables of the User object after getting Session are null.

NullPointerExepotion occurs when trying to access a member variable of User However, even if you look at the contents of Redis, you can not read it because it is serialized ...

Once, create a Serializer that converts to JSON and registers it in Redis like the following

HttpSessionConfig.java


@ConditionalOnProperty(name = "spring.session.store-type", havingValue = "redis", matchIfMissing = false)
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 20 * 24 * 60 * 60) //default=1800 -> 20days
@Configuration
public class HttpSessionConfig implements BeanClassLoaderAware {

   @Autowired
   private LettuceConnectionFactory lettuceConnectionFactory;

   private ClassLoader classLoader;

   @Bean
   public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
	final ObjectMapper mapper = new ObjectMapper()
			.registerModules(SecurityJackson2Modules.getModules(classLoader))
			.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
	return new GenericJackson2JsonRedisSerializer(mapper);
  }

      //For Elasticache
      @Bean
      public static ConfigureRedisAction configureRedisAction() {
          return ConfigureRedisAction.NO_OP;
      }

	@Override
	public void setBeanClassLoader(final ClassLoader classLoader) {
		this.classLoader = classLoader;
	}
}

Then, when transitioning to Controller after login is completed, it was set in Redis with all fields null.

After logging in, the Controller determines whether ʻid in the ʻUser object is null, and if it is null, repacks it. After logging in, I can handle the session without any problems.

Actually I wanted to be able to have session information in JSON format, but since the structure of the User class was a complicated nested structure, I gave up due to time constraints ... Too much information in the User class ...

Correspondence at the time of production deployment

It seems that the SerialVersionUID handled by serialization / deserialization has changed internally with the version upgrade of SpringSession, so when deploying the upgraded code, users who are already logged in cannot deserialize the session information and get stuck.

So, put in the correspondence when deserializing.

https://sdqali.in/blog/2016/11/02/handling-deserialization-errors-in-spring-redis-sessions/

The article is a little old and the dependent libraries have changed, so I changed it as follows.

HttpSessionConfig.java


- public class HttpSessionConfig {
+ public class HttpSessionConfig extends RedisHttpSessionConfiguration {

+	@Autowired
+	RedisTemplate<Object, Object> redisTemplate;
+ 	@Bean
+	@Override
+	public <S extends Session> SessionRepositoryFilter<? extends Session> springSessionRepositoryFilter(SessionRepository<S> sessionRepository) {
+		return super.springSessionRepositoryFilter(new SafeDeserializationRepository<>(sessionRepository, redisTemplate));
+	}

SafeDeserializationRepository.java


public class SafeDeserializationRepository<S extends Session> implements SessionRepository<S> {
    private final SessionRepository<S> delegate;
    private final RedisTemplate<Object, Object> redisTemplate;
     private static final String BOUNDED_HASH_KEY_PREFIX = "spring:session:sessions:";
     public SafeDeserializationRepository(SessionRepository<S> delegate,
            RedisTemplate<Object, Object> redisTemplate) {
        this.delegate = delegate;
        this.redisTemplate = redisTemplate;
    }
     @Override
    public S createSession() {
        return delegate.createSession();
    }
     @Override
    public void save(S session) {
        delegate.save(session);
    }
     @Override
    public S findById(String id) {
        try {
            return delegate.findById(id);
        } catch(SerializationException e) {
            log.info("Deleting non-deserializable session with key {}", id);
            redisTemplate.delete(BOUNDED_HASH_KEY_PREFIX + id);
            return null;
        }
    }
     @Override
    public void deleteById(String id) {
        delegate.deleteById(id);
    }
}

This will allow existing users to log out once, re-login to save new Session data in Redis, and then refer to it.

Summary

In general, the above support has made it work in a production environment. (I think there are others)

Also, originally, Tomcat should be 8.5 series or higher, but 8.0 seems to be no problem, so I forgot to upgrade Tomcat.

If you skip some major versions of SpringBoot, it will be very difficult, so let's upgrade frequently ...! !!

Reference page

Recommended Posts

What I did in the migration from Spring Boot 1.4 series to 2.0 series
What I did in the migration from Spring Boot 1.5 series to 2.0 series
What I did in the version upgrade from Ruby 2.5.2 to 2.7.1
Tokoro I rewrote in the migration from Wicket 7 to 8
Upgrade spring boot from 1.5 series to 2.0 series
The story of raising Spring Boot from 1.5 series to 2.1 series part2
The story of raising Spring Boot 1.5 series to 2.1 series
Try Spring Boot from 0 to 100.
03. I sent a request from Spring Boot to the zip code search API
05. I tried to stub the source of Spring Boot
I tried to reduce the capacity of Spring Boot
What is @Autowired in Spring boot?
I want to control the maximum file size in file upload for each URL in Spring Boot
02. I made an API to connect to MySQL (MyBatis) from Spring Boot
I want to control the default error message of Spring Boot
[Android] I want to get the listener from the button in ListView
Story when moving from Spring Boot 1.5 to 2.1
Changes when migrating from Spring Boot 1.5 to Spring Boot 2.0
Changes when migrating from Spring Boot 2.0 to Spring Boot 2.2
What I got into @Transactional in Spring
I want to know the Method of the Controller where the Exception was thrown in the ExceptionHandler of Spring Boot
[Spring Boot] I investigated how to implement post-processing of the received request.
Procedure to make the value of the property file visible in Spring Boot
What I did when JSF couldn't display database information in the view
My memorandum that I want to make ValidationMessages.properties UTF8 in Spring Boot
How to get the setting value (property value) from the database in Spring Framework
I want to find out what character the character string appears from the left
What is CHECKSTYLE: OFF found in the Java source? Checkstyle to know from
Summary of what I learned about Spring Boot
[Rails] I tried to raise the Rails version from 5.0 to 5.2
Migration from Eclipse to IntelliJ (on the way)
I tried to organize the session in Rails
How to add a classpath in Spring Boot
What I did when I converted java to Kotlin
How to bind to property file in Spring Boot
Try to automate migration with Spring Boot Flyway
I wanted to gradle spring boot with multi-project
[Spring Boot] How to refer to the property file
Summary of what I learned in Spring Batch
View the Gradle task in the Spring Boot project
I want to get the value in Ruby
[Spring Boot] How to get properties dynamically from a string contained in a URL
How to set environment variables in the properties file of Spring boot application
What is ... (3 dots) found in the Java source? Variadic arguments to know from
I was addicted to the NoSuchMethodError in Cloud Endpoints
Specify the encoding of static resources in Spring Boot
I tried to organize the cases used in programming
What I was addicted to when developing a Spring Boot application with VS Code
[Java] I want to calculate the difference from the date
I want to embed any TraceId in the log
I checked asynchronous execution of queries in Spring Boot 1.5.9
I want to get the information of the class that inherits the UserDetails class of the user who is logged in with Spring Boot.
paiza Ruby What I did to become B rank
How to create a Spring Boot project in IntelliJ
A memo of what I did from a blank state to searching for characters using the "grep command" on "Ubuntu"
What I got from continuing to develop open source
How to use CommandLineRunner in Spring Batch of Spring Boot
Introduce swagger-ui to REST API implemented in Spring Boot
[Rilas] What I learned in implementing the pagination function.
Deploy the Spring Boot project to Tomcat on XAMPP
I tried to implement the Euclidean algorithm in Java