hibernate-orm/design/working/circular-fetching.adoc

208 lines
4.1 KiB
Plaintext

= Circular fetching
[IMPORTANT]
====
We need to make sure that any association is only join-fetched once. E.g.
```
from LineItem l
join fetch l.order o
join fetch l.order o2
```
This should be illegal. It is possible to handle it specially, but that would be very complicated.
`FromClauseIndex#findJoinFetch(NavigablePath parentPath, String fetchableName)` - if this does not return null we
know that the association is already join fetched and we should throw and exception
====
== one-to-one
```
@Entity
class Root {
...
@OneToOne(mappedBy="root1")
Leaf leaf1;
@OneToOne(mappedBy="root2")
Leaf leaf2;
}
@Entity
class Leaf {
...
@OneToOne
@JoinColumn(name="root_id_1")
Root root1;
@OneToOne
@JoinColumn(name="root_id_2")
Root root2;
}
```
Given this model, we have the following mappings from modelPart to "identifying columns":
* `Root#leaf1` -> `leaf.root_id_1`
* `Root#leaf2` -> `leaf.root_id_2`
* `Leaf#root1` -> `leaf.root_id_1`
* `Leaf#root2` -> `leaf.root_id_2`
So given a query like:
```
from Root r
join fetch r.leaf1 l
join fetch l.root1
join fetch l.root2
```
`l.root1` is circular whereas `l.root2` is not. We'd know this by looking at the "identifying columns".
Specifically, `l.root1` is considered circular **not** because it refers back to `Root(r)` but because it maps to the
same column(s) as its parent: `leaf.root_id_1`
// we need to be able to ultimately be able to resolve the "identifying columns" for a given path. E.g.
```
interface DomainResultCreationState {
...
Fetchable resolveFetchable(NavigablePath navigablePath) {
// the path passed in here would be `pathToParent` (i.e. `Root(r).leaf1(l)`)
// we'd used that to determine the lhs's identifying columns via
// `Fetchable#getIdentifyingColumnExpressions` and check them against the
// identifying columns for the Fetchable we are processing
}
}
```
== many-to-one
```
@Entity
@Table(name="orders")
class Order {
...
@OneToMany(mappedBy="order")
List<LineItem> lineItems;
}
@Entity
@Table(name="lines")
class LineItem {
...
@ManyToOne
@JoinColumn(name="order_id")
Order order;
}
```
Given this model, we have the following mappings from modelPart to "identifying columns":
* `LineItem#order` -> `lines.order_id`
* `Order#lineItems#{element}` -> `lines.order_id`
Once we find a circularity we should build the `BiDirectionalFetch` reference pointing to the
Initializer for the "parent parent path". See `RowProcessingState#.resolveInitializer`
Hibernate needs to handle circularity in a fetch-graph. E.g.:
```
select o
from Order o
join fetch o.lineItems l
join fetch l.order o2
join fetch o2.lineItems
```
Here, the join fetch of `l.order` is circular, meaning we do not want to render a join in the SQL for it
because it is already part of the from-clause via `Order o`.
Recognizing circularity needs to happen in a number of mapping scenarios and I believe the conditions vary
depending on the type of mapping involved (one-to-one, many-to-one, many-to-many). Ideally we can find commonality
and handle these conditions uniformly.
== with embeddables
```
@Entity
@Table(name="root")
class RootEntity {
...
@Embedded
IntermediateComponent intermediateComponent;
}
@Embeddable
class IntermediateComponent {
...
@OneToMany( mappedBy = "rootEntity" )
Set<LeafEntity> leaves
}
@Entity
@Table(name="leaf")
class LeafEntity {
...
@ManyToOne
@JoinColumn(name="root_id)
RootEntity rootEntity;
}
```
Given this model, we have the following mappings from modelPart to "identifying columns":
* `RootEntity#intermediateComponent#leaves -> `leaf.root_id`
* `RootEntity#intermediateComponent#leaves -> `leaf.root_id`
*
* `RootEntity#intermediateComponent#leaves#{element}
* `Order#lineItems#{element}` -> `lines.order_id`
class Order {
@OneToMany(mappedBy="order")
List<LineItem> lineItems;
}
```
------------
"orders"
------------
id INTEGER
name VARCHAR
------------
"order_items"
------------
orders_id
items_id
------------
"items"
------------
id
qty
```