Резюме
Я адаптировал базовое приложение Corda Bootcamp для выдачи токенов, чтобы продемонстрировать эту проблему. Я хочу создать двунаправленное сопоставление между TokenStates и TokenChildren, где связь - один ко многим.
Каковы лучшие практики для сохранения иерархических данных? Можно ли реализовать это с помощью аннотаций JPA в схемах состояний?
У меня есть одно состояние - TokenState, которое содержит некоторые произвольные данные, а также Collection объектов с классом TokenChild. Назначение этого списка - облегчить связь «один ко многим» между записями в H2. Схема, связанная с государством, имеет соответствующие аннотации JPA (@OneToMany и @ManyToOne - см. Фрагмент кода ниже). Класс TokenState ссылается на соответствующую схему - TokenSchemaV1 в методах supportedSchemas и generateMappedObject.
Когда я запускаю TokenIssueFlow (также включенный в виде фрагмента) с консоли после развертывания и запуска узлов, транзакция выполняется успешно, но таблица token_child_states не сохраняется в h2.
Прочие примечания
Я также попытался реализовать другую стратегию, в которой оба Tokens
и 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));
}
}Привет, Джоэл! Это действительно так. TokenSchema очень проста, это просто пустой класс. -> открытый класс TokenSchema {}





Я подозреваю, что вам может потребоваться добавить явную аннотацию @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
}
}
Пожалуйста, измените свой код в соответствии с приведенным выше и сообщите об этом.
Привет, Хосе, это сработало! Я добавил аннотацию, и дочерняя таблица была сохранена в БД. Синтаксис java для аннотации был @OneToMany (cascade = CascadeType.PERSIST)
Мне также пришлось включить тег @JoinColumns, ссылающийся на output_index и transaction_id. Если я не укажу оба этих поля, будет выдана ошибка. Есть идеи, почему это так?
Привет, Хосе, не могли бы вы поделиться ссылкой, как обновить это состояние.
@Jose Coll - Спасибо, это определенно было. Далее я расскажу о моей реализации.
См. Фрагмент ниже для рабочей реализации классов схемы обновления. @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;
}
}
}
Привет, Ник, есть ли где-нибудь определение
TokenSchema?