androidkotlinjpaandroid-roomkotlinx.serialization

Extend existing Hibernate Entity as Android Room Entity with Kotlin Serialization annotation


I need to utilize an existing database structure which has Entity classes compatible with JPA (Jakarta Persistence API) for use in Spring Boot via the Hibernate ORM. I want to basically copy this database structure as an Android Room database and also use the Kotlin serialization plugin / library. The original database library cannot use any Android dependencies. The problem I'm confronted with is PrimaryKey annotations in the Android Room database. I cannot find a way to designate the fields as primary keys from the original Entity. Example follows...

CustomerEntity.kt - JPA Entity class compatible with Spring Hibernate

@Serializable
@Entity
@Table(
    name = CustomerEntity.Constants.TABLE_NAME,
    indexes = [Index(name = "IX_Customer_CustomerName", columnList = "CustomerName")]
)
open class CustomerEntity(
    @Id
    @Column(name = Constants.COLUMN_ID_NAME, nullable = false, length = 15)
    open var id: String = "",

    @Column(name = Constants.COLUMN_CUSTOMERNAME_NAME, length = 40)
    var customerName: String = "",
) {
    object Constants {
        const val TABLE_NAME: String = "Customer"
        const val COLUMN_ID_NAME: String = "CustomerID"
        const val COLUMN_CUSTOMERNAME_NAME: String = "CustomerName"
    }
}

CustomerAndroid.kt - attempted to subclass JPA Entity class

@Serializable
@Entity(tableName = CustomerSchema.TABLE_NAME)
class CustomerAndroid() : CustomerEntity() {
    constructor(
        customerID: String,
        customerName: String = ""
    ) : this() {
        this.id = customerID
        this.customerName = customerName
    }

    @PrimaryKey
    override var id: String = ""
}

Attempting to use override produces a problem with kotlinx.serialization:

Serializable class has duplicate serial name of property 'id', either in the class itself or its supertypes

I'm trying to find a way to attach the annotation to the primary key field in Android Room, but I'm running into a lot of headaches with getting these frameworks and libraries to play nicely with each other.


Solution

  • I was able to solve this problem by creating a new class containing the @Embedded annotation on the Entity enclosed as a property in the new class. This allowed me to build DAO classes which could directly reference the fields in the Entity class. This was the best compromise I could find.

    Example:

    @Serializable  
    @Entity(tableName = CustomerEntity.Constants.TABLE_NAME)  
    data class CustomerAndroid(  
        @Embedded  
        var entity: CustomerEntity,  
      
        @PrimaryKey(autoGenerate = true)  
        var localID: Long = 0L  
    ) {  
        //...  
    }  
    

    And the DAO:

    @Dao
    interface CustomerDAO {
        @Query("DELETE FROM ${CustomerEntity.Constants.TABLE_NAME}")
        suspend fun deleteAll()
    
        @Insert
        fun insertAll(vararg customers: CustomerAndroid)
    
        @Insert
        fun insertAll(customers: List<CustomerAndroid>)
    
        @Insert
        fun insert(customer: CustomerAndroid)
    
        @Query("SELECT * FROM ${CustomerEntity.Constants.TABLE_NAME}")
        fun findAll(): List<CustomerAndroid>
    
        @Query("SELECT * FROM ${CustomerEntity.Constants.TABLE_NAME} " +
                "WHERE customerName LIKE '%' || :search || '%' OR id LIKE '%' || :search || '%'")
        fun findAllByCustomerNameOrCustomerIdLike(search: String): List<CustomerAndroid>
    
        @Query("SELECT * FROM ${CustomerEntity.Constants.TABLE_NAME} " +
                "WHERE id = :customerId")
        fun findByCustomerID(customerId: String): CustomerAndroid
    
        @Query("SELECT * FROM ${CustomerEntity.Constants.TABLE_NAME}")
        fun observeAll(): Flow<List<CustomerAndroid>>
    
        @Query("SELECT * FROM ${CustomerEntity.Constants.TABLE_NAME} " +
                "WHERE customerName LIKE '%' || :search || '%' OR id LIKE '%' || :search || '%'")
        fun observeAllByCustomerNameOrCustomerIdLike(search: String): Flow<List<CustomerAndroid>>
    
        @Query("SELECT * FROM ${CustomerEntity.Constants.TABLE_NAME} " +
                "WHERE id = :customerId")
        fun observeByCustomerID(customerId: String): Flow<List<CustomerAndroid>>
    }
    

    So as you can see, you can refer to the fields in CustomerEntity directly in the @Query annotations.