How to map a PostgreSQL array with Hibernate - java

Has anyone successfully mapped a numeric array in PostgreSQL to a numeric array in Java via Hibernate?
SQL:
CREATE TABLE sal_emp (name text, pay_by_quarter integer[]);
INSERT INTO sal_emp VALUES ('one', '{1,2,3}');
INSERT INTO sal_emp VALUES ('two', '{4,5,6}');
INSERT INTO sal_emp VALUES ('three', '{2,4,6}');
Mapping:
<hibernate-mapping>
<class name="SalEmp" table="sal_emp">
<id name="name" />
<property name="payByQuarter" column="pay_by_quarter" />
</class>
</hibernate-mapping>
Class:
public class SalEmp implements Serializable{
private String name;
private Integer[] payByQuarter;
...// getters & setters
}
I get an exception when querying the table.

Maven dependency
The first thing you need to do is to set up the following Hibernate Types Maven dependency in your project pom.xml configuration file:
<dependency>
<groupId>com.vladmihalcea</groupId>
<artifactId>hibernate-types-52</artifactId>
<version>${hibernate-types.version}</version>
</dependency>
Assuming you have this table in your database:
create table event (
id int8 not null,
version int4,
sensor_names text[],
sensor_values integer[],
primary key (id)
)
And you want to map it like this:
#Entity(name = "Event")
#Table(name = "event")
#TypeDefs({
#TypeDef(
name = "string-array",
typeClass = StringArrayType.class
),
#TypeDef(
name = "int-array",
typeClass = IntArrayType.class
)
})
public static class Event extends BaseEntity {
#Type( type = "string-array" )
#Column(
name = "sensor_names",
columnDefinition = "text[]"
)
private String[] sensorNames;
#Type( type = "int-array" )
#Column(
name = "sensor_values",
columnDefinition = "integer[]"
)
private int[] sensorValues;
//Getters and setters omitted for brevity
}
The string-array and int-array are custom types which can be defined in the BaseEntity superclass:
#TypeDefs({
#TypeDef(
name = "string-array",
typeClass = StringArrayType.class
),
#TypeDef(
name = "int-array",
typeClass = IntArrayType.class
)
})
#MappedSuperclass
public class BaseEntity {
#Id
private Long id;
#Version
private Integer version;
//Getters and setters omitted for brevity
}
The StringArrayType and IntArrayType are classes offered by the Hibernate Types project.
Testing time
Now, when you insert a couple of entities;
Event nullEvent = new Event();
nullEvent.setId(0L);
entityManager.persist(nullEvent);
Event event = new Event();
event.setId(1L);
event.setSensorNames(
new String[] {
"Temperature",
"Pressure"
}
);
event.setSensorValues(
new int[] {
12,
756
}
);
entityManager.persist(event);
Hibernate is going to generate the following SQL statements:
INSERT INTO event (
version,
sensor_names,
sensor_values,
id
)
VALUES (
0,
NULL(ARRAY),
NULL(ARRAY),
0
)
INSERT INTO event (
version,
sensor_names,
sensor_values,
id
)
VALUES (
0,
{"Temperature","Pressure"},
{"12","756"},
1
)

Hibernate does not support database arrays (e.g. ones mapped to java.sql.Array) out of the box.
array and primitive-array types provided by Hibernate are for mapping Java arrays into backing table - they're basically a variation of one-to-many / collection-of-elements mappings, so that's not what you want.
Latest PostgreSQL JDBC driver (8.4.whatever) supports JDBC4 Connection.createArrayOf() method as well as ResultSet.getArray() and PreparedStatement.setArray() methods, though, so you can write your own UserType to provide array support.
Here is a UserType implementation dealing with Oracle array that provides a good starting point, it's reasonably straightforward to adapt it to handle java.sql.Array instead.

This has been tested against string arrays. Maybe some modifications in the converter is required for numeric arrays. This works with Spring JPA.
1) add PostgreSQLTextArray to your project
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Map;
/**
* This is class provides {#link java.sql.Array} interface for PostgreSQL <code>text</code> array.
*
* #author Valentine Gogichashvili
*
*/
public class PostgreSQLTextArray implements java.sql.Array {
private final String[] stringArray;
private final String stringValue;
/**
* Initializing constructor
* #param stringArray
*/
public PostgreSQLTextArray(String[] stringArray) {
this.stringArray = stringArray;
this.stringValue = stringArrayToPostgreSQLTextArray(this.stringArray);
}
#Override
public String toString() {
return stringValue;
}
private static final String NULL = "NULL";
/**
* This static method can be used to convert an string array to string representation of PostgreSQL text array.
* #param a source String array
* #return string representation of a given text array
*/
public static String stringArrayToPostgreSQLTextArray(String[] stringArray) {
final int arrayLength;
if ( stringArray == null ) {
return NULL;
} else if ( ( arrayLength = stringArray.length ) == 0 ) {
return "{}";
}
// count the string length and if need to quote
int neededBufferLentgh = 2; // count the beginning '{' and the ending '}' brackets
boolean[] shouldQuoteArray = new boolean[stringArray.length];
for (int si = 0; si < arrayLength; si++) {
// count the comma after the first element
if ( si > 0 ) neededBufferLentgh++;
boolean shouldQuote;
final String s = stringArray[si];
if ( s == null ) {
neededBufferLentgh += 4;
shouldQuote = false;
} else {
final int l = s.length();
neededBufferLentgh += l;
if ( l == 0 || s.equalsIgnoreCase(NULL) ) {
shouldQuote = true;
} else {
shouldQuote = false;
// scan for commas and quotes
for (int i = 0; i < l; i++) {
final char ch = s.charAt(i);
switch(ch) {
case '"':
case '\\':
shouldQuote = true;
// we will escape these characters
neededBufferLentgh++;
break;
case ',':
case '\'':
case '{':
case '}':
shouldQuote = true;
break;
default:
if ( Character.isWhitespace(ch) ) {
shouldQuote = true;
}
break;
}
}
}
// count the quotes
if ( shouldQuote ) neededBufferLentgh += 2;
}
shouldQuoteArray[si] = shouldQuote;
}
// construct the String
final StringBuilder sb = new StringBuilder(neededBufferLentgh);
sb.append('{');
for (int si = 0; si < arrayLength; si++) {
final String s = stringArray[si];
if ( si > 0 ) sb.append(',');
if ( s == null ) {
sb.append(NULL);
} else {
final boolean shouldQuote = shouldQuoteArray[si];
if ( shouldQuote ) sb.append('"');
for (int i = 0, l = s.length(); i < l; i++) {
final char ch = s.charAt(i);
if ( ch == '"' || ch == '\\' ) sb.append('\\');
sb.append(ch);
}
if ( shouldQuote ) sb.append('"');
}
}
sb.append('}');
assert sb.length() == neededBufferLentgh;
return sb.toString();
}
#Override
public Object getArray() throws SQLException {
return stringArray == null ? null : Arrays.copyOf(stringArray, stringArray.length);
}
#Override
public Object getArray(Map<String, Class<?>> map) throws SQLException {
return getArray();
}
#Override
public Object getArray(long index, int count) throws SQLException {
return stringArray == null ? null : Arrays.copyOfRange(stringArray, (int) index, (int) index + count);
}
#Override
public Object getArray(long index, int count, Map<String, Class<?>> map) throws SQLException {
return getArray(index, count);
}
#Override
public int getBaseType() throws SQLException {
return java.sql.Types.VARCHAR;
}
#Override
public String getBaseTypeName() throws SQLException {
return "text";
}
#Override
public ResultSet getResultSet() throws SQLException {
throw new UnsupportedOperationException();
}
#Override
public ResultSet getResultSet(Map<String, Class<?>> map) throws SQLException {
throw new UnsupportedOperationException();
}
#Override
public ResultSet getResultSet(long index, int count) throws SQLException {
throw new UnsupportedOperationException();
}
#Override
public ResultSet getResultSet(long index, int count, Map<String, Class<?>> map) throws SQLException {
throw new UnsupportedOperationException();
}
#Override
public void free() throws SQLException {
}
}
2) Add ListToArrayConverter to your code
import org.postgresql.jdbc4.Jdbc4Array;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
#Converter(autoApply = true)
public class ListToArrayConveter implements AttributeConverter<List<String>, Object> {
#Override
public PostgreSQLTextArray convertToDatabaseColumn(List<String> attribute) {
if (attribute == null || attribute.isEmpty()) {
return null;
}
String[] rst = new String[attribute.size()];
return new PostgreSQLTextArray(attribute.toArray(rst));
}
#Override
public List<String> convertToEntityAttribute(Object dbData) {
List<String> rst = new ArrayList<>();
try {
String[] elements = (String[]) ((Jdbc4Array) dbData).getArray();
for (String element : elements) {
rst.add(element);
}
} catch (SQLException e) {
e.printStackTrace();
}
return rst;
}
}
3) Use it!
#Entity
#Table(name = "emails")
public class Email {
[...]
#SuppressWarnings("JpaAttributeTypeInspection")
#Column(name = "subject", columnDefinition = "text[]")
#Convert(converter = ListToArrayConveter.class)
private List<String> subject;
[...]

Perhaps this is useful for someone else: I found that in my case it performs poorly and could not be used with c3p0. (Only explored these issues briefly, is they can be resolved please correct me!)
Hibernate 3.6:
import java.io.Serializable;
import java.sql.Array;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import org.apache.commons.lang.ArrayUtils;
import org.hibernate.HibernateException;
import org.hibernate.usertype.UserType;
public class IntArrayUserType implements UserType {
protected static final int SQLTYPE = java.sql.Types.ARRAY;
#Override
public Object nullSafeGet(final ResultSet rs, final String[] names, final Object owner) throws HibernateException, SQLException {
Array array = rs.getArray(names[0]);
Integer[] javaArray = (Integer[]) array.getArray();
return ArrayUtils.toPrimitive(javaArray);
}
#Override
public void nullSafeSet(final PreparedStatement statement, final Object object, final int i) throws HibernateException, SQLException {
Connection connection = statement.getConnection();
int[] castObject = (int[]) object;
Integer[] integers = ArrayUtils.toObject(castObject);
Array array = connection.createArrayOf("integer", integers);
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 : ((int[]) 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;
}
#Override
public Class<int[]> returnedClass() {
return int[].class;
}
#Override
public int[] sqlTypes() {
return new int[] { SQLTYPE };
}
}

I was able to save a String[] to PostgreSQL 9.4 and EclipseLink 2.6.2 via the JPA Converter approach posted here
which seems to be the source for the answer of
Tk421 of 1st July 2016.
Loading an Array from DB also works well.
Additionally added to persistence.xml my path to ListToArrayConverter:
<class> com.foo1.foo2.foo3.backend.jpa.convert.ListToArrayConverter </class>
Please mention that Jdbc4Array is not present in Postgre JDBC driver anymore, please instead use:
org.postgresql.jdbc.PgArray
See here:
Package org.postgresql.jdbc4 is missing since 9.4-1207

Here is the int[] UserType I used to do what you're after which also includes the null checks for nullSafeGet() and nullSafeSet():
import org.apache.commons.lang.ArrayUtils;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.usertype.UserType;
import java.io.Serializable;
import java.sql.Array;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class IntegerArrayUserType implements UserType {
protected static final int SQLTYPE = java.sql.Types.ARRAY;
#Override
public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) throws HibernateException, SQLException {
Array array = rs.getArray(names[0]);
if (array == null) {
return null;
}
Integer[] javaArray = (Integer[]) array.getArray();
return ArrayUtils.toPrimitive(javaArray);
}
#Override
public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session) throws HibernateException, SQLException {
Connection connection = st.getConnection();
if (value == null) {
st.setNull( index, sqlTypes()[0] );
} else {
int[] castObject = (int[]) value;
Integer[] integers = ArrayUtils.toObject(castObject);
Array array = connection.createArrayOf("integer", integers);
st.setArray(index, 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 : ((int[]) 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;
}
#Override
public Class<int[]> returnedClass() {
return int[].class;
}
#Override
public int[] sqlTypes() {
return new int[] { SQLTYPE };
}
}

Related

How to pass a parameter to the hibernate AttributeConverter

If I have a AttributeConverter that changes the length of the input before persisting it in the database, what would be the correct way to make sure that the modified input doesn't exceed the maximum length allowed by that column (without hardcoding it in the converter) ?
#Column(length = 1024)
#Convert(converter = MyConverter.class)
private String comment;
public class MyConverter implements AttributeConverter<String, String> {
#Override
public String convertToDatabaseColumn(String attribute) {
return "hello world " + attribute;
}
...
}
You can achieve what you want with hibernate custom basic type that implements DynamicParameterizedType interface.
Below you can see a simple example of declaration custom type that read the length property of #Column annotation.
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Objects;
import java.util.Properties;
import javax.persistence.Column;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.usertype.DynamicParameterizedType;
import org.hibernate.usertype.UserType;
public class PersistableString implements UserType, DynamicParameterizedType
{
private int sqlType;
private int columnLength;
public PersistableString()
{
this.sqlType = Types.VARCHAR;
}
#Override
public void setParameterValues(Properties parameters)
{
ParameterType reader = (ParameterType) parameters.get(PARAMETER_TYPE);
this.columnLength = getLength(reader);
}
private int getLength(ParameterType reader)
{
int length = -1; // default length
for (Annotation annotation : reader.getAnnotationsMethod()){
if (annotation instanceof Column) {
length = ((Column) annotation).length();
}
}
return length;
}
#Override
public int[] sqlTypes()
{
return new int[] {sqlType};
}
#Override
public Class<?> returnedClass()
{
return String.class;
}
#Override
public boolean equals(Object x, Object y) throws HibernateException
{
return Objects.equals(x, y);
}
#Override
public int hashCode(Object x) throws HibernateException
{
return Objects.hashCode(x);
}
/*
This method will be called when hibernate initializes your entity's
field from the appropriate database table row
*/
#Override
public Object nullSafeGet(ResultSet rs,
String[] names,
SharedSessionContractImplementor session,
Object owner) throws HibernateException, SQLException
{
// you can use this.columnLength here
return rs.getString(names[0]);
}
/*
This method will be called when hibernate persists your entity's field
to the appropriate database table row
*/
#Override
public void nullSafeSet(PreparedStatement st,
Object value,
int index,
SharedSessionContractImplementor session) throws HibernateException, SQLException
{
// you can use this.columnLength here
if (value == null) {
st.setNull(index, sqlType);
}
else {
String val = (String) value;
st.setString(index, val);
}
}
#Override
public Object deepCopy(Object value) throws HibernateException
{
return value;
}
#Override
public boolean isMutable()
{
return false;
}
#Override
public Serializable disassemble(Object value) throws HibernateException
{
return Objects.toString(value);
}
#Override
public Object assemble(Serializable cached, Object owner) throws HibernateException
{
return cached;
}
#Override
public Object replace(Object original, Object target, Object owner) throws HibernateException
{
return original;
}
}
and usage:
import org.hibernate.annotations.Type;
#Entity
#Table
public class Account
{
#Column(name = "acc_name", length = 50)
#Type(type = "com.example.hibernate.PersistableString")
private String name;
#Column(name = "acc_pass", length = 30)
#Type(type = "com.example.hibernate.PersistableString")
private String pass;
#Column(name = "acc_email", length = 355)
#Type(type = "com.example.hibernate.PersistableString")
private String email;
}

How to use Postgres JSONB datatype with JPA?

Im not finding a way to map the JSON and JSONB datatypes from PostgreSQL using JPA (EclipseLink). Is some one using this datatypes with JPA and can give me some working examples?
All the answers helped me to reach the final solution that is ready for JPA and not EclipseLink or Hibernate specifically.
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import javax.json.Json;
import javax.json.JsonObject;
import javax.persistence.Converter;
import org.postgresql.util.PGobject;
#Converter(autoApply = true)
public class JsonConverter implements javax.persistence.AttributeConverter<JsonObject, Object> {
private static final long serialVersionUID = 1L;
private static ObjectMapper mapper = new ObjectMapper();
#Override
public Object convertToDatabaseColumn(JsonObject objectValue) {
try {
PGobject out = new PGobject();
out.setType("json");
out.setValue(objectValue.toString());
return out;
} catch (Exception e) {
throw new IllegalArgumentException("Unable to serialize to json field ", e);
}
}
#Override
public JsonObject convertToEntityAttribute(Object dataValue) {
try {
if (dataValue instanceof PGobject && ((PGobject) dataValue).getType().equals("json")) {
return mapper.reader(new TypeReference<JsonObject>() {
}).readValue(((PGobject) dataValue).getValue());
}
return Json.createObjectBuilder().build();
} catch (IOException e) {
throw new IllegalArgumentException("Unable to deserialize to json field ", e);
}
}
}
Edit: I see now that this is pretty much Hibernate dependent. But perhaps you can find something similar for EclipseLink.
I'll just add what I have as an answer, it originates from another SO answer but whatever. This will map jsonb to JsonObject of Google gson, but you can change it to something else if needed. To change to something else, change nullSafeGet, nullSafeSetand deepCopy methods.
public class JsonbType implements UserType {
#Override
public int[] sqlTypes() {
return new int[] { Types.JAVA_OBJECT };
}
#Override
public Class<JsonObject> returnedClass() {
return JsonObject.class;
}
#Override
public boolean equals(final Object x, final Object y) {
if (x == y) {
return true;
}
if (x == null || y == null) {
return false;
}
return x.equals(y);
}
#Override
public int hashCode(final Object x) {
if (x == null) {
return 0;
}
return x.hashCode();
}
#Nullable
#Override
public Object nullSafeGet(final ResultSet rs,
final String[] names,
final SessionImplementor session,
final Object owner) throws SQLException {
final String json = rs.getString(names[0]);
if (json == null) {
return null;
}
final JsonParser jsonParser = new JsonParser();
return jsonParser.parse(json).getAsJsonObject();
}
#Override
public void nullSafeSet(final PreparedStatement st,
final Object value,
final int index,
final SessionImplementor session) throws SQLException {
if (value == null) {
st.setNull(index, Types.OTHER);
return;
}
st.setObject(index, value.toString(), Types.OTHER);
}
#Nullable
#Override
public Object deepCopy(#Nullable final Object value) {
if (value == null) {
return null;
}
final JsonParser jsonParser = new JsonParser();
return jsonParser.parse(value.toString()).getAsJsonObject();
}
#Override
public boolean isMutable() {
return true;
}
#Override
public Serializable disassemble(final Object value) {
final Object deepCopy = deepCopy(value);
if (!(deepCopy instanceof Serializable)) {
throw new SerializationException(
String.format("deepCopy of %s is not serializable", value), null);
}
return (Serializable) deepCopy;
}
#Nullable
#Override
public Object assemble(final Serializable cached, final Object owner) {
return deepCopy(cached);
}
#Nullable
#Override
public Object replace(final Object original, final Object target, final Object owner) {
return deepCopy(original);
}
}
To use this, do:
public class SomeEntity {
#Column(name = "jsonobject")
#Type(type = "com.myapp.JsonbType")
private JsonObject jsonObject;
In addition, you need to set your dialect to indicate that JAVA_OBJECT = jsonb:
registerColumnType(Types.JAVA_OBJECT, "jsonb");
I think I found an analogy to Hibernate's UserType for EclipseLink.
http://www.eclipse.org/eclipselink/documentation/2.6/jpa/extensions/annotations_ref.htm#CHDEHJEB
You have to make a class that implements org.eclipse.persistence.mappings.converters.Converter and does the conversion for you, then use the #Convert annotation on every field where you are using that type.
For anyone looking for a Mysql solution with the JSON column type, here it is. FWIW I am using EclipseLink but this is a pure JPA solution.
#Column(name = "JSON_DATA", columnDefinition="JSON")
#Convert(converter=JsonAttributeConverter.class)
private Object jsonData;
and
#Converter
public class JsonAttributeConverter implements AttributeConverter <Object, String>
{
private JsonbConfig cfg = new JsonbConfig().withFormatting(true);
private Jsonb jsonb = JsonbBuilder.create(cfg);
#Override
public String convertToDatabaseColumn(Object object)
{
if (object == null) return null;
return jsonb.toJson(object);
}
#Override
public Object convertToEntityAttribute(String value)
{
if (value == null) return null;
return jsonb.fromJson(value, value.getClass());
}
}

Jpa annotated custom user type error property mapping has wrong number of columns

I recently mapped a field of a class with a custom hibernate UserType.
this is my custom user type
package service.dao.hibernate;
import java.io.IOException;
import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Properties;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.usertype.ParameterizedType;
import org.hibernate.usertype.UserType;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.SimpleType;
import com.google.common.base.Objects;
public abstract class JSONUserType implements UserType { //ParameterizedType, Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private static final ObjectMapper Mapper;
private static final String CLASS_TYPE = "classType";
private static final String TYPE = "type";
private static final int[] SQL_TYPES = new int[] { Types.LONGVARCHAR,
Types.CLOB, Types.BLOB };
private Class classType;
private int sqlType = Types.LONGVARCHAR; // before any guessing
static {
Mapper = new ObjectMapper();
Mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
}
#Override
public Object assemble(Serializable cached, Object owner)
throws HibernateException {
return this.deepCopy(cached);
}
#Override
public Object deepCopy(Object value) throws HibernateException {
Object copy = null;
if (value != null) {
try {
return Mapper.readValue(Mapper.writeValueAsString(value),
this.classType);
} catch (IOException e) {
throw new HibernateException("unable to deep copy object", e);
}
}
return copy;
}
#Override
public Serializable disassemble(Object value) throws HibernateException {
try {
return Mapper.writeValueAsString(value);
} catch (JsonProcessingException e) {
throw new HibernateException("unable to disassemble object", e);
}
}
#Override
public boolean equals(Object x, Object y) throws HibernateException {
if (x == y) {
return true;
} else if (x == null || y == null) {
return false;
} else {
return x.equals(y);
}
}
#Override
public int hashCode(Object x) throws HibernateException {
return null == x ? 0 : x.hashCode();
}
#Override
public boolean isMutable() {
return true;
}
#Override
public Object nullSafeGet(ResultSet rs, String[] names,
SessionImplementor session, Object owner)
throws HibernateException, SQLException {
Object obj = null;
if (!rs.wasNull()) {
if (this.sqlType == Types.CLOB || this.sqlType == Types.BLOB) {
byte[] bytes = rs.getBytes(names[0]);
if (bytes != null) {
try {
obj = Mapper.readValue(bytes, createJavaType(Mapper));
} catch (IOException e) {
throw new HibernateException(
"unable to read object from result set", e);
}
}
} else {
try {
String content = rs.getString(names[0]);
if (content != null) {
obj = Mapper.readValue(content, createJavaType(Mapper));
}
} catch (IOException e) {
throw new HibernateException(
"unable to read object from result set", e);
}
}
}
return obj;
}
#Override
public void nullSafeSet(PreparedStatement st, Object value, int index,
SessionImplementor session) throws HibernateException, SQLException {
if (value == null) {
st.setNull(index, this.sqlType);
} else {
if (this.sqlType == Types.CLOB || this.sqlType == Types.BLOB) {
try {
st.setBytes(index, Mapper.writeValueAsBytes(value));
} catch (JsonProcessingException e) {
throw new HibernateException(
"unable to set object to result set", e);
}
} else {
try {
st.setString(index, Mapper.writeValueAsString(value));
} catch (JsonProcessingException e) {
throw new HibernateException(
"unable to set object to result set", e);
}
}
}
}
#Override
public Object replace(Object original, Object target, Object owner)
throws HibernateException {
return this.deepCopy(original);
}
// #Override
// public Class returnedClass() {
// return this.classType;
// }
#Override
public int[] sqlTypes() {
return SQL_TYPES;
}
// #Override
// public void setParameterValues(Properties params) {
// String classTypeName = params.getProperty(CLASS_TYPE);
// try {
// this.classType = ReflectHelper.classForName(classTypeName,
// this.getClass());
// } catch (ClassNotFoundException cnfe) {
// throw new HibernateException("classType not found", cnfe);
// }
// String type = params.getProperty(TYPE);
// if (type != null) {
// this.sqlType = Integer.decode(type).intValue();
// }
// }
/**
* By default we are expecting to use a simple object / not a collection (Set, List)
*
* #param mapper : instance jackson object mapper
*
* #return A jackson JavaType to specify wich object represent the json string representation
*
*/
public JavaType createJavaType (ObjectMapper mapper){
return SimpleType.construct(returnedClass());
}
}
this is the specific user type
package model.common;
import service.dao.hibernate.JSONUserType;
public class DocumentInfoType extends JSONUserType {
#Override
public Class returnedClass() {
return DocumentInfo.class;
}
}
Here is my entity with custom type field
package model.common;
import model.SimpleAuditedEntity;
import model.lk.DocumentMode;
import model.lk.DocumentType;
import service.dao.hibernate.JSONUserType;
import java.io.Serializable;
import javax.persistence.*;
import org.hibernate.annotations.Parameter;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
import org.hibernate.annotations.TypeDefs;
import java.sql.Timestamp;
/**
* The persistent class for the documents database table.
*
*/
#Entity
#Table(name = "documents")
#NamedQuery(name = "Document.findAll", query = "SELECT d FROM Document d")
public class Document extends SimpleAuditedEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name = "content_type")
private String contentType;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "type")
private DocumentType documentType;
#Column
private Timestamp created;
#Column
private String description;
#Column
private String filename;
#Column
private String name;
#Column
private String ref;
#Type(type = "model.common.DocumentInfoType")
#Column
private DocumentInfo info;
public Document() {
}
public int getId() {
return this.id;
}
public void setId(int id) {
this.id = id;
}
public String getContentType() {
return this.contentType;
}
public void setContentType(String contentType) {
this.contentType = contentType;
}
public Timestamp getCreated() {
return this.created;
}
public void setCreated(Timestamp created) {
this.created = created;
}
public String getDescription() {
return this.description;
}
public void setDescription(String description) {
this.description = description;
}
public String getFilename() {
return this.filename;
}
public void setFilename(String filename) {
this.filename = filename;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getRef() {
return this.ref;
}
public void setRef(String ref) {
this.ref = ref;
}
/**
* #return the documentType
*/
public DocumentType getDocumentType() {
return documentType;
}
/**
* #param documentType
* the documentType to set
*/
public void setDocumentType(DocumentType documentType) {
this.documentType = documentType;
}
public DocumentMode getDocumentMode() {
return this.documentType != null ? DocumentMode
.getType(this.documentType.getId()) : DocumentMode.UNDEFINED;
}
/**
* #return the info
*/
public DocumentInfo getInfo() {
return info;
}
/**
* #param info the info to set
*/
public void setInfo(DocumentInfo info) {
this.info = info;
}
}
The problem is when I launch the application I get immediately the exception
Caused by: org.hibernate.MappingException: property mapping has wrong number of columns: model.common.Document.info type: model.common.DocumentInfoType
at org.hibernate.mapping.PersistentClass.validate(PersistentClass.java:497) [hibernate-core-4.3.7.Final.jar:4.3.7.Final]
at org.hibernate.mapping.RootClass.validate(RootClass.java:270) [hibernate-core-4.3.7.Final.jar:4.3.7.Final]
at org.hibernate.cfg.Configuration.validate(Configuration.java:1360) [hibernate-core-4.3.7.Final.jar:4.3.7.Final]
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1851) [hibernate-core-4.3.7.Final.jar:4.3.7.Final]
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$4.perform(EntityManagerFactoryBuilderImpl.java:852) [hibernate-entitymanager-4.3.7.Final.jar:4.3.7.Final]
Any idea about? I've mapped all the columns and I've tried many modifications but nothing!
Thanks in advance
You return an array of SQLTypes from JSONUserType.sqlTypes() that contains 3 elements:
private static final int[] SQL_TYPES = new int[] { Types.LONGVARCHAR,
Types.CLOB, Types.BLOB };
This tells hibernate that your type maps to 3 columns.
You should choose one of the types only.
See the javadoc for UserType.sqlTypes():
Return the SQL type codes for the columns mapped by this type

Implementing Custom hibernate type

I am trying to implement hibernate mapping to sample schema (Order Entry) provided by Oracle default installation
The orderentry schema has several table one of them is Customer table which is having few Columns as customTyped columns for example
the custom type is created with the following statement ;
CREATE OR REPLACE TYPE "CUST_ADDRESS_TYP" AS OBJECT
( street_address VARCHAR2(40)
, postal_code VARCHAR2(10)
, city VARCHAR2(30)
, state_province VARCHAR2(10)
, country_id CHAR(2)
);
and the customer table is created with the below statement
CREATE TABLE OE.CUSTOMERS
(
CUSTOMER_ID NUMBER (6) NOT NULL ,
CUST_FIRST_NAME VARCHAR2 (20 BYTE)
CONSTRAINT CUST_FNAME_NN NOT NULL ,
CUST_LAST_NAME VARCHAR2 (20 BYTE)
CONSTRAINT CUST_LNAME_NN NOT NULL ,
CUST_ADDRESS OE.CUST_ADDRESS_TYP ,
)
I came to know that we need to implement the UserType interface from here but when I try to retrieve it was giving the error
org.hibernate.MappingException: property mapping has wrong number of columns: com.dto.oe.Customers.cust_addressData type: com.dto.oe.CustAddressType
at org.hibernate.mapping.PersistentClass.validate(PersistentClass.java:497)
at org.hibernate.mapping.RootClass.validate(RootClass.java:270)
at org.hibernate.cfg.Configuration.validate(Configuration.java:1360)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1851)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1930)
at com.test.connection.HibernateUtil.getSession(HibernateUtil.java:82)
at com.test.connection.HibernateUtil.getCustomer(HibernateUtil.java:70)
at com.test.connection.HibernateUtil.main(HibernateUtil.java:18)
Wheras my Customtype object pojo class is Cust_addressData and the class which implemented usertype interface is CustAddressType
import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Types;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.usertype.UserType;
public class CustAddressType implements UserType {
/**
* Returns the object from the 2 level cache
*/
public Object assemble(Serializable cached, Object owner)
throws HibernateException {
return cached;
}
/**
* Used to create Snapshots of the object
*/
public Object deepCopy(Object value) throws HibernateException {
final Cust_addressData recievedParam = (Cust_addressData) value;
final Cust_addressData addressData = new Cust_addressData(recievedParam);
return addressData;
}
/**
* method called when Hibernate puts the data in a second level cache. The
* data is stored in a serializable form
*/
public Serializable disassemble(final Object value)
throws HibernateException {
return (Serializable) value;
}
public boolean equals(final Object o1, final Object o2)
throws HibernateException {
boolean isEqual = false;
if (o1 == o2) {
isEqual = true;
}
if (null == o1 || null == o2) {
isEqual = false;
} else {
isEqual = o1.equals(o2);
}
return isEqual;
// for this to work correctly the equals()
// method must be implemented correctly by Cust_addressData class
}
public int hashCode(final Object arg0) throws HibernateException {
return arg0.hashCode();
}
public boolean isMutable() {
return true;
}
public Object nullSafeGet(final ResultSet resultSet, final String[] names,
SessionImplementor sessionImp, final Object owner)
throws HibernateException, SQLException {
// owner here is class from where the call to retrieve data was made.
// In this case the Test class
Cust_addressData addresssData = new Cust_addressData();
// Order of columns is given by sqlTypes() method
if (!resultSet.wasNull()) {
final String street_address = resultSet.getString(names[0]);
final String postal_code = resultSet.getString(names[1]);
final String city = resultSet.getString(names[2]);
final String state_province = resultSet.getString(names[3]);
final String country_id = resultSet.getString(names[4]);
addresssData.setCity(city);
addresssData.setCountry_id(country_id);
addresssData.setPostal_code(postal_code);
addresssData.setState_province(state_province);
addresssData.setStreet_address(street_address);
System.out.println("street_address "+street_address +" names "+names[0]);
} else {
System.err.println("resultSet is null in CustAddressType");
}
return addresssData;
}
public void nullSafeSet(final PreparedStatement statement, final Object value, final int index,
SessionImplementor arg3) throws HibernateException, SQLException {
if (null == value) {
statement.setNull(index, Types.VARCHAR);
statement.setNull(index + 1, Types.VARCHAR);
statement.setNull(index + 2, Types.VARCHAR);
statement.setNull(index + 3,Types.VARCHAR);
statement.setNull(index + 4,Types.VARCHAR);
} else {
Cust_addressData addressData = (Cust_addressData) value;
if (null != addressData.getStreet_address()) {
String street_address = new String(addressData.getStreet_address());
statement.setString(index , street_address);
} else {
statement.setNull(index , Types.VARCHAR);
}
if (null != addressData.getPostal_code()) {
String postal_Code = new String(addressData.getPostal_code());
statement.setString(index+1 , postal_Code);
} else {
statement.setNull(index +1, Types.VARCHAR);
}
if (null != addressData.getCity()) {
String city = new String(addressData.getCity());
statement.setString(index+2 , city);
} else {
statement.setNull(index +2, Types.VARCHAR);
}
if (null != addressData.getState_province()) {
String postal_Code = new String(addressData.getState_province());
statement.setString(index+3 , postal_Code);
} else {
statement.setNull(index +3, Types.VARCHAR);
}
if (null != addressData.getCountry_id()) {
String postal_Code = new String(addressData.getCountry_id());
statement.setString(index+4 , postal_Code);
} else {
statement.setNull(index +4, Types.VARCHAR);
}
}
}
public Object replace(final Object original, final Object target,
final Object owner) throws HibernateException {
return this.deepCopy(original);
}
#SuppressWarnings("rawtypes")
public Class returnedClass() {
return Cust_addressData.class;
}
public int[] sqlTypes() {
return new int[] { Types.VARCHAR, Types.VARCHAR, Types.VARCHAR,
Types.VARCHAR, Types.VARCHAR };
}
}
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
#SuppressWarnings("serial")
#Entity
#Table(name="CUST_ADDRESS_TYP")
public class Cust_addressData implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
#Column(name = "street_address")
private String street_address;
#Column(name = "postal_code")
private String postal_code;
#Column(name = "city")
private String city;
#Column(name = "state_province")
private String state_province;
#Column(name = "country_id")
private String country_id;
public Cust_addressData() {
}
public Cust_addressData(Cust_addressData other) {
this.setCity(other.getCity());
this.setCountry_id(other.getCountry_id());
this.setPostal_code(other.getPostal_code());
this.setStreet_address(other.getStreet_address());
this.setState_province(other.getState_province());
}
public String getStreet_address() {
return street_address;
}
public void setStreet_address(String street_address) {
this.street_address = street_address;
}
public String getPostal_code() {
return postal_code;
}
public void setPostal_code(String postal_code) {
this.postal_code = postal_code;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getState_province() {
return state_province;
}
public void setState_province(String state_province) {
this.state_province = state_province;
}
public String getCountry_id() {
return country_id;
}
public void setCountry_id(String country_id) {
this.country_id = country_id;
}
#Override
public boolean equals(Object obj) {
boolean isEqual = false;
if (obj instanceof Cust_addressData) {
Cust_addressData addressData = (Cust_addressData) obj;
isEqual = addressData.getCity().equals(this.getCity())
&& addressData.getState_province().equals(this.getState_province())
&& addressData.getCountry_id().equals(this.getCountry_id())
&& addressData.getPostal_code().equals(this.getPostal_code())
&& addressData.getStreet_address().equals(this.getStreet_address())
;
}
return isEqual;
}
#Override
public int hashCode() {
int hash = this.getCountry_id().hashCode();
hash = hash * 17 + this.getPostal_code().hashCode();
hash = hash * 31 + this.getStreet_address().hashCode();
hash = hash * 13 + this.getState_province().hashCode();
hash = hash * 14 + this.getCity().hashCode();
return hash;
}
#Override
public String toString() {
return "[ " + this.getClass() + " { city : " + city
+ ", street_address: " + street_address + ", postal_code: "
+ postal_code + ", state_province: " + state_province + ""
+ ",country_id :"+country_id+"}]";
}
}
Full sources:
CustAddressType
Cust_addressData
Customers
One more observation is the sqlTypes method which is in CustAddressType class
public int[] sqlTypes() {
return new int[] { Types.VARCHAR, Types.VARCHAR, Types.VARCHAR,
Types.VARCHAR, Types.VARCHAR };
}
is the one which is returning the number of columns if i make it to return only one column i'm getting the column value as null.
not sure exactly what is goin wrong,Any suggestion where I am doing wrong or any clue will be appreciated
Using STRUCT class in my CustAddressType class has resolved the issue the source code the CustAddressType class is as follows.
import java.io.Serializable;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Struct;
import java.sql.Types;
import oracle.sql.STRUCT;
import oracle.sql.StructDescriptor;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.usertype.UserType;
public class CustAddressType implements UserType {
private static final int SQL_TYPE = Types.STRUCT;
private static final String OBJECT_TYPE = "CUST_ADDRESS_TYP";
/**
* Returns the object from the 2 level cache
*/
public Object assemble(Serializable cached, Object owner)
throws HibernateException {
return cached;
}
/**
* Used to create Snapshots of the object
*/
public Object deepCopy(Object value) throws HibernateException {
if (value == null) {
return null;
}
final Cust_addressData recievedParam = (Cust_addressData) value;
final Cust_addressData addressData = new Cust_addressData(recievedParam);
return addressData;
}
/**
* method called when Hibernate puts the data in a second level cache. The
* data is stored in a serializable form
*/
public Serializable disassemble(final Object value)
throws HibernateException {
return (Serializable) value;
}
public boolean equals(final Object o1, final Object o2)
throws HibernateException {
boolean isEqual = false;
if (o1 == o2) {
isEqual = true;
}
if (null == o1 || null == o2) {
isEqual = false;
} else {
isEqual = o1.equals(o2);
}
return isEqual;
// for this to work correctly the equals()
// method must be implemented correctly by Cust_addressData class
}
public int hashCode(final Object arg0) throws HibernateException {
return arg0.hashCode();
}
public boolean isMutable() {
return true;
}
public Object nullSafeGet(final ResultSet resultSet, final String[] names,
SessionImplementor sessionImp, final Object owner)
throws HibernateException, SQLException {
// owner here is class from where the call to retrieve data was made.
// In this case the Test class
final Cust_addressData addresssData = new Cust_addressData();
final Struct struct = (Struct) resultSet.getObject(names[0]);
if (resultSet.wasNull()) {
return null;
}
addresssData.setCity((String)struct.getAttributes()[0]);
addresssData.setCountry_id((String)struct.getAttributes()[1]);
addresssData.setPostal_code((String)struct.getAttributes()[2]);
addresssData.setState_province((String)struct.getAttributes()[3]);
addresssData.setStreet_address((String)struct.getAttributes()[4]);
return addresssData;
}
public void nullSafeSet(final PreparedStatement statement, final Object value, final int index,
SessionImplementor arg3) throws HibernateException, SQLException {
if (value == null) {
statement.setNull(index, SQL_TYPE, OBJECT_TYPE);
}
else {
final Cust_addressData addresssData = (Cust_addressData) value;
final Object[] values = new Object[] { addresssData.getCity(),addresssData.getCountry_id(), addresssData.getPostal_code(),
addresssData.getState_province(),addresssData.getStreet_address() };
final Connection connection = statement.getConnection();
final STRUCT struct = new STRUCT(StructDescriptor.createDescriptor( OBJECT_TYPE,connection), connection, values);
statement.setObject(index, struct, SQL_TYPE);
}
}
public Object replace(final Object original, final Object target,
final Object owner) throws HibernateException {
return this.deepCopy(original);
}
#SuppressWarnings("rawtypes")
public Class returnedClass() {
return Cust_addressData.class;
}
public int[] sqlTypes() {
return new int[] {SQL_TYPE};
}
}
Thanks to #SteveChambers for pointing it out,
my friend where are the hibernate Annotations ? you have to clear define your each field with the name including your table name ...
import java.io.Serializable;
#Entity
#Table(name = "CUST_ADDRESS_TYP")
public class Cust_addressData implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
#Column(name ="column_name")
private String street_address;
#Column(name ="column_name")
private String postal_code;
#Column(name ="column_name")
private String city;
#Column(name ="column_name")
private String state_province;
#Column(name ="column_name")
private String country_id;
public Cust_addressData() {
}
public Cust_addressData(Cust_addressData other) {
this.setCity(other.getCity());
this.setCountry_id(other.getCountry_id());
this.setPostal_code(other.getPostal_code());
this.setStreet_address(other.getStreet_address());
this.setState_province(other.getState_province());
}
public String getStreet_address() {
return street_address;
}
public void setStreet_address(String street_address) {
this.street_address = street_address;
}
public String getPostal_code() {
return postal_code;
}
public void setPostal_code(String postal_code) {
this.postal_code = postal_code;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getState_province() {
return state_province;
}
public void setState_province(String state_province) {
this.state_province = state_province;
}
public String getCountry_id() {
return country_id;
}
public void setCountry_id(String country_id) {
this.country_id = country_id;
}
#Override
public boolean equals(Object obj) {
boolean isEqual = false;
if (obj instanceof Cust_addressData) {
Cust_addressData addressData = (Cust_addressData) obj;
isEqual = addressData.getCity().equals(this.getCity())
&& addressData.getState_province().equals(this.getState_province())
&& addressData.getCountry_id().equals(this.getCountry_id())
&& addressData.getPostal_code().equals(this.getPostal_code())
&& addressData.getStreet_address().equals(this.getStreet_address())
;
}
return isEqual;
}
#Override
public int hashCode() {
int hash = this.getCountry_id().hashCode();
hash = hash * 17 + this.getPostal_code().hashCode();
hash = hash * 31 + this.getStreet_address().hashCode();
hash = hash * 13 + this.getState_province().hashCode();
hash = hash * 14 + this.getCity().hashCode();
return hash;
}
#Override
public String toString() {
return "[ " + this.getClass() + " { city : " + city
+ ", street_address: " + street_address + ", postal_code: "
+ postal_code + ", state_province: " + state_province + ""
+ ",country_id :"+country_id+"}]";
}
try to put the correct name for each column on the top of each variable and dont make it public .. good luck

PostgreSQL custom type + Hibernate mapping

I am using PostgreSQL (9.1) and Hibernate (4.1.4). I want to map my custom PostgreSQL type into object in Hibernate.
I've created type in PostgreSQL like this:
create type nill_int as (value int8, nill varchar(100));
Now I want to map this type on Hibernate:
package my;
import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.commons.lang.ObjectUtils;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.type.IntegerType;
import org.hibernate.type.StringType;
import org.hibernate.type.Type;
import org.hibernate.usertype.CompositeUserType;
public class PGNillableIntegerType implements CompositeUserType {
#Override
public String[] getPropertyNames() {
return new String[] {"value","nill"};
}
#Override
public Type[] getPropertyTypes() {
return new Type[] {IntegerType.INSTANCE, StringType.INSTANCE};
}
#Override
public Object getPropertyValue(Object component, int property)
throws HibernateException {
if( component == null ) {
return null;
}
final NillableInteger nillable = (NillableInteger)component;
switch (property) {
case 0: {
return nillable.getValue();
}
case 1: {
return nillable.getNill();
}
default: {
throw new HibernateException("Invalid property index [" + property + "]");
}
}
}
#Override
public void setPropertyValue(Object component, int property, Object value)
throws HibernateException {
if(component == null)
return;
final NillableInteger nillable = (NillableInteger) component;
switch (property) {
case 0: {
nillable.setValue((Integer)value);
break;
}
case 1: {
nillable.setNill((String)value);
break;
}
default: {
throw new HibernateException("Invalid property index [" + property + "]");
}
}
}
#Override
public Object nullSafeGet(ResultSet rs, String[] names,
SessionImplementor session, Object owner)
throws HibernateException, SQLException {
assert names.length == 2;
Integer value = (Integer) IntegerType.INSTANCE.get(rs, names[0], session);
String nill = (String) StringType.INSTANCE.get(rs, names[1], session);
return new NillableInteger(value, nill);
}
#Override
public void nullSafeSet(PreparedStatement st, Object value, int index,
SessionImplementor session) throws HibernateException, SQLException {
if(value == null) {
IntegerType.INSTANCE.set(st, null, index, session);
StringType.INSTANCE.set(st, null, index + 1, session);
} else {
final NillableInteger nillable = (NillableInteger)value;
IntegerType.INSTANCE.set(st, nillable.getValue(), index, session);
StringType.INSTANCE.set(st, nillable.getNill(), index + 1, session);
}
}
#Override
public Class returnedClass() {
return NillableInteger.class;
}
#Override
public boolean equals(Object x, Object y) throws HibernateException {
return ObjectUtils.equals(x, y);
}
#Override
public int hashCode(Object x) throws HibernateException {
assert (x != null);
return x.hashCode();
}
#Override
public Object deepCopy(Object value) throws HibernateException {
return value;
}
#Override
public boolean isMutable() {
return false;
}
#Override
public Serializable disassemble(Object value, SessionImplementor session)
throws HibernateException {
return (Serializable) value;
}
#Override
public Object assemble(Serializable cached, SessionImplementor session,
Object owner) throws HibernateException {
return cached;
}
#Override
public Object replace(Object original, Object target,
SessionImplementor session, Object owner) throws HibernateException {
return original;
}
}
and use this in my entity:
package my;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.TableGenerator;
import org.hibernate.annotations.Type;
#Entity
#Table(name = "test_test")
public class TestObj {
#Id
#GeneratedValue(strategy = GenerationType.TABLE, generator = "IdGenerator")
#TableGenerator(
name = "IdGenerator",
pkColumnValue = "test",
table="SeqTable",
allocationSize=1, initialValue=1)
private Long id;
private String test;
#Type(type = "my.PGNillableIntegerType")
#Column(columnDefinition = "nill_int")
// #Columns(columns = {
// #Column(name = "val"),
// #Column(name = "reason")
// })
private NillableInteger nill;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTest() {
return test;
}
public void setTest(String test) {
this.test = test;
}
public NillableInteger getNill() {
return nill;
}
public void setNill(NillableInteger nill) {
this.nill = nill;
}
}
where NillableInteger looks like this:
package my;
import javax.persistence.Column;
import javax.persistence.Entity;
public class NillableInteger {
private Integer value;
private String nill;
public NillableInteger() {
}
public NillableInteger(String str) {
str = str.substring(1,str.length()-1);
String[] splitted = str.split(",");
value = Integer.parseInt(splitted[0]);
nill = splitted[1];
}
public NillableInteger(Integer value, String nill) {
this.value = value;
this.nill = nill;
}
#Override
public String toString() {
return "(" + value + "," + nill + ")";
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((nill == null) ? 0 : nill.hashCode());
result = prime * result + ((value == null) ? 0 : value.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
NillableInteger other = (NillableInteger) obj;
if (nill == null) {
if (other.nill != null)
return false;
} else if (!nill.equals(other.nill))
return false;
if (value == null) {
if (other.value != null)
return false;
} else if (!value.equals(other.value))
return false;
return true;
}
public String getNill() {
return nill;
}
public void setNill(String nill) {
this.nill = nill;
}
public Integer getValue() {
return value;
}
public void setValue(Integer value) {
this.value = value;
}
}
This configuration throws something like this:
org.hibernate.MappingException: property mapping has wrong number of columns: my.TestObj.nill type: my.PGNillableIntegerType
Everything works fine when I use #Columns annotation instead of #Column in the TestObj, but this creates two separate columns in test_test table (TestObj mapping table) with types integer and character varying(255). What I want to achieve is that in the table will be one column with type nill_int (created custom PostgreSQL type) and Java objects will looks like above.
Any ideas?
Thanks, Arek
Ok, this is what I've done to achieve above goal: I've changed my type to something which looks like this:
package my;
import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.usertype.UserType;
public class PGNillableIntegerType implements UserType {
#Override
public int[] sqlTypes() {
return new int[] { Types.OTHER };
}
#Override
public Class returnedClass() {
return NillableInteger.class;
}
#Override
public Object nullSafeGet(ResultSet rs, String[] names,
SessionImplementor session, Object owner)
throws HibernateException, SQLException {
return new NillableInteger(rs.getString(names[0]));
}
#Override
public void nullSafeSet(PreparedStatement st, Object value, int index,
SessionImplementor session) throws HibernateException, SQLException {
if(value == null) {
st.setObject(index, null, Types.OTHER);
} else {
final NillableInteger nillable = (NillableInteger)value;
st.setObject(index, nillable.toString(), Types.OTHER);
// IntegerType.INSTANCE.set(st, nillable.getValue(), index, session);
// StringType.INSTANCE.set(st, nillable.getNill(), index + 1, session);
}
}
#Override
public Object replace(Object original, Object target, Object owner)
throws HibernateException {
return original;
}
public Object assemble(Serializable cached, Object owner)
throws HibernateException {
return cached;
}
public Object deepCopy(Object value) throws HibernateException {
return value;
}
public Serializable disassemble(Object value) throws HibernateException {
return (Serializable) value;
}
public boolean equals(Object arg0, Object arg1) throws HibernateException {
return arg0.equals(arg1);
}
public int hashCode(Object object) throws HibernateException {
return object.hashCode();
}
public boolean isMutable() {
return false;
}
}
Now you can use #Column with PostgreSQL type in columnDefinition. Unfortunately, the problem are queries to hibernate (after some searching - I think the only way is to use SQL queries like this - if anyone knows how to do this with HQL or criteria it would be great...).

Categories