admin管理员组文章数量:1200344
I'm following Spring training with Java and decided to replicate the content of each class in a Kotlin project as well. However, I came across an error in one of the entity cascade validation steps using the Javax Validation @ConvertGroup annotation in the project made in Kotlin, which does not happen in Java.
The project has a Restaurant entity that has a @ManyToOne relationship with the Cuisine entity and that will have its attributes validated separately without the validation of one affecting the validation of the other. However, after applying these validations, I can register a Restaurant normally in the Java project, but in the Kotlin project I receive an error saying that cuisine.name cannot be blank, which should not happen.
// Payload for Restaurant registration
{
"name": "Thai Delivery",
"deliveryFee": 12,
"cuisine": {
"id": 1
}
}
Below are the respective codes in each language:
// Groups.java
package br.dev.s2w.jfoods.api;
public interface Groups {
public interface CuisineId {}
}
// Groups.kt
package br.dev.s2w.kfoods.api
interface Groups {
interface CuisineId
}
// Restaurant.java
package br.dev.s2w.jfoods.api.domain.model;
import br.dev.s2w.jfoods.api.Groups;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import javax.persistence.*;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.PositiveOrZero;
import javax.validation.groups.ConvertGroup;
import javax.validation.groups.Default;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Data
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@Entity
public class Restaurant {
@EqualsAndHashCode.Include
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank
@Column(nullable = false)
private String name;
@PositiveOrZero
@Column(name = "delivery_fee", nullable = false)
private BigDecimal deliveryFee;
@Valid
@ConvertGroup(from = Default.class, to = Groups.CuisineId.class)
@NotNull
@ManyToOne
@JoinColumn(name = "cuisine_id", nullable = false)
private Cuisine cuisine;
@JsonIgnore
@Embedded
private Address address;
@JsonIgnore
@CreationTimestamp
@Column(nullable = false, columnDefinition = "datetime")
private LocalDateTime registrationDate;
@JsonIgnore
@UpdateTimestamp
@Column(nullable = false, columnDefinition = "datetime")
private LocalDateTime lastUpdateDate;
@JsonIgnore
@ManyToMany
@JoinTable(name = "restaurant_payment_method",
joinColumns = @JoinColumn(name = "restaurant_id"),
inverseJoinColumns = @JoinColumn(name = "payment_method_id"))
private List<PaymentMethod> paymentMethods = new ArrayList<>();
@JsonIgnore
@OneToMany(mappedBy = "restaurant")
private List<Product> products = new ArrayList<>();
}
// Restaurant.kt
package br.dev.s2w.kfoods.api.domain.model
import br.dev.s2w.kfoods.api.Groups
import com.fasterxml.jackson.annotation.JsonIgnore
import org.hibernate.annotations.CreationTimestamp
import org.hibernate.annotations.UpdateTimestamp
import java.math.BigDecimal
import java.time.LocalDateTime
import javax.persistence.*
import javax.validation.Valid
import javax.validation.constraints.NotBlank
import javax.validation.constraints.NotNull
import javax.validation.constraints.PositiveOrZero
import javax.validation.groups.ConvertGroup
import javax.validation.groups.Default
@Entity
data class Restaurant(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null,
@field:NotBlank
@Column(nullable = false)
var name: String? = null,
@field:PositiveOrZero
@Column(name = "delivery_fee", nullable = false)
var deliveryFee: BigDecimal? = null,
@field:Valid
@field:ConvertGroup(from = Default::class, to = Groups.CuisineId::class)
@field:NotNull
@ManyToOne
@JoinColumn(name = "cuisine_id", nullable = false)
var cuisine: Cuisine? = null,
@Embedded
@JsonIgnore
var address: Address? = null,
@CreationTimestamp
@Column(nullable = false, columnDefinition = "datetime")
@JsonIgnore
var registrationDate: LocalDateTime? = null,
@UpdateTimestamp
@Column(nullable = false, columnDefinition = "datetime")
@JsonIgnore
var lastUpdateDate: LocalDateTime? = null,
@ManyToMany
@JoinTable(
name = "restaurant_payment_method",
joinColumns = [JoinColumn(name = "restaurant_id")],
inverseJoinColumns = [JoinColumn(name = "payment_method_id")]
)
@JsonIgnore
var paymentMethods: MutableList<PaymentMethod> = mutableListOf(),
@OneToMany(mappedBy = "restaurant")
@JsonIgnore
var products: MutableList<Product> = mutableListOf()
)
// Cuisine.java
package br.dev.s2w.jfoods.api.domain.model;
import br.dev.s2w.jfoods.api.Groups;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;
@Data
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@Entity
public class Cuisine {
@NotNull(groups = Groups.CuisineId.class)
@EqualsAndHashCode.Include
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank
@Column(nullable = false)
private String name;
@JsonIgnore
@OneToMany(mappedBy = "cuisine")
private List<Restaurant> restaurants = new ArrayList<>();
}
// Cuisine.kt
package br.dev.s2w.kfoods.api.domain.model
import br.dev.s2w.kfoods.api.Groups
import com.fasterxml.jackson.annotation.JsonIgnore
import javax.persistence.*
import javax.validation.constraints.NotBlank
import javax.validation.constraints.NotNull
@Entity
data class Cuisine(
@field:NotNull(groups = [Groups.CuisineId::class])
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null,
@field:NotBlank
@Column(nullable = false)
var name: String? = null,
@OneToMany(mappedBy = "cuisine")
@JsonIgnore
var restaurants: MutableList<Restaurant> = mutableListOf()
)
// POST /restaurants
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Restaurant add(@RequestBody @Valid Restaurant restaurant) {
try {
return restaurantRegister.save(restaurant);
} catch (CuisineNotFoundException e) {
throw new BusinessException(e.getMessage(), e);
}
}
// POST /restaurants
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
fun add(@RequestBody @Valid restaurant: Restaurant): Restaurant =
try {
restaurantRegister.save(restaurant)
} catch (e: CuisineNotFoundException) {
throw BusinessException(e.message, e)
}
Yeah! I will make all attributes in Kotlin immutable and handle the ones that cannot receive a null value.
Response to each call:
// Java Status: 201 Created Response:
{
"id": 7,
"name": "Thai Delivery",
"deliveryFee": 12,
"cuisine": {
"id": 1,
"name": "Thai"
}
}
// Kotlin Status: 400 Bad Request Response:
{
"timestamp": "2025-01-21T22:35:14.4081533",
"status": 400,
"type": ";,
"title": "Invalid data",
"detail": "One or more fields are invalid. Fill in correctly and try again!",
"userMessage": "One or more fields are invalid. Fill in correctly and try again!",
"fields": [
{
"name": "cuisine.name",
"userMessage": "must not be blank"
}
]
}
// Kotlin/pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns=".0.0" xmlns:xsi=";
xsi:schemaLocation=".0.0 .0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>br.dev</groupId>
<artifactId>s2w-kfoods-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>s2w-kfoods-api</name>
<description>Your favorite food delivery service!</description>
<developers>
<developer>
<id>SW</id>
<name>Wybson Santana</name>
<url>/</url>
</developer>
</developers>
<properties>
<java.version>17</java.version>
<kotlin.version>1.8.22</kotlin.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-kotlin</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-reflect</artifactId>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<version>9.8.3</version>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-mysql</artifactId>
<version>9.8.3</version>
</dependency>
<dependency>
<groupId>org.apachemons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-test-junit5</artifactId>
<version>${kotlin.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<configuration>
<args>
<arg>-Xjsr305=strict</arg>
</args>
<compilerPlugins>
<plugin>spring</plugin>
<plugin>jpa</plugin>
<plugin>no-arg</plugin>
</compilerPlugins>
</configuration>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-allopen</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-noarg</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>
I tested passing the annotations with and without field:, get:, set: etc, as well as changing the versions of Spring (not in the 3.x + range), Java, Kotlin and dependencies, but nothing had any practical effect.
I believe it could be a bug in Kotlin, as I found an issue about something similar in a Quarkus project: Validation groups for REST endpoint does not work with Kotlin · Issue #20395 · quarkusio/quarkus
Any kind of help and/or possible solution will be greatly appreciated!
Thank you very much!
本文标签:
版权声明:本文标题:java - Javax Validation's @ConvertGroup annotation does not work in a Spring project using Kotlin - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1738578863a2101040.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论