'Spring Boot one to one mapping serialization

I just started out with Spring Boot and currently working on a home project. I have the following entities:

Currency

@Entity
@Table(name = "currencies")
data class Currency(
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long = 0,
    val code: String,
    val name: String,
    val countryName: String,
    val iconUrl: String
): java.io.Serializable

RateEntity

@Entity
@Table(name = "rates")
data class RateEntity(
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long = 0,
    val date: Date,
    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "currency_code", referencedColumnName = "code")
    val currency: Currency,
    val rate: Double
)

Everything seems to be working fine, but json response looks like this:

{
"id": 126,
"date": "2022-03-16",
"currency": null,
"rate": 1.0336
}

So the currency value is always null. In database everything looks normal:

+-----+------------+--------+---------------+
| id  | date       | rate   | currency_code |
+-----+------------+--------+---------------+
| 126 | 2022-03-16 | 1.0336 | CHF           |
+-----+------------+--------+---------------+

So I'm thinking that I made something wrong with relationship mapping. Or do I have to map RateEntity to a different data class in my Repo/Service ?

UPDATE:

Rates Controller, Service and Repo

@RestController
@RequestMapping("/rates")
class RatesController(private val service: RatesService) {
    @GetMapping("/all")
    fun getAllRates() = service.getAllRates()
}

@Service
class RatesService(private val repo: RatesRepository) {
    fun getAllRates(): List<RateEntity> = repo.findAll()

    fun addRate(rate: RateEntity){
        repo.save(rate)
    }
}

@Repository
interface RatesRepository: JpaRepository<RateEntity, Long>

I also probably wasn't clear what I want to achieve. I believe I don't need an annotation in Currency Entity at all, because I don't need bidirectional relation. I want only Rates Table to have a code from Currency Table and not by PK, but by code, which is also unique in this Table.

Update 2

I updated Entities code again (see above). I removed annotation from Currency Table. After this I was getting a Serializable Exception on Currency object each time I requested Rates endpoint result. I fixed it by extending the Currency Class with Serializable interface and now I am getting the following result:

{
"id": 1,
"date": "2022-03-18",
"currency": {
      "id": 28,
      "code": "CHF",
      "name": "Swiss Franc\r",
      "countryName": "Liechtenstein; Switzerland",
      "iconUrl": ""
},
"rate": 1.025
}

So it serializes the whole Currency object. I would like to show only the code value under currency in Rates Table, but at the same time to keep it as Currency object.

I think I need a custom Json serializer component for Rate object or map it to a different one (like RateView object or something) before giving it to the controller.

Update 3: Using simple @JsonComponent I finally achieved what I wanted:

@JsonComponent
class RateJsonSerializer: JsonSerializer<RateEntity>() {
    override fun serialize(rateEntity: RateEntity?, jsonGenerator: JsonGenerator?, p2: SerializerProvider?) {
        jsonGenerator?.let { generator ->
            generator.writeStartObject()
            rateEntity?.let {  rate ->
                generator.writeNumberField("id", rate.id)
                generator.writeStringField("date", rate.date.toString())
                generator.writeStringField("currency", rate.currency.code)
                generator.writeNumberField("rate", rate.rate)
            }
            generator.writeEndObject()
        }
    }
}

-----RESULT-----
{
"id": 2,
"date": "2022-03-17",
"currency": "CHF",
"rate": 1.0385
},
{
"id": 3,
"date": "2022-03-17",
"currency": "HRK",
"rate": 7.573
},
{
"id": 4,
"date": "2022-03-17",
"currency": "MXN",
"rate": 22.788
}

The question now, is it a viable solution? How are this kinds of thing usually done in Spring Boot?



Solution 1:[1]

Try this code snippets, hope it should work.

@Entity
@Table(name = "currencies")
data class Currency(
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long = 0,
    val code: String,
    val name: String,
    val countryName: String,
    val iconUrl: String,

    @OneToOne(mappedBy = "currency")
    val rateEntity: RateEntity
)


@Entity
@Table(name = "rates")
data class RateEntity(
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long = 0,
    val date: Date,
    val rate: Double,

    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "currency_code", referencedColumnName = "id")
    val currency: Currency
)

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Zakaria Hossain