Corda: Как реализовать иерархические отношения между данными состояния, сохраненными до H2 - PullRequest
0 голосов
/ 09 ноября 2018

Резюме

Я адаптировал базовое приложение Token Issuance Corda Bootcamp, чтобы продемонстрировать эту проблему. Я хочу создать двунаправленное сопоставление между TokenStates и TokenChildren, где отношения один-ко-многим.

Каковы лучшие практики для сохранения иерархических данных? Можно ли реализовать это, используя аннотации JPA в схемах состояний?

У меня есть одно состояние - TokenState, которое содержит некоторые произвольные данные, а также Collection объектов с классом TokenChild. Цель этого списка состоит в том, чтобы облегчить отношение один ко многим между записями в H2. Схема, связанная с состоянием, имеет соответствующие аннотации JPA (@OneToMany и @ManyToOne - см. Фрагмент кода ниже). Класс TokenState ссылается на соответствующую схему - TokenSchemaV1 в методах supportedSchemas и generateMappedObject.

Когда я запускаю TokenIssueFlow (также включенный как фрагмент) из консоли после развертывания и запуска узлов, транзакция завершается успешно, но таблица token_child_states не сохраняется до h2.

Другие примечания

  • Я также пытался реализовать другую стратегию, в которой оба жетона
    и TokenChildren - уникальные состояния (а не одно монолитное
    государство). См. этот выпуск Github для получения более подробной информации.

  • Другим решением может быть использование Tokens и TokenChildren в качестве отдельные состояния и вручную сохраняемые внешние ключи в h2 для облегчить эти отношения, но это кажется обходным решение.

  • Каковы последствия для еще более глубоко вложенных отношений между классами? (например, надуманный пример, когда TokenChildren TokenGrandChildren и пр.). Как я могу использовать generateMappedObject() и supportedSchemas() для создания нужной мне модели данных?

TokenState

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

}

TokenSchemaV1

@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;
        }
    }
}

TokenIssueFlow

@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));
    }
}

Ответы [ 2 ]

0 голосов
/ 13 ноября 2018

@ Хосе Колл - Спасибо, это определенно было. Следите за дополнительной информацией о моей реализации.

См. Фрагмент ниже для работающей реализации классов схемы обновления. @OneToMany (cascade = CascadeType.PERSIST) заставил дочернюю таблицу быть сохраненной в БД при инициализации узла. Мне также пришлось включить тег @JoinColumn с соответствующими полями.

@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(cascade = CascadeType.PERSIST)
        @JoinColumns({
                @JoinColumn(name = "output_index", referencedColumnName = "output_index"),
                @JoinColumn(name = "transaction_id", referencedColumnName = "transaction_id"),
        })
        private final List<PersistentChildToken> listOfPersistentChildTokens;

        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;
        }
    }
}
0 голосов
/ 12 ноября 2018

Я подозреваю, что вам может понадобиться добавить явную аннотацию @Cascade (CascadeType.PERSIST) в ваши отношения @OneToMany (в родительском классе).

Взгляните на следующий рабочий код:

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

Пожалуйста, настройте свой код в соответствии с вышеприведенным и сообщите обратно.

...