How to map PostgreSQL enum with JPA and Hibernate - java

I am trying to map a PostgreSQL custom type,named transmission_result, to a Hibernate/JPA POJO. The PostgreSQL custom type is more or less an enum type of string values.
I have created a custom EnumUserType called PGEnumUserType as well as an enum class representing the PostgreSQL enumerated values. When I run this against a real database, I receive the following error:
'ERROR: column "status" is of type transmission_result but expression is of type
character varying
Hint: You will need to rewrite or cast the expression.
Position: 135 '
Upon seeing this, I figured I needed to change my SqlTypes to Types.OTHER. But doing so breaks my integration tests (using HyperSQL in memory DB) with the message:
'Caused by: java.sql.SQLException: Table not found in statement
[select enrollment0_."id" as id1_47_0_,
enrollment0_."tpa_approval_id" as tpa2_47_0_,
enrollment0_."tpa_status_code" as tpa3_47_0_,
enrollment0_."status_message" as status4_47_0_,
enrollment0_."approval_id" as approval5_47_0_,
enrollment0_."transmission_date" as transmis6_47_0_,
enrollment0_."status" as status7_47_0_,
enrollment0_."transmitter" as transmit8_47_0_
from "transmissions" enrollment0_ where enrollment0_."id"=?]'
I'm not sure why changing the sqlType results in this error. Any help is appreciated.
JPA/Hibernate Entity:
#Entity
#Access(javax.persistence.AccessType.PROPERTY)
#Table(name="transmissions")
public class EnrollmentCycleTransmission {
// elements of enum status column
private static final String ACCEPTED_TRANSMISSION = "accepted";
private static final String REJECTED_TRANSMISSION = "rejected";
private static final String DUPLICATE_TRANSMISSION = "duplicate";
private static final String EXCEPTION_TRANSMISSION = "exception";
private static final String RETRY_TRANSMISSION = "retry";
private Long transmissionID;
private Long approvalID;
private Long transmitterID;
private TransmissionStatusType transmissionStatus;
private Date transmissionDate;
private String TPAApprovalID;
private String TPAStatusCode;
private String TPAStatusMessage;
#Column(name = "id")
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
public Long getTransmissionID() {
return transmissionID;
}
public void setTransmissionID(Long transmissionID) {
this.transmissionID = transmissionID;
}
#Column(name = "approval_id")
public Long getApprovalID() {
return approvalID;
}
public void setApprovalID(Long approvalID) {
this.approvalID = approvalID;
}
#Column(name = "transmitter")
public Long getTransmitterID() {
return transmitterID;
}
public void setTransmitterID(Long transmitterID) {
this.transmitterID = transmitterID;
}
#Column(name = "status")
#Type(type = "org.fuwt.model.PGEnumUserType" , parameters ={#org.hibernate.annotations.Parameter(name = "enumClassName",value = "org.fuwt.model.enrollment.TransmissionStatusType")} )
public TransmissionStatusType getTransmissionStatus() {
return this.transmissionStatus ;
}
public void setTransmissionStatus(TransmissionStatusType transmissionStatus) {
this.transmissionStatus = transmissionStatus;
}
#Column(name = "transmission_date")
public Date getTransmissionDate() {
return transmissionDate;
}
public void setTransmissionDate(Date transmissionDate) {
this.transmissionDate = transmissionDate;
}
#Column(name = "tpa_approval_id")
public String getTPAApprovalID() {
return TPAApprovalID;
}
public void setTPAApprovalID(String TPAApprovalID) {
this.TPAApprovalID = TPAApprovalID;
}
#Column(name = "tpa_status_code")
public String getTPAStatusCode() {
return TPAStatusCode;
}
public void setTPAStatusCode(String TPAStatusCode) {
this.TPAStatusCode = TPAStatusCode;
}
#Column(name = "status_message")
public String getTPAStatusMessage() {
return TPAStatusMessage;
}
public void setTPAStatusMessage(String TPAStatusMessage) {
this.TPAStatusMessage = TPAStatusMessage;
}
}
Custom EnumUserType:
public class PGEnumUserType implements UserType, ParameterizedType {
private Class<Enum> enumClass;
public PGEnumUserType(){
super();
}
public void setParameterValues(Properties parameters) {
String enumClassName = parameters.getProperty("enumClassName");
try {
enumClass = (Class<Enum>) Class.forName(enumClassName);
} catch (ClassNotFoundException e) {
throw new HibernateException("Enum class not found ", e);
}
}
public int[] sqlTypes() {
return new int[] {Types.VARCHAR};
}
public Class returnedClass() {
return enumClass;
}
public boolean equals(Object x, Object y) throws HibernateException {
return x==y;
}
public int hashCode(Object x) throws HibernateException {
return x.hashCode();
}
public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws HibernateException, SQLException {
String name = rs.getString(names[0]);
return rs.wasNull() ? null: Enum.valueOf(enumClass,name);
}
public void nullSafeSet(PreparedStatement st, Object value, int index) throws HibernateException, SQLException {
if (value == null) {
st.setNull(index, Types.VARCHAR);
}
else {
st.setString(index,((Enum) value).name());
}
}
public Object deepCopy(Object value) throws HibernateException {
return value;
}
public boolean isMutable() {
return false; //To change body of implemented methods use File | Settings | File Templates.
}
public Serializable disassemble(Object value) throws HibernateException {
return (Enum) value;
}
public Object assemble(Serializable cached, Object owner) throws HibernateException {
return cached;
}
public Object replace(Object original, Object target, Object owner) throws HibernateException {
return original;
}
public Object fromXMLString(String xmlValue) {
return Enum.valueOf(enumClass, xmlValue);
}
public String objectToSQLString(Object value) {
return '\'' + ( (Enum) value ).name() + '\'';
}
public String toXMLString(Object value) {
return ( (Enum) value ).name();
}
}
Enum class:
public enum TransmissionStatusType {
accepted,
rejected,
duplicate,
exception,
retry}

If you have following post_status_info enum type in PostgreSQL:
CREATE TYPE post_status_info AS ENUM (
'PENDING',
'APPROVED',
'SPAM'
)
You can easily map Java Enum to a PostgreSQL Enum column type using the following custom Hibernate Type:
public class PostgreSQLEnumType extends org.hibernate.type.EnumType {
public void nullSafeSet(
PreparedStatement st,
Object value,
int index,
SharedSessionContractImplementor session)
throws HibernateException, SQLException {
if(value == null) {
st.setNull( index, Types.OTHER );
}
else {
st.setObject(
index,
value.toString(),
Types.OTHER
);
}
}
}
To use it, you need to annotate the field with the Hibernate #Type annotation as illustrated in the following example:
#Entity(name = "Post")
#Table(name = "post")
#TypeDef(
name = "pgsql_enum",
typeClass = PostgreSQLEnumType.class
)
public static class Post {
#Id
private Long id;
private String title;
#Enumerated(EnumType.STRING)
#Column(columnDefinition = "post_status_info")
#Type( type = "pgsql_enum" )
private PostStatus status;
//Getters and setters omitted for brevity
}
That's it, it works like a charm. Here's a test on GitHub that proves it.

I figured it out. I needed to use setObject instead of setString in the nullSafeSet function and pass in the Types.OTHER as the java.sql.type to let jdbc know that it was a postgres type.
public void nullSafeSet(PreparedStatement st, Object value, int index) throws HibernateException, SQLException {
if (value == null) {
st.setNull(index, Types.VARCHAR);
}
else {
// previously used setString, but this causes postgresql to bark about incompatible types.
// now using setObject passing in the java type for the postgres enum object
// st.setString(index,((Enum) value).name());
st.setObject(index,((Enum) value), Types.OTHER);
}
}

The following might also help to have Postgres convert strings silently to your SQL enum type so you can use #Enumerated(STRING) and don't need #Type.
CREATE CAST (character varying as post_status_type) WITH INOUT AS IMPLICIT;

A quick solution will be
jdbc:postgresql://localhost:5432/postgres?stringtype=unspecified
?stringtype=unspecified is the answer

build.gradle.kts
dependencies {
api("javax.persistence", "javax.persistence-api", "2.2")
api("org.hibernate", "hibernate-core", "5.4.21.Final")
}
In Kotlin it is important to make a generic extension with EnumType<Enum<*>>()
PostgreSQLEnumType.kt
import org.hibernate.type.EnumType
import java.sql.Types
class PostgreSQLEnumType : EnumType<Enum<*>>() {
#Throws(HibernateException::class, SQLException::class)
override fun nullSafeSet(
st: PreparedStatement,
value: Any,
index: Int,
session: SharedSessionContractImplementor) {
st.setObject(
index,
value.toString(),
Types.OTHER
)
}
}
Custom.kt
import org.hibernate.annotations.Type
import org.hibernate.annotations.TypeDef
import javax.persistence.*
#Entity
#Table(name = "custom")
#TypeDef(name = "pgsql_enum", typeClass = PostgreSQLEnumType::class)
data class Custom(
#Id #GeneratedValue #Column(name = "id")
val id: Int,
#Enumerated(EnumType.STRING) #Column(name = "status_custom") #Type(type = "pgsql_enum")
val statusCustom: StatusCustom
)
enum class StatusCustom {
FIRST, SECOND
}
A simpler option that I don't recommend is the first option in Arthur's answer which adds a parameter in the connection URL to the db so that the enum data type is not lost. I believe that the responsibility of mapping the data type between the backend server and the database is precisely the backend.
<property name="connection.url">jdbc:postgresql://localhost:5432/yourdatabase?stringtype=unspecified</property>
Source

As TypeDef has disappeared in Hibernate 6, and we thus need to annotate each affected property anyway, I've found that using
#ColumnTransformer(write="?::transmission_result ")
to force a type cast works, without any Hibernate usertype classes needed.

Related

How to store a List of a class object into MySql using Spring boot

I want to store a List of class : RestApiResponse into MySql. But getting below error:
org.hibernate.HibernateException: Could not determine a type for class: com.try.sreapi.beans.RestApiResponse
Below are my classes:
Entity class : SREAPITestingHistory.java
#NamedQueries(#NamedQuery(name="getSREAPITestHistory.findAll", query="SELECT a FROM SREAPITestingHistory a"))
#SqlResultSetMapping(name="sreapitestinghistoryres",
entities=#EntityResult(entityClass=SREAPITestingHistory.class))
#Entity
#Table(name="sreapi_testing_history_details")
#Transactional
public class SREAPITestingHistory implements Serializable{
private static final long serialVersionUID = -7221709766109001257L;
#Id
#Column(name="request_time")
private String request_time;
#Column(name="req_id")
private String req_id;
#Column(name="app_name")
private String app_name;
#Column(name="request_name")
private String request_name;
#Lob
#Column(name="response_body")
private List<RestApiResponse> response;
public String getRequest_time() {
return request_time;
}
public void setRequest_time(String request_time) {
this.request_time = request_time;
}
public String getReq_id() {
return req_id;
}
public void setReq_id(String req_id) {
this.req_id = req_id;
}
public String getApp_name() {
return app_name;
}
public void setApp_name(String app_name) {
this.app_name = app_name;
}
public String getRequest_name() {
return request_name;
}
public void setRequest_name(String request_name) {
this.request_name = request_name;
}
public List<RestApiResponse> getResponse() {
return response;
}
public void setResponse(List<RestApiResponse> response) {
this.response = response;
}
}
Repository Class: SREAPITestingRepository.java
#Repository
public interface SREAPITestingRepository extends CrudRepository< SREAPITestingHistory, String> {
#Modifying
#Transactional
#Query(value="INSERT into sreapi_testing_history_details (request_time,req_id,app_name,request_name,response_body)"+ "VALUES (:request_time,:req_id,:app_name,:request_name,:response_body)", nativeQuery = true)
public void setApiTestHistoryDetails(#Param("request_time") String request_time,#Param("req_id") String req_id,#Param("app_name") String app_name,#Param("request_name") String request_name,#Param("response_body") List<RestApiResponse> response_body);
}
When I am trying to add values for response_body which is actually a List of RestApiResponse class and I am getting Could not determine a type for class: com.try.sreapi.beans.RestApiResponse exception
From Official doc
A Lob may be either a binary or character type.
The Lob type is inferred from the type of the persistent field or
property, and except for string and character-based types defaults to
Blob.
Example 1:
#Lob #Basic(fetch=LAZY) #Column(name="REPORT")
String report;
Example 2:
#Lob #Basic(fetch=LAZY) #Column(name="EMP_PIC",
columnDefinition="BLOB NOT NULL") protected byte[] pic;
So you can convert your list of data into json string or bytes to store.

How to get inet in entity class in spring using hibernate

public List getAllEmployees()
{
return sessionFactory.openSession().createSQLQuery("select * from employee order by eid").list();
}
employee table has a column ipadres,type is inet in postgresql.
#Entity
#Table(name="employee")
public class Employee {
#Id
#Column(name="eid")
private int eid;
private int dept_id;
private String name;
private String address;
private String project;
private String password;
#Column(name="ipadres")
private String ipadres;
private double salary;
private Date Doj;
This is my pojo class. I have taken ipadres as string,but it gives me following exception.
org.hibernate.MappingException: No Dialect mapping for JDBC type: 1111
I was faced with similar problem when i had to create a custom Money type.
The solution is to create your own class which implements the UserType interface.
Here is a great article regarding the matter: example
In a nuthshell you are interested in implementing the following mehods:
import org.hibernate.usertype.UserType;
public InetType impelements UserType{
public Class<String> returnedClass() {
return String.class;
}
public int[] sqlTypes() {
return new int[] { Types.OTHER }; // as the db type is inet and not directly transmutable to hibernate type.
}
public Object nullSafeGet(ResultSet resultSet, String[] names, Object owner)
throws HibernateException, SQLException {
String value = (String) Hibernate.STRING.nullSafeGet(resultSet, names[0]);
return value;
}
public void nullSafeSet(PreparedStatement preparedStatement, Object value, int index)
throws HibernateException, SQLException {
Hibernate.STRING.nullSafeSet(preparedStatement,
(value != null) ? value.toString() : null, index);
}
}
Then in your Employee entity, you need to add type definition annotations:
#Entity
#Table(name="employee")
#TypeDefs(value={#TypeDef(name="inetType",typeClass=InetType.class)})
public class Employee {
#Column(name="ipadres")
#Type(type="inetType")
private String ipadres;
To complete the answer of Maciej, that is correct, add:
In versions of hibernate 4+ the methods nullSafeGet and nullSafeSet will be:
public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner)
throws HibernateException, SQLException {
final String value = (String) StandardBasicTypes.STRING.nullSafeGet(rs, names, session, owner);
return value;
}
public void nullSafeSet(PreparedStatement ps, Object value, int index,
SharedSessionContractImplementor session)
throws HibernateException, SQLException {
StandardBasicTypes.STRING.nullSafeSet(ps, (value != null) ?
value.toString(): null, index, session);
}
Also say that you can define the type in the package-info.java like
#org.hibernate.annotations.TypeDef(name = "inetType", typeClass = InetType.class)
After few days research, I find out no need to reinvent the wheel in 2022.
There is a lib vladmihalcea/hibernate-types implement Inet column type & Inet Hibernate Type.
resource
mvn repos: https://search.maven.org/search?q=g:com.vladmihalcea
tutorial: https://vladmihalcea.com/postgresql-inet-type-hibernate/
GitHub: https://github.com/vladmihalcea/hibernate-types
Example from tutorial
#Entity(name = "Event")
#Table(name = "event")
#TypeDef(
name = "ipv4",
typeClass = PostgreSQLInetType.class,
defaultForType = Inet.class
)
public class Event {
#Id
#GeneratedValue
private Long id;
#Column(
name = "ip",
columnDefinition = "inet"
)
private Inet ip;
public Long getId() {
return id;
}
public Inet getIp() {
return ip;
}
public void setIp(String address) {
this.ip = new Inet(address);
}
}
Work as expected in spring-boot 2.7.1, hibernate 5.6.9.
Found a solution by poking around:
CREATE CAST (CHARACTER VARYING as inet) WITH INOUT AS IMPLICIT;
And mark entity with
#Column(columnDefinition = "inet")
var ip: String? = null

Null columns are created on either tables when accessing data using Spring Data JPA

I am new to Spring Data JPA and Hibernate. By looking at different examples I built a working model for CRUD operations on one entity, I am having trouble in joining two tables to extract AF_NAME using AF_ID from another table which is Foreign key. A null column is created with the names of and while accessing, null is returned.please check if I am following preocedure for joins and point me to any tutorial know.
I followed this solution and still there is no progress.
#Entity
#Configuration
#EnableAutoConfiguration
#Table(name = "AFF_CONFIG")
public class AFF_CONFIG implements Serializable {
#Id
#Column(name = "AFF_CONFIG_ID")
private String AFF_CONFIG_ID;
#Column(name = "AFF_ID")
private String AFF_ID;
#Column(name = "CH_ID")
private String CH_ID;
#Column(name = "M_ID")
private Long M_ID;
#Column(name = "KEY")
private String KEY;
#Column(name = "VALUE")
private String VALUE;
#Column(name = "SYSTEM")
private String SYSTEM;
private AFF aff;
#LazyCollection(LazyCollectionOption.TRUE)
#ManyToOne
#JoinColumn(name = "AFF_ID")
public AFF getAff() {
return aff;
}
public void setAffiliate(AFF aff) {
this.aff = aff;
}
public String getAFF_CONFIG_ID() {
return AFF_CONFIG_ID;
}
public void setAFF_CONFIG_ID(String aFF_CONFIG_ID) {
AFF_CONFIG_ID = aFF_CONFIG_ID;
}
public String getAFF_ID() {
return AFF_ID;
}
public void setAFF_ID(String aFF_ID) {
AFF_ID = AFF_ID;
}
public String getCH_ID() {
return CH_ID;
}
public void setCHANNEL_ID(String cH_ID) {
CH_ID = cH_ID;
}
public Long getM_ID() {
return M_ID;
}
public void setM_ID(Long m_ID) {
M_ID = m_ID;
}
public String getKEY() {
return KEY;
}
public void setKEY(String kEY) {
KEY = kEY;
}
public String getVALUE() {
return VALUE;
}
public void setVALUE(String vALUE) {
VALUE = vALUE;
}
public String getSYSTEM() {
return SYSTEM;
}
public void setSYSTEM(String sYSTEM) {
SYSTEM = sYSTEM;
}
Second entity is:
#Entity
#Table(name = "AFF")
public class AFF implements Serializable {
#Column(name = "AFF_NAME")
private String AFF_NAME;
#Column(name = "AFF_CODE")
private String AFF_CODE;
#Id
#Column(name = "AFF_ID")
private String AFF_ID;
private Set<AFF_CONFIG> someAff = new HashSet<AFF_CONFIG>();
#LazyCollection(LazyCollectionOption.TRUE)
#OneToMany(cascade = CascadeType.ALL, mappedBy = "aff")
public Set<AFF_CONFIG> getSomeAff() {
return someAff;
}
public void setSomeAff(Set<AFF_CONFIG> someAff) {
this.someAff = someAff;
}
public String getAFF_ID() {
return AFF_ID;
}
public void setAFF_ID(String aFF_ID) {
AFF_ID = aFF_ID;
}
public String getAFF_NAME() {
return AFF_NAME;
}
public void setAFF_NAME(String aFF_NAME) {
AFF_NAME = aFF_NAME;
}
public String getAFF_CODE() {
return AFF_CODE;
}
public void setAFF_CODE(String aFF_CODE) {
AFF_CODE = aFF_CODE;
}
Since this is many to one relation I created set type in one and object type in another as defined in other places.Created a repository by extending crud and added a query. Excise the bunch of different annotations, I included them in hoping to solve the null entry.
#Repository
public interface MarketRepository extends CrudRepository<AFF_CONFIG,String> {
Page<AFF_CONFIG> findAll(Pageable pageable);
#Query("Select a,b from AFF_CONFIG a, AFF b where a.AFF_ID = b.AFF_ID" )
public List<AFF_CONFIG> getAffData();
}
the applicatoin is working fine even after some tinkering until I Included these annotations. Now there is this error:
Caused by: org.hibernate.MappingException: Could not determine type for: java.util.Set, at table: aff.
I solved the issue with the help of my Supervisor. Looks like we have to follow naming specifications for Class and variables. And one more correction is to remove collection type object and change it to just object (removed set in aff class).I will post the corrected later, to compare and contrast.

How to map multiple parameter names to POJO when binding spring mvc command objects

My question is actually a spin-off of this question as seen here... so it might help to check that thread before proceeding.
In my Spring Boot project, I have two entities Sender and Recipient which represent a Customer and pretty much have the same fields, so I make them extend the base class Customer;
Customer base class;
#MappedSuperclass
public class Customer extends AuditableEntity {
#Column(name = "firstname")
private String firstname;
#Transient
private CustomerRole role;
public Customer(CustomerRole role) {
this.role = role;
}
//other fields & corresponding getters and setters
}
Sender domain object;
#Entity
#Table(name = "senders")
public class Sender extends Customer {
public Sender(){
super.setRole(CustomerRole.SENDER);
}
}
Recipient domain object;
#Entity
#Table(name = "recipients")
public class Recipient extends Customer {
public Recipient(){
super.setRole(CustomerRole.RECIPIENT);
}
}
NOTE - Sender and Recipient are exactly alike except for their roles. These can be easily stored in a single customers Table by making the Customer base class an entity itself, but I intentionally separate the entities this way because I have an obligation to persist each customer type in separate database tables.
Now I have one form in a view that collects details of both Sender & Recipient, so for example to collect the firstname, I had to name the form fields differently as follows;
Sender section of the form;
<input type="text" id="senderFirstname" name="senderFirstname" value="$!sender.firstname">
Recipient section of the form;
<input type="text" id="recipientFirstname" name="recipientFirstname" value="$!recipient.firstname">
But the fields available for a customer are so many that I'm looking for a way to map them to a pojo by means of an annotation as asked in this question here. However, the solutions provided there would mean that I have to create separate proxies for both domain objects and annotate the fields accordingly e.g
public class SenderProxy {
#ParamName("senderFirstname")
private String firstname;
#ParamName("senderLastname")
private String lastname;
//...
}
public class RecipientProxy {
#ParamName("recipientFirstname")
private String firstname;
#ParamName("recipientLastname")
private String lastname;
//...
}
So I got very curious and was wondering, is there a way to map this Proxies to more than one #ParamName such that the base class for example can just be annotated as follows?;
#MappedSuperclass
public class Customer extends AuditableEntity {
#Column(name = "firstname")
#ParamNames({"senderFirstname", "recipientFirstname"})
private String firstname;
#Column(name = "lastname")
#ParamNames({"senderLastname", "recipientLastname"})
private String lastname;
#Transient
private CustomerRole role;
public Customer(CustomerRole role) {
this.role = role;
}
//other fields & corresponding getters and setters
}
And then perhaps find a way to select value of fields based on annotation??
A suggestion from Zhang Jie like ExtendedBeanInfo
so i do it this way
#Target(ElementType.FIELD)
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface Alias {
String[] value();
}
public class AliasedBeanInfoFactory implements BeanInfoFactory, Ordered {
#Override
public BeanInfo getBeanInfo(Class<?> beanClass) throws IntrospectionException {
return supports(beanClass) ? new AliasedBeanInfo(Introspector.getBeanInfo(beanClass)) : null;
}
private boolean supports(Class<?> beanClass) {
Class<?> targetClass = beanClass;
do {
Field[] fields = targetClass.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(Alias.class)) {
return true;
}
}
targetClass = targetClass.getSuperclass();
} while (targetClass != null && targetClass != Object.class);
return false;
}
#Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE - 100;
}
}
public class AliasedBeanInfo implements BeanInfo {
private static final Logger LOGGER = LoggerFactory.getLogger(AliasedBeanInfo.class);
private final BeanInfo delegate;
private final Set<PropertyDescriptor> propertyDescriptors = new TreeSet<>(new PropertyDescriptorComparator());
AliasedBeanInfo(BeanInfo delegate) {
this.delegate = delegate;
this.propertyDescriptors.addAll(Arrays.asList(delegate.getPropertyDescriptors()));
Class<?> beanClass = delegate.getBeanDescriptor().getBeanClass();
for (Field field : findAliasedFields(beanClass)) {
Optional<PropertyDescriptor> optional = findExistingPropertyDescriptor(field.getName(), field.getType());
if (!optional.isPresent()) {
LOGGER.warn("there is no PropertyDescriptor for field[{}]", field);
continue;
}
Alias alias = field.getAnnotation(Alias.class);
addAliasPropertyDescriptor(alias.value(), optional.get());
}
}
private List<Field> findAliasedFields(Class<?> beanClass) {
List<Field> fields = new ArrayList<>();
ReflectionUtils.doWithFields(beanClass,
fields::add,
field -> field.isAnnotationPresent(Alias.class));
return fields;
}
private Optional<PropertyDescriptor> findExistingPropertyDescriptor(String propertyName, Class<?> propertyType) {
return propertyDescriptors
.stream()
.filter(pd -> pd.getName().equals(propertyName) && pd.getPropertyType().equals(propertyType))
.findAny();
}
private void addAliasPropertyDescriptor(String[] values, PropertyDescriptor propertyDescriptor) {
for (String value : values) {
if (!value.isEmpty()) {
try {
this.propertyDescriptors.add(new PropertyDescriptor(
value, propertyDescriptor.getReadMethod(), propertyDescriptor.getWriteMethod()));
} catch (IntrospectionException e) {
LOGGER.error("add field[{}] alias[{}] property descriptor error", propertyDescriptor.getName(),
value, e);
}
}
}
}
#Override
public BeanDescriptor getBeanDescriptor() {
return this.delegate.getBeanDescriptor();
}
#Override
public EventSetDescriptor[] getEventSetDescriptors() {
return this.delegate.getEventSetDescriptors();
}
#Override
public int getDefaultEventIndex() {
return this.delegate.getDefaultEventIndex();
}
#Override
public PropertyDescriptor[] getPropertyDescriptors() {
return this.propertyDescriptors.toArray(new PropertyDescriptor[0]);
}
#Override
public int getDefaultPropertyIndex() {
return this.delegate.getDefaultPropertyIndex();
}
#Override
public MethodDescriptor[] getMethodDescriptors() {
return this.delegate.getMethodDescriptors();
}
#Override
public BeanInfo[] getAdditionalBeanInfo() {
return this.delegate.getAdditionalBeanInfo();
}
#Override
public Image getIcon(int iconKind) {
return this.delegate.getIcon(iconKind);
}
static class PropertyDescriptorComparator implements Comparator<PropertyDescriptor> {
#Override
public int compare(PropertyDescriptor desc1, PropertyDescriptor desc2) {
String left = desc1.getName();
String right = desc2.getName();
for (int i = 0; i < left.length(); i++) {
if (right.length() == i) {
return 1;
}
int result = left.getBytes()[i] - right.getBytes()[i];
if (result != 0) {
return result;
}
}
return left.length() - right.length();
}
}
}

Hibernate custom usertype for List of Strings only works with immutable entity

This is a spring boot app using JPA, and PostgreSQL
I am trying to persist a list of Strings (or an array of Strings) in a postgres table.
The table looks something like this:
CREATE TABLE test_table (
id BIGSERIAL PRIMARY KEY NOT NULL,
test_text text NOT NULL UNIQUE,
test_array TEXT[]
);
The problem that occurs is when I try to annotate a field with my custom usertype.
#Entity
#Table(name = "test_table")
public class TestTable extends BaseEntity {
#Column(name = "test_text", nullable = false, unique = true)
public String testText;
#Type(type = "com.test.model.utils.StringListType")
#Column(name = "test_array")
public List<String> testArray;
}
The error that I get: No Dialect mapping for JDBC type: 2003
I have worked with this custom usertype before, but only on immutable entities (views).
So, this code works
#Entity
#Subselect("SELECT * FROM test_table")
#Immutable
public class TestView extends BaseEntity {
#Type(type = "com.test.model.utils.StringListType")
#Column(name = "test_array")
public List<String> testArray;
}
I presume it has something to do with the fact that with the #Subselect and #Immutable annotations do not allow for insert/update/delete actions on the entity, where with the #Table annotation I am able to do all of those actions.
Any experience or similar work with a problem like this?
Here is the custom usertype class:
public class StringListType implements UserType
{
protected static final int SQLTYPE = java.sql.Types.ARRAY;
#Override
public Object nullSafeGet(final ResultSet rs, final String[] names,
final SessionImplementor sessionImplementor, final Object owner) throws HibernateException,
SQLException
{
Array array = rs.getArray(names[0]);
List<String> stringList = Arrays.asList((String[]) array.getArray());
return stringList;
}
#Override
public void nullSafeSet(final PreparedStatement statement, final Object object, final int i,
final SessionImplementor sessionImplementor) throws HibernateException, SQLException
{
Connection connection = statement.getConnection();
#SuppressWarnings("unchecked")
List<String> castObject = (List<String>) object;
Array array = connection.createArrayOf("text", castObject.toArray());
statement.setArray(i, array);
}
#Override
public Object assemble(final Serializable cached, final Object owner) throws HibernateException
{
return cached;
}
#Override
public Object deepCopy(final Object o) throws HibernateException
{
return o == null ? null : ((String[]) o).clone();
}
#Override
public Serializable disassemble(final Object o) throws HibernateException
{
return (Serializable) o;
}
#Override
public boolean equals(final Object x, final Object y) throws HibernateException
{
return x == null ? y == null : x.equals(y);
}
#Override
public int hashCode(final Object o) throws HibernateException
{
return o == null ? 0 : o.hashCode();
}
#Override
public boolean isMutable()
{
return false;
}
#Override
public Object replace(final Object original, final Object target, final Object owner)
throws HibernateException
{
return original;
}
#SuppressWarnings("unchecked")
#Override
public Class<List<String>> returnedClass()
{
return (Class<List<String>>) Collections.<String> emptyList().getClass();
}
#Override
public int[] sqlTypes()
{
return new int[] { SQLTYPE };
}
}

Categories

Resources