Personally, I wouldn't try to overcomplicate the bindings too much beyond simple applications of built-in support for the high-level binding APIs. Once you add a few bindings things get complicated enough already.
Alternative 1
What I suggest you do is:
Create a filtered list of selected accounts.
Use the filtered list of selected accounts as the backing list for the second table.
As the second table is only to display cashflow data and not full account data, for column data, provide custom value factories to access the cashflow data in the account.
Making the second table a TreeTableView may make sense, that way it can group the cashflows by account.
This may or may not be a valid approach for your app.
Alternative 2
Alternately, also working off the filteredlist of accounts, add a list change listener to the filtered list, when it changes, update the content of a separate list of related cashflows which you use as the backing list for the cashflow table.
Handling your use cases.
An account is added or removed
Just add or remove from the account list.
A cashflow is added to or removed from an account
An extractor on the account list and a list listener can be triggered when associated cashflows change to trigger an update to the cashflow list.
an account gets selected or unselected
See the linked filtered list example, it is based on an extractor combined with a filtered list.
Alternative 3
Alternately, you could change your UI. For example, have separate edit and commit pages for cashflow data and account data with user button presses for committing or discarding changes. The commits update the backing database. Then, after committing, navigate back to the original page which just reads the new data from the source database again. That's generally how these things often work rather than a bunch of binding.
I realize that none of these options are what you are asking about and some of them probably do work you were trying to avoid through a different binding type, but, those are the ideas I came up with.
Example
FWIW, here is an example of Alternative 2, which relies on an account list, a filtered account list a separate cashflow list, and extractors and listeners to keep stuff in sync.
It won't be exactly what you want, but perhaps you can adapt it or learn something from it.
I would note that the cashflow list doesn't tie a given cashflow to a given account, so, if you want to do that, you might want to add additional functionality to support visual feedback for that association.
Initial state:

Select only a single account:

Remove an account and change the cashflow data for a given account:

import javafx.application.Application;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.collections.*;
import javafx.collections.transformation.FilteredList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.CheckBoxTableCell;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class FlowApp extends Application {
private final ObservableList<Account> accounts = FXCollections.observableArrayList(
account -> new Observable[] { account.selectedProperty(), account.getCashflows() }
);
private final ObservableList<Account> cashflowAccounts = new FilteredList<>(
accounts,
account -> account.selectedProperty().get()
);
private final ObservableList<Cashflow> cashflows = FXCollections.observableArrayList(
cashflow -> new Observable[] { cashflow.amountProperty() }
);
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage stage) {
cashflowAccounts.addListener((ListChangeListener<Account>) c -> updateCashflows());
initDataStructures();
final TableView<Account> accountSelectionTableView =
createAccountSelectionTableView();
final TableView<Cashflow> cashflowView =
createCashflowView();
final Button change = new Button("Change");
change.setOnAction(e -> changeData(change));
final Button reset = new Button("Reset");
reset.setOnAction(e -> { initDataStructures(); change.setDisable(false); });
final VBox vbox = new VBox(
10,
new TitledPane("Accounts", accountSelectionTableView),
new TitledPane("Cashflows", cashflowView),
new HBox(10, change, reset)
);
vbox.setPadding(new Insets(10));
stage.setScene(new Scene(vbox));
stage.show();
}
private void changeData(Button change) {
accounts.get(accounts.size() - 1);
// Paul dies.
accounts.removeIf(
account -> account.firstNameProperty().get()
.equals("Paul")
);
// Albert.
Account albert = accounts.stream()
.filter(
account -> account.firstNameProperty().get().equals(
"Albert"
)
).findFirst().orElse(null);
if (albert == null) {
return;
}
// Albert stops receiving alimony.
albert.getCashflows().removeIf(
c -> c.sourceProperty().get().equals(
CashflowSource.ALIMONY
)
);
// Albert's rent increases.
Cashflow albertsRent = albert.getCashflows().stream()
.filter(
cashflow -> cashflow.sourceProperty().get().equals(
CashflowSource.RENT
)
).findFirst().orElse(null);
if (albertsRent == null) {
return;
}
albertsRent.amountProperty().set(
albertsRent.amountProperty().get() + 5
);
// only allow one change.
change.setDisable(true);
}
private void initDataStructures() {
accounts.setAll(
new Account("Ralph", "Alpher", true, "ralph.alpher@example.com",
new Cashflow(CashflowSource.RENT, 10),
new Cashflow(CashflowSource.ALIMONY, 5)
),
new Account("Hans", "Bethe", false, "hans.bethe@example.com"),
new Account("George", "Gammow", true, "george.gammow@example.com",
new Cashflow(CashflowSource.SALARY, 3)
),
new Account("Paul", "Dirac", false, "paul.dirac@example.com",
new Cashflow(CashflowSource.RENT, 17),
new Cashflow(CashflowSource.SALARY, 4)
),
new Account("Albert", "Einstein", true, "albert.einstein@example.com",
new Cashflow(CashflowSource.RENT, 2),
new Cashflow(CashflowSource.ALIMONY, 1),
new Cashflow(CashflowSource.DIVIDENDS, 8)
)
);
}
private void updateCashflows() {
cashflows.setAll(
cashflowAccounts.stream()
.flatMap(a ->
a.getCashflows().stream()
).toList()
);
}
private TableView<Account> createAccountSelectionTableView() {
final TableView<Account> selectionTableView = new TableView<>(accounts);
selectionTableView.setPrefSize(540, 180);
TableColumn<Account, String> firstName = new TableColumn<>("First Name");
firstName.setCellValueFactory(cd -> cd.getValue().firstNameProperty());
selectionTableView.getColumns().add(firstName);
TableColumn<Account, String> lastName = new TableColumn<>("Last Name");
lastName.setCellValueFactory(cd -> cd.getValue().lastNameProperty());
selectionTableView.getColumns().add(lastName);
TableColumn<Account, Boolean> selected = new TableColumn<>("Selected");
selected.setCellValueFactory(cd -> cd.getValue().selectedProperty());
selected.setCellFactory(CheckBoxTableCell.forTableColumn(selected));
selectionTableView.getColumns().add(selected);
TableColumn<Account, String> email = new TableColumn<>("Email");
email.setCellValueFactory(cd -> cd.getValue().emailProperty());
selectionTableView.getColumns().add(email);
TableColumn<Account, Integer> numCashflows = new TableColumn<>("Num Cashflows");
numCashflows.setCellValueFactory(cd -> Bindings.size(cd.getValue().getCashflows()).asObject());
numCashflows.setStyle("-fx-alignment: baseline-right;");
selectionTableView.getColumns().add(numCashflows);
selectionTableView.setEditable(true);
return selectionTableView;
}
private TableView<Cashflow> createCashflowView() {
TableView<Cashflow> cashflowView = new TableView<>();
TableColumn<Cashflow, CashflowSource> source = new TableColumn<>("Source");
source.setCellValueFactory(cd -> cd.getValue().sourceProperty());
cashflowView.getColumns().add(source);
TableColumn<Cashflow, Integer> amount = new TableColumn<>("Amount");
amount.setCellValueFactory(cd -> cd.getValue().amountProperty().asObject());
amount.setStyle("-fx-alignment: baseline-right;");
cashflowView.getColumns().add(amount);
cashflowView.setItems(cashflows);
cashflowView.setPrefHeight(160);
return cashflowView;
}
private static class Account {
private final StringProperty firstName;
private final StringProperty lastName;
private final BooleanProperty selected;
private final StringProperty email;
private final ObservableList<Cashflow> cashflows;
private Account(String fName, String lName, boolean selected, String email, Cashflow... cashflows) {
this.firstName = new SimpleStringProperty(fName);
this.lastName = new SimpleStringProperty(lName);
this.selected = new SimpleBooleanProperty(selected);
this.email = new SimpleStringProperty(email);
this.cashflows = FXCollections.observableArrayList(cashflows);
}
public StringProperty firstNameProperty() {
return firstName;
}
public StringProperty lastNameProperty() {
return lastName;
}
public BooleanProperty selectedProperty() {
return selected;
}
public StringProperty emailProperty() {
return email;
}
public ObservableList<Cashflow> getCashflows() {
return cashflows;
}
}
class Cashflow {
private final ObjectProperty<CashflowSource> source;
private final IntegerProperty amount;
public Cashflow(CashflowSource source, int amount) {
this.source = new SimpleObjectProperty<>(source);
this.amount = new SimpleIntegerProperty(amount);
}
public ObjectProperty<CashflowSource> sourceProperty() {
return source;
}
public IntegerProperty amountProperty() {
return amount;
}
}
enum CashflowSource {
RENT, SALARY, DIVIDENDS, ALIMONY
}
}