Cannot use an #IdClass attribute for a #ManyToOne relationship - jpa

I have a Gfh_i18n entity, with a composite key (#IdClass):
#Entity #IdClass(es.caib.gesma.petcom.data.entity.id.Gfh_i18n_id.class)
public class Gfh_i18n implements Serializable {
#Id #Column(length=10, nullable = false)
private String localeId = null;
#Id <-- This is the attribute causing issues
private Gfh gfh = null;
....
}
And the id class
public class Gfh_i18n_id implements Serializable {
private String localeId = null;
private Gfh gfh = null;
...
}
As this is written, this works. The issue is that I also have a Gfh class which will have a #OneToMany relationship to Gfh_i18n:
#OneToMany(mappedBy="gfh")
#MapKey(name="localeId")
private Map<String, Gfh_i18n> descriptions = null;
Using Eclipse Dali, this gives me the following error:
In attribute 'descriptions', the "mapped by" attribute 'gfh' has an invalid mapping type for this relationship.
If I just try to do, in Gfh_1i8n
#Id #ManyToOne
private Gfh gfh = null;
it solves the previous error but gives one in Gfh_i18n, stating that
The attribute matching the ID class attribute gfh does not have the correct type es.caib.gesma.petcom.data.entity.Gfh
This question is similar to mine, but I do not fully understand why I should be using #EmbeddedId (or if there is some way to use #IdClass with #ManyToOne).
I am using JPA 2.0 over Hibernate (JBoss 6.1)
Any ideas? Thanks in advance.

You are dealing with a "derived identity" (described in the JPA 2.0 spec, section 2.4.1).
You need to change your ID class so the field corresponding to the "parent" entity field in the "child" entity (in your case gfh) has a type that corresponds to either the "parent" entity's single #Id field (e.g. String) or, if the "parent" entity uses an IdClass, the IdClass (e.g. Gfh_id).
In Gfh_1i8n, you should declare gfh like this:
#Id #ManyToOne
private Gfh gfh = null;
Assuming GFH has a single #Id field of type String, your ID class should look like this:
public class Gfh_i18n_id implements Serializable {
private String localeId = null;
private String gfh = null;
...
}

Related

Posting an entity with manyToMany, ManyToOne

I've always avoided #xToX relationships in my APIs but finally wanted to give it a go, and I'm struggling with it.
I've managed to make the get methods returning me the right informations with any recursiveness (at least I made a part), and now I want to post data to my API with those relationships in place (and seems working so far).
So I have a few objects a book, a book style, a book category and an author.
A book has only one author (even if it's false sometimes but as I only read Sci-Fi it will do the trick) and an author have many books (same for category).
And a style can have many books, also books can have many styles.
So I went with a #ManyToOne on the book's author property and with #OneToMany on the author's books property (same for category).
And between the style and the bok #ManyToMany
I want to be able to post a book to my API like follows, using the author and style ids :
{
"isbn": "15867jhg8",
"title": "Le portrait de Dorian Gray",
"author": 1,
"category":1,
"style": [1,2,3]
}
I've annotated my properties with #JsonBackReference, #JsonManagedReference, #JsonSerialize, #JsonIdentityInfo but I truly think I might have made it too much ...
I'm not sure about the way I used the #JsonSerialize, nor the #JsonIdentityInfo.
I've omitted the useless properties to keep the example 'simple'.
So let's dive in.
The Book class first :
#Entity
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id"
)
public class Book implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(updatable = false, nullable = false)
private Integer id;
private String isbn;
private String title;
#ManyToOne
#JoinColumn(name="category_id", nullable = false)
#JsonBackReference(value = "category")
private BookCategory category;
#ManyToOne
#JoinColumn(name="author_id", nullable = false)
#JsonBackReference(value = "author")
private Author author;
#ManyToMany
#JsonManagedReference(value = "styles")
#JsonSerialize(using = BookStyleListSerializer.class)
private List <BookStyle> styles;
}
Then the author class :
#Entity
#JsonIdentityInfo (
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id"
)
public class Author implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(updatable = false, nullable = false)
private Integer id;
#OneToMany(mappedBy = "author")
#JsonManagedReference(value = "authorBooks")
#JsonSerialize(using = BookListSerializer.class)
private List <Book> books;
}
As the category is quite the same, I'm only pasting the books property of it :
#OneToMany(mappedBy = "category")
#JsonManagedReference(value = "categoryBooks")
#JsonSerialize(using = BookListSerializer.class)
private List <Book> books;
And finally my bookStyle class :
#Entity
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id"
)
public class BookStyle implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(updatable = false, nullable = false)
private Integer id;
#ManyToMany
#JsonManagedReference(value = "styleBooks")
#JsonSerialize(using = BookListSerializer.class)
private List <Book> books;
}
The serializer are the same, it's only the type that changes :
public class BookStyleListSerializer extends StdSerializer <List <BookStyle>> {
public BookStyleListSerializer() {
this(null);
}
public BookStyleListSerializer(Class<List<BookStyle>> t) {
super(t);
}
#Override
public void serialize(List <BookStyle> bookStyles, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
List<Integer> ids = new ArrayList <> ( );
for (BookStyle style: bookStyles){
ids.add(style.getId ());
}
jsonGenerator.writeObject ( ids );
}
}
Of what I understood (and as english is not my native language i might misunderstood a few things here and there while coding) :
#JsonBackReference is to be used for the property we don't want to be serialized as opposite to #JsonManagedReference
#JsonSerialize is the custom serializer so that the elements will be serialized as we want them to (in this case, only using IDs)
And as it might be obvious to some of you : none of what I've coded works for posting data, here's the exception as i received it when i post something via API (and it's doubled, not a copy paste error) :
.c.j.MappingJackson2HttpMessageConverter : Failed to evaluate Jackson deserialization for type [[simple type, class com.rz.librarian.domain.entity.Author]]: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot handle managed/back reference 'categoryBooks': no back reference property found from type [collection type; class java.util.List, contains [simple type, class com.rz.librarian.domain.entity.Book]]
.c.j.MappingJackson2HttpMessageConverter : Failed to evaluate Jackson deserialization for type [[simple type, class com.rz.librarian.domain.entity.Author]]: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot handle managed/back reference 'categoryBooks': no back reference property found from type [collection type; class java.util.List, contains [simple type, class com.rz.librarian.domain.entity.Book]]
I've tried many things but after three days on this issue i wanted to ask (cry?) for help.
Thank you guys and sorry for the long post!

Eclipselink JPA - Update entity with #EmbeddedCollection and map does not insert new entry

I am looking at a very simple entity with a Map #ElementCollection.
The entity:
#Entity
#RequiredArgsConstructor #NoArgsConstructor #Data
public class Attribute {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NonNull
private String name;
#ElementCollection
private Map<Integer, AttributeValue> attributes = new HashMap<>();
}
The Embeddable used as the map value:
#Embeddable
#RequiredArgsConstructor #NoArgsConstructor(access = PRIVATE) #Data
public class AttributeValue {
#NonNull
private String name;
}
When I try to update the entity with a new map entry where there is already an equal persisted value (with the same name in this case) the map entry is not inserted.
This test reproduces this issue:
#Test
public void should_persist_attribute_with_same_value() {
Attribute name = new Attribute("name");
name.getAttributes().put(1, new AttributeValue("1"));
name.getAttributes().put(2, new AttributeValue("2"));
name = attributeRepository.saveAndFlush(name);
name.getAttributes().put(3, new AttributeValue("2")); //this value is already in the map and is not inserted
name.getAttributes().put(4, new AttributeValue("4"));
name = attributeRepository.saveAndFlush(name);
entityManager.clear();
//the assertion fails and finds only three items (with key 1,2 and 4)
then(attributeRepository.findOne(name.getId()).getAttributes()).hasSize(4);
}
It seems that there is a bug in eclipselink that ignores map values that are already used with another map key in the map.
I also tried with hibernate - there the same code works as expected.
Any ideas?
I am using:
spring-boot 1-4-0.M2
spring-data-jpa 1.10.1.RELEASE
eclipselink 2.6.1

spring data jpa fine granular auditing, custom audit

I have requirement where I need to insert user name and group name to which the user belongs (both available in SecurityContext) in the same table.
class Entity
{
#createdBy
String username
#createdBy
String groupname
other fields ...
}
As per requirement. I cant solve this issue by making a user class and referencing it through a foreign key.
With current implementation of AuditingHandler both fields are getting the same value. How do I make sure they get respective values.
Can this be achieved using current implementation ?
If not thn how can I provide custom implementation of AuditingHandler ?
You could make a separate embeddable class and annotate it with #CreatedBy in your parent class. One way is to define a bean implementing AuditorAware, then you can make it return custom object, containing your two required fields. For example, your parent class would look like this (note the listener annotation):
#Entity
#EntityListeners(AuditingEntityListener.class)
public class AuditedEntity {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid")
private String id;
#Embedded
#CreatedBy
private AuditorDetails createdBy;
// setters and getters
}
where AuditorDetails is:
#Embeddable
public class AuditorDetails {
private String username;
private String groupname;
// setters and getters
}
and finally, your AuditorAware bean:
#Component
class AuditorAwareImpl implements AuditorAware<AuditorDetails> {
#Override
public AuditorDetails getCurrentAuditor() {
return new AuditorDetails()
.setUsername("someUser")
.setGroupname("someGroup");
}
}
AuditingHandler fetches your custom AuditorDetails from your AuditorAware bean (it must be single bean implementing it) and sets it in your auditable entity.

Merge with JPA and subclass ID

I got two entities like this (second one have a relation with first one) :
#Entity
#Table(name="FOA_ADRESSE_ICX")
public class FoaAdresseIcx implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name="ID_ADRESSE", unique=true, nullable=false, precision=5)
private long idAdresse;
#Column(length=32)
private String bat;
#Column(name="COD_POSTAL", length=5)
private String codPostal;
// getters and setters ....
}
#Entity
#Table(name="FOA_INFOS_ICX")
public class FoaInfosIcx implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name="COD_ICX", unique=true, nullable=false, length=8)
private String codIcx;
#Column(name="DATE_RAFFRAICHISEMENT")
#Temporal(TemporalType.TIMESTAMP)
private Date dateRaffraichisement;
#Column(name="LIB_AGENCE", nullable=false, length=98)
private String libAgence;
//uni-directional many-to-one association to FoaAdresseIcx
#ManyToOne
#JoinColumn(name="ID_ADRESSE", nullable=false)
private FoaAdresseIcx foaAdresseIcx;
// getters and setters....
}
I got a problem with the merge :
myEntityMgr.merge(myFoaInfosIcx);
Got this exception :
GRAVE: EJB Exception: : javax.persistence.EntityNotFoundException: Unable to find com.groupama.middlgan.entities.FoaAdresseIcx with id 0
In myFoaInfosIcx id is 0 because I don't initialise it, because I want JPA to create new FoaAdresseIcx in database if doesn't exist.
How can I do that ?
Using primitive types as DB ids has the downside you are currently experiencing. The default value is 0, which is a perfectly valid value for a DB id.
The persistence provider assumes that the entity is not new, but detached and behaves accordingly.
A solution is to use wrapper classes (or other non-primitive classes like UUID) as ids - Long in you case. Unless explicitly instantiated, the id attributes will be null and the provider will correctly identify an entity as new.

JPA / Hibernate OneToMany Mapping, using a composite PrimaryKey

I'm currently struggling with the right mapping annotations for a scenario using a composite Primary Key Class. First, what I am trying to achieve in words:
I have 2 classes: group and FieldAccessRule. A Group can have many FieldAccessRules, while a FieldAccessRule only has ONE Group assigned. Modling this is not a problem so far (simplified):
public class Group{
...
#OneToMany(mappedBy = "group")
private Set<FieldAccessRule> fieldAccessRules;
...
}
and for the FieldAccessRule:
public class FieldAccessRule {
...
#ManyToOne
#JoinColumn(name = "group_id")
private Group group;
...
}
Now, I decided to use a Composite PK for the FieldAccessRule, because a Rule should be unique for ONE Group and ONE Field. It looks like this:
#Embeddable
public class FieldAccessRulePK implements Serializable{
private String fieldKey;
private Group group;
...
}
And ofc. the FieldAccessRule needs to change to
public class FieldAccessRule {
...
#EmbeddedId
private FieldAccessRulePK fieldAccessRulePK;
...
}
How do I create the right Mapping for the FieldAccessRuleSet of Group now?
Doing it like this, I get :
In attribute 'fieldAccessRules', the "mapped by" value 'group' cannot be resolved to an >attribute on the target entity.
Whats the right way of creating the mapping from Group to A PART of the PrimaryKey?
Edit:
I know found out, that using
public class Group{
...
#OneToMany(mappedBy = "fieldAccessRolePK.group")
private Set<FieldAccessRule> fieldAccessRules;
...
}
is EXACTLY working as expected. It compiles fine, it creates the DB fine and after loading a group with predefined Roles, they are available as expected.
However, Eclipse still says
In attribute 'fieldAccessRules', the "mapped by" value 'fieldAccessRulePK.group' cannot be resolved to an attribute on the target entity.
Im not sure, if it's good to ignore Error and "assume" everythings fine... (I found a post, where it has been said, that a mapping of the pattern attr1.attr2 is supported by Hibernate but not JPA-confirm.)
In your code, EntityManager cannot resolve mappedBy attribute fieldAccessRulePK.group.
Reason
EntityManager assume that FieldAccessRule entity have an attribute name with fieldAccessRulePK.group during the FieldInjection.
According to Java Naming Variables Rule, you cannot name fieldAccessRulePK.group by using characters dot > '.'
Java Naming Variables Rule Reference
All variable names must begin with a letter of the alphabet, an underscore ( _ ), or a dollar sign ($). The rest of the characters may be any of those previously mentioned plus the digits 0-9.
The convention is to always use a letter of the alphabet. The dollar sign and the underscore are discouraged.
One more thing:
Don't use group instance in your FieldAccessRulePK embeddable class. For more reference here.
Try as below :
#Embeddable
public class FieldAccessRulePK implements Serializable{
#Column(name = "FIELD_KEY")
private String fieldKey;
#Column(name = "GROUP_ID")
private String groupId;
}
public class FieldAccessRule {
#EmbeddedId
private FieldAccessRulePK id;
#ManyToOne
#JoinColumn(name = "GROUP_ID", referencedColumnName = "ID")
private Group group;
}
public class Group{
#OneToMany(mappedBy = "group")
private Set<FieldAccessRule> fieldAccessRules;
}
I've fixed a similar problem by modifying the mappedBy attribute of the parent class (using dotted notation)
public class Group{
...
#OneToMany(mappedBy = "fieldAccessRulePK.group")
private Set<FieldAccessRule> fieldAccessRules;
...
}
and by keeping the annotations in the PK class:
#Embeddable
public class FieldAccessRulePK implements Serializable{
private String fieldKey;
#ManyToOne
#JoinColumn(name = "group_id")
private Group group;
...
}