一尘不染

Corda:如何实现状态数据之间持久化的层次关系

hibernate

摘要

我修改了基本的令牌发行Corda
Bootcamp应用程序来演示此问题。我想在TokenStates与TokenChildren之间建立一对多关系的双向映射。

持久保存分层数据的最佳实践是什么? 是否可以在状态模式中使用JPA注释来实现此目的?

我有一个状态-
TokenState,其中包含一些任意数据以及Collection带有类的对象TokenChild。该列表的目的是促进H2中的记录之间的一对多关系。该州的关联架构具有相应的JPA批注(@OneToMany和@ManyToOne-
请参见下面的代码段)。本TokenState类引用适当的模式-
TokenSchemaV1supportedSchemasgenerateMappedObject方法。

当我TokenIssueFlow在部署和运行节点之后从控制台运行(也包含在代码段中)时,事务成功完成,但没有任何token_child_states表持久化到h2。

其他注意事项

  • 我还尝试实现一种不同的策略,其中Token
    和TokenChildren都是唯一状态(而不是一个整体
    状态)。有关更多详细信息,请参见此Github问题

  • 另一个解决方案可能是将Tokens和TokenChildren作为单独的状态,并在h2中手动保留外键以促进这种关系,但这似乎是一种解决方法,而不是解决方案。

  • 类之间更深层嵌套的关系有哪些后果?(例如-具有TokenGrandChildren的TokenChildren的人为示例)。如何使用generateMappedObject()supportedSchemas()创建所需的数据模型?

令牌状态

public class TokenState implements LinearState, QueryableState {



    private final Party owner;

    private final Party issuer;

    private final int amount;

    private final UniqueIdentifier linearId;

    private List<TokenSchemaV1.PersistentChildToken> listOfPersistentChildTokens;



    public TokenState (Party issuer, Party owner, int amount, UniqueIdentifier linearId, List<TokenSchemaV1.PersistentChildToken> listOfPersistentChildTokens) {

        this.owner = owner;

        this.issuer = issuer;

        this.amount = amount;

        this.linearId = linearId;

        this.listOfPersistentChildTokens = listOfPersistentChildTokens;

    }



    public Party getOwner() {

        return owner;

    }



    public Party getIssuer() {

        return issuer;

    }



    public int getAmount() {

        return amount;

    }



    @Override

    public UniqueIdentifier getLinearId() {

        return linearId;

    }



    public List<TokenSchemaV1.PersistentChildToken> getListOfPersistentChildTokens() {

        return listOfPersistentChildTokens;

    }



    @Override

    public PersistentState generateMappedObject(MappedSchema schema) {

        if (schema instanceof TokenSchemaV1) {

            return new TokenSchemaV1.PersistentToken(

                    this.getOwner().getName().toString(),

                    this.getIssuer().getName().toString(),

                    this.getAmount(),

                    this.linearId.getId(),

                    this.getListOfPersistentChildTokens()

            );

        } else {

            throw new IllegalArgumentException("Unrecognised schema $schema");

        }

    }



    @Override

    public Iterable<MappedSchema> supportedSchemas() {

        return ImmutableList.of(new TokenSchemaV1());

    }



    @NotNull

    @Override

    public List<AbstractParty> getParticipants() {

        return ImmutableList.of(issuer, owner);

    }



}

令牌模式V1

@CordaSerializable

public class TokenSchemaV1 extends MappedSchema {



    public TokenSchemaV1() {

        super(TokenSchema.class, 1, ImmutableList.of(PersistentToken.class, PersistentChildToken.class));

    }



    @Entity

    @Table(name = "token_states")

    public static class PersistentToken extends PersistentState {

        @Column(name = "owner") private final String owner;

        @Column(name = "issuer") private final String issuer;

        @Column(name = "amount") private final int amount;

        @Column(name = "linear_id") private final UUID linearId;

        @OneToMany(mappedBy = "persistentToken") private final List<PersistentChildToken> listOfPersistentChildTokens;

        //get() = field



        public PersistentToken(String owner, String issuer, int amount, UUID linearId, List<PersistentChildToken> listOfPersistentChildTokens) {

            this.owner = owner;

            this.issuer = issuer;

            this.amount = amount;

            this.linearId = linearId;

            this.listOfPersistentChildTokens = listOfPersistentChildTokens;

        }



        // Default constructor required by hibernate.

        public PersistentToken() {

            this.owner = "";

            this.issuer = "";

            this.amount = 0;

            this.linearId = UUID.randomUUID();

            this.listOfPersistentChildTokens = null;

        }



        public String getOwner() {

            return owner;

        }



        public String getIssuer() {

            return issuer;

        }



        public int getAmount() {

            return amount;

        }



        public UUID getLinearId() {

            return linearId;

        }



        public List<PersistentChildToken> getChildTokens() { return listOfPersistentChildTokens; }

    }



    @Entity

    @CordaSerializable

    @Table(name = "token_child_states")

    public static class PersistentChildToken {

        @Id

        private final UUID Id;

        @Column(name = "owner")

        private final String owner;

        @Column(name = "issuer")

        private final String issuer;

        @Column(name = "amount")

        private final int amount;

        @Column(name = "child proof")

        private final String childProof;

        @ManyToOne(targetEntity = PersistentToken.class)

        private final TokenState persistentToken;



        public PersistentChildToken(String owner, String issuer, int amount) {

            this.Id = UUID.randomUUID();

            this.owner = owner;

            this.issuer = issuer;

            this.amount = amount;

            this.persistentToken = null;

            this.childProof = "I am a child";

        }



        // Default constructor required by hibernate.

        public PersistentChildToken() {

            this.Id = UUID.randomUUID();

            this.owner = "";

            this.issuer = "";

            this.amount = 0;

            this.persistentToken = null;

            this.childProof = "I am a child";

        }



        public UUID getId() {

            return Id;

        }



        public String getOwner() {

            return owner;

        }



        public String getIssuer() {

            return issuer;

        }



        public int getAmount() {

            return amount;

        }



        public TokenState getPersistentToken() {

            return persistentToken;

        }

    }

}

令牌发行流程

@InitiatingFlow

@StartableByRPC

public class TokenIssueFlow extends FlowLogic<SignedTransaction> {

    private final Party owner;

    private final int amount;



    public TokenIssueFlow(Party owner, int amount) {

        this.owner = owner;

        this.amount = amount;

    }



    private final ProgressTracker progressTracker = new ProgressTracker();



    @Override

    public ProgressTracker getProgressTracker() {

        return progressTracker;

    }



    @Suspendable

    @Override

    public SignedTransaction call() throws FlowException {

        // We choose our transaction's notary (the notary prevents double-spends).

        Party notary = getServiceHub().getNetworkMapCache().getNotaryIdentities().get(0);

        // We get a reference to our own identity.

        Party issuer = getOurIdentity();



        /* ============================================================================

         *         Create our TokenState to represent on-ledger tokens

         * ===========================================================================*/



        List<TokenSchemaV1.PersistentChildToken> listOfPersistentChildTokens = new ArrayList<>();



        for (int count = 0; count <=5; count++) {

            TokenSchemaV1.PersistentChildToken child = new TokenSchemaV1.PersistentChildToken(owner.getName().toString(), issuer.getName().toString(), amount + 2);

            listOfPersistentChildTokens.add(child);

        }



        // We create our new TokenState.

        TokenState tokenState = new TokenState(issuer, owner, amount, new UniqueIdentifier(), listOfPersistentChildTokens);



        /* ============================================================================

         *      Build our token issuance transaction to update the ledger

         * ===========================================================================*/

        // We build our transaction.

        TransactionBuilder txBuilder = new TransactionBuilder();



        txBuilder.setNotary(notary);



        txBuilder.addOutputState(tokenState, TokenContract.ID);



        TokenContract.Commands.Issue commandData = new TokenContract.Commands.Issue();

        List<PublicKey> requiredSigners = ImmutableList.of(issuer.getOwningKey());

        txBuilder.addCommand(commandData, requiredSigners);



        /* ============================================================================

         *          Write our TokenContract to control token issuance!

         * ===========================================================================*/

        // We sign the transaction with our private key, making it immutable.

        SignedTransaction signedTransaction = getServiceHub().signInitialTransaction(txBuilder);



        // We check our transaction is valid based on its contracts.

        txBuilder.verify(getServiceHub());



        // We get the transaction notarised and recorded automatically by the platform.

        return subFlow(new FinalityFlow(signedTransaction));

    }

}

阅读 253

收藏
2020-06-20

共1个答案

一尘不染

我怀疑您可能需要在@OneToMany关系上(在父类中)添加一个显式的@Cascade(CascadeType.PERSIST)批注。

看一下下面的工作代码片段:

class SchemaFamily

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()
    }

    @Suppress("unused")
    @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
    }
}

请根据以上内容调整您的代码,然后报告。

2020-06-20