Home > OS >  Can not store custom type into database as json string - java.lang.IllegalArgumentException: Can not
Can not store custom type into database as json string - java.lang.IllegalArgumentException: Can not

Time:06-11

I have a problem using a custom type into my JPA entity and then convert and store it into the database as JSON string.

Here is the base JPA entity: Discount (We are using Lombok and builders for creating object instances. The issue occurs on the two fields of type LocalizedTexts):

@Value
@NoArgsConstructor(force = true)
@AllArgsConstructor
@Builder(builderMethodName = "internalBuilder")
@Entity
@Table(name="discount")
public class Discount {
    
    @Id
    @Column(name = "ID", nullable = false, unique = true)
    @Type(type="uuid-char")
    UUID id;
    
    @Column(name="NO", nullable = false, unique = true)
    String no;
    
    @Column(name="I_NO", nullable = false, unique = true)
    Integer iNo;
    
    @Convert(converter = JpaJsonConverter.class)
    @Column(name="DESIGNATION", nullable = false)
    LocalizedTexts designation;
    
    @Convert(converter = JpaJsonConverter.class)
    @Column(name="PRINT_TEXT")
    LocalizedTexts printText;
    
    ...

    /**
     * Builder with required parameters
     */
    public static Discount.DiscountBuilder builder(UUID id, String no, LocalizedTexts designation) {
        return internalBuilder()
                .id(id)
                .no(no)
                .designation(designation);
    }

    // Getters
    ...

}

I am using an Attribute converter to transform the custom type (extending a Map<Object, String):

@Converter
public class JpaJsonConverter implements AttributeConverter<Object, String> {

    private final static ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public String convertToDatabaseColumn(Object localizedTexts) {
        String localizedTextsJson = null;
        try {
            localizedTextsJson = objectMapper.writeValueAsString(localizedTexts);
        } catch (JsonProcessingException ex) {
            //throw new RuntimeException();
        }
        return localizedTextsJson;
    }

    @Override
    public Object convertToEntityAttribute(String localizedTextsJSON) {
        Object localizedTexts = null;
        try {
            localizedTexts = objectMapper.readValue(localizedTextsJSON, Object.class);
        } catch (IOException ex) {
            //throw new RuntimeException();
        }
        return localizedTexts;
    }
}

And here is the custom type: LocalizedTexts (Map<Enum,String>):

public class LocalizedTexts extends HashMap<Language, String> implements EntityBase {
    public LocalizedTexts() {
    }

    public LocalizedTexts(Map map) {
        this.putAll(map);
    }

    public static LocalizedTextsBuilder internalBuilder() {
        return new LocalizedTextsBuilder();
    }

    public static class LocalizedTextsBuilder {
        LocalizedTextsBuilder() {
        }

        public LocalizedTexts build() {
            return new LocalizedTexts();
        }

        public String toString() {
            return "LocalizedTexts.LocalizedTextsBuilder()";
        }
    }
}

The issue occurs when I am trying to save a Discount object into the database:

...
discountRepository.save(discount);
...

I have checked and the attribute converter works as expected (transform LocalizedTexts into JSON strings). The full stack trace of the exception I get is the following:

org.springframework.orm.jpa.JpaSystemException: Could not set field value [{de=Rabatt 1, en=Discount 1}] value by reflection : [class net.xxxxx.yyyyy.svc_voucher.entity.Discount.designation] setter of net.xxxxx.yyyyy.svc_voucher.entity.Discount.designation; nested exception is org.hibernate.PropertyAccessException: Could not set field value [{de=Rabatt 1, en=Discount 1}] value by reflection : [class net.xxxxx.yyyyy.svc_voucher.entity.Discount.designation] setter of net.xxxxx.yyyyy.svc_voucher.entity.Discount.designation

    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:331)
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:233)
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:551)
    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:152)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:174)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215)
    at jdk.proxy2/jdk.proxy2.$Proxy116.save(Unknown Source)
    at net.xxxxx.yyyyy.svc_voucher.messagehandler.DiscountHandler.handleCommand(DiscountHandler.java:58)
    at net.xxxxx.yyyyy.svc_voucher.handlers.TestDiscountHandler.Sending_script_to_messageHandler_handleCommand(TestDiscountHandler.java:109)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:74)
    at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:84)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
    at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
    at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
    at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
    at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235)
    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)
Caused by: org.hibernate.PropertyAccessException: Could not set field value [{de=Rabatt 1, en=Discount 1}] value by reflection : [class net.xxxxx.yyyyy.svc_voucher.entity.Discount.designation] setter of net.xxxxx.yyyyy.svc_voucher.entity.Discount.designation
    at org.hibernate.property.access.spi.SetterFieldImpl.set(SetterFieldImpl.java:72)
    at org.hibernate.tuple.entity.AbstractEntityTuplizer.setPropertyValues(AbstractEntityTuplizer.java:681)
    at org.hibernate.tuple.entity.PojoEntityTuplizer.setPropertyValues(PojoEntityTuplizer.java:144)
    at org.hibernate.persister.entity.AbstractEntityPersister.setPropertyValues(AbstractEntityPersister.java:5252)
    at org.hibernate.event.internal.DefaultMergeEventListener.copyValues(DefaultMergeEventListener.java:498)
    at org.hibernate.event.internal.DefaultMergeEventListener.entityIsTransient(DefaultMergeEventListener.java:241)
    at org.hibernate.event.internal.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:318)
    at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:172)
    at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:70)
    at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:107)
    at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:829)
    at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:816)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:311)
    at jdk.proxy2/jdk.proxy2.$Proxy113.merge(Unknown Source)
    at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:650)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at org.springframework.data.repository.core.support.RepositoryMethodInvoker$RepositoryFragmentMethodInvoker.lambda$new$0(RepositoryMethodInvoker.java:289)
    at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:137)
    at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:121)
    at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:529)
    at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:285)
    at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:639)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:163)
    at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:138)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:80)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
    at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:137)
    ... 42 more
Caused by: java.lang.IllegalArgumentException: Can not set final net.xxxxx.yyyyy.entity.internal.LocalizedTexts field net.xxxxx.yyyyy.svc_voucher.entity.Discount.designation to java.util.LinkedHashMap
    at java.base/jdk.internal.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:167)
    at java.base/jdk.internal.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:171)
    at java.base/jdk.internal.reflect.UnsafeQualifiedObjectFieldAccessorImpl.set(UnsafeQualifiedObjectFieldAccessorImpl.java:83)
    at java.base/java.lang.reflect.Field.set(Field.java:799)
    at org.hibernate.property.access.spi.SetterFieldImpl.set(SetterFieldImpl.java:52)
    ... 81 more

CodePudding user response:

I followed and reproduced your code, so let's talk about the issue by understanding the root cause.

First, you need to have a reading of stacktrace/logging we read that we can't set final a field which IllegalArumentException shows us.

Caused by: java.lang.IllegalArgumentException: Can not set final

and second exception wrapped by the first one says something seemed to the first one but more generic.

org.springframework.orm.jpa.JpaSystemException: Could not set field value 

Second, we must know what annotations and library we use on our Entity in this case is Discount, not adding by adding, but knowing it before to use by reading the official documentation. so you make sure what you use.

By using @Value each field of Discount is made private and final, so pointing designation as final won't be modified which we can't set any field, to ensure what I am posting you can read the documentation below.

The @Value lombok documentation says:

@Value is the immutable variant of @Data; all fields are made private and final by default, and setters are not generated. The class itself is also made final by default, because immutability is not something that can be forced onto a subclass

The issue was resolved by removing @Value which is not necessary have it.

CodePudding user response:

At first it's tempting to believe that the final modifier is the cause of the issue, but in fact it's not.

Let's try this simple test:

import java.lang.reflect.Field;

class Discount
{
    private final Integer id = 0;
}

public class Test
{
    public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException
    {
        Discount d = new Discount();
        Field f = Discount.class.getDeclaredField("id");
        f.set(d, 1);
    }
}

We get:

java.lang.IllegalAccessException: class Test cannot access a member of class Discount with modifiers "private final"

This is not the error that you are getting (your stacktrace shows an IllegalArgumentException, not an IllegalAccessException).

Let's change the code to add a setAccessible(true):

import java.lang.reflect.Field;

class Discount
{
    private final Integer id = 0;
}

public class Test
{
    public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException
    {
        Discount d = new Discount();
        Field f = Discount.class.getDeclaredField("id");
        f.setAccessible(true);
        f.set(d, 1);
    }
}

This time it works, no exception is thrown.

Now let's replace the 1 with "1":

import java.lang.reflect.Field;

class Discount
{
    private final Integer id = 0;
}

public class Test
{
    public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException
    {
        Discount d = new Discount();
        Field f = Discount.class.getDeclaredField("id");
        f.setAccessible(true);
        f.set(d, "1");
    }
}

We get:

java.lang.IllegalArgumentException: Can not set final java.lang.Integer field Discount.id to java.lang.String

This time this is exactly the same error as yours. The IllegalArgumentException means that we're trying to assign an incompatible value (here, a String to an Integer).

Your stacktrace says that you're trying to assign a LinkedHashMap to the designation field, which is incompatible indeed (designation being a LocalizedTexts).

To understand where the LinkedHashMap comes from, let's take a look at your JpaJsonConverter. The method responsible for converting a JSON string to a LocalizedTexts object is:

public Object convertToEntityAttribute(String localizedTextsJSON) {
    Object localizedTexts = null;
    try {
        localizedTexts = objectMapper.readValue(localizedTextsJSON, Object.class);
    } catch (IOException ex) {
        //throw new RuntimeException();
    }
    return localizedTexts;
}

You're calling readValue() with a JSON string containing an object, and Object.class as second argument. In such a case, Jackson returns a LinkedHashMap. It has no way to know that you want a LocalizedTexts.

Here is a possible way to fix the method:

@SuppressWarnings("unchecked")
public Object convertToEntityAttribute(String localizedTextsJSON) {
    LocalizedTexts localizedTexts = new LocalizedTexts();
    try {
        Map<String,String> map = (Map<String,String>)objectMapper.readValue(localizedTextsJSON, Object.class);
        for(Map.Entry<String,String> e : map.entrySet())
            localizedTexts.put(Language.valueOf(e.getKey()), e.getValue());
    } catch (IOException ex) {
        throw new RuntimeException(ex);
    }
    return localizedTexts;
}
  • Related