pojobuilder
pojobuilder copied to clipboard
BiDirectional References
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
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
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
@kanchev, This last is #80. @mkarneim This issue should be closed!
This issue should be closed!