-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Description
I am upgrading from spring-data-mongodb 3.2.3 to 3.3.1, and also to Java 17. Here is a test that worked before:
EDIT: this seems to be a Java 17 problem, the same issue exists in Spring Data Mongo 3.2.9 with Java 17 and Kotlin 1.6. Although this did work ok with Java 11.
package com.shipwell.opus.utils.numerics
import org.assertj.core.api.Assertions.assertThat
import org.bson.types.ObjectId
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.data.mongo.DataMongoTest
import org.springframework.data.annotation.Id
import org.springframework.data.mongodb.core.mapping.Document
import org.springframework.data.mongodb.repository.MongoRepository
import java.math.BigDecimal
@DataMongoTest
class RateDecimalMongoTest(@Autowired private val repository: RateDecimalMongoTestDocumentRepository) {
@Test
fun `test rate decimal saved and loaded properly`() {
val rateDecimal = RateDecimal("203.0008")
val objectId = repository.save(RateDecimalMongoTestDocument(someObj = rateDecimal)).id!!
val savedObject = repository.findById(objectId).get()
assertThat(savedObject.someObj).isInstanceOf(RateDecimal::class.java)
assertThat(savedObject.someObj).isEqualTo(BigDecimal("203.0008"))
}
}
@Document(collection = "RateDecimalMongoTestDocumentRepository")
class RateDecimalMongoTestDocument(
var someObj: Any,
@JvmField @Id var id: ObjectId? = null
)
interface RateDecimalMongoTestDocumentRepository : MongoRepository<RateDecimalMongoTestDocument, ObjectId>
This test was fine in 3.2.3 was fine with Java 11, but now fails with:
Unable to make field private final java.math.BigInteger java.math.BigDecimal.intVal accessible: module java.base does not "opens java.math" to unnamed module @7dc36524
java.lang.reflect.InaccessibleObjectException: Unable to make field private final java.math.BigInteger java.math.BigDecimal.intVal accessible: module java.base does not "opens java.math" to unnamed module @7dc36524
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354)
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297)
at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:178)
at java.base/java.lang.reflect.Field.setAccessible(Field.java:172)
at org.springframework.util.ReflectionUtils.makeAccessible(ReflectionUtils.java:787)
at org.springframework.data.mapping.context.AbstractMappingContext$PersistentPropertyCreator.doWith(AbstractMappingContext.java:550)
at org.springframework.util.ReflectionUtils.doWithFields(ReflectionUtils.java:710)
at org.springframework.data.mapping.context.AbstractMappingContext.doAddPersistentEntity(AbstractMappingContext.java:424)
at org.springframework.data.mapping.context.AbstractMappingContext.addPersistentEntity(AbstractMappingContext.java:381)
at org.springframework.data.mapping.context.AbstractMappingContext.getPersistentEntity(AbstractMappingContext.java:266)
at org.springframework.data.mapping.context.AbstractMappingContext.getPersistentEntity(AbstractMappingContext.java:92)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter.readDocument(MappingMongoConverter.java:341)
at org.springframework.data.mongodb.core.convert.MappingMongoConverter$ConversionContext.convert(MappingMongoConverter.java:2077)
Where the RateDecimalConfig
class looks like this:
public class RateDecimal extends AbstractFixedDecimal implements Serializable {
private static final long serialVersionUID = 1L;
private static final int scale = 4;
/**
* Necessary constructor that MongoDB will use to create the instance
* @param val String whose value is used to construct the instance
*/
@PersistenceConstructor
public RateDecimal(@Value("#root.amount") String val) {
super(scaleToString(new BigDecimal(val), scale));
}
public RateDecimal(double val) {
super(scaleToString(new BigDecimal(val), scale));
}
public RateDecimal(int val) {
super(scaleToString(new BigDecimal((val)), scale));
}
public RateDecimal(long val) {
super(scaleToString(new BigDecimal(val), scale));
}
}
And then AbstractFixedDecimal
:
public abstract class AbstractFixedDecimal extends BigDecimal {
AbstractFixedDecimal(String val) {
super(val);
}
static String scaleToString(BigDecimal unscaledVal, int scale) {
return unscaledVal.setScale(scale, RoundingMode.HALF_UP).toString();
}
}
To deal with the serialization of a RateDecimal
to a document, I have this converter registered as a mongoCustomConversions
bean:
@Bean
fun mongoCustomConversions(): MongoCustomConversions {
return MongoCustomConversions(mutableListOf(
RateDecimalConverter()
))
}
private final static class FixedDecimalToDocumentConverter<T extends AbstractFixedDecimal> implements Converter<T, Document> {
@Override
public Document convert(@NotNull T source) {
Document doc = new Document();
doc.append(DefaultMongoTypeMapper.DEFAULT_TYPE_KEY, source.getClass().getName());
// Mongo by default has BigDecimal converters to both Decimal128 and String, so there is no obvious
// correct format to use when storing the value. Choosing String because of spring data Mongo documentation.
// See https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#mapping-conversion
doc.append("amount", source.toString());
return doc;
}
}
My question is, what changed in 3.3 that caused the process to look at these private fields when before it didn't? It kind of makes me think that my PersistentConstructor
is being ignored.
This is the same behavior in 3.2, but has to do with using Java 17.