Corda中创建自定义查询的可行方法及DataState状态查询咨询
Great question! Since your DataState implements both LinearState and QueryableState (and it's a create-only, never-consumed state), you have three solid approaches to implement custom queries in Corda. Let's start with the critical prerequisite—finishing your QueryableState implementation—then dive into each query method.
Prerequisite: Complete the QueryableState Mapping
First, you need to define how your DataState maps to a persistent database entity (since QueryableState requires this for database queries). Here's the updated DataState class with the necessary schema and persistent entity mapping:
import net.corda.core.contracts.LinearState; import net.corda.core.contracts.QueryableState; import net.corda.core.identity.AbstractParty; import net.corda.core.identity.Party; import net.corda.core.schemas.MappedSchema; import net.corda.core.schemas.PersistentState; import net.corda.core.utilities.UniqueIdentifier; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Table; import java.util.Arrays; import java.util.List; import java.util.UUID; public class DataState implements LinearState, QueryableState { private Party partyA; private Party partyB; private String partyAId; private String partyBId; private String sharedIdentifierNumber; private UniqueIdentifier linearId; public DataState(Party partyA, Party partyB, String partyAId, String partyBId, String sharedIdentifierNumber) { this.partyA = partyA; this.partyB = partyB; this.partyAId = partyAId; this.partyBId = partyBId; this.sharedIdentifierNumber = sharedIdentifierNumber; this.linearId = new UniqueIdentifier(); } // LinearState required methods @Override public List<AbstractParty> getParticipants() { return Arrays.asList(partyA, partyB); } @Override public UniqueIdentifier getLinearId() { return linearId; } // QueryableState required methods @Override public PersistentState generateMappedObject(MappedSchema schema) { if (schema instanceof DataStateSchemaV1) { // Convert Party objects to their X500 string representations for persistence return new PersistentDataState( partyA.getName().toString(), partyB.getName().toString(), partyAId, partyBId, sharedIdentifierNumber, linearId.getId() ); } else { throw new IllegalArgumentException("Unsupported schema: " + schema); } } @Override public Iterable<MappedSchema> supportedSchemas() { return Arrays.asList(new DataStateSchemaV1()); } // Define the schema version public static class DataStateSchemaV1 extends MappedSchema { public DataStateSchemaV1() { super(DataState.class, 1, Arrays.asList(PersistentDataState.class)); } } // Persistent entity mapped to the database table @Entity @Table(name = "data_states") public static class PersistentDataState extends PersistentState { @Column(name = "party_a") private String partyA; @Column(name = "party_b") private String partyB; @Column(name = "party_a_id") private String partyAId; @Column(name = "party_b_id") private String partyBId; @Column(name = "shared_identifier_number") private String sharedIdentifierNumber; @Column(name = "linear_id") private UUID linearId; // JPA requires a no-arg constructor public PersistentDataState() {} public PersistentDataState(String partyA, String partyB, String partyAId, String partyBId, String sharedIdentifierNumber, UUID linearId) { this.partyA = partyA; this.partyB = partyB; this.partyAId = partyAId; this.partyBId = partyBId; this.sharedIdentifierNumber = sharedIdentifierNumber; this.linearId = linearId; } // Getters for all fields (required for JPA) public String getPartyA() { return partyA; } public String getPartyB() { return partyB; } public String getPartyAId() { return partyAId; } public String getPartyBId() { return partyBId; } public String getSharedIdentifierNumber() { return sharedIdentifierNumber; } public UUID getLinearId() { return linearId; } } }
Approach 1: Use Vault Query API with Custom Criteria
This is the most straightforward method for simple to moderately complex queries. You'll build QueryCriteria objects to filter against your state's fields, then execute the query via Corda's VaultService.
Example: Query by Party A and Unconsumed Status
Since your state is never consumed, you can filter for UNCONSUMED status to avoid irrelevant results:
// Inside a Flow or Corda Service val vaultService = serviceHub.vaultService // Filter for states where partyA matches a specific Party val partyACriteria = QueryCriteria.VaultCustomQueryCriteria( Builder.equal(PersistentDataState::partyA, targetPartyA.name.toString()) ) // Filter for only unconsumed states (since your state is never consumed) val unconsumedCriteria = QueryCriteria.VaultQueryCriteria(Vault.StateStatus.UNCONSUMED) // Combine criteria val combinedCriteria = partyACriteria.and(unconsumedCriteria) // Execute the query and get the results val matchingStates = vaultService.queryBy<DataState>(combinedCriteria).states
Example: Query by Shared Identifier Number
val sharedIdCriteria = QueryCriteria.VaultCustomQueryCriteria( Builder.equal(PersistentDataState::sharedIdentifierNumber, "SHARED-12345") ) val results = vaultService.queryBy<DataState>(sharedIdCriteria.and(unconsumedCriteria)).states
Approach 2: Custom JPA Queries (For Complex Logic)
If you need advanced queries (like fuzzy matches, multi-field joins, or aggregate functions), use JPA directly with a custom repository interface. Corda supports Spring Data JPA-style repositories for this.
Step 1: Define a Repository Interface
import net.corda.core.schemas.CordaJpaRepository import java.util.UUID interface DataStateRepository : CordaJpaRepository<PersistentDataState, UUID> { // Find states by partyBId and sharedIdentifierNumber fun findByPartyBIdAndSharedIdentifierNumber(partyBId: String, sharedId: String): List<PersistentDataState> // Fuzzy search for shared identifiers containing a substring fun findBySharedIdentifierNumberContaining(partialSharedId: String): List<PersistentDataState> }
Step 2: Execute the Query
// Inside a Flow or Service val repo = serviceHub.cordappSchemaService.getSchemaRepository(DataStateRepository::class.java) // Run the custom query val matchingStates = repo.findByPartyBIdAndSharedIdentifierNumber("B-9876", "SHARED-12345") // Convert persistent entities back to DataState if needed val dataStates = matchingStates.map { serviceHub.vaultService.toState(it) }
Approach 3: Named Queries (Predefined Reusable Queries)
For frequently used complex queries, define named queries directly on your persistent entity. This keeps query logic centralized and easy to maintain.
Step 1: Add Named Query to PersistentDataState
Update the PersistentDataState class with an @NamedQuery annotation:
import javax.persistence.NamedQuery; @Entity @Table(name = "data_states") @NamedQuery( name = "PersistentDataState.findByPartyAId", query = "SELECT p FROM PersistentDataState p WHERE p.partyAId = :partyAId" ) public static class PersistentDataState extends PersistentState { // ... existing fields and methods ... }
Step 2: Execute the Named Query
val entityManager = serviceHub.jpaEntityManagerFactory.createEntityManager() val query = entityManager.createNamedQuery( "PersistentDataState.findByPartyAId", PersistentDataState::class.java ).setParameter("partyAId", "A-1234") val results = query.resultList
Key Notes for Your Use Case
- Since your state is never consumed, always include
Vault.StateStatus.UNCONSUMEDin your criteria to avoid redundant data. - As a
LinearState, you can also combine custom criteria withLinearStateQueryCriteriato filter bylinearIdor participants. - Ensure all fields in
PersistentDataStateare properly mapped to yourDataStatefields (e.g., convertingPartyto X500 strings for persistence).
内容的提问来源于stack exchange,提问作者Ben Tan

