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.
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.