Javamoney.moneta.Money serialized/deserialized as null 虽然我有 jackson-datatype-money 依赖

Javamoney.moneta.Money serialized/deserialized as null although I have jackson-datatype-money dependency

我只想提供一个休息端点,用户可以在其中发送金额。我决定第一次使用 javamoney.moneta.Money 并且它在 Postgres 中一直作为 null.

这是模型:

import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.Lob;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;

import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;
import org.javamoney.moneta.Money;


import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.JsonIdentityReference;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;

@Entity
@Table(name = "accounts")
public class Account extends AuditModel  {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotNull
    private Money money;

    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    @JoinColumn(name = "person_id", nullable = false)
    @OnDelete(action = OnDeleteAction.CASCADE)
    @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
    @JsonIdentityReference(alwaysAsId = true)
    @JsonProperty("person_id")
    private Person person;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Money getMoney() {
        return money;
    }

    public void setMoney(Money money) {
        this.money = money;
    }

    public Person getPerson() {
        return person;
    }

    public void setPerson(Person person) {
        this.person = person;
    }
    
}

如果相关,这里是连接列的相关模型

import java.util.Date;

import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

@Entity
@Table(name = "persons")
public class Person extends AuditModel {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotNull
    @Size(max = 50)
    @Column(unique = true)
    private String name;

    @NotNull
    private String cpf;

    @NotNull
    @Temporal(TemporalType.DATE)
    private Date birthDate;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getCpf() {
        return cpf;
    }

    public void setCpf(String cpf) {
        this.cpf = cpf;
    }

    public Date getBirthDate() {
        return birthDate;
    }

    public void setBirthDate(Date birthDate) {
        this.birthDate = birthDate;
    }
    
   
}

及其审计模型

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@JsonIgnoreProperties(value = { "createdAt", "updatedAt" }, allowGetters = true)
public abstract class AuditModel implements Serializable {
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "created_at", nullable = false, updatable = false)
    @CreatedDate
    private Date createdAt;

    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "updated_at", nullable = false)
    @LastModifiedDate
    private Date updatedAt;

    public Date getCreatedAt() {
        return createdAt;
    }

    public void setCreatedAt(Date createdAt) {
        this.createdAt = createdAt;
    }

    public Date getUpdatedAt() {
        return updatedAt;
    }

    public void setUpdatedAt(Date updatedAt) {
        this.updatedAt = updatedAt;
    }
}

存储库

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import com.mybank.accountmanagement.model.Account;

@Repository
public interface AccountRepository extends JpaRepository<Account, Long> {

}

并且 AccountController 暴露端点

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import com.mybank.accountmanagement.jpa.exception.*;
import com.mybank.accountmanagement.model.Account;
import com.mybank.accountmanagement.repository.AccountRepository;
import com.mybank.accountmanagement.repository.PersonRepository;

@RestController
public class AccountController {
    @Autowired
    AccountRepository accountRepository;

    @Autowired
    PersonRepository personRepository;

    @PostMapping("/accounts/{personId}")
    public Account createAccount(@PathVariable(value = "personId") Long personId, @Valid @RequestBody Account account) {
        return personRepository.findById(personId).map(person -> {
            account.setPerson(person);
            return accountRepository.save(account);
        }).orElseThrow(() -> new ResourceNotFoundException("PersonId " + personId + " not found"));
    }
}

这是我调用其余端点的方式

curl --location --request POST 'localhost:2000/accounts/3' --header 'Content-Type: application/json' --data-raw '{
  "amount": 29.95,
  "currency": "EUR",
  "formatted": "29,95 EUR"
}'

这导致在 Account table 中创建新记录,但 Money 列为空

我尝试在 之后添加 jackson-datatype-money 这是我的 POM

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.2.RELEASE</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.mybank</groupId>
    <artifactId>accountmanagement</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>accountmanagement</name>
    <description>Test</description>

    <properties>
        <java.version>11</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <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>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
            <scope>runtime</scope>
        </dependency>


        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>2.0.1.Final</version>
        </dependency>


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <!-- exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> 
                </exclusion> </exclusions -->
        </dependency>

        <dependency>
            <groupId>org.javamoney</groupId>
            <artifactId>moneta</artifactId>
            <version>1.3</version>
            <type>pom</type>
        </dependency>


        <dependency>
            <groupId>org.zalando</groupId>
            <artifactId>jackson-datatype-money</artifactId>
            <version>1.2.0</version>
        </dependency>


    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

如果它增加了一些有用的点,这个单元测试通过了

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.javamoney.moneta.Money;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;
import org.springframework.test.context.junit4.SpringRunner;

import com.mybank.accountmanagement.model.Account;
import com.mybank.accountmanagement.model.Person;
import com.mybank.accountmanagement.repository.AccountRepository;
import com.mybank.accountmanagement.repository.PersonRepository;

@RunWith(SpringRunner.class)
@DataJpaTest(showSql = true)
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class JpaAccountUnitTest {

    @Autowired
    PersonRepository personRepository;

    @Autowired
    AccountRepository accountRepository;

    @Autowired
    private TestEntityManager entityManager;

    @Test
    public void should_save_an_account() {

        Person p1 = new Person();
        p1.setCpf("12345678901");
        p1.setName("Fulano de tal");
        try {
            p1.setBirthDate(new SimpleDateFormat("yyyy-MM-dd").parse("1978-04-14"));
        } catch (ParseException e) {
            e.printStackTrace();
        }
        p1.setCreatedAt(new Date());
        p1.setUpdatedAt(new Date());

        Person personSaved = personRepository.save(p1);

        Account ac = new Account();
        ac.setPerson(personSaved);
        ac.setMoney(Money.of(1, "EUR"));
        ac.setCreatedAt(new Date());
        ac.setUpdatedAt(new Date());
        
        Account accountSaved = accountRepository.save(ac);

        Assert.assertEquals(accountSaved.getMoney(), Money.of(1, "EUR")); 

    }
}

最后,我认为我做错了什么,在控制器端点上它填充了空值。

您必须在 Config 中添加 Serializer 和 Deserialize class(使用 @Configuration 注释)

使用 zalando / jackson-datatype-money,你可以只添加这个 bean

    @Bean
    public MoneyModule moneyModule() {
        return new MoneyModule();
    }

cf https://github.com/andrewhj/jackson-money-demo