JaVers, SpringDatat, JPA: Querying for Entity Update inside a Collection - jpa

I'm new to Stackoverflow, so I will make my best to conforms with usage. I was wondering if there were a way to get a complete list of changes/snapshots of a given Entity. For now it works well with edition of Singular Properties, as well as Addition and Deletion to Collection Property. But I'm unable to find when a Child Entity in the Collection Property was updated.
Given two Entities, and a LinkEntity:
#Entity
class Person {
#Id
Long id;
#OneToMany(mappedBy = "person", cascade = CascadeType.ALL)
Set<LinkAddress> addresses;
}
#Entity
class Address {
#Id
Long id;
#OneToMany(mappedBy = "address")
Set<Address> persons;
}
#Entity
class LinkPersonAddress {
#Id
Long id;
#ManyToOne
#ShallowReference
Person person;
#ManyToOne
#ShallowReference
Address address;
String linkType;
}
My use case is following. I get a specific Person by Id #1, and then mutate the type of specific Address (ie. HOME --> WORK). I save the Person back with the modified Set and let JPA Cascade my changes. Although all Spring Data Repositories for Person, Address, and LinkPersonAddress are annotated with #JaversSpringDataAuditable, I cannot retrieve this "update" using Javers QueryBuilder with the class Person and Id #1. It makes sense as I should query the class LinkPersonAddress instead, but how can I specify that I want only the changes from LinkPersonAddress relevant to Person with Id #1.
PS: Please apologize any typos in code snippets, as I didn't write it in my Dev Environment.

Let's start from the mapping. You did it wrong, Address is a classical ValueObject (see https://javers.org/documentation/domain-configuration/#value-object) not Entity. Because:
Address doesn't have its own identity (primary key genereted by a db sequence doesn't count)
Address is owned by the Person Entity. Person with its Addresses forms the Aggregate.
When you correct the mapping, you can use ChildValueObjects filter, see https://javers.org/documentation/jql-examples/#child-value-objects-filter

Related

Modelling a series of elements of the same entity in JPA (one-to-one relation)

I created an entity/table for a book, and I want to achieve that each element can reference another book to create a series.
I came up with this:
#Entity
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer bookId;
#Lob
private String text;
#OneToOne
#JoinColumn(name = "book_id")
private Book previousPart;
}
However, the attribute previousPart is not seen as a column in my Database, while the others are. Is there a different annotation I have to use or is this the wrong way to model a relationship like this? Or do I have to use the type Integer instead of Book directly (I thought this was what the framework does internally)?
Try this way:
#OneToOne
#JoinColumn
private Book previousPart;
In your code you defined the book_id field as the join-column, which is an already existing column (the id itself), so actually you linked all entries to themselves.

Hibernate Search - search in ManyToOne relation

I have two indexed entities.
Player:
#Indexed
#Entity
public class Player {
#Field
private String firstName;
#ContainedIn
#ManyToOne
private Club playersClub;
}
and Club:
#Indexed
#Entity
public class Club {
#Fields({
#Field(store=Store.COMPRESS),
#Field(name = "sorting_name", analyze = Analyze.NO)
})
private String name;
#IndexedEmbedded(depth = 1)
#OneToMany(mappedBy = "playersClub")
private Set<Player> players;
}
Now when I search into ClubSearchService like this:
luceneQuery = queryBuilder
...
.onField("name").andField("players.firstName")
...
it works fine, but when I want search in other way(PlayerSearchService):
.onField("firstName").andField("lastName").andField("number").andField("country").andField("playersClub.name")
there is an error
org.hibernate.search.SearchException: Unable to find field playersClub.name in pl.domain.Player
Hibernate Search cannot search into ManyToOne item?
Yes it is possible to index ManyToOne relations as well.
In most simple cases you have one entity such as Club marked as #Indexed and then you want to index some of its fields and also embed via #IndexedEmbedded attributes from a related entity Player.
So far you got it right, and this is defining essentially the "flattened" schema for your club index.
The problem is that when defining the schema for the player index you marked firstname as an indexed field, but didn't instruct it to embed the relation to playersClub. Essentially you're missing the #IndexedEmbedded on this property.
To not be confused with the #ContainedIn annotation: this is purely to make sure that Hibernate Search will re-index the entities in which it is contained; it is not making both sides embedding each other in a symmetric way.
In more formal terms, the relation established by #IndexedEmbedded is a directed graph: if you want it to follow both directions you have to establish them both explicitly.

Using JPA CompositeKey uses a property from another Entity

I tried searching but was having problems finding what I want. I have the following schema( * indicates primary key)
USER
*UserId
-Desc
Registration
*DeviceId
*UserId
-date
So I want to create a primary key for Registration like...
#Embeddable
public class RegPk{
private String deviceId;
private User user;
#Column(name="DEV_ID")
public String getDevId(){
return deviceId;
}
...
#ManyToOne
#JoinColumn(name="USER_ID", referencedColumnName="USER_ID")
public User getUser() {
return user;
}
...
}
Is this right? Will it update the USER_ID field properly update in registration when I persist?
When I try this kind of thing out I get the following....
[10/7/13 13:37:07:156 EDT] 000000ae webapp E com.ibm.ws.webcontainer.webapp.WebApp logServletError SRVE0293E: [Servlet Error]-[Hello]: org.apache.openjpa.util.MetaDataException: The id class specified by type "class org.me.mfa.jpa.Registration" does not match the primary key fields of the class. Make sure your identity class has the same primary keys as your persistent type, including pk field types. Mismatched property: "user"
So what now?
JPA does not allow primary key classes to contain relationships, only basic types. JPA 2.0 allows relationships to be apart of the ID, but you would move the relationship to the entity class, and have RegPk contain a deviceId and UserId. The Device-User relationship would then be marked with either #Id or #MapsId("user") depending on if you wanted to use a #PKClass or #EmbeddedId within your entity. See http://wiki.eclipse.org/EclipseLink/Examples/JPA/2.0/DerivedIdentifiers for an example using a pk class.
In JPA 1.0, you would use a similar setup, except that your Device-User relationship would be marked read-only (either by specifying the #PrimaryKeyJoinColumn annotation, or by marking the #JoinColumn with insertable=false, updatable=false). You would then need to set the primary key basic mapping value manually, pulling the value from the referenced User entity directly. This of course requires that the id in User already be assigned, which might require additional work if both objects are new.
I would add cascade = CascadeType.ALL to #ManyToOne annotation to forward persist action for User entity.
It will look like:
...
#ManyToOne(optional=true, cascade = CascadeType.ALL)
#JoinColumn(name="USER_ID", referencedColumnName="USER_ID")
public User getUser() {
return user;
}
...
Well, if there is not other error, it could work. I'm little confused with optional=true, cuz it can provide id with deviceId,null but that could be OK also.

How do I properly annotate two JPA entities which are in a parent child relationship?

Maybe this is a question with an easy answer ... but I don't get it running. At persist() I get the exception that the referential key in the child table is null (which of course is not allowed by the database). I have a recipe and some steps for preparation.
I'm using EclipseLink 2.4.1
Recipe.java (rcpid is autoset by JPA)
#Entity
public class Recipe {
#Id
long rcpid;
List<Recipestep> recipesteps = new ArrayList<>();
#OneToMany(
cascade=CascadeType.ALL,
fetch=FetchType.EAGER,
mappedBy="recipe",
targetEntity=Recipestep.class )
// This does NOT work. Following line tries to access a join-table !!!
// #JoinColumn(name="rcpid", referencedColumnName="rcpid")
public List<Recipestep> getRecipesteps() { return recipesteps; }
// some more attributes, getters and setters
}
Recipestep.java (rpsid is autoset by JPA)
#Entity
public class Recipestep {
#Id
long rpsid;
Recipe recipe;
#ManyToOne( targetEntity=Recipe.class )
#JoinColumn( name="rcpid" )
public Recipe getRecipe() { return recipe; }
// some more attributes, getters and setters
}
The code above is a valid workaround. However to have clean (and supportable) code, the relationship should be only one-way with a collection in the parent which references all its children.
You have mapped this as a unidirectional one to many, but have two mappings for the recipestep rcpid database column. Try changing the long rcpid to
#ManyTOne
Recipe rcp;
And then remove the joincolumn definition from the oneToMany and make it bidirectional by marking it as mappedby the rcp manyToOne relation. An example is posted here http://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Mapping/Relationship_Mappings/Collection_Mappings/OneToMany
Eclipselink will always insert nulls on unidirectional oneToMany relations using a joincolumn when first inserting the target entity, and then update it later when it processes the Recipe entity. Your rcpid mapping in Recipestep is also likely null, which means you have two write able mappings for the same field which is bad especially when they conflict like this.
You are experiencing the default JPA behaviour. Adding an entity to the recipesteps list is not sufficient to create a bidirectional relation.
To solve the issue you need to set the rcpid explicitly on every element in the list.
EDIT: I think the issue is that JPA does not know where to store the id of the Recipe in the Recipestep table. It assumes a name ("recipebo_rcpid"), but your table seems to lack it.
Try adding the column "recipe_id" to the Recipestep table and a mappedBy attribute to the #OneToMany annotation:
#OneToMany(
cascade=CascadeType.ALL,
fetch = FetchType.EAGER,
mappedBy = "recipe" )
You probably do not need the targetEntity attribute in the annotation- the List is typed already.

Behavior of JPA ORM of two entities with different CascadeType parameter?

I have a question regarding the JPA OR mapping between two persistent entities with a different CascadeType parameter on their annotations.
To clarify things, here is a simple example:
#Entity
public class Article
{
#Id
#GeneratedValue
private Long id;
#ManyToOne( cascade = CascadeType.ALL )
private Author author;
// Getters and Setters follow here
}
_
#Entity
public class Author
{
#Id
#GeneratedValue
private Long id;
#OneToMany( mappedBy = "author", cascade = CascadeType.REFRESH,
orphanRemoval = true )
private List< Article > articles;
// Getters and Setters follow here
}
As you can see, the "author" property has a different CascadeType set
(CascadeType.REFRESH) then the "articles" property (CascadeType.ALL). At first, I thought that a different CascadeType for the same property mapping is not allowed - but I tried it, and it is allowed.
Now, what I would like to know is, how does this behave? And makes such a (artifical) example any sense at all (as you see, this is more a theoretical question)?
Thanks a lot for your help!
cascade = CascadeType.XXX means: when you do the XXX operation on this object, automatically do the same XXX operation on the object(s) referenced by the association.
So, in your case, if you persist/merge/delete an article, it will also persist/merge/delete its author. This is thus very questionable. I don't think you really want that.
And when you'll refresh an author, it will also refresh its articles.
Note that if you refresh an article, it will refresh its author (because of CascadeType.ALL), and since the association form author to articles also has the REFRESH cascade type, it will also refresh all the articles of the author.