Bruno Militzer
Bruno Militzer

Reputation: 13

Double nested entities in r2dbc for postgresql

I'm facing an issue when I'm trying to save an Expense onto the Postgresdb.

When I try to save the entity I'm getting the following error, which dosen't say exactly what the problem is.

java.lang.IllegalArgumentException: Cannot encode parameter of type org.springframework.data.r2dbc.mapping.OutboundRow at io.r2dbc.postgresql.codec.DefaultCodecs.encode(DefaultCodecs.java:210) Suppressed: The stacktrace has been enhanced by Reactor, refer to additional information below: Error has been observed at the following site(s): *__checkpoint ⇢ Handler io.neoinsights.medici.expense.rest.ExpenseController#createExpense(String, Mono) [DispatcherHandler] Original Stack Trace: at io.r2dbc.postgresql.codec.DefaultCodecs.encode(DefaultCodecs.java:210) at

Bellow are the following etities, all created using r2dbc equivelant. Expense, Category, Amount and Currency

package io.neoinsights.medici.expense.model;

import io.neoinsights.medici.core.model.ReactiveBaseAuditedEntity;
import io.neoinsights.medici.core.converter.annotations.ConverterProperties;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Table;

import javax.validation.constraints.NotNull;
import java.io.Serial;
import java.time.LocalDate;

/**
 * Expense entity for expenses service.
 */
@Getter
@Setter
@Builder
@ConverterProperties
@ToString(of = { "date", "description" })
@NoArgsConstructor
@AllArgsConstructor
@Table("expense")
public class Expense extends ReactiveBaseAuditedEntity<Long> {

    @Serial
    private static final long serialVersionUID = 8130153991065353058L;

    @Column("date")
    @NotNull(message = "Date is required")
    private LocalDate date;

    @Column("description")
    @NotNull(message = "Description is required")
    private String description;

    @Column("category_id")
    @NotNull(message = "Category is required")
    private Category category;

    @Column("amount_id")
    @NotNull(message = "Amount is required")
    private Amount amount;

    @Column("user_id")
    private String userId;

    @Override
    public Long getId() {
        return super.getId();
    }

    @Override
    public void setId( final Long id ) {
        super.setId( id );
    }

}

package io.neoinsights.medici.expense.model;

import io.neoinsights.medici.core.model.ReactiveBaseAuditedEntity;
import io.neoinsights.medici.core.converter.annotations.ConverterProperties;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Table;

import javax.validation.constraints.NotNull;
import java.io.Serial;

/**
 * Category entity for expenses service.
 */
@Table("category")
@Getter
@Setter
@Builder
@ConverterProperties
@NoArgsConstructor
@AllArgsConstructor
@ToString(of = "name", includeFieldNames = false)
public class Category extends ReactiveBaseAuditedEntity<Long> {

    @Serial
    private static final long serialVersionUID = -3671545604906372145L;

    @NotNull(message = "Name is required")
    @Column("name")
    private String name;

    @Override
    public Long getId() {
        return super.getId();
    }

    @Override
    public void setId( final Long id ) {
        super.setId( id );
    }

}

package io.neoinsights.medici.expense.model;

import io.neoinsights.medici.core.model.ReactiveBaseAuditedEntity;
import io.neoinsights.medici.core.converter.annotations.ConverterProperties;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Table;

import javax.validation.constraints.NotNull;
import java.io.Serial;
import java.math.BigDecimal;

/**
 * Amount entity for expenses service.
 */
@Getter
@Setter
@Builder
@ConverterProperties
@ToString(of = { "amount" })
@NoArgsConstructor
@AllArgsConstructor
@Table("amount")
public class Amount extends ReactiveBaseAuditedEntity<Long> {

    @Serial
    private static final long serialVersionUID = -5851171944174365153L;

    @Column("amount")
    @NotNull(message = "Amount is required")
    private BigDecimal amount;

    @Column("currency_code")
    @NotNull(message = "Currency is required")
    private Currency currency;

    @Override
    public Long getId() {
        return super.getId();
    }

    @Override
    public void setId( final Long id ) {
        super.setId( id );
    }

}

package io.neoinsights.medici.expense.model;

import io.neoinsights.medici.core.model.BaseModel;
import io.neoinsights.medici.core.converter.annotations.ConverterProperties;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Table;

import javax.validation.constraints.NotNull;
import java.io.Serial;

/**
 * Currency entity for expenses service.
 */
@Getter
@Setter
@Builder
@ConverterProperties
@ToString(of = { "currency", "code" })
@NoArgsConstructor
@AllArgsConstructor
@Table("currency")
public class Currency implements BaseModel<String> {

    @Serial
    private static final long serialVersionUID = -1190998708188852136L;

    @Id
    @Column("code")
    @NotNull(message = "Code is required")
    private String code;

    @Column("country_name")
    @NotNull(message = "Country name is required")
    private String countryName;

    @Column("currency")
    @NotNull(message = "Currency is required")
    private String currency;

    @Column("symbol")
    @NotNull(message = "Symbol is required")
    private String symbol;

    @Override
    public String getId() {
        return this.code;
    }

    @Override
    public void setId( final String code ) {
        this.code = code;
    }

}

I also wrote the writing converters for all the entities.

package io.neoinsights.medici.expense.converter;

import io.neoinsights.medici.expense.model.Expense;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.WritingConverter;
import org.springframework.data.r2dbc.mapping.OutboundRow;
import org.springframework.r2dbc.core.Parameter;

/**
 * Expense Writing Converter.
 */
@WritingConverter
public class ExpenseWriteConverter implements Converter<Expense, OutboundRow> {

    @Override
    public OutboundRow convert( final Expense expense ) {
        final OutboundRow row = new OutboundRow();

        if (expense.getId() != null) {
            row.put( "id", Parameter.from( expense.getId() ) );
        }

        row.put( "date", Parameter.from( expense.getDate() ) );
        row.put( "description", Parameter.from( expense.getDescription() ) );
        row.put( "category_id", Parameter.from( expense.getCategory() ) );
        row.put( "amount_id", Parameter.from( expense.getAmount() ) );
        row.put( "user_id", Parameter.from( expense.getUserId() ) );

        return row;
    }
}


package io.neoinsights.medici.expense.converter;

import io.neoinsights.medici.expense.model.Category;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.WritingConverter;
import org.springframework.data.r2dbc.mapping.OutboundRow;
import org.springframework.r2dbc.core.Parameter;

/**
 * Category Writing Converter.
 */
@WritingConverter
public class CategoryWriteConverter implements Converter<Category, OutboundRow> {

    @Override
    public OutboundRow convert( final Category category ) {
        final OutboundRow row = new OutboundRow();

        if (category.getId() != null) {
            row.put( "id", Parameter.from( category.getId() ) );
        }

        row.put( "name", Parameter.from( category.getName() ) );

        return row;
    }
}

package io.neoinsights.medici.expense.converter;

import io.neoinsights.medici.expense.model.Amount;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.WritingConverter;
import org.springframework.data.r2dbc.mapping.OutboundRow;
import org.springframework.r2dbc.core.Parameter;

/**
 * Amount Writing Converter.
 */
@WritingConverter
public class AmountWriteConverter implements Converter<Amount, OutboundRow> {

    @Override
    public OutboundRow convert( final Amount amount ) {
        final OutboundRow row = new OutboundRow();

        if (amount.getId() != null) {
            row.put( "id", Parameter.from( amount.getId() ) );
        }

        row.put( "amount", Parameter.from( amount.getAmount() ) );
        row.put( "currency_code", Parameter.from( amount.getCurrency() ) );

        return row;
    }
}


package io.neoinsights.medici.expense.converter;

import io.neoinsights.medici.expense.model.Currency;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.WritingConverter;
import org.springframework.data.r2dbc.mapping.OutboundRow;
import org.springframework.r2dbc.core.Parameter;

/**
 * Currency Writing Converter.
 */
@WritingConverter
public class CurrencyWriteConverter implements Converter<Currency, OutboundRow> {

    @Override
    public OutboundRow convert( final Currency currency ) {
        final OutboundRow row = new OutboundRow();

        row.put( "code", Parameter.from( currency.getCode() ) );
        row.put( "country_name", Parameter.from( currency.getCountryName() ) );
        row.put( "currency", Parameter.from( currency.getCurrency() ) );
        row.put( "symbol", Parameter.from( currency.getSymbol() ) );

        return row;
    }
}

So I spent 4 days trying to solve it but with no success.

Upvotes: 1

Views: 523

Answers (0)

Related Questions