Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BiDirectional References #130

Open
kanchev opened this issue Jan 27, 2017 · 4 comments
Open

BiDirectional References #130

kanchev opened this issue Jan 27, 2017 · 4 comments
Labels

Comments

@kanchev
Copy link

kanchev commented Jan 27, 2017

Hi there,

I'm thinking about using the pojobuilder for generating/testing a JPA entity model. The problem is that the model has bidirectional references, e.g. parent entity would have a list of children and each child would have a reference to the parent.

I've looked through your tutorials and experimented a bit but I didn't see a way to do this. Did I miss something? Is this a supported feature of Pojobuilder?

Cheers

@mkarneim
Copy link
Owner

That shouldn't be a problem.
Actually we are using bidirectional relations in all our customer projects.

You don't need to do something special for PB. Just implement the bidirectional relations as this is typically done. Make sure that both sides of the relation are always 'in sync'. That means, when you add a child to a parent make sure that the parent tells the child to set its parent reference accordingly - and vice versa.

Here is some simplified code example about how this could be done with Orders and Order Items:

@Entity
@Table(name = "ORD")
public class Order extends ... {
  ...
  @GeneratePojoBuilder(
      withBuilderInterface = Builder.class, 
      withBuilderProperties = true,      
      withOptionalProperties = Optional.class)
  public Order() {
    state = OrderState.NEW;
    creationDate = LocalDateTime.now();
  }
  ...

  @OneToMany(mappedBy = "order", orphanRemoval = true, cascade = CascadeType.ALL)
  private Set<OrderItem> items = new HashSet<>();

  public Set<OrderItem> getItems() {
    return Collections.unmodifiableSet(items);
  }

  public void setItems(Set<OrderItem> items) {
    checkNotNull(items);
    for (OrderItem item : new ArrayList<>(this.items)) {
      removeItem(item);
    }
    for (OrderItem item : items) {
      addItem(item);
    }
  }

  public void addItem(OrderItem item) {
    checkNotNull(item);
    boolean added = items.add(item);
    if (added) {
      item.setOrder(this);
    }
  }

  public void removeItem(OrderItem item) {
    checkNotNull(item);
    boolean removed = items.remove(item);
    if (removed) {
      item.setOrder(null);
    }
  }
  ...
}

@Entity
@Table(name = "ORDER_ITEM")
public class OrderItem extends ... {
  ...
  @ManyToOne
  @JoinColumn(name = "ORDER_ID", nullable = false)
  @Nullable
  private Order order;
  ...
  public void setOrder(@Nullable Order order) {
    if (Objects.equals(this.order, order)) {
      return;
    }
    if (this.order != null) {
      this.order.removeItem(this);
    }
    this.order = order;

    if (this.order != null) {
      this.order.addItem(this);
    }
  }
}

Here is how a simple integration test (for JPA beans and DB) looks like:

  @Test
  public void test_persist_order_with_items() {
    // Given:
    Order order = some($Order().withItems($setOf(several(), $OrderItem())));

    // When:
    db().persist(order);

    // Then:
    Order actual = db().reload(order);
    assertThat(actual).matches(order);
  }

This test inserts an order with several items into the database. Then it checks if the reloaded order matches the original one.

To create the test data we use so-called Builder Factories, which in our case, follow a handy naming convention. The leading $-Sign marks methods that return preconfigured Builder instances.

The builders themselves are generated wit PB's Nested Builders feature enabled.

Here is a code snippet that shows how builder factories typically are implemented:

  public OrderBuilder $Order() {
    return new OrderBuilder()
        .withId($OrderId())
        .withCustomer($Customer())
        .withState(OrderState.NEW);
  }

  public OrderItemBuilder $OrderItem() {
    return new OrderItemBuilder()
        .withId($OrderItemId())
        .withOrder($Order())
        .withProduct($Product())
        .withAmount($int());
  }

For details about the test data factory please have a look into the cookbook's Domain Specific Language page.

Please let me know if this helps you.

Cheers
Michael

@kanchev
Copy link
Author

kanchev commented Jan 27, 2017

Thanks Michael, this looks good!

We're currently doing the same with sync'ing the relations but with our hand written fluent builders. Our jpa entities are very simple: getters/setters only, e.g. to add an Item we'd have in the builder withItem(Item item) { order.getItems().add(item); item.setOrder(order); }. Unfortunately we don't have much of flexibility and can't move this logic to the entities themselves.

But anyways, since it's very basic repetitive code to add an item and sync it with the parent I was hoping that the PojoBuilder will generate it for us and we can throw away our current builders.

Were there any plans to support something like this? Some work in progress? Maybe I could help out when I have some spare time.

Cheers Aleks

@drekbour
Copy link
Contributor

@kanchev, This last is #80. @mkarneim This issue should be closed!

@drekbour
Copy link
Contributor

drekbour commented Mar 3, 2019

This issue should be closed!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants