JPA Repository findByEnum does not cast the Argument Enum to String or Postgres Cast not working as expected - postgresql

I have a Spring Boot (2.5.4) backend pointing to a Postgres (9.6) database. I have an entity in Spring that makes use of the #Enumerated(EnumType.String) annotation on a field of an Enum type. Persisting this entity works as expected and converts the Enum into a String. In Postgres, I have the respective enum casted to character varying. Things are working to this point except invoking a custom findBy "Enum" method in the JPA Repository interface. Now in Spring and Postgres I have defined the following:
Enum:
public enum EnumExampleType {
TYPE1, TYPE2
}
Entity:
#Entity
#Table(name = "enumexampletable")
#Data
#NoArgsConstructor
public class EnumExampleTable {
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name = "enum_example_table_id", columnDefinition="serial primary key")
private int enumExampleTableId;
#Column(unique = true, name="enum_example_type")
#Enumerated(EnumType.STRING)
public EnumExampleType enumExampleType;
}
Repo:
public interface EnumExampleTableRepo extends JpaRepository<EnumExampleTable, Integer> {
EnumExampleTable findByEnumExampleType(EnumExampleType enumExampleType);
}
Working Code as Expected
EnumExampleTable ex1 = new EnumExampleTable();
EnumExampleTable ex2 = new EnumExampleTable();
ex1.setEnumExampleType(EnumExampleType.TYPE1);
ex2.setEnumExampleType(EnumExampleType.TYPE2);
enumExampleTableRepo.save(ex1);
enumExampleTableRepo.save(ex2);
RestController: (to invoke) (not working)
#Autowired
EnumExampleTableRepo enumExampleTableRepo;
#GetMapping("/findByTest")
public EnumExampleTable enumTest() {
return enumExampleTableRepo.findByEnumExampleType(EnumExampleType.TYPE1);
}
When calling this code the following error is received:
Blockquote org.postgresql.util.PSQLException: ERROR: operator does not exist: enumexampletype = character varying
Hint: No operator matches the given name and argument type(s). You might need to add explicit type casts.
Postgres Database:
drop table if exists enumexampletable;
drop type if exists enumexampletype cascade;
drop cast if exists (character varying as enumexampletype);
create type enumexampletype as enum('TYPE1', 'TYPE2');
CREATE CAST (character varying as enumexampletype) with inout as implicit;
create table enumexampletable (
enum_example_table_id serial primary key,
enum_example_type enumexampletype
);
This suggests to me that either:
A: The findByEnumExampleType method does not convert the enum to a string
B: Postgres does not invoke this cast in this particular call
Also to Note: (A hard coded native query will function properly, but this is not the dynamic functionality I need)
#Query(value="select * from enumexampletable e where e.emum_example_type = 'TYPE1'", nativeQuery=true)
EnumExampleTable testNQ();
Thoughts or suggestions?

Related

EclipseLink + PostgreSQL: UUID stopped to work after the upgrade to 4.0.0

I'm using the UUID as a primary key in my solution and it worked perfectly on the older EclipseLink version 2.7.3, however, when I tried to upgrade to 4.0.0 I started getting an error during the invocation of the find method.
PK is defined as:
#Id
#Column(name="ID", columnDefinition="UUID")
#Convert(converter = UuidConverter.class)
protected UUID id;
Converter:
#Converter(autoApply = true)
public class UuidConverter implements AttributeConverter<UUID, UUID> {
#Override
public UUID convertToDatabaseColumn(UUID attribute) {
return attribute;
}
#Override
public UUID convertToEntityAttribute(UUID dbData) {
return dbData;
}
}
PostgreSQL trick to bypass the cast issue (please see https://www.eclipse.org/forums/index.php/t/1073632/
for the details):
create or replace function uuid(_text character varying) returns uuid language sql as 'select uuid_in(_text::cstring)';
create cast (character varying as uuid) with function uuid(character varying) as assignment;
Find method:
public T find(Object id) {
return getEntityManager().find(entityClass, id);
}
Error:
Internal Exception: org.postgresql.util.PSQLException: ERROR: operator does not exist: uuid = character varying
Hint: No operator matches the given name and argument types. You might need to add explicit type casts.
Position: 96
So, the cast issue has returned despite that both the function and the cast were defined as listed above.
Entities are being stored in the DB fine, the problem appears only when I try to fetch them.

Is there a way to generate liquibase changelog from jpa entities with hibernate TypeDef?

I am using jpa-buddy to generate liquibase changelog (sql format) from jpa (hibernate) entities.
I am using hibernate-types-55 to map java Enum to posgres Enum. I do this as follow:
#Entity
#TypeDef(
name = "pgsql_enum",
typeClass = PostgreSQLEnumType.class
)
public class MyEntity {
#Enumerated(EnumType.STRING)
#Type(type = "pgsql_enum")
private MyEnumType myEnum;
}
The generated DDL with jpa-buddy is:
CREATE TABLE my_entity
(
my_enum VARCHAR(255),
);
when remove
#Enumerated(EnumType.STRING)
I get
CREATE TABLE my_entity
(
my_enum UNKNOWN__COM.VLADMIHALCEA.HIBERNATE.TYPE.BASIC.POSTGRESQLENUMTYPE,
)
The problem is I can't generate postgres enum type from entity.
what I am expecting is a generated DDL like:
create type my_enum_type as enum ('ENUM1', 'ENUM2', 'ENUM3');
CREATE TABLE my_entity (
my_enum my_enum_type,
);
Has anyone managed to do this in the past ?
Thank you
I found the issue same with your issue:
Java Enums, JPA and Postgres enums - How do I make them work together?
You must create MyEnumConverter.class after imoprt Hibernate-core.jar.
In your case, try:
#TypeDef(name="myEnumConverter", typeClass=MyEnumConverter.class)
public #Entity class MyEntity {
public static enum Mood {ENUM1, ENUM2, ENUM3}
#Type(type="myEnumConverter") MyEnumType myEnum;
}
But I think, using JPA entity isn't good for create table.
You should create table, view, etc,... by SQL native after that you create table if run success SQL statement.

JPA StoredProcedureQuery: pass UUID as a parameter

I use JPA 2.1 (Hibernate), Postgres 9.6, and I need to pass java.util.UUID as a parameter to StoredProcedureQuery like this:
StoredProcedureQuery proc = em.createStoredProcedureQuery(myProc)
.registerStoredProcedureParameter(0, UUID.class, ParameterMode.IN)
.registerStoredProcedureParameter(1, ...)
.setParameter(0, myUuid)
.setParameter(1, ...);
By default, the Java type UUID is interpreted as Postgres type bytea and I get something like:
ERROR: function my_function(bytea, ...) does not exist.
Of course it does not exist, because my function is:
my_function(UUID, ...)
So, is there any way to define explicitly, which database-level type must be used for a particular parameter?
Might be something like the one we use in entity classes with the annotation:
#Type(type="pg-uuid")
private UUID uuid;
One obvious workaround is to pass the value as a String, and then cast it to UUID inside the function, but...
With EclipseLink 2.7.6 and Postgres 11.8 it works, I expect it should work with Hibernate too; originally I ended with the "bytea" too. First I needed this trivial converter, I have no idea why I have to convert UUID to UUID, but it works.
import java.util.UUID;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
#Converter(autoApply = true)
public class UUIDConverter implements AttributeConverter<UUID, UUID> {
#Override
public UUID convertToDatabaseColumn(final UUID uuid) {
return uuid;
}
#Override
public UUID convertToEntityAttribute(final UUID uuid) {
return uuid;
}
}
The function api is this:
create or replace function generate_namespace(idSubscription uuid) returns integer as
...
Just a side note - I wasn't able to return the whole record as managed entity, because JPA will not receive all required metadata and does not know what (and if) is the primary key etc. So instead of returns Namespace I return only it's primary key and then I call entityManager.find to get the managed entity generated by the function:
final StoredProcedureQuery query = manager.createStoredProcedureQuery("generate_namespace");
query.registerStoredProcedureParameter(1, UUID.class, ParameterMode.IN);
query.setParameter(1, idSubscription);
final Object[] result = (Object[]) query.getSingleResult();
return manager.find(Namespace.class, result[0]);

#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.

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.