Activiti JPA support (activiti-spring-boot-starter-jpa) unable to detect generic primary key type - spring

I'm working on a project in which our JPA Entities inherit org.springframework.data.jpa.domain.AbstractPersistable, which looks as below:
#MappedSuperclass
public abstract class AbstractPersistable<PK extends Serializable> implements Persistable<PK> {
#Id #GeneratedValue private PK id;
...
And our domain classes are defined as below:
public class User extends AbstractPersistable<Long> {
...
}
So, eventually, all our primary keys are Long. However, when I'm trying to start a process instance with a domain object, I'm getting this error:
org.activiti.engine.ActivitiException: Error while evaluating expression: ${reviewer}
...
Caused by: org.activiti.engine.ActivitiIllegalArgumentException: Unsupported Primary key type for JPA-Entity: java.io.Serializable
at org.activiti.engine.impl.variable.JPAEntityMappings.createId(JPAEntityMappings.java:168)
at org.activiti.engine.impl.variable.JPAEntityMappings.getJPAEntity(JPAEntityMappings.java:120)
...
So, looks like activiti-spring-boot-starter-jpa won't work when the domain classes inherit from a base class which has generic primary key, or I'm missing something?

Looking at the source, the only supported ID types are primitives.
There is a comment in the code that goes:
<snip>
Class<?> type = metaData.getIdType();
// According to JPA-spec all primitive types (and wrappers) are supported, String, util.Date, sql.Date,
// BigDecimal and BigInteger
</snip>
By using a generic, the code is passing through a serializable that is not properly matched.
Seems this would be a relatively easy override in the JPAEntityMappings class.

Related

MappedSuperclass produces 'has an unbound type and no explicit target entity'

I have this entity. It should work on both, internal and external, users.
#Entity(name = "TokenAuthentication")
class TokenAuthenticationEntity<T>(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null,
...
#NotNull
#ManyToOne(fetch = FetchType.EAGER)
val user: T,
) : BaseEntity()
When I run this, I get
Property TokenAuthenticationEntity.user has an unbound type and no explicit target entity. Resolve this Generic usage issue or set an explicit target attribute
So Hibernate tells me to f*** off with my generics, it needs explicit definitions. Any of you have an idea how to get this running with generics?
This is not possible. The reason being T type erasure at compile-time. This T type is used at compile-time to check types and guarantee type safety, but then it is removed. Having said that, each instance of TokenAuthenticationEntity can have a different value for T, and as explained before this information is lost.
What you can actually do is the following:
#MappedSuperclass
public abstract class TokenAuthenticationEntity<T> {
private T user;
}
Now you can create your entities based on this generic superclass:
#Entity
public class InternalUser extends TokenAuthenticationEntity<IUser> { }
#Entity
public class ExternalUser extends TokenAuthenticationEntity<EUser> { }
Why? Because each concrete subclass of TokenAuthenticationEntity has a type T which is defined and is retained and unique inside the subclasses. In this case, JPA will store your subclasses in one or more tables, depending on the chosen #InheritanceStrategy (if the Inheritance annotation is not specified or if no inheritance type is specified for an entity class hierarchy, the SINGLE_TABLE mapping strategy is used). Additional details at https://www.baeldung.com/hibernate-inheritance#single-table.

Can we fetch data using JPA without using primary key

My Table:
Emotion
EID (Primary Key)
user_mood
Latitude
Longitude
uid
#Entity
public class Emotion
{
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer eId;
#Column(name = "user_mood")
private String mood;
private String latitude;
private String longitude;
private String uid;
}
My Interface:
public interface EmotionRepository extends JpaRepository<Emotion, String>{}
When I try to fetch values using uid
emotionRepo.findById(uid)
I am getting below type mismatch error
Error message:
org.hibernate.TypeMismatchException: Provided id of the wrong type for class mood_sensor.mood_sensor.pojos.Emotion. Expected: class java.lang.Integer, got class java.lang.String
Can I retrieve data using uid or I should use only the primaryKey(Integer) to retrieve data?
Yes, it's possible, but the method findById, from CrudRepository<T, ID> always expect to receive the ID as the argument - see here (be aware that JpaRepository extends from CrudRepository).
You can query by another field in a variety of ways, but as I saw that your use case is pretty simple, I'd suggest you to make use of Spring Data's query creation deriving from the method's name - see here and here
It's really simple, just create a method called findByUid in your EmotionRepository and Spring will take care of the rest by deriving the desired query from the method's name:
public interface EmotionRepository extends JpaRepository<Emotion, Integer> {
Optional<Emotion> findByUid(String uid);
}
Spring Data will then generate something like the following query:
SELECT e.* FROM Emotion e WHERE e.uid = <the-value>
Now, you can use findByUid method to query using the uid field.
And another fix: change from
EmotionRepository extends JpaRepository<Emotion, String>
to
EmotionRepository extends JpaRepository<Emotion, Integer>
this is because the id from Emotion is of type Integer, not String.
Inside your JpaRepository (EmotionRepository), you can make a function signature, anmd and the JPA will create a method to fetch whatever data you need.
This doc should help you understand exactly what is happening: http://static.springsource.org/spring-data/data-jpa/docs/1.0.0.M1/reference/html/#jpa.query-methods.query-creation

Spring Data JPA and Generics

I have an entity that looks like this
#Entity(name = "encounter_pdf_export")
public class EncounterPDFExport<T extends Encounter> implements Serializable {
public static final long serialVersionUID = 1L;
#Id
#GeneratedValue
private Long pdfExportId;
#Any(metaColumn = #Column(name = "encounter_type"))
#Cascade(CascadeType.ALL)
#AnyMetaDef(
idType = "long",
metaType = "string",
metaValues = {
#MetaValue(value = "FooEncounter", targetEntity = FooEncounter.class)
})
#JoinColumn(name = "encounter_id")
private T encounter;
The abstract type that I'm extending is:
public abstract class Encounter {
public abstract Long getEncounterId();
}
Here is my Spring Data Repository
#Repository
public interface EncounterPDFExportRepository extends PagingAndSortingRepository<EncounterPDFExport, Long> {
EncounterPDFExport findOneByEncounter_encounterId(#Param("encounterId") Long encounterId);
}
I am getting a stack trace when starting up the application related to to the findOneByEncounter_encounterId method:
Caused by: java.lang.IllegalArgumentException: Unable to locate Attribute with the the given name [encounter] on this ManagedType [com.iimassociates.distiller.domain.EncounterPDFExport]
at org.hibernate.jpa.internal.metamodel.AbstractManagedType.checkNotNull(AbstractManagedType.java:144)
at org.hibernate.jpa.internal.metamodel.AbstractManagedType.getAttribute(AbstractManagedType.java:130)
at org.springframework.data.jpa.repository.query.QueryUtils.toExpressionRecursively(QueryUtils.java:468)
at org.springframework.data.jpa.repository.query.JpaQueryCreator$PredicateBuilder.getTypedPath(JpaQueryCreator.java:300)
at org.springframework.data.jpa.repository.query.JpaQueryCreator$PredicateBuilder.build(JpaQueryCreator.java:243)
at org.springframework.data.jpa.repository.query.JpaQueryCreator.toPredicate(JpaQueryCreator.java:148)
at org.springframework.data.jpa.repository.query.JpaQueryCreator.create(JpaQueryCreator.java:88)
at org.springframework.data.jpa.repository.query.JpaQueryCreator.create(JpaQueryCreator.java:46)
at org.springframework.data.repository.query.parser.AbstractQueryCreator.createCriteria(AbstractQueryCreator.java:109)
at org.springframework.data.repository.query.parser.AbstractQueryCreator.createQuery(AbstractQueryCreator.java:88)
at org.springframework.data.repository.query.parser.AbstractQueryCreator.createQuery(AbstractQueryCreator.java:73)
at org.springframework.data.jpa.repository.query.PartTreeJpaQuery$QueryPreparer.<init>(PartTreeJpaQuery.java:116)
at org.springframework.data.jpa.repository.query.PartTreeJpaQuery$CountQueryPreparer.<init>(PartTreeJpaQuery.java:237)
at org.springframework.data.jpa.repository.query.PartTreeJpaQuery.<init>(PartTreeJpaQuery.java:65)
at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$CreateQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:100)
I am assuming that either Spring Data JPA doesn't support abstracted/generic fields? If that's the case, would creating a #Query be a sufficient workaround?
Not sure if this will be helpful to anyone, but I did get this working.
Removed the abstract class and made it an interface with a single public getEncounterId() method
Modified FooEncounter to implement the above interface
Removed generics from the EncounterPDFExport class
Modified the encounter field to utilize the above interface rather than a generic
Apparently, I'm hitting some Hibernate bug/limitation when accessing fields within FooEncounter. Accessing Encounter within EncounterPDFExport works OK, though. I modified my Spring Data JPA Repository to look like the following (note the modification from finding by encounter.encounterId vs. just encounter):
#Repository
public interface EncounterPDFExportRepository extends PagingAndSortingRepository<EncounterPDFExport, Long> {
EncounterPDFExport findOneByEncounter(#Param("encounter") Encounter encounter);
}
The Hibernate bug in question seems to be related to https://jira.spring.io/browse/DATAJPA-836.

spring data rest hateoas dynamically hide repository

I'm still trying to figure what exactly it is I am asking but this is fallout from a discussion in the office. So the dilemma is that on a mapping set to eager with a repository defined for the entity the mapping is to, a link is produced. Some of the time that is fine but some of the time I'd rather have the object fetched itself. If there is not a repository defined for that entity then that is what will occur with the eager fetch strategy. What would be ideal is if I could pass in a parameter and have the existence of that repository disappear or reappear.
Not totally following, but either the repo exists or not. If you want to be able to access entities of type X independently of other entity types, then you have to define a repo for type X.
I think you could achieve something similar using projections.
So you define define a repository for your association entity. By default spring data rest will just render a link to this entity and not embed it in the response.
Then you define a projection with a getter for your associated entity. You can choose on the client side if you want the projection by adding the projection query parameter.
So lets say you have a person with an address - an exported repository exists for Person and Address:
#Entity
public class Person {
#Id #GeneratedValue
private Long id;
private String firstName, lastName;
#OneToOne
private Address address;
…
}
interface PersonRepository extends CrudRepository<Person, Long> {}
interface AddressRepository extends CrudRepository<Address, Long> {}
Your projection could look like this:
#Projection(name = "inlineAddress", types = { Person.class })
interface InlineAddress {
String getFirstName();
String getLastName();
Address getAddress();
}
And if you call http://localhost/persons/1?projection=inlineAddress you have the address embedded - and by default it is just linked.

Embedded object inheritance in EJB3

I am developping an user/password system with EJB3.
An user have an embedded password.
And I have two kinds of passwords: user defined or not.
Therefore I have a superclass Password, and its subclass GeneratedPassword. Architecture is indeed debatable.
Here are the "signatures" :
#Entity
#NamedQueries({ //... })
#Table(name="UserAccount")
public class UserAccount implements Serializable {
#Id
#Email
private String email;
#Embedded
private Password password;
public UserAccount(String email) {
this.email = email;
this.password = new GeneratedPassword();
}
// ...
}
#Embeddable
public class Password implements Serializable {
private String encryptedPassword;
// ...
}
#Embeddable
public class GeneratedPassword extends Password {
private String tmpPassword;
// ...
}
Problem is I am having a weird exception (weird because I don't understand it...):
Caused by: javax.persistence.EntityExistsException:
Exception Description: No subclass matches this class [class entities.user.GeneratedPassword] for this Aggregate mapping with inheritance.
Mapping: org.eclipse.persistence.mappings.AggregateObjectMapping[password]
Descriptor: RelationalDescriptor(entities.user.UserAccount --> [DatabaseTable(UserAccount)])
2nd part:
Caused by: Exception [EclipseLink-126] (Eclipse Persistence Services - 2.3.0.v20110604-r9504): org.eclipse.persistence.exceptions.DescriptorException
Exception Description: No subclass matches this class [class entities.user.GeneratedPassword] for this Aggregate mapping with inheritance.
Mapping: org.eclipse.persistence.mappings.AggregateObjectMapping[password]
Descriptor: RelationalDescriptor(entities.user.UserAccount --> [DatabaseTable(UserAccount)])
So what I understand from these exceptions is that the GeneratedPassword is not recognized as an entity. But if I use the Password class, evrything works fine! So I'm back to the incomprehension state...
Anybody knows how to use embeddable entities within a hierarchy? Is that even the problem???
Specification does not tell anything about inheritance of embeddables, so looks like it is not supported. Probably because of simplicity as target.
Of course some implementations can have it. Unfortunately Hibernate is not one of those: https://hibernate.onjira.com/browse/HHH-1910
Eclipselink supports, but not via annotation or XML descriptor: http://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Entities/Embeddable#Inheritance
By the way, question is tagged with hibernate, but you use EclipseLink.

Resources