Hibernate5Module doesn't fetch OneToMany collection - spring

First of all I have had two related entities mapped with bidirectional OneToMany:
#Data
#Entity
#Table(name = "tbl_parent")
public class Parent {
#Id
#Column(name = "parent_id")
private Long id;
...
#OneToMany(mappedBy = "parent")
private List<Child> children = new ArrayList<>();
}
and
#Data
#Entity
#Table(name = "tbl_child")
#ToString(exclude = "parent")
public class Child {
#Id
#Column(name = "child_id")
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "parent_id")
private Parent parent;
}
However, I had StackOverFlow for method findAll() for Parent entity as far as infinite loop occurred during serialization.
Then I added property in application.yml:
spring:jsp:open-in-view: false
And had another problem with serialization. To solve it I added dependency in build.gradle:
implementation "com.fasterxml.jackson.datatype:jackson-datatype-hibernate5"
And configuration class:
#Configuration
public class JacksonConfiguration {
#Bean
public JavaTimeModule javaTimeModule() {
return new JavaTimeModule();
}
#Bean
public Jdk8Module jdk8TimeModule() {
return new Jdk8Module();
}
#Bean
public Hibernate5Module hibernate5Module() {
return new Hibernate5Module();
}
}
Now I have no error but list in parent entity is always null.
What I'm doing wrong? How can I fetch all related children entities without StackOverflow?

#Data annotation from Lombok can cause stackoverflows due to circular dependencies between classes if both classes have #Data and collections/toStrings mentioning each other.
See Lombok.hashCode issue with "java.lang.StackOverflowError: null"

The issue is quite simple: you cannot hope to serialize both sides of a bidirectional association. If you try to do that, you'll get a JSON representation of Parent with a children property, and each object in children will have a parent property, with each parent containing a children property, and so on, ad nauseam.
From what you describe, I'm assuming you want to opt out of serializing the parent property, in which case you should put #JsonIgnore on Child.parent.
Also, since you enabled the HibernateModule, which by default ignores lazy properties, you should either disable it, enable its FORCE_LAZY_LOADING feature, or configure Parent.children to be eagerly fetched. The third option also eliminates the need for #JsonIgnore.
(all three options have potentially far-reaching consequences, so you need to decide which one works best for your use case; also note that having to use open-jpa-in-view is a code smell, because forcing the lazy load happens outside of any transactional context)

Related

Axon - State Stored Aggregates exception in test

Environment setup : Axon 4.4, H2Database( we are doing component testing as part of the CI)
Code looks something like this.
#Aggregate(repository = "ARepository")
#Entity
#DynamicUpdate
#Table(name = "A")
#Getter
#Setter
#NoArgsConstructor
#EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false)
#Log4j2
Class A implements Serializable {
#CommandHandler
public void handle(final Command1 c1) {
apply(EventBuilder.buildEvent(c1));
}
#EventSourcingHandler
public void on(final Event1 e1) {
//some updates to the modela
apply(new Event2());
}
#Id
#AggregateIdentifier
#EntityId
#Column(name = "id", length = 40, nullable = false)
private String id;
#OneToMany(
cascade = CascadeType.ALL,
fetch = FetchType.LAZY,
orphanRemoval = true,
targetEntity = B.class,
mappedBy = "id")
#AggregateMember(eventForwardingMode = ForwardMatchingInstances.class)
#JsonIgnoreProperties("id")
private List<C> transactions = new ArrayList<>();
}
#Entity
#Table(name = "B")
#DynamicUpdate
#Getter
#Setter
#NoArgsConstructor
#EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false)
#Log4j2
Class B implements Serializable {
#Id
#EntityId
#Column(name = "id", nullable = false)
#AggregateIdentifier
private String id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumns({#JoinColumn(name = "id", referencedColumnName = "id")})
#JsonIgnoreProperties("transactions")
private A a;
#EventSourcingHandler
public void on(final Event2 e2) {
//some updates to the model
}
}
I'm using a state store aggregate but I keep getting the error randomly during Spring Test with embedded H2. The same issue does not occur with a PGSQL DB in non embedded mode but than we are not capable of runnign it in the pipeline.
Error : "java.lang.IllegalStateException: The aggregate identifier has not been set. It must be set at the latest when applying the creation event"
I stepped through AnnotatedAggregate
protected <P> EventMessage<P> createMessage(P payload, MetaData metaData) {
if (lastKnownSequence != null) {
String type = inspector.declaredType(rootType())
.orElse(rootType().getSimpleName());
long seq = lastKnownSequence + 1;
String id = identifierAsString();
if (id == null) {
Assert.state(seq == 0,
() -> "The aggregate identifier has not been set. It must be set at the latest when applying the creation event");
return new LazyIdentifierDomainEventMessage<>(type, seq, payload, metaData);
}
return new GenericDomainEventMessage<>(type, identifierAsString(), seq, payload, metaData);
}
return new GenericEventMessage<>(payload, metaData);
}
The sequence for this gets set to 2 and hence it throws the exception instead of lazily initializing the aggregate
Whats the fix for this? Am i missing some configuration or needs a fix in Axon code?
I believe the exception you are getting is the pointer to what you are missing #Rohitdev. When an aggregate is being created in Axon, it at the very least assume you will set the aggregate identifier. Thus, that you will fill in the #AggregateIdentifier annotated field present in your Aggregate.
This is a mandatory validation as without an Aggregate Identifier, you are essentially missing the external reference towards the Aggregate. Due to this, you would simply to be able to dispatch following commands to this Aggregate, as there is no means to route them.
From the code snippets you've shared, there is nothing which indicates that the #AggregateIdentifier annotated String id fields in Aggregate A or B are ever set. Not doing this in combination with using Axon's test fixtures will lead you the the exception you are getting.
When using a state-stored aggregate, know that you will change the state of the aggregate inside the command handler. This means that next to invoke in the AggregateLifecycle#apply(Object) method in your command handler, you will set the id to the desired aggregate identifier.
There are two main other pointers to share based on the question.
There is no command handler inside your aggregate which creates the aggregate itself. You should either have an #CommandHandler annotated constructor in your aggregates, or use the #CreationPolicy annotation to define a regular method as the creation point of the aggregate (as mentioned here in the reference guide).
Lastly, your sample still uses #EventSourcingHandler annotated functions, which should be used when you have an Event Sourced Aggregate. It sounds like you have made a conscious decision against Event Sourcing, hence I wouldn't use those annotations either in your model. Right now it will likely only confuse developers that a mix of state-stored and event sourced aggregate logic is being used.
Finally after debugging we found out that in class B we were not setting the id for update event
#EventSourcingHandler
public void on(final Event2 e2) {
this.id=e2.getId();
}
Once we did that the issue went away.

JaversException ENTITY_INSTANCE_WITH_NULL_ID for ignored id

Using javers 5.11.2 I get the following exception although the id is set to be ignored. Why is that?
JaversException ENTITY_INSTANCE_WITH_NULL_ID: Found Entity instance 'my.package.javers.Leaf' with null Id-property 'id'
Update: I learned that
JaVers matches only objects with the same GlobalId
The id is specified using javax.persistence.Id. However, with each ORM it is possible to have an entity with a collection, then add a new element without id to that entity and then save it (CascadeType.Persist).
Is there any way to compare objects with javers in such a case?
Example (used lombok for boiler plate code).
The leaf:
#AllArgsConstructor
#NoArgsConstructor
#Builder
#Data
#Entity
public class Leaf {
#DiffIgnore <============ id is ignored
#Id
private Long id;
private String color;
}
The tree:
#Builder
#Data
#Entity
public class Tree {
#Id
private Long id;
private String name;
#OneToMany
private Set<Leaf> leafs;
}
Test adds a leaf to the oakSecond without an id set. The diff cannot be made. An Exception is thrown.
#Test
public void testCompare_AddLeafToTree() {
Leaf leaf = Leaf.builder().id(1L).color("11").build();
Set<Leaf> leafsOfOakFirst = new HashSet<>();
leafsOfOakFirst.add(leaf);
Tree oakFirst = Tree.builder().id(1L).name("oakFirst").build();
oakFirst.setLeafs(leafsOfOakFirst);
Set<Leaf> leafsOfOakSecond = new HashSet<>();
leafsOfOakSecond.add(leaf);
leafsOfOakSecond.add(Leaf.builder().color("12").build());
Tree oakSecond = Tree.builder().id(1L).name("oakFirst").build();
oakSecond.setLeafs(leafsOfOakSecond);
Javers javers = JaversBuilder.javers().build();
Changes changes = javers.compare(oakFirst, oakSecond).getChanges();
assertThat(changes).isNotEmpty();
}
Same with the following definition of the Javers instance:
EntityDefinition leafEntityDefinition = EntityDefinitionBuilder.entityDefinition(Leaf.class).withIgnoredProperties("id").build();
Javers javers = JaversBuilder.javers().registerEntity(leafEntityDefinition).build();
You can't pass an Entity to Javers with null Id because it would be non-identifiable. If you use Hibernate to generate your Ids, make sure that you pass your object to javers.commit() after hibernate are done with its job. That's how the #JaversSpringDataAuditable aspect works.
Alternatively, you can model those objects with unstable IDs as Value Object in Javers.

Cannot Query Neo4j Repositories

Hey everyone I am fairly new to Neo4j and am having an issue querying my repositories.
Repository is the follow:
public interface NodeOneRepository extends GraphRepository<NodeOne> {
List<NodeOne> findByNodeTwoNodeThreeAndActiveTrue(NodeThree nodeThree);
}
My entities are the following:
#NodeEntity(label = "NodeOne")
public class NodeOne {
#GraphId
private Long id;
private Boolean active = TRUE;
#Relationship(type = "IS_ON")
private NodeTwo nodeTwo;
}
#NodeEntity(label = "NodeTwo")
public class NodeTwo {
#GraphId
private Long id;
#Relationship(type = "CONTAINS", direction = "INCOMING")
private NodeThree nodeThree;
#Relationship(type = "IS_ON", direction = "INCOMING")
private List<NodeOne> nodeOnes = new ArrayList<>();
}
#NodeEntity(label = "NodeThree")
public class NodeThree {
#GraphId
private Long id;
#Relationship(type = "CONTAINS")
private List<NodeTwo> nodeTwos = new ArrayList<>();
}
Getters & Setters omitted. When I call the method I get an empty list. Is there something I am doing incorrectly?
You didn't describe exactly what you wanted to achieve, but I can see two problems:
Problem 1:
The current version of Spring Data Neo4j and OGM only allow nested finders, that is, finders that specify a relationship property, to one depth.
Supported
findByNodeTwoSomePropertyAndActiveTrue(String relatedNodePropertyValue)
Not Supported
findByNodeTwoNodeThree //Nesting relationships in finders is not supported
Problem 2:
Derived Finders Allow Matching Properties and Nested Properties. Not a whole instance of that class.
You can probably achieve what you would like using a custom query.
#Query("custom query here")
List<NodeOne> findByNodeTwoNodeThreeAndActiveTrue(NodeThree nodeThree);
If you need help to write a custom query, you can post another question or join the neo4j-users public slack channel.

Spring Data Rest - sort by nested property

I have a database service using Spring Boot 1.5.1 and Spring Data Rest. I am storing my entities in a MySQL database, and accessing them over REST using Spring's PagingAndSortingRepository. I found this which states that sorting by nested parameters is supported, but I cannot find a way to sort by nested fields.
I have these classes:
#Entity(name = "Person")
#Table(name = "PERSON")
public class Person {
#ManyToOne
protected Address address;
#ManyToOne(targetEntity = Name.class, cascade = {
CascadeType.ALL
})
#JoinColumn(name = "NAME_PERSON_ID")
protected Name name;
#Id
protected Long id;
// Setter, getters, etc.
}
#Entity(name = "Name")
#Table(name = "NAME")
public class Name{
protected String firstName;
protected String lastName;
#Id
protected Long id;
// Setter, getters, etc.
}
For example, when using the method:
Page<Person> findByAddress_Id(#Param("id") String id, Pageable pageable);
And calling the URI http://localhost:8080/people/search/findByAddress_Id?id=1&sort=name_lastName,desc, the sort parameter is completely ignored by Spring.
The parameters sort=name.lastName and sort=nameLastName did not work either.
Am I forming the Rest request wrong, or missing some configuration?
Thank you!
The workaround I found is to create an extra read-only property for sorting purposes only. Building on the example above:
#Entity(name = "Person")
#Table(name = "PERSON")
public class Person {
// read only, for sorting purposes only
// #JsonIgnore // we can hide it from the clients, if needed
#RestResource(exported=false) // read only so we can map 2 fields to the same database column
#ManyToOne
#JoinColumn(name = "address_id", insertable = false, updatable = false)
private Address address;
// We still want the linkable association created to work as before so we manually override the relation and path
#RestResource(exported=true, rel="address", path="address")
#ManyToOne
private Address addressLink;
...
}
The drawback for the proposed workaround is that we now have to explicitly duplicate all the properties for which we want to support nested sorting.
LATER EDIT: another drawback is that we cannot hide the embedded property from the clients. In my original answer, I was suggesting we can add #JsonIgnore, but apparently that breaks the sort.
I debugged through that and it looks like the issue that Alan mentioned.
I found workaround that could help:
Create own controller, inject your repo and optionally projection factory (if you need projections). Implement get method to delegate call to your repository
#RestController
#RequestMapping("/people")
public class PeopleController {
#Autowired
PersonRepository repository;
//#Autowired
//PagedResourcesAssembler<MyDTO> resourceAssembler;
#GetMapping("/by-address/{addressId}")
public Page<Person> getByAddress(#PathVariable("addressId") Long addressId, Pageable page) {
// spring doesn't spoil your sort here ...
Page<Person> page = repository.findByAddress_Id(addressId, page)
// optionally, apply projection
// to return DTO/specifically loaded Entity objects ...
// return type would be then PagedResources<Resource<MyDTO>>
// return resourceAssembler.toResource(page.map(...))
return page;
}
}
This works for me with 2.6.8.RELEASE; the issue seems to be in all versions.
From Spring Data REST documentation:
Sorting by linkable associations (that is, links to top-level resources) is not supported.
https://docs.spring.io/spring-data/rest/docs/current/reference/html/#paging-and-sorting.sorting
An alternative that I found was use #ResResource(exported=false).
This is not valid (expecially for legacy Spring Data REST projects) because avoid that the resource/entity will be loaded HTTP links:
JacksonBinder
BeanDeserializerBuilder updateBuilder throws
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of ' com...' no String-argument constructor/factory method to deserialize from String value
I tried activate sort by linkable associations with help of annotations but without success because we need always need override the mappPropertyPath method of JacksonMappingAwareSortTranslator.SortTranslator detect the annotation:
if (associations.isLinkableAssociation(persistentProperty)) {
if(!persistentProperty.isAnnotationPresent(SortByLinkableAssociation.class)) {
return Collections.emptyList();
}
}
Annotation
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface SortByLinkableAssociation {
}
At project mark association as #SortByLinkableAssociation:
#ManyToOne
#SortByLinkableAssociation
private Name name;
Really I didn't find a clear and success solution to this issue but decide to expose it to let think about it or even Spring team take in consideration to include at nexts releases.
Please see https://stackoverflow.com/a/66135148/6673169 for possible workaround/hack, when we wanted sorting by linked entity.

spring jpa projection nested bean

is it possible to have a projection with nested collection with Spring JPA?
I have the following 2 simple entity (to explain the problem)
#Entity
#Table(name = "person")
public class Person implements Serializable {
private Integer id;
private String name;
#OneToMany
private List<Address> addressList = new ArrayList<>();
}
#Entity
#Table(name = "address")
public class Address implements Serializable {
private Integer id;
private String city;
private String street;
}
Is it possible to have a projection of Person with following attributes filled in ? {person.name, address.city}
I might be wrong in semantics of word Projection. but the problem is what i need to achieve. Maybe it is not possible with Projection, but is there another way to achieve the end goal? Named Entity graph perhaps ?
P.S. please suggest a solution for Spring JPA not Spring Jpa REST
thanks in advance
You're right, Entity Graphs serve this exact purpose - control field loading.
Create entity graphs dynamically from the code or annotate target entities with Named Entity Graphs and then just use their name.
Here is how to modify your Person class to use Named Entity Graphs:
#Entity
#Table(name = "person")
#NamedEntityGraph(name = "persion.name.with.city",
attributeNodes = #NamedAttributeNode(value = "addressList", subgraph = "addresses.city"),
subgraphs = #NamedSubgraph(name = "addresses.city", attributeNodes = #NamedAttributeNode("city")))
public class Person implements Serializable {
private Integer id;
private String name;
#OneToMany
private List<Address> addressList;
}
And then when loading your person:
EntityGraph graph = em.getEntityGraph("person.name.with.city");
Map hints = new HashMap();
hints.put("javax.persistence.fetchgraph", graph);
return em.find(Person.class, personId, hints);
The same applies for queries, not only em.find method.
Look this tutorial for more details.
I think that that's not usual scenario of Data JPA usage. But you can achieve your goal with pure JPQL:
SELECT a.street, a.person.name FROM Address a WHERE …
This solution has 2 drawbacks:
It forces you to have bidirectional relationship Address ←→ Person
It returns List
Another solution (and that's preferred JPA way) is to create DTO like this:
class MyPersonDTO {
private String personName;
private List<String> cities;
public MyPersonDTO(String personName, List<Address> adresses) {
this.personName = personName;
cities = adresses
.stream()
.map(Address::getCity)
.collect(Collectors.toList());
}
}
And the execute JPQL query like this:
SELECT NEW package.MyPersonDTO(p.name, p.addressList) FROM Person p WHERE …
Return type will be List<MyPersonDTO> in that case.
Of course you can use any of this solutions inside #Query annotation and it should work.

Resources