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.
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>
I was originally using HikariPC, so I will remove it from the dependencies.
pom.xml
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
The package of SpringBootServletInitializer
has changed, so reimport it.
The package has changed to ʻorg.springframework.boot.web.servlet.`, so reimport it
The package has changed, so reimport it
Change ʻextends WebMvcConfigurerAdapter to ʻimplements WebMvcConfigurer
Change to @EnableWebSecurity
.
https://docs.spring.io/spring-security/site/docs/current/reference/html/mvc.html
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.
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
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
I will fix it silently
https://spring.io/blog/2017/06/20/a-preview-on-spring-data-kay#improved-naming-for-crud-repository-methods
import org.springframework.boot.test.autoconfigure.orm.jpa.AutoConfigureTestDatabase;
From
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
Change package to
https://www.thymeleaf.org/doc/articles/thymeleaf3migration.html
Mechanically like this
find src/main/resources -type f -name "*.html" -print | xargs sed -i -e 's/th:substituteby/th:replace/g'
Mechanically like this
find src/main/resources -type f -name "*.html" -print | xargs sed -i -e 's@type=\"text/css\"@@g'
While looking at the contents, delete it.
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>
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 {
}
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)
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
<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`.
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 ...
@SessionScope
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 ...
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.
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 ...! !!
Recommended Posts