Reputation: 657
I have 3 tables in the DB and 3 JPA entities respectively in Java application.
@Data
@Entity
public class Fraud {
@Id
@Column(name = "id")
private Integer id;
@Column(name = "fraud_type")
private String fraudType;
@Column(name = "fraud_value")
private String fraudValue;
@OneToMany(mappedBy = "fraud", fetch = FetchType.EAGER)
private List<FraudActionEntity> fraudActions;
}
@Data
@Entity
public class FraudActionEntity {
@Id
@Column(name = "id")
private Integer id;
@ManyToOne
@JoinColumn(name = "fraud_id")
private Fraud fraud;
@ManyToOne
@JoinColumn(name = "action_id")
private Action action;
@Column(name = "enabled")
private Boolean enabled;
}
@Data
@Entity
public class Action {
@Id
@Column(name = "id")
private Integer id;
@Column(name = "attribute_key")
private String attributeKey;
@Column(name = "attribute_value")
private String attributeValue;
}
@Repository
public interface FraudRepository extends JpaRepository<Fraud, Integer> {
public Fraud findByFraudTypeAndFraudValue(String fraudType, String fraudValue);
}
On a certain type of fraud, I want to traverse all the actions that triggers from that type of fraud and act on them.
Fraud fraud = fraudRepository.findByFraudTypeAndFraudValue("Type", "Value");
log.info(fraud.getFraudActions().get(0).getAction());
When I above code runs, everything works OK. I get the fraud
and fraudActions
associations as well, without getting any error.
I was under the impression that as both entities Fraud
and FraudActionEntity
are fetching each other eagerly, so it should give some error like cyclic fetch/infinite fetch loop, but it didn't!
Why did it work? And when exactly will give it error like cyclic fetch error OR infinite fetch loop error? And if it does give a cyclic fetch error, can we fix it using lazy fetch at @ManyToOne side as given below:
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "fraud_id")
private Fraud fraud;
Upvotes: 0
Views: 35
Reputation: 21165
These are JPA annotations and cycles, which providers know about and so knows how to deal with it - how isn't detailed exactly in the specification, but that they work pretty much is.
You've only got one cycle, the bidirectional Fraud-FraudActionEntity relationship. Some level of entity caching is required by JPA providers for entity identity management already. if you peform:
Fraud fraudA = em.find(1, Fraud.class);
Fraud fraudAPrime = em.find(1, Fraud.class);
assertTrue(fraudA == fraudAPrime)
JPA requires that fraudA and fraudAPrime must be the same object instance.
JPA mandated that references (FKs) reference only an entities primary key, making cache lookups to build these references manageable. So when building a Fraud and fetching FraudActionEntity, it can check the cache for each and just return that instance if it is already there. Same if it is building a FraudActionEntity instance - it can check if the Fraud referenced by the FK value already exists and return if from the cache instead of having to build it from the DB. This short circuits problems with bidirectional and cyclic references, and ensure that you only ever get the 'fraudA' instance when traversing any object graph read from a context, no matter how you traverse the graph or fetch it. Object identity is fundamental to JPA.
Lazy is one other method JPA allows for providers to deal with large object graphs: If it doesn't need to fetch something, that solves the problem too. This can help with cycles, but is more aimed at pruning large object graphs. Reading in a Fraud might be a problem if you had the Actions have a 1:M to FraudActionEntity. You just didn't map the relationship, pruning the graph when fetching frauds similar to how lazy does, but without the hooks to fetch it later (you'd have to write your own find by action method).
Where problems with cycles usually occur is when some other form of serialization is involved. Common with REST/Spring and the like is that these entity instances will be serialized into JSON. These serialization tools (Jackson is common) are NOT JPA implementations, so do not know about or even look at JPA annotations. They are what would hit issues with this circular object reference, and you can easily find questions and solutions on how to deal with that here - in short, this level of serialization needs to be looked at and dealt with, and the solutions can be quite different from the model you need to read from the database. They don't have the same entity caching mechanisms built in, so should be tuned independently based on application performance needs and requirements.
Upvotes: 1