I' ve this data classe
#Parcelize
data class IdTotalTotal(
val idEnvioMarrocos: Int,
val clifor: String,
val nomeClifor: String,
val nomeUser: String,
val termina: Int,
var dados: List<IdTotal>
): Parcelable {
#Parcelize
data class IdTotal(
val produto: String,
val modelo: String,
var idPartePeso: List<IdPartePeso>,
var idOpPedido: List<IdOpPedido>
) : Parcelable {
#Parcelize
data class IdPartePeso(
val parteID: Int,
val nomeParte: String,
var pesoTamanho: List<PesoTamanho>
) : Parcelable {
#Parcelize
data class PesoTamanho(
val nomeT: String,
var pesoT: Int
) : Parcelable
}
#Parcelize
data class IdOpPedido(
val op: String,
var pedidos: List<Pedido>
) : Parcelable {
#Parcelize
data class Pedido(
val pedido: String,
val pedidoCliente: String,
var pedidoEntra: Boolean
) : Parcelable
}
}
}
Now I'm trying to achieve the same result with a room entity for persistence and ease.
This is my code:
#Entity
data class EnviosPesosDestinosBd(
#PrimaryKey(autoGenerate = false)
val idEnvioMarrocos: Int,
val clifor: String,
val nomeClifor: String,
val nomeUser: String,
val termina: Int,
val updatedAt: Long = System.currentTimeMillis()
)
#Entity(primaryKeys = ["idEnvioMarrocos", "produto"])
data class EnvioProduto(
val idEnvioMarrocos: Int,
val produto: String,
val modelo: String,
)
#Entity(primaryKeys = ["idEnvioMarrocos", "produto", "parteID"],
foreignKeys = [
ForeignKey(
entity = TamanhoPeso::class,
parentColumns = ["idEnvioMarrocos", "produto", "parteID"],
childColumns = ["idEnvioMarrocos", "produto", "parteID"]
)
]
)
data class PesoParte(
val idEnvioMarrocos: Int,
val produto: String,
val parteID: Int,
val nomeParte: String,
)
#Entity(primaryKeys = ["idEnvioMarrocos", "produto", "parteID", "nomeT"])
data class TamanhoPeso(
val idEnvioMarrocos: Int,
val produto: String,
val parteID: Int,
val nomeT: String,
var pesoT: Int
)
#Entity(primaryKeys = ["idEnvioMarrocos", "op", "produto"],
foreignKeys = [
ForeignKey(
entity = OpPedido::class,
parentColumns = ["idEnvioMarrocos", "op"],
childColumns = ["idEnvioMarrocos", "op"]
)
])
data class EnvioDestinoComOp(
val idEnvioMarrocos: Int,
val produto: String,
val op: String,
)
#Entity(primaryKeys = ["idEnvioMarrocos", "op", "pedido"])
data class OpPedido(
val idEnvioMarrocos: Int,
val op: String,
val pedido: String,
val pedidoCliente: String,
var pedidoEntra: Boolean
)
Thanks
It would appear that you are stuck with how to create appropriate Entities to represent your original classes and trying to make complex primary keys to create the relationships between the tables.
I would suggest utilising unique id's (single columns) as the basis for the relationships.
The following code, based upon a hierarchical inspection of your original classes and using the original class names as the basis for the Database Table/Entity names is a quick interpretation of what you may wish to consider as the basis.
So the topmost class directly or indirectly referenced is the IdTotalTotal class, and it appears that idEnvioMarrocos would be a unique value.
This what was coded (basically little different to what you coded) :-
#Entity
data class DBIdTotalTotal(
#PrimaryKey
val idEnvioMarrocos: Long,
val clifor: String,
val nomeClifor: String,
val nomeUser: String,
val termina: Int,
val updatedAt: Long = System.currentTimeMillis()
)
Long rather Int has been used as I prefer to use Long as at least for Java the value can be Long rather than int.
No need for autoGenerate = false as just #PrimaryKey will default to false.
Note that I have prefixed the original class name with DB to indicate that it's a DB related class (and I've used this throughout).
Now according to your original classes the IdTotalToal class has sub classes (aka a relationship) with a list of IdTotal's. So DBIdTotal looks like :-
#Entity(
foreignKeys = [
ForeignKey(
entity = DBIdTotalTotal::class,
parentColumns = ["idEnvioMarrocos"],
childColumns = ["ref_DBIdTotalTotal"]
)
]
)
data class DBIdTotal(
#PrimaryKey
val id_DBIdTotal: Long,
val ref_DBIdTotalTotal: Long,
val produto: String,
val modelo: String
)
i.e. A DBIdTotal is a child of a DBIdTotalTotal and the parent is stored in the ref_DBIdTotalTotal column that has been added.
you will see a pattern where ref_ prefixes the new column added to reference the parent column.
note (I have not included indexes on the Foreign keys Room will warn you to do this)
As the IdTotal class has two lists then it is a parent to the IdPartPeso(s) and the IdOpPedido(s) these two DB classes are as follows:-
#Entity(
foreignKeys = [
ForeignKey(
entity = DBIdTotal::class,
parentColumns = ["id_DBIdTotal"],
childColumns = ["ref_DBIdTotal"]
)
]
)
data class DBIdPartePeso(
#PrimaryKey
val id_DBIdPartePeso: Long,
val ref_DBIdTotal: Long,
val parteID: Int,
val nomeParte: String
)
and
#Entity(
foreignKeys = [
ForeignKey(entity = DBIdTotal::class,
parentColumns = ["id_DBIdTotal"],
childColumns = ["ref_DBIdTotal"]
)
]
)
data class DBIdOpPedido(
#PrimaryKey
val id_DBIdOpPedido: Long,
val ref_DBIdTotal: Long,
val op: String
)
IdPartePeso has a list of PesoTamanho's so DBPesoTamanho is :-
#Entity(
foreignKeys = [
ForeignKey(
entity = DBIdPartePeso::class,
parentColumns = ["id_DBIdPartePeso"],
childColumns = ["ref_DBIdPartePeso"]
)
]
)
data class DBPesoTamanho(
#PrimaryKey
val id_DBPesoTamanho: Long,
val ref_DBIdPartePeso: Long,
val nomeT: String,
var pesoT: Int
)
Likewise IdOpPedido has a list of Pedido's SO DBPedido is :-
#Entity(
foreignKeys = [
ForeignKey(
entity = DBIdOpPedido::class,
parentColumns = ["id_DBIdOpPedido"],
childColumns = ["ref_DBIdOpPedido"]
)
]
)
data class DBPedido(
#PrimaryKey
val id_DBPedido: Long,
val ref_DBIdOpPedido: Long,
val pedido: String,
val pedidoCliente: String,
var pedidoEntra: Boolean
)
That deals with the tables. However, it is very likely that you want to get a DBIdTotalTotal with all the underlying objects (The DBIdTotal's .... the DBPedido's and DBPesoTamanho's).
So we work back up the hierachy this time creating POJO's for combining children with the parent into a List of the children.
So for a DBIdOpPedido we want the DBIdOpPedido object with a List so the following POJO would suit:-
data class PojoDBIdOpPedidoWithDbPedido (
#Embedded
val dbIdOpPedido: DBIdOpPedido,
#Relation(
entity = DBPedido::class,
parentColumn = "id_DBIdOpPedido",
entityColumn = "ref_DBIdOpPedido"
)
val dbPedidoList: List<DBPedido>
)
The parent is Embedded, the children use and #Relation
Similar for DBPartePpeso with the List :-
data class PojoDBIdPartePesoWithDBPesoTamanho(
#Embedded
val dbIdPartePeso: DBIdPartePeso,
#Relation(
entity = DBPesoTamanho::class,
parentColumn = "id_DBIdPartePeso",
entityColumn = "ref_DBIdPartePeso"
)
val dbPesoTamanhoList: List<DBPesoTamanho>
)
Next up the Hierarchy is the DBIdTotal, this has 2 lists which sublists. However, it's not complicated because the previously created POJO's can be utilised. As such there is:-
data class PojoDBIdTotalWithDBIdPartePesoAndWIthDBIdOpPedido(
#Embedded
val dbIdTotal: DBIdTotal,
#Relation(
entity = DBIdPartePeso::class,
parentColumn = "id_DBIdTotal",
entityColumn = "ref_DBIdTotal"
)
val pojoDBIdPartePesoWithDBPesoTamanhoList: List<PojoDBIdPartePesoWithDBPesoTamanho>,
#Relation(
entity = DBIdOpPedido::class,
parentColumn = "id_DBIdTotal",
entityColumn = "ref_DBIdTotal"
)
val pojoDBIdOpPedidoList: List<PojoDBIdOpPedidoWithDbPedido>
)
NOTE the entities referred to are the entities NOT the POJO's as it is the columns of the tables that form the relationships.
So last but not least the top level DBIdTotalTotal aka everything all together (as such a bit of a different name for this) :-
data class PojoDBIdTotalTotalWithDBIdTotalEtcetera(
#Embedded
val dbIdTotalTotal: DBIdTotalTotal,
#Relation(
entity = DBIdTotal::class,
parentColumn = "idEnvioMarrocos",
entityColumn = "ref_DBIdTotalTotal"
)
val pojoDBIdTotalWithDBIdPartePesoAndWIthDBIdOpPedidoList: List<PojoDBIdTotalWithDBIdPartePesoAndWIthDBIdOpPedido>
)
The above code can be used with appropriate Dao's nothing that Room builds the underlying queries to obtain ALL relations when using #Relation. This makes the queries very simple as you only have to specfify the higher level table. The following are suitable Dao's (in a single class named DBDao) as per :-
#Dao
interface DBDao {
#Insert
fun insertDBIdTotalTotal(dbIdTotalTotal: DBIdTotalTotal): Long
#Insert
fun insertDBIdTotal(dbIdTotal: DBIdTotal): Long
#Insert
fun insertDBIdPartePeso(dbIdPartePeso: DBIdPartePeso): Long
#Insert
fun insertDBPesoTamanho(dbPesoTamanho: DBPesoTamanho): Long
#Insert
fun insertDBIdOpPedido(dbIdOpPedido: DBIdOpPedido): Long
#Insert
fun insertDBPedido(dbPedido: DBPedido): Long
#Transaction
#Query("SELECT * FROM dbidoppedido")
fun getIdOpPedidoWithAllPedidos(): List<PojoDBIdOpPedidoWithDbPedido>
#Transaction
#Query("SELECT * FROM dbidpartepeso")
fun getIdPartePesoWithAllPesoTamanhod(): List<PojoDBIdPartePesoWithDBPesoTamanho>
#Transaction
#Query("SELECT * FROM dbidtotal")
fun getIdTotalWithAllIdPartePesosAndAllIdOpPedidos(): List<PojoDBIdTotalWithDBIdPartePesoAndWIthDBIdOpPedido>
#Transaction
#Query("SELECT * FROM dbidtotaltotal")
fun getIdTotalTotalWithEverything(): List<PojoDBIdTotalTotalWithDBIdTotalEtcetera>
}
not that the queries that have an underlying #Relation all have #Transaction (Room will issue warnings if not (best to use #Transaction as child objects are built by using additional queries)).
Testing/Demo/Example
An #Database named *TheDatabase was created :-
#Database(entities = [
DBIdTotalTotal::class,
DBIdTotal::class,
DBIdOpPedido::class,
DBIdPartePeso::class,
DBPesoTamanho::class,
DBPedido::class
],
version = 1
)
abstract class TheDatabase: RoomDatabase() {
abstract fun getDao(): DBDao
}
The the following was then used for testing/demonstrating the code above (note allowMainThreadQueries used for brevity and convenience) :-
class MainActivity : AppCompatActivity() {
lateinit var db: TheDatabase
lateinit var dao: DBDao
val TAG = "MYDBINFO"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
db = Room.databaseBuilder(this,TheDatabase::class.java,"mydb.db")
.allowMainThreadQueries().build()
dao = db.getDao()
var firstTotalTotalId = dao.insertDBIdTotalTotal(DBIdTotalTotal(1L,"CLIFOR1","CLIFORNAME1","USERNAME1",10))
var firstTotal = dao.insertDBIdTotal(DBIdTotal(2L,firstTotalTotalId,"PRODUTO1","MODELO1"))
var secondTotal = dao.insertDBIdTotal(DBIdTotal(3L,firstTotalTotalId,"PRODUTO2","MODELO2"))
var firstPartePeso = dao.insertDBIdPartePeso(DBIdPartePeso(4L,firstTotal,100,"Parte100"))
var secondPartePeso = dao.insertDBIdPartePeso(DBIdPartePeso(5L,secondTotal,200,"PARTE200"))
var thirdPartePeso = dao.insertDBIdPartePeso( DBIdPartePeso(6,firstTotal,300,"PARTE300"))
var fourthPartePes = dao.insertDBIdPartePeso(DBIdPartePeso(7,secondTotal,400,"PARTE400"))
var firstPesoTamanho = dao.insertDBPesoTamanho(DBPesoTamanho(8,firstPartePeso,"PESONTAMANHO1000",1000))
var secondPesoTamanho = dao.insertDBPesoTamanho(DBPesoTamanho(9,thirdPartePeso,"PARTEPESO2000",2000))
var firstIdOpPedido = dao.insertDBIdOpPedido(DBIdOpPedido(10,firstTotal,"OPPEDIDO10000"))
var sedcondIdOpPedido = dao.insertDBIdOpPedido(DBIdOpPedido(11,firstTotal,"OPPEDIDO2000"))
var firstPedido = dao.insertDBPedido(DBPedido(12,firstIdOpPedido,"PEDIDO100000","CLIENTE1",true))
var getall = dao.getIdTotalTotalWithEverything()
for(all: PojoDBIdTotalTotalWithDBIdTotalEtcetera in getall) {
logIdTotalTotal(all.dbIdTotalTotal,"")
for(allTotals: PojoDBIdTotalWithDBIdPartePesoAndWIthDBIdOpPedido in all.pojoDBIdTotalWithDBIdPartePesoAndWIthDBIdOpPedidoList) {
logIdTotal(allTotals.dbIdTotal,"\t")
for(allIdPartePeso: PojoDBIdPartePesoWithDBPesoTamanho in allTotals.pojoDBIdPartePesoWithDBPesoTamanhoList) {
logIdPartePeso(allIdPartePeso.dbIdPartePeso,"\t\t")
for(pesoTamanho: DBPesoTamanho in allIdPartePeso.dbPesoTamanhoList) {
logPesoTamanho(pesoTamanho,"\t\t\t")
}
}
for(allIdOpPedido: PojoDBIdOpPedidoWithDbPedido in allTotals.pojoDBIdOpPedidoList) {
logIdOpPedido(allIdOpPedido.dbIdOpPedido,"\t\t")
for(pedido: DBPedido in allIdOpPedido.dbPedidoList) {
logPedido(pedido,"\t\t\t")
}
}
}
}
}
private fun logPedido(p: DBPedido,prefix: String) {
Log.d(TAG,"$prefix PEDIDIO = ${p.pedido} CLIENTE = ${p.pedidoCliente} ID=${p.id_DBPedido} ENTRA=${p.pedidoEntra} REFERENCES IDOPPedido=${p.ref_DBIdOpPedido}")
}
private fun logIdOpPedido(iop: DBIdOpPedido, prefix: String) {
Log.d(TAG,"$prefix OP=${iop.op} ID=${iop.id_DBIdOpPedido} REFERNCES TOTAL=${iop.ref_DBIdTotal}" )
}
private fun logPesoTamanho(pt: DBPesoTamanho, prefix: String) {
Log.d(TAG,"$prefix NOME=${pt.nomeT} PESO=${pt.pesoT} ID=${pt.id_DBPesoTamanho} REFRENCES IDPARTEPESO=${pt.ref_DBIdPartePeso}")
}
private fun logIdPartePeso(ipp: DBIdPartePeso, prefix: String) {
Log.d(TAG,"$prefix NOMEPARTE=${ipp.nomeParte} PARTEID=${ipp.parteID} ID=${ipp.id_DBIdPartePeso} REFERNCES TOTAL=${ipp.ref_DBIdTotal}")
}
private fun logIdTotal(it: DBIdTotal, prefix: String) {
Log.d(TAG,"$prefix MODELO=${it.modelo} PRODUTO=${it.produto} ID=${it.id_DBIdTotal} REFERENCES TOTALTOTAL= ${it.ref_DBIdTotalTotal}")
}
private fun logIdTotalTotal(itt: DBIdTotalTotal, prefix: String) {
Log.d(TAG,"$prefix NOMUSER=${itt.nomeUser} CLIFOR=${itt.clifor} NOMCLIFOR=${itt.nomeClifor} TERMINA=${itt.termina} UPDATED= ${itt.updatedAt} ID=${itt.idEnvioMarrocos}")
}
}
The above has been designed to run and work just the once purely for testing/demonstrating.
Result
D/MYDBINFO: NOMUSER=USERNAME1 CLIFOR=CLIFOR1 NOMCLIFOR=CLIFORNAME1 TERMINA=10 UPDATED= 1620189450079 ID=1
D/MYDBINFO: MODELO=MODELO1 PRODUTO=PRODUTO1 ID=2 REFERENCES TOTALTOTAL= 1
D/MYDBINFO: NOMEPARTE=Parte100 PARTEID=100 ID=4 REFERNCES TOTAL=2
D/MYDBINFO: NOME=PESONTAMANHO1000 PESO=1000 ID=8 REFRENCES IDPARTEPESO=4
D/MYDBINFO: NOMEPARTE=PARTE300 PARTEID=300 ID=6 REFERNCES TOTAL=2
D/MYDBINFO: NOME=PARTEPESO2000 PESO=2000 ID=9 REFRENCES IDPARTEPESO=6
D/MYDBINFO: OP=OPPEDIDO10000 ID=10 REFERNCES TOTAL=2
D/MYDBINFO: PEDIDIO = PEDIDO100000 CLIENTE = CLIENTE1 ID=12 ENTRA=true REFERENCES IDOPPedido=10
D/MYDBINFO: OP=OPPEDIDO2000 ID=11 REFERNCES TOTAL=2
D/MYDBINFO: MODELO=MODELO2 PRODUTO=PRODUTO2 ID=3 REFERENCES TOTALTOTAL= 1
D/MYDBINFO: NOMEPARTE=PARTE200 PARTEID=200 ID=5 REFERNCES TOTAL=3
D/MYDBINFO: NOMEPARTE=PARTE400 PARTEID=400 ID=7 REFERNCES TOTAL=3
That is the Single IdTotalTotal is extracted with the underlying related objects
The above is intended to show the principle, there may be some issues as the output hasn't been rigorously checked that it conforms to expectations.
I am trying to query a state from vault without using the linear Id of the state and instead an Int(unique) variable present in Schema
val sNumber = AState.ASchemaV1.AEntity::SNumber
val QueryCriteria = QueryCriteria.VaultCustomQueryCriteria(sNumber.equal(SalesNumber))
val StateAndRef = serviceHub.vaultService.queryBy<AState>(QueryCriteria).states.single()
val outState = StateAndRef.state.data
The Query criteria is not throwing any error but I am also not getting any output but on debugging I got an error response
javax.persistence.PersistenceException: org.hibernate.InstantiationException: No default constructor for entity: AState.ASchemaV1.AEntity
but I have defined all the columns in the function. What am I missing?
Here is code for Schema
override fun supportedSchemas() = listOf(ASchemaV1)
override fun generateMappedObject(schema: MappedSchema) = ASchemaV1.AEntity(this)
object ASchemaV1 : MappedSchema(AState::class.java, 1, listOf(AEntity::class.java)) {
#Entity
#Table(name = "Table")
class AEntity(A: AState) : PersistentState() {
#Column
var CONumber: String = A.linearId.id.toString()
#Column
var SalesNumber: Int = A.SalesNumber
#Column
var ProductID: Int = A.ProductID
#Column
var Quantity: Int = A.Quantity
#Column
var Rate: Double = A.Rate
#Column
var DeliveryDate: Date = A.DeliveryDate
#Column
var DeliveryLocation: String = A.DeliveryLocation
#Column
var Status: String = A.Status.toString()
}
}
AState.ASchemaV1 is missing the constructor.
object ASchemaV1 : MappedSchema(AState::class.java, 1, listOf(AEntity::class.java)) {
#Entity
#Table(name = "Table")
class AEntity(
#Column
var CONumber: String,
#Column
var SalesNumber: Int,
#Column
var ProductID: Int,
#Column
var Quantity: Int,
#Column
var Rate: Double,
#Column
var DeliveryDate: Date,
#Column
var DeliveryLocation: String,
#Column
var Status: String
): PersistentState() {
constructor(A: AState): this(A.linearId.id.toString(), A.SalesNumber, A.ProductID, A.Quantity, A.Rate, A.DeliveryDate, A.DeliveryLocation, A.Status.toString())
}
}
I have the following situation .
I have a linear state as given below .
class INDENTState(
val indentId:String,
val itemType: String,
val model: String,
val quantity: Int,
val specifications:String,
val product : String,
val comment:String,
val branchName:String,
val branchAddress:String,
val state:String,
override val linearId: UniqueIdentifier = UniqueIdentifier(indentId)):
LinearState, QueryableState {
I would like to include the above linear state in another linearState and insert several of the INDENTState into a list and create another linear state .
The second linear state I would like to be persistent .
object CollatedIndentsSchemaV1 : MappedSchema(
schemaFamily = CollatedIndentsSchema.javaClass,
version = 1,
mappedTypes = listOf(PersistanceCIs::class.java)) {
#Entity
#Table(name = "collated_Indents")
class PersistanceCIs(
#Column(name = "ciNo")
var ciNo: String,
#ElementCollection
var borrowerName: Set<INDENTState>,
#Column(name = "party")
var party: String
) : PersistentState() {
// Default constructor required by hibernate.
constructor(): this("", setOf(), "")
}
}
But I am getting the following hibernate exception .
E 23:39:14+0530 [main] internal.Node.run - Exception during node startup {}
org.hibernate.MappingException: Could not determine type for: com.example.state.INDENTState, at table: CollatedIndentsSchemaV1$PersistanceCIs_borrowerName, for columns: [org.hibernate.mapping.Column(borrowerName)]
at org.hibernate.mapping.SimpleValue.getType(SimpleValue.java:455) ~[hibernate-core-5.2.6.Final.jar:5.2.6.Final]
at org.hibernate.mapping.SimpleValue.isValid(SimpleValue.java:422) ~[hibernate-core-5.2.6.Final.jar:5.2.6.Final]
at org.hibernate.mapping.Collection.validate(Collection.java:310) ~[hibernate-core-5.2.6.Final.jar:5.2.6.Final]
at org.hibernate.mapping.Set.validate(Set.java:27) ~[hibernate-core-5.2.6.Final.jar:5.2.6.Final]
at org.hibernate.boot.internal.MetadataImpl.validate(MetadataImpl.java:333) ~[hibernate-core-5.2.6.Final.jar:5.2.6.Final]
at org.hibernate.boot.internal.SessionFactoryBuilderImpl.build(SessionFactoryBuilderImpl.java:444) ~[hibernate-core-5.2.6.Final.jar:5.2.6.Final]
at net.corda.nodeapi.internal.persistence.HibernateConfiguration.buildSessionFactory(HibernateConfiguration.kt:113) ~[corda-node-api-corda-3.0.jar:?]
at net.corda.nodeapi.internal.persistence.HibernateConfiguration.makeSessionFactoryForSchemas(HibernateConfiguration.kt:63) ~[corda-node-api-corda-3.0.jar:?]
at net.corda.nodeapi.internal.persistence.HibernateConfiguration.access$makeSessionFactoryForSchemas(HibernateConfiguration.kt:26) ~[corda-node-api-corda-3.0.jar:?]
at net.corda.nodeapi.internal.persistence.HibernateConfiguration$sessionFactoryForSchemas$1.apply(HibernateConfiguration.kt:44) ~[corda-node-api-corda-3.0.jar:?]
at net.corda.nodeapi.internal.persistence.HibernateConfiguration$sessionFactoryForSchemas
Any insights to tackle the above scenario ?
You cannot store the INDENTStates directly as state instances. You need to define an entity to represent the INDENTStates in the database.
Here is an example from another question:
object TestSchema : MappedSchema(SchemaFamily::class.java, 1, setOf(Parent::class.java, Child::class.java)) {
#Entity
#Table(name = "Parents")
class Parent : PersistentState() {
#OneToMany(fetch = FetchType.LAZY)
#JoinColumns(JoinColumn(name = "transaction_id", referencedColumnName = "transaction_id"), JoinColumn(name = "output_index", referencedColumnName = "output_index"))
#OrderColumn
#Cascade(CascadeType.PERSIST)
var children: MutableSet<Child> = mutableSetOf()
}
#Entity
#Table(name = "Children")
class Child {
#Id
#GeneratedValue
#Column(name = "child_id", unique = true, nullable = false)
var childId: Int? = null
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumns(JoinColumn(name = "transaction_id", referencedColumnName = "transaction_id"), JoinColumn(name = "output_index", referencedColumnName = "output_index"))
var parent: Parent? = null
}
}
I have a one-to-many relationship where I am trying to add a list of object/class in my state. i.e
I have a contract state that has a list of attachments List<Attachment>, where Attachmentis just a class with fields like
attachmentHash, uploadedDate, fileType
I wanted to query with something in the child but I get syntax error "AttachmentEntity is not a subtype of PersistentState"
QueryCriteria.VaultCustomQueryCriteria(
builder { (ContractSchemaV1.AttachmentEntity::uploadDate).equal(givenDate) }))
I let AttachmentEntity be a subclass of PersistentState and the node started up with the error
org.hibernate.AnnotationException: net.corda.core.schemas.PersistentStateRef
must not have #Id properties when used as an #EmbeddedId: project.schemas.ContractSchemaV1$AttachmentEntity.stateRef
Seems like I'm doing something wrong, whats the best way to represent a collection of data classes in the state and translate that in a schema? Or is this already the correct way, but there's no way to query the nested collection using VaultCustomQuery?
The example entity below.
object ContractSchema
object ContractSchemaV1 : MappedSchema(schemaFamily = ContractSchema.javaClass, version = 1,
mappedTypes = listOf(ContractEntity::class.java, AttachmentEntity:class.java)) {
#Entity
#Table(name = "contracts")
class ContractEntity(
#Column(name = "issued_date")
var issuedDate: Instant,
#Column(name = "linear_id")
var linearId: String,
#OneToMany(fetch = FetchType.LAZY, cascade = arrayOf(CascadeType.PERSIST))
#JoinColumns(
JoinColumn(name = "transaction_id", referencedColumnName = "transaction_id"),
JoinColumn(name = "output_index", referencedColumnName = "output_index"))
var attachments: MutableSet<AttachmentEntity> = emptyList(),
) : PersistentState()
#Entity
#Table(name = "attachments")
class AttachmentEntity (
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", unique = true, nullable = false)
var id: Long? = null,
#Column(name = "attachment_hash", nullable = false)
var attachmentHash: String? = null,
#Column(name = "attachment_name", nullable = false)
var attachmentName: String? = null,
#Column(name = "upload_date", nullable = true)
var uploadDate: Instant? = null)
}
Your schema definition is correct (and you can see another example here: Querying nested collections in LinearState states).
However, querying nested collections is not supported by VaultCustomQueryCriteria. You have to do direct JDBC queries to query attributes of the nested collections.
Here is an example of a direct JDBC query in Corda:
#Test
fun `test calling an arbitrary JDBC native query`() {
val nativeQuery = "SELECT v.transaction_id, v.output_index FROM vault_states v WHERE v.state_status = 0"
database.transaction {
val jdbcSession = services.jdbcSession()
val prepStatement = jdbcSession.prepareStatement(nativeQuery)
val rs = prepStatement.executeQuery()
var count = 0
while (rs.next()) {
val stateRef = StateRef(SecureHash.parse(rs.getString(1)), rs.getInt(2))
Assert.assertTrue(cashStates.map { it.ref }.contains(stateRef))
count++
}
Assert.assertEquals(cashStates.count(), count)
}
}
The code below is about reflection.
It tries to do 2 things:
case1() creates an instance from SimpleStudent class, it works.
case2() creates an instance from Student class, not work.
The reason that case2() not work as well as the question, is that inside that generateValue():
I don't know how to check it is kotlin type or my own type(I have a dirty way to check param.type.toString() not contain "kotlin" but I wonder if there is a better solution
I don't know how to get its class reference when it's a custom class. The problem is that even though param.type.toString() == "Lesson", when I tried to get param.type::class, it's class kotlin.reflect.jvm.internal.KTypeImpl
So, how to solve it? Thanks
==============
import kotlin.reflect.KParameter
import kotlin.reflect.full.primaryConstructor
import kotlin.test.assertEquals
data class Lesson(val title:String, val length:Int)
data class Student(val name:String, val major:Lesson )
data class SimpleStudent(val name:String, val age:Int )
fun generateValue(param:KParameter, originalValue:Map<*,*>):Any? {
var value = originalValue[param.name]
// if (param.type is not Kotlin type){
// // Get its ::class so that we could create the instance of it, here, I mean Lesson class?
// }
return value
}
fun case1(){
val classDesc = SimpleStudent::class
val constructor = classDesc.primaryConstructor!!
val value = mapOf<Any,Any>(
"name" to "Tom",
"age" to 16
)
val params = constructor.parameters.associateBy (
{it},
{generateValue(it, value)}
)
val result:SimpleStudent = constructor.callBy(params)
assertEquals("Tom", result.name)
assertEquals(16, result.age)
}
fun case2(){
val classDesc = Student::class
val constructor = classDesc.primaryConstructor!!
val value = mapOf<Any,Any>(
"name" to "Tom",
"major" to mapOf<Any,Any>(
"title" to "CS",
"length" to 16
)
)
val params = constructor.parameters.associateBy (
{it},
{generateValue(it, value)}
)
val result:Student = constructor.callBy(params)
assertEquals("Tom", result.name)
assertEquals(Lesson::class, result.major::class)
assertEquals("CS", result.major.title)
}
fun main(args : Array<String>) {
case1()
case2()
}
Problem solved:
You could get that ::class by using param.type.classifier as KClass<T> where param is KParameter