Projeyi adım adım Spring Boot 3.x'e güncelleme (Jakarta,Security,Hibernate 6, OpenApi,Cache)

Serdar A.
5 min readApr 18, 2023

--

Merhabalar;

For English : https://lastjavabuilder.medium.com/step-by-step-migration-to-spring-3-x-jakarta-security-hibernate-6-openapi-cache-925401fa7b2c

Yakın zamanda üzerinde çalıştığım Spring Boot 2.x projeyi Spring Boot 3.x’e taşımayla ilgili tecrübelerimi bu yazıda anlatmaya çalışacağım.

Spring 3.x ile birlikte mevcut Spring altyapısında radikal değişiklikler oldu. Spring 3.x ile birlikte Spring Framework 6 desteği geldi. Bununla birlikte Spring dökümantasyonunu incelerseniz (https://spring.io/blog/2022/05/24/preparing-for-spring-boot-3-0) Java 17 desteği geldi. Spring 3.x kullanmak istiyorsanız projedeki Java versiyonu minimum 17 olmalıdır.

Spring 3.x ile birlikte en radikal değişikliklerden biri Java EE 8 (javax.*) yerine Jakarta EE 9 API (jakarta.*) kütüphanesinin gelmesidir.

Spring 3.x ile birlikte Hibernate 6 kullanılması tavsiye ediliyor. Hibernate 6 ile birlikte de birazdan detayına gireceğim bazı radikal değişiklikler oldu.

Bunun yanında Spring 3.x ile birlikte OpenApi tarafında yeni bağımlılıklar projeye dahil edilmesi gerekiyor. (https://springdoc.org/v2/)

Bu yazıda hem projeyi Spring 2.x den Spring 3.x’e nasıl taşıyacağımız ile ilgili geliştirmeleri hem de ne tarz sorunlar ile karşılaştığımı paylaşacağım.

Başlayabiliriz. :)

  1. Öncelikle eski bağımlılıkları kaldırıp yerine yeni bağımlılıkları pom.xml de (Maven kullanıyorsanız) tanımlamak gerekli.

pom.xml de spring-boot-starter-parent bağımlılığın versiyonunu 3.x e güncellememiz gerekiyor. Örnek olarak 3.0.2 versiyon kullanılmıştır.

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

2. Projenizin pom.xml dosyasında spring-boot-maven-plugin kullanıyorsanız bunun da versiyonunu 3.x e olarak güncellememiz gerekmektedir.

<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>3.0.2</version>
<executions>
<execution>
<id>build-info</id>
<goals>
<goal>build-info</goal>
</goals>
</execution>
</executions>
</plugin>

Not : Eğer ki projenizde spring’e ait moduller (web,security vs) versiyon bilgisini parent’dan almıyorsa, ayrıca tanımlama yaptıysanız, bu modüllerin Spring 3.x ile uyumlu olması için bu modüllerin versiyon bilgisini minimum 6.x olarak güncellemelisiniz.

Örnek olarak;

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>6.0.3</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>6.0.1</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-client</artifactId>
<version>6.0.1</version>
</dependency>

3. Yukarıda da bahsettiğim üzere projenizin Java versiyonunu minimum 17'ye güncellemeniz gerekmektedir.

4. pom.xml de değişiklikleri yaptıktan sonra maven clean install veya maven update yaptığınızda kodlarınızda birçok hata göreceksiniz. Şimdi adım adım bu hataları çözmeye başlayalım.

5. Hata alınan birçok kod satırının javax.* paketlerinin import edildiği yerlerde olduğunu göreceksiniz. Spring 3.x ile birlikte javax.* libs yerine jakarta.* libs geldi.

6. Bu hataları çözmek için öncelikle pom.xml imize aşağıdaki iki bağımlılığı eklememiz gerekiyor.

<dependency>
<groupId>jakarta.persistence</groupId>
<artifactId>jakarta.persistence-api</artifactId>
</dependency>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
</dependency>

7. Projemizdeki tüm javax.* import tanımlarını jakarta.* import olarak değiştirmemiz gerekiyor.

Örnek olarak aşağıdaki tanımlamaları kaldırmamız gerekiyor

//Kodlar temizlenmeli
//import javax.persistence.CascadeType;
//import javax.persistence.Column;
//import javax.persistence.Convert;
//import javax.persistence.Entity;
//import javax.persistence.FetchType;
//import javax.persistence.Index;
//import javax.persistence.JoinColumn;
//import javax.persistence.OneToMany;
//import javax.persistence.Table;
//import javax.persistence.Transient
//import javax.persistence.criteria.CriteriaBuilder;
//import javax.persistence.criteria.CriteriaQuery;
//import javax.persistence.criteria.Join;
//import javax.persistence.criteria.Root;
//import javax.persistence.criteria.Subquery;
//import javax.persistence.LockModeType;
//import javax.persistence.QueryHint;
//import javax.persistence.AttributeConverter;
//import javax.persistence.PostPersist;
//import javax.persistence.PostRemove;
//import javax.persistence.PostUpdate;

//import javax.validation.constraints.NotNull;
//import javax.transaction.Transactional;

//javax.persistence.criteria.Predicate

Bunun yerine

import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Convert;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Index;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import jakarta.persistence.Transient;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.Root;
import jakarta.persistence.criteria.Subquery;
import jakarta.persistence.LockModeType;
import jakarta.persistence.QueryHint;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.PostPersist;
import jakarta.persistence.PostRemove;
import jakarta.persistence.PostUpdate;

import jakarta.validation.constraints.NotNull;
import jakarta.transaction.Transactional;

import jakarta.persistence.criteria.Predicate

tanımlamaları eklemeliyiz.

8. Projenizde Hibernate kullanıyorsanız Spring 3.x ile birlikte Hibernate minimum 6.x versiyonu kullanmanız tavsiye ediliyor. Bunun için Hibernate bağımlılıkların versiyonlarını 6.x olarak güncellemeliyiz.

Örnek olarak hibernate-core için;

<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
<version>6.2.0.CR2</version>
</dependency>

bağımlılığı eklenmelidir.

9. Projenizde Hibernate’e ait @TypeDef ve @Type gibi tanımlamalar varsa Hibernate 6.x ile birlikte bu tanımlamalar kullanılmıyor. Bunun yerine Jakarta Persistent API ile birlikte gelen @Converter annotation kullanabilirsiniz.

Örnek olarak Hibernate 6.x den önceki tanımlamalarda;

import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;

@TypeDef(name = "json", typeClass = StringJsonUserType.class)
public class EntityName {

@Type(type = "json")
private propertyName

şeklinde tanımlama yapabilirken, Spring 3.x ve Jakarta ile aşağıdaki gibi bir tanımlama yapabiliriz.

import jakarta.persistence.Convert;
import org.hibernate.annotations.JdbcTypeCode;


@Convert(attributeName = "entityAttrName", converter = StringJsonUserType.class)
public class EntityName {


@JdbcTypeCode(SqlTypes.JSON)
private propertyName

Not : entityAttrName istediğiniz bir tanımlama yapabilirsiniz.

10. Hibernate 6.x ile birlikte L2 Cache tanımlamaları değiştirildi.Ecache için application.properties de tanımlanan factory class tanımı hata vermeye başlıyor. Bunun çözümü olarak da Ecache ile birlike JCache implementasyonu kullanarak bu sorunu çözebilirsiniz.

Eski kod tanımında;

Bağımlılık olarak;

 <dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-ehcache</artifactId>
<version>5.6.12.Final</version>
</dependency>

Kod tanımı olarak;

@javax.persistence.Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class EntityName
{
...
}

application.properties de tanım olarak;

spring.jpa.properties.hibernate.cache.use_second_level_cache=true
spring.jpa.properties.hibernate.cache.use_query_cache=true
spring.jpa.properties.hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory

tanımlama yapılırken,

Yeni kod tanımında;

Bağımlılık olarak;

<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-ehcache</artifactId>
<version>6.0.0.Alpha7</version>
<exclusions>
<exclusion>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-jcache</artifactId>
<version>6.2.0.CR2</version>
</dependency>
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.10.8</version>
</dependency>

Kod tanımı olarak;

@jakarta.persistence.Cacheable
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class EntityName {

....

}

application.properties tanımı olarak;

spring.jpa.properties.hibernate.cache.use_second_level_cache=true
spring.jpa.properties.hibernate.cache.use_query_cache=true
spring.jpa.properties.hibernate.cache.region.factory_class=jcache
spring.jpa.hibernate.javax.cache.provider=org.ehcache.jsr107.EhcacheCachingProvider

11. Hibernate UserType kullanıyorsanız projenizde, buradaki kod satırlarında da hata alıyor olabilirsiniz. Bunun çözümü olarak;

Eski kod tanımında;

public class CustomUserType implements UserType {

@Override
public int[] sqlTypes() {
return new int[]{Types.JAVA_OBJECT};
}

@Override
public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner)
throws HibernateException, SQLException {

.....
}

....
}

Yeni kod tanımında;

public class CustomUserType implements UserType {

@Override
public int getSqlType() {
return Types.JAVA_OBJECT;
}

@Override
public Object nullSafeGet(ResultSet resultSet, int i, SharedSessionContractImplementor sharedSessionContractImplementor, Object o) throws SQLException {

....
}

}

olarak güncelleyebilirsiniz.

12. Spring 3.x ile birlikte OpenAPI bağımlılıklarında da değişiklikler oldu.

(https://springdoc.org/v2/)

Öncelikle eski bağımlılıkları kaldırmanız gerekmektedir.

Eski bağımlılık tanımında

<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.5.13</version>
</dependency>

Yeni bağımlılık tanımında;

<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.0.2</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
<version>2.0.2</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-common</artifactId>
<version>2.0.2</version>
</dependency>

eklemeniz gerekmektedir.

13. Spring 3.x ile birlikte karşıma çıkan hatalı durumlardan biri de application.properties veya application.yml deki bazı tanımlamaların değişmiş olması

Eğer ki projenizde profile tanımlamaları kullanıyorsanız;

Eski prop tanımlamasında;

spring:
profiles: "dev"

--
spring:
profiles: "test"

Yeni prop tanımlamasında;

spring:
config:
activate:
on-profile: "dev"


spring:
config:
activate:
on-profile: "test"

şeklinde değiştirmeniz gerekmektedir.

Properties veya yaml da bir List tanımlaması yapıyorsanız. Burada da hata aldığım bir durum oluştu. Bu hata durumunu çözmek için;

Eski kod tanımında;

service:
uris: >
http://x.y.z.1:121,
http://x.y.z.2:122,
http://x.y.z.3:123

Yeni kod tanımında;

service:
uris:
- 'http://x.y.z.1:121’
- 'http://x.y.z.2:122’
- 'http://x.y.z.3:123’'

şeklinde tanımlama yapınca hata durumlarının düzeldiği görüldü.

14. Spring 3.x ile birlikte security tanımlamalarında da bazı radikal değişiklikler oldu. Adapter yapısı tamamen kaldırıldı. Daha önceki versiyonda genelde WebSecurityConfigurerAdapter class extend edilerek konfigürasyon tanımlamaları yapılıyordu ancak Spring 3.x ile birlikte WebSecurityConfigurerAdapter class yapısı kaldırıldı. Bunun yerine filterChain Bean tanımlaması ile ilgili konfigürasyonlarınızı yapabiliyorsunuz.

Eski kod örneği;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(
securedEnabled = true,
jsr250Enabled = true,
prePostEnabled = true
)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

//code

@Override
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.userDetailsService(userDetailsService);
}

@Override
public void configure(WebSecurity web) {
web.ignoring()
.antMatchers("/v3/api-docs/**")
.antMatchers("/swagger-resources/**")
.antMatchers("/swagger-ui/**")
.antMatchers("/swagger-ui.html")
.antMatchers("/service")
.antMatchers("/actuator/**");
}


@Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/checkService")
.permitAll()
.anyRequest().authenticated()
.and()
.userDetailsService(userDetailsService)
.addFilter(
new JWTAuthorizationFilter(this.authenticationManager(),filterService)
)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}

Yeni kod tanımında;

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

private static final String[] AUTH_WHITELIST = {
"/v3/api-docs/**",
"/swagger-resources/**",
"/swagger-ui/**",
"/swagger-ui.html",
"/ service ",
"/actuator/**",
"/checkService "
};

@Bean
public SecurityFilterChain filterChain(HttpSecurity http,AuthenticationManager authnManager) throws Exception {


http.
cors().configurationSource(corsConfigurationSource())
.and()
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
.requestMatchers(AUTH_WHITELIST)
.permitAll()
.anyRequest()
.authenticated())
.authenticationManager(authnManager)
.addFilter(new JWTAuthorizationFilter(authnManager, filterService))
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
return http.build();




}

private CorsConfigurationSource corsConfigurationSource() {


List<String> list = new ArrayList<>();
list.add("*");

final var corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowCredentials(true);
corsConfiguration.setAllowedOrigins(Arrays.stream(corsPropList.getOrigins()).toList());
corsConfiguration.setAllowedHeaders(list);
corsConfiguration.setAllowedMethods(list);

final var source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfiguration);

return source;
}
}

filterChain Bean tanımlaması ile birçok konfigürasyonu bu Bean içerisinden yapılabiliyor.

15. Projenizde spring-security kullanıyorsanız OpenAPI Spring 3.x için eski bağımlılığı kaldırmanız yerine springdoc-openapi-security bağımlılığını eklemeniz gerekmektedir.

Eski bağımlılık tanımında;

<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-security</artifactId>
<version>1.5.13</version>
</dependency>

Yeni bağımlılık tanımında;

<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-security</artifactId>
<version>1.6.4</version>
</dependency>

olarak güncellemelisiniz.

Bu güncelleme esnasında karşılaştığım durumlar ve yaptığım aksiyonlar şimdilik bunlar. Zaman zaman başka sorunlar yaşarsam part-2 olarak güncelleme yapabilirim.

Okuduğunuz için teşekkür ederim.

--

--

Serdar A.

Senior Software Developer & Architect at Havelsan Github: https://github.com/serdaralkancode #Java & #Spring & #BigData & #React & #Microservice