JPA eclipselink sum of integer in JPQL are not integers? - postgresql

I'm using Eclipselink with postrgresql.
My entity is
public class PedaneMovimenti extends EntityBaseGest implements Serializable {
private static final long serialVersionUID = 1L;
...
#Column(name = "importo", nullable = false)
private Integer importo = 0;
...
In my JPQL Named query I sum the column importo, then use this value in a costructor of a class.
I have two constructor for the class used as projection:
public SaldoPedaneCliente(AnagraficaPGF anagrafica, TipoBancale tipo, Integer saldo);
public SaldoPedaneCliente(AnagraficaPGF anagrafica, TipoBancale tipo, Long saldo);
The JPQL query is
SELECT new com.path.SaldoPedaneCliente(
mov.mastro.anagrafica,
mov.tipobancale,
(
COALESCE(
SELECT SUM(m.importo)
FROM PedaneMovimenti m
WHERE m.mastro.anagrafica = mov.mastro.anagrafica AND m.tipobancale = mov.tipobancale
AND m.verso = com.bsssrl.bssstdgest.enums.VersoMovimento.IN
, 0)
))
from PedaneMovimenti mov WHERE mov.mastro.anagrafica IS NOT NULL
GROUP BY mov.mastro.anagrafica, mov.tipobancale
The query is ok, it works.
I've a type mismatch in the costructor:
javax.persistence.PersistenceException: java.lang.IllegalArgumentException: argument type mismatch
If I change the subquery with a constant (1 for example), it works fine, but if I use the sum, I've the exception.
So, does the sum on Integers is not an Integers or a Long?
EDIT: I've tryed also SELECT SUM(1) but I've the same error.

The sum of "Integer"s is a "Long" !
I've changed the order of the constructors: first the Long version, then the Integer version and it works.

Related

Multi-level subquery with JPA CriteriaBuilder

I have the following JPA entities
#Entity
#Table(name="application_user")
public class ApplicationUser {
#Id
#Column(name="user_id")
private String userid;
#Column(name="last_write_time")
private Instant lastWrite;
//other fields omitted
}
#Entity
#Table(name="demographic")
public class Demographic {
#Id
#Column(name="user_id")
private String userid;
//primary key is a foreign key link
#OneToOne
#PrimaryKeyJoinColumn(name="user_id", referencedColumnName="user_id")
private ApplicationUser user;
//other fields omitted
}
My goal is to retrieve all of the Demographics that contains users where the last write time is the max value in the column. I pretty much want to write the following SQL using the JPA CriteriaBUilder
select * from demographic where
userid in (
select userid from application_user where
last_write in (
select max(last_write) from application_user
)
)
I tried writing the following CriteriaBuilder Code to accomplish this goal and it compiles successfully. Note I am using the generated Metamodel classes.
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Demographic> c = cb.createQuery(Demographic.class);
Root<Demographic> root = c.from(Demographic.class);
root.fetch(Demographic_.user, JoinType.INNER);
Subquery<Instant> sqLatestUsers = c.subquery(Instant.class);
Root<ApplicationUser> subRootLatestUsers = sqLatestUsers.from(ApplicationUser.class);
sqLatestUsers.select(cb.greatest(subRootLatestUsers.<Instant>get(ApplicationUser_.LAST_WRITE)));
Predicate predicateLatestUsers = subRootLatestUsers.get(ApplicationUser_.LAST_WRITE).in(sqLatestUsers);
Subquery<ApplicationUser> sq = c.subquery(ApplicationUser.class);
Root<Demographic> subRoot = sq.from(Demographic.class);
sq.select(subRoot.<ApplicationUser>get(Demographic_.USER)).where(predicateLatestUsers);
Predicate containsUsers = subRoot.get(Demographic_.USER).in(sq);
c.select(root).where(containsUsers);
The code compiles and successfully deploys in Wildfly 14, but when I execute the code, the get the following error (with white space to improve readability):
Invalid path: 'generatedAlias2.user' : Invalid path: 'generatedAlias2.user'
...
Caused by: org.hibernate.hql.internal.ast.QuerySyntaxException: Invalid path: 'generatedAlias2.user' [
select generatedAlias0 from com.company.model.Demographic as generatedAlias0
inner join fetch generatedAlias0.user as generatedAlias1
where generatedAlias2.user in (
select generatedAlias2.user from com.company.model.Demographic as generatedAlias2 where generatedAlias3.lastWrite in (
select max(generatedAlias3.lastWrite) from com.company.model.StarfishUser as generatedAlias3
)
)
]
Is chaining subqueries (nested subqueries) allowed by the JPA spec? Did I find something that is syntactically correctly but not actually allowed?
I figure out how to get the subquery to work. First is my updated Utility method
public static <R, T> Subquery<T> getLatestSubelement(CriteriaBuilder cb, CriteriaQuery<R> c, Class<T> clazz, SingularAttribute<T, Instant> attribute) {
//Get latest timestamp
Subquery<Instant> sq = c.subquery(Instant.class);
Root<T> subRoot = sq.from(clazz);
sq.select(cb.greatest(subRoot.<Instant>get(attribute)));
//Get object with the latest timestamp
Subquery<T> sq2 = c.subquery(clazz);
Root<T> subRoot2 = sq2.from(clazz);
sq2.where(subRoot2.get(attribute).in(sq));
return sq2;
}
Here is the code that uses the utility method
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Demographic> c = cb.createQuery(Demographic.class);
Root<Demographic> root = c.from(Demographic.class);
joinType = JoinType.INNER;
//use fetch instead of join to prevent duplicates in Lists
root.fetch(Demographic_.user, joinType);
Subquery<ApplicationUser> sq = JpaUtil.getLatestSubelement(cb, c, ApplicationUser.class, ApplicationUser_.lastWrite);
c.where(root.get(Demographic_.user).in(sq));
TypedQuery<Demographic> q = em.createQuery(c);
Stream<Demographic> stream = q.getResultStream();

JPQL - implement conditional logic based on most recent element

I'm new to JPA and Spring data. I would like to implement a function with the following logic in my ordering system:
If no order after given timestamp, return 1
otherwise return last order's counter+1
Can I implement such logic with Pure JPQL?
Order entity:
#Entity
public class Order {
#Id
#GeneratedValue
private UUID id;
private Integer counter;
#CreationTimestamp
private Timestamp creationTimestamp;
...
OrderRepository.java:
#Repository
public interface OrderRepository extends CrudRepository<Order, UUID> {
// TODO what goes after else?
#Query("select case when count(o) < 1 then 1 else ... from Order o where o.creationTimestamp > :timestamp order by o.creationTimestamp desc")
Integer nextCounter(#Param("timestamp") Timestamp timestamp);
}
I'm curious as to what the exact use case is. If you could rely on the fact that the counters for orders created after the instant provided as the parameter are increasing, you could simply select NVL(MAX(o.counter), 0) + 1, or event COUNT(o) + 1. I understand this is not the case.
What you want could be achieved with a subquery as follows:
SELECT NVL(MAX(o.counter), 0) + 1
FROM Order o
WHERE o.creationTimestamp = (
SELECT MAX(o.creationTimestamp)
FROM Order o
WHERE o.creationTimestamp > :timestamp
)

#Enumerated Mapping with Postgresql Enum

I created a simple entity called Agent that have an enumerated category. I already know that JPA will not map this enum with Postgresql type enum so I tried to force this mapping.
What I Have:
Java Parts: in the java part we've defined the Person.java entity and the category enumerated class.
Person.java
#Entity
public class Agentimplements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(length = 50, nullable = false)
private String code;
#Column(length = 50, nullable = false)
private String first_name;
#Column(length = 50, nullable = false)
private String family_name;
#Enumerated(EnumType.STRING)
#Column(nullable = false)
private TypeEntree category;
}
CategoryEn.java
public enum CategoryEn{
CUSTOMER,
PROVIDER,
DRIVER
}
Sql Forcing:
CREATE TYPE category_enum AS ENUM ('CUSTOMER','PROVIDER','DRIVER');
CREATE FUNCTION dummy_cast(varchar) RETURNS category_enum AS $$
SELECT CASE $1
WHEN 'CUSTOMER' THEN 'CUSTOMER'::category_enum
WHEN 'PROVIDER' THEN 'PROVIDER'::category_enum
WHEN 'DRIVER' THEN 'DRIVER'::category_enum
END;
$$ LANGUAGE SQL;
CREATE CAST (varchar AS category_enum) WITH FUNCTION dummy_cast(varchar) AS ASSIGNMENT;
ALTER TABLE public.agent
ALTER COLUMN category
SET DATA TYPE category_enum
USING agent::text::category_enum;
Until here, everything is working fine, but when I try to execute this query in the the AgentFacade:
String jpql ="SELECT a FROM Agent a"
+ " WHERE a.category = :cat";
Query query = em.createQuery(jpql);
query.setParameter("cat", CategoryEn.DRIVER);
I'm having the following error:
Caused by: javax.persistence.PersistenceException: Exception [EclipseLink-4002]
(Eclipse Persistence Services - 2.5.2.v20140319-9ad6abd):
org.eclipse.persistence.exceptions.DatabaseException Internal
Exception: org.postgresql.util.PSQLException: ERREUR: operator does not exist : category_enum= character varying
Indication :No operator matches the given name and argument type(s). You might need to add explicit type casts
My questions are:
Why I am having this error ?
Can I solve this error ? How?
Why the JPA doesn't have a tool that map automatically a Java enum to an Sql type enum ?
PS: I've already seen almost all the stackoverflow questions/answers that are similare to this topic
You are getting this error because your driver/ORM is likely casting that parameter to varchar.
You could create operator for that comparison:
CREATE OR REPLACE FUNCTION texteq(
category_enum,
text)
RETURNS boolean AS $q$ SELECT texteq($1::text, $2) $q$
LANGUAGE SQL IMMUTABLE STRICT
COST 1;
CREATE OPERATOR =(
PROCEDURE = texteq,
LEFTARG = category_enum,
RIGHTARG = text,
COMMUTATOR = =,
NEGATOR = <>,
RESTRICT = eqsel,
JOIN = eqjoinsel,
HASHES,
MERGES);
I didn't test if it actually works in JOIN merges/hashes, but simple comparison looks fine.

Ebean Annotations - Using sequences to generate IDs in DB2

I'm trying to use sequences to generate incremented IDs for my tables in DB2. It works when I send SQL statements directly to the database, but when using ebean the statement fails. Here's the field in Java:
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "TABLENAME_IDNAME_TRIG")
#SequenceGenerator(name = "TABLENAME_IDNAME_TRIG", sequenceName = "TABLENAME_IDNAME_SEQ")
#Column(name = "IDNAME")
private Long id;
Here's the column in SQL (From TOAD):
Name Data type Not Null Default Generated Bit Data Scope Identity
IDNAME INTEGER Yes No No
And here's the sequence definition in SQL:
CREATE OR REPLACE SEQUENCE SCHEMA.TABLENAME_IDNAME_SEQ
AS INTEGER CACHE 50 ORDER;
And the trigger:
CREATE OR REPLACE TRIGGER SCHEMA.TABLENAME_IDNAME_TRIG
NO CASCADE BEFORE INSERT
ON TABLENAME
REFERENCING
NEW AS OBJ
FOR EACH ROW
BEGIN
SET obj.IDNAME=NEXT VALUE FOR SCHEMA.TABLENAME_IDNAME_SEQ;
END;
What is the issue with my annotations here? As a(n important) side note - when I set GenerationType to AUTO, TABLE, or IDENTITY, it works, even though it shouldn't, because I'm also using this object to represent a parallel oracle table which also uses sequences for ID generation.
Edited to include error message:
javax.persistence.PersistenceException: Error getting sequence nextval
...
Caused by: com.ibm.db2.jcc.am.SqlSyntaxErrorException: DB2 SQL Error: SQLCODE=-348, SQLSTATE=428F9, SQLERRMC=NEXTVAL FOR SCHEMA.TABLENAME_IDNAME_SEQ, DRIVER=4.19.49
EDIT 2: The specific Sql statement that is failing is:
values nextval for QA_CONNECTION_ICONNECTIONI_SEQ union values nextval for QA_CONNECTION_ICONNECTIONI_SEQ union values nextval for QA_CONNECTION_ICONNECTIONI_SEQ
Which is SQL generated by Ebean. This is a smaller version of the real statement, which is repeated 20 times, so I'm guessing something screws up when generating the caching query.
EDIT 3: I believe this might be a bug in Ebean's use of DB2 sequences. This function generates SQl that returns an error for me when used with db2
public DB2SequenceIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int batchSize) {
super(be, ds, seqName, batchSize);
this.baseSql = "values nextval for " + seqName;
this.unionBaseSql = " union " + baseSql;
}
EDIT 4: Based on this SO link I think it is a bug.
Can't insert multiple values into DB2 by using UNION ALL and generate IDs from sequence
The correct class probably looks like this? Though I haven't ever tried building the library, so I couldn't test it. Time to learn how to open a defect I guess.
public class DB2SequenceIdGenerator extends SequenceIdGenerator {
private final String baseSql;
private final String unionBaseSql;
private final String startSql;
public DB2SequenceIdGenerator(BackgroundExecutor be, DataSource ds, String seqName, int batchSize) {
super(be, ds, seqName, batchSize);
this.startSql = "values "
this.baseSql = "(nextval for " + seqName);
this.unionBaseSql = ", " + baseSql;
}
public String getSql(int batchSize) {
StringBuilder sb = new StringBuilder();
sb.append(startSql);
sb.append(baseSql);
for (int i = 1; i < batchSize; i++) {
sb.append(unionBaseSql);
}
return sb.toString();
}
}
Temporary workaround for those interested: in ebean.properties, set
ebean.databaseSequenceBatchSize=1

Mapping Java enum on Postgres enum with EclipseLink

I am making first attempts with JPA (EclipseLink implementation) and feel quite stuck:
In PostgreSQL I have the following db schema
CREATE TYPE mood AS ENUM ( 'sad', 'happy', 'enthusiastic' );
CREATE TABLE person (
pk BIGINT PRIMARY KEY,
name VARCHAR NOT NULL,
mood mood NOT NULL
);
CREATE SEQUENCE person_pk_seq INCREMENT BY 100 MINVALUE 100;
Which works pretty fine, as this insert shows INSERT INTO PERSON (PK, mood, name) VALUES (3, 'happy', 'Joe') (Committing the pk as String makes no difference.)
On the JPA side I wrote the following class:
package testdb;
import java.io.Serializable;
import javax.persistence.*;
import org.eclipse.persistence.annotations.*;
#Entity
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
public enum Mood {
sad, happy, enthusiastic;
}
#Id
#SequenceGenerator(
name="PERSON_PK_GENERATOR",
sequenceName="PERSON_PK_SEQ",
allocationSize = 100
)
#GeneratedValue(
strategy=GenerationType.SEQUENCE,
generator="PERSON_PK_GENERATOR"
)
public Long pk;
#Enumerated( EnumType.STRING )
#Column( name = "mood" )
#ObjectTypeConverter( name = "moodConverter", objectType = Mood.class,
dataType = String.class, conversionValues = {
#ConversionValue( objectValue = "sad", dataValue = "sad" ),
#ConversionValue( objectValue = "happy", dataValue = "happy" ),
#ConversionValue( objectValue = "enthusiastic", dataValue = "enthusiastic" )
})
#Convert( "moodConverter" )
public Mood mood;
#Column( name = "name" )
public String name;
public static void main(String[] args) {
EntityManagerFactory factory = Persistence.createEntityManagerFactory("TestDb.jpa.tests" );
EntityManager em = factory.createEntityManager();
em.getTransaction().begin();
Person p = new Person();
em.persist( p );
System.out.println(p.pk);
p.name = "Joe";
p.mood = Mood.enthusiastic;
em.getTransaction().commit();
Query q = em.createQuery( "select p from Person p" );
Person x = (Person)q.getResultList().get(0);
System.out.println( x.pk + " :: " +x.mood );
em.close();
}
}
However, this example is not working and I have no clue what the problem is:
[EL Warning]: 2012-06-05 15:28:20.646--UnitOfWork(845463623)--Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.3.2.v20111125-r10461): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: java.sql.BatchUpdateException: Batch-Eintrag 0 INSERT INTO PERSON (PK, mood, name) VALUES ('801', 'enthusiastic', 'Joe') wurde abgebrochen. Rufen Sie 'getNextException' auf, um die Ursache zu erfahren.
Error Code: 0
Call: INSERT INTO PERSON (PK, mood, name) VALUES (?, ?, ?)
bind => [3 parameters bound]
When I alter the column type of table person to varchar and remove the annotations #Convert and #ObjectTypeConverter everything is working as expected, as well.
Any ideas?
Why do you use a #ObjectTypeConverter, you can map Enumerations out of the box with eclipse link as shown here. #Enumerated is part of JSR-220 whereas #ObjectTypeConverter is a proprietary extension from EclipseLink JPA Extensions.
#Enumerated(EnumType.STRING)
#Column(name = "mood")
private Mood mood;
Try removing the #Enumerated( EnumType.STRING ) as it might be overriding the converter settings.
What is the mood type? This is not a standard JDBC type, so this is the reason for your error.
How does Postgres require this type to be bound through JDBC? It seems odd it does not auto convert varchar values.
I did a little looking, and it seems to return this type as PGObject, so you will need to own custom Converter that converts between your Java enum, and a Postgres enum. You will also need to set the jdbcType on the DatabaseField in your converters init method to OTHER.
Please log a bug on EclipseLink to have support added to the Postgres platform for this type.
I think disabling parameter binding would also work.
I'm 10 years late but...
Adding stringtype=unspecified to the connection string will do the trick.
I use connection strings like:
jdbc:postgresql://localhost:5432/mydatabase?stringtype=unspecified
And then, all fields annotated with #Enumerated(STRING) are automatically converted to the pg enum type.