jpa, eclips-link 2.5.1: OneToMany not working on columns not primary key - jpa

I have these two entities:
Anagrafica
#Entity
#Access(AccessType.FIELD)
#Table(name = "S_MC_CC_USER")
#SequenceGenerator(name = "SEQ_ID", sequenceName = "SEQ_ID", allocationSize = 1)
public class Anagrafica implements Serializable{
private static final long serialVersionUID = 332466838544720886L;
#EmbeddedId
private AnagraficaId anagraficaId;
#Column(name = "USER_ID")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_ID")
private Long userId;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "USER_ID", updatable = false, insertable = false)
private List<Mobile> mobiles;
/**
* La classe di dominio che modella la chiave primaria di un {#link Anagrafica}
*
* #author Massimo Ugues
*
*/
#Embeddable
static public class AnagraficaId implements Serializable {
private static final long serialVersionUID = -54640203292300521L;
#Column(name = "ANAG_UTENTE")
private String bt;
#Column(name = "COD_ABI")
private String abi;
public AnagraficaId() {
super();
}
Mobile
#Entity
#Table(name = "S_MOBILE")
#SequenceGenerator(name = "SEQ_MOBILE", sequenceName = "SEQ_MOBILE", allocationSize = 1)
public class Mobile implements Serializable{
private static final long serialVersionUID = 5999493664911497370L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_MOBILE_DEVICE_REGISTRY")
#Column(name = "ID_MOBILE")
private Long mobileId;
#Column(name = "DEVICE_TOKEN")
private String deviceToken;
#Column(name = "DATA_INSERIMENTO")
#Temporal(TemporalType.TIMESTAMP)
private Calendar dataInserimento = Calendar.getInstance();
With eclispe-link 2.1.2 all works great, but with eclispe-link 2.5.1 I got this exception:
Caused by: javax.persistence.PersistenceException: Exception [EclipseLink-28018] (Eclipse Persistence Services - 2.5.1.v20130918-f2b9fc5): org.eclipse.persistence.exceptions.EntityManagerSetupException
Exception Description: Predeployment of PersistenceUnit [persistence-unit] failed.
Internal Exception: Exception [EclipseLink-7220] (Eclipse Persistence Services - 2.5.1.v20130918-f2b9fc5): org.eclipse.persistence.exceptions.ValidationException
Exception Description: The #JoinColumns on the annotated element [field mobiles] from the entity class [class com.intesasanpaolo.domain.entities.sub.Anagrafica] is incomplete. When the source entity class uses a composite primary key, a #JoinColumn must be specified for each join column using the #JoinColumns. Both the name and the referencedColumnName elements must be specified in each such #JoinColumn.
at org.eclipse.persistence.internal.jpa.EntityManagerSetupImpl.createPredeployFailedPersistenceException(EntityManagerSetupImpl.java:1954)
at org.eclipse.persistence.internal.jpa.EntityManagerSetupImpl.predeploy(EntityManagerSetupImpl.java:1945)
at org.eclipse.persistence.jpa.PersistenceProvider.createContainerEntityManagerFactory(PersistenceProvider.java:322)
at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:288)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:310)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1571)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1509)
... 40 more
The problem is the OneToMany association based on a foreign key that is not primary key.
Since I cannot change the database model how can I make it work?
Kind regards
Massimo

The reason it worked in a prior version was that it EclipseLink doesn't look at the fields in the mapping, but with JPA adding derived Id support, EclipseLink now validates the number of foreign keys match the number of ID fields.
James' answer here
JPA #JoinColumn issues while joining on non primary key columns
explains it that you'll need to use a descriptorCustomizer to change the JPA mapping. So you would either not map the field in JPA (mark it as #Transient) and then add a mapping in the customizer, or have the JPA mapping to use all primary key fields and then change the mapping in the customizer to only use the USER_ID->USER_ID fields.
EclipseLink customizers are shown here:
http://eclipse.org/eclipselink/documentation/2.4/jpa/extensions/a_customizer.htm

Ok, this is the Customizer I created:
public void customize(ClassDescriptor descriptor) throws Exception {
// handle the oneToManyMapping to non foreign keys
ManyToManyMapping mapping = (ManyToManyMapping) descriptor.getMappingForAttributeName("mobileDevices");
ExpressionBuilder builder = new ExpressionBuilder();
mapping.setSelectionCriteria(builder.getField("USER_ID").equal(builder.getParameter("USER_ID")));
// handle the insert statement
mapping.setInsertCall(new SQLCall(""));
}
As suggested from Chris this works great with the selection.
I had to modify the Insert Call since eclipse-link tried to create and insert statement on a mapping table that I haven't.
The problem now is on the delete: when I try to delete the collection from the source association (i.e. Cliente) as described here
Cliente.ClienteId id = new Cliente.ClienteId(abi, bt);
Cliente cliente = clienteRepository.findOne(id);
cliente.setMobileDevices(null);
I need eclipse link to delete the orphan.
The dml generated is the following:
DELETE FROM S_MC_CC_CLIENTI_S_MOBILE_DEVICE_REGISTRY WHERE ((mobileDevices_ID_MOBILE_DEVICE_REGISTRY = 13) AND ((ANAG_UTENTE = '71576493') AND (COD_ABI = '01025')))
Since I haven't the mapping table I modified the customizer adding a setDeleteCall statement :
mapping.setDeleteCall(new SQLCall("DELETE FROM S_MOBILE_DEVICE_REGISTRY WHERE USER_ID = #USER_ID"));
In this way eclipse link generates 2 dml:
DELETE FROM S_MOBILE_DEVICE_REGISTRY WHERE USER_ID = NULL
DELETE FROM S_MOBILE_DEVICE_REGISTRY WHERE (ID_MOBILE_DEVICE_REGISTRY = 13)
The first is the translation of my SQLCall, but without the correct parameter: any idea how to generate only the correct delete statement?
Kind regards.
Massimo

Related

Spring batch JdbcCursorItemReader : reading from tables having FK relation

Here's my Reader :
private static final String SELECT_ALL_BOOKS = "SELECT * FROM BOOKS WHERE COLOR = 'yellow'";
#Bean
public JdbcCursorItemReader<BookEntity> itemReader(final DataSource dataSource) {
return new JdbcCursorItemReaderBuilder<BookEntity>()
.name("book_reader")
.sql(SELECT_ALL_BOOKS)
.dataSource(dataSource)
.rowMapper(new BeanPropertyRowMapper<>(BookEntity.class))
.build();
}
And my entity :
#Entity
#Getter
#Setter
#Table(name = "book")
#AllArgsConstructor
#NoArgsConstructor
public class BookEntity implements java.io.Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id_book")
private Integer idBook;
#Column(name = "color")
private String color;
#Column(name = "page_number")
private Integer pageNumber;
#Column(name = "read")
private Boolean read;
#ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.ALL})
#JoinColumn(name = "id_author")
private Author author;
...
...
}
Problem is : with my job executing my step, I'm having the books but with an author = null. So the foreign key is not mapped correctly, all the other String/Integer/Boolean fields are retrieved correctly.
I'm new with Spring batch so maybe I'm using a wrong kind of reader.
Any one has an idea about the problem ? Thanks
Please refer this- You are using JDBC Item reader that is mapped to native columns and properties are binded by BeanPropertyRowMapper
https://docs.spring.io/spring-batch/docs/current/reference/html/readersAndWriters.html#JdbcCursorItemReader
Change the query join with AUTHOR tables as a native query or use JPA readers to support ORM
Below reference will give repository reader
https://docs.spring.io/spring-batch/docs/current/api/org/springframework/batch/item/data/RepositoryItemReader.html
Below example give some idea to implement and its SO references
https://github.com/gpassini/Spring-Batch-RepositoryItemReader-NativeQuery-Example/tree/master/src/main

JPA entity sequence generating

In spring boot JPA I tried to implement sequence generator but it is not working.
the following is my entity
#Entity
#Table(name = "role_level")
public class RoleLevel implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
#Id
#SequenceGenerator(name = "role_level_sequence", sequenceName = "role_level_id_seq",allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "role_level_sequence")
#Column(name = "id", updatable = false)
private Long id;
#Column(name = "role_level")
private String roleLevel;
#Column(name = "role_level_description")
private String roleLevelDescription;
//getters and setters
}
when I insert value in directly through the database then next sequence from the db is not getting in jpa.it shows
org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "role_level_pkey"
Detail: Key (id)=(7) already exists.
But the console shows
Hibernate: select nextval ('role_level_id_seq')
I think its not working.
Is there any solution for this.?

Hibernate #Column annotation cannot mapping to database

I have a Entity in Spring Boot and PostgreSql, I'm using #Column annotation to mapping to database. This is my Entity snip code :
#Entity(name = "users")
#Table(name = "users", schema = "public")
public class User implements Serializable {
/**
*
*/
private static final long serialVersionUID = 12355345L;
#Id
#Column(name = "user_id")
private String userid;
#Column(name = "user_name")
private Integer username;
When id run and test with postman, i get an error :
org.springframework.dao.InvalidDataAccessResourceUsageException: could
not extract ResultSet; SQL [n/a]; nested exception is
org.hibernate.exception.SQLGrammarException: could not extract
ResultSet
Caused by: org.postgresql.util.PSQLException: ERROR: column users0_.usersid does not exist
Hint: Perhaps you meant to reference the column "users0_.user_id".
I don't know why. How to resolve this ?
There are couple of things for your info.
1) Need to check your spring.jpa.hibernate.ddl-auto property as depends on that property, the database tables, columns will be populated by Hibernate.
2) Next drop the existing table and change the value as spring.jpa.hibernate.hbm2ddl.auto=update so that it will create/update table according to the annotations provided in the entity class
3) Remove unnecessary annotations. Following is enough.
#Entity
#Table(name = "users")
public class User implements Serializable {
#Id
#Column(name = "user_id")
private String userid;
................

Exception using Compsite Keys on EclipseLink

Im having trouble using composite primary keys with JPA EclipseLink. The problem is when I theres a foreign key that is the primary key of another table. I have this simple scenario.
User
public class Users implements Serializable {
...
private Collection<UserCompany> userCompanyCollection;
#JoinColumn(name = "user_roles", referencedColumnName = "user_role_id")
#ManyToOne(optional = false)
private UserRoles userRoles;
...
}
User Roles
public class UserRoles implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
protected UserRolesPK userRolesPK;
…
}
User Roles PK
#Embeddable
public class UserRolesPK implements Serializable {
#Basic(optional = false)
#Column(name = "user_role_id")
private int userRoleId;
#Basic(optional = false)
#NotNull
#Column(name = "user_role_company_id")
private int userRoleCompanyId;
...
}
With that objects, I get this exception:
Caused by: Exception [EclipseLink-7220] (Eclipse Persistence Services - 2.3.0.v20110604-r9504): org.eclipse.persistence.exceptions.ValidationException
Exception Description: The #JoinColumns on the annotated element [field userRoles] from the entity class [class jpa.Users] is incomplete. When the source entity class uses a composite primary key, a #JoinColumn must be specified for each join column using the #JoinColumns. Both the name and the referencedColumnName elements must be specified in each such #JoinColumn.
at org.eclipse.persistence.exceptions.ValidationException.incompleteJoinColumnsSpecified(ValidationException.java:1805)
at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.MappingAccessor.getJoinColumnsAndValidate(MappingAccessor.java:575)
at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.MappingAccessor.getJoinColumns(MappingAccessor.java:525)
at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.ObjectAccessor.processOneToOneForeignKeyRelationship(ObjectAccessor.java:629)
at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.ObjectAccessor.processOwningMappingKeys(ObjectAccessor.java:686)
at org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.ManyToOneAccessor.process(ManyToOneAccessor.java:119)
at org.eclipse.persistence.internal.jpa.metadata.MetadataProject.processOwningRelationshipAccessors(MetadataProject.java:1432)
at org.eclipse.persistence.internal.jpa.metadata.MetadataProject.processStage3(MetadataProject.java:1667)
at org.eclipse.persistence.internal.jpa.metadata.MetadataProcessor.processORMMetadata(MetadataProcessor.java:521)
at org.eclipse.persistence.internal.jpa.deployment.PersistenceUnitProcessor.processORMetadata(PersistenceUnitProcessor.java:526)
at org.eclipse.persistence.internal.jpa.EntityManagerSetupImpl.predeploy(EntityManagerSetupImpl.java:1320)
... 36 more
|#]
Thanks in advance for all the help.
Regards,
Daniel
JPA requires using the full primary key in relationship mappings, which is why it doesn't like your mapping - you are not using the user_role_company_id pk field. If user_role_id is enough to uniquely identify userRoles, then it should not be using a composite key and instead only use the single field.
EclipseLink is capable of mapping foreign keys to non or incomplete ID fields, but I recommend against it: Entities are cached on their primary keys, so resolving relationships may require unnecessary database queries even when the entity is in the cache already. Mapping it requires using a customizer to either create or modify the mapping. An example using a customizer is here
http://wiki.eclipse.org/EclipseLink/Examples/JPA/MappingSelectionCriteria

merging entity with onetomany mapping and #version field causes delete of the previous mapping

Hi! All,
I have a mapping issue with two entities. mapped through a #OneToMany unidirectional relation. I have an entity Artifact which can have multiple Revision. Here's how I have mapped them
#Entity
#Table(name = "artifact")
public class Artifact implements Serializable {
private static final long serialVersionUID = 248298400283358441L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Version
private Integer version;
...
#OneToMany(cascade = { CascadeType.DETACH, CascadeType.MERGE, CascadeType.REFRESH, CascadeType.REMOVE })
#JoinTable(name = "artifact_revisions", joinColumns = #JoinColumn(name = "artifact_id"), inverseJoinColumns = #JoinColumn(name = "revision_id"))
private Set<Revision> revisions;
And the revisions entity
#Entity
#Table(name = "revision")
public class Revision implements Serializable {
private static final long serialVersionUID = -1823230375873326645L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
...
#Column(name = "date_created", nullable = false)
#Temporal(TemporalType.TIMESTAMP)
private Date creationDate;
The revision table saves the filed name that was updated; old value and new value etc.
The problem I face is that when I update the artifact; the last mapping gets deleted and then it inserts a new one, so if effect I only have the last but one revision available not the entire revision history.
Hibernate:
update
artifact
set
description=?,
estimate=?,
name=?,
rank=?,
status=?,
sysId=?,
version=?
where
id=?
and version=?
Hibernate:
delete
from
artifact_revisions
where
artifact_id=?
and revision_id=?
Hibernate:
insert
into
artifact_revisions
(artifact_id, revision_id)
values
(?, ?)
If I remove #version annotation from the artifact it works fine.
Is it because I am mapping the relation in a wrong manner? Should this relation be mapped as an element collection instead?
There is another Entity Task which is to be mapped with the Revision entity. So what will be the best approach here?
Maybe this is not a straight answer to your question but I think you should look into hibernate envers. I think it's doing pretty similar thing. (envers stands for entity versioning). You just annotate entity with #Audited put some listeners into config and the rest magic is done for you.