Hibernate and Oracle VARRAYS/NESTED TABLE - java

Oracle supports the use of VARRAYS and NESTED TABLE data types, allowing multivalued attributes. (http://www.orafaq.com/wiki/NESTED_TABLE)
I am currently using Hibernate 3 as my ORM framework, but I can't see how I can map Hibernate to a NESTED TABLE/VARRAY data type in my database.
I looked at defining custom types in Hibernate, with no success. (Can Hibernate even handle the "COLUMN_VALUE" Oracle keyword necessary to unnest the subtable?)
Does anyone know how to implement these data types in Hibernate?
Thank you all for your help.
-- TBW.

Hibernate's UserType for Oracle's TABLE OF NUMBERS.
OracleNativeExtractor found here : https://community.jboss.org/wiki/MappingOracleXmlTypeToDocument . String YOUR_CUSTOM_ARRAY_TYPE replace with your name.
import oracle.sql.ARRAY;
import oracle.sql.ArrayDescriptor;
import org.apache.commons.lang.ArrayUtils;
import org.hibernate.HibernateException;
import org.hibernate.usertype.UserType;
import java.io.Serializable;
import java.sql.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class ArrayUserType
implements UserType, Serializable {
private static final OracleNativeExtractor EXTRACTOR = new OracleNativeExtractor();
#Override
public int[] sqlTypes() {
return new int[]{Types.ARRAY};
}
#Override
public Class returnedClass() {
return List.class;
}
#Override
public boolean equals(Object x, Object y) throws HibernateException {
if (x == null && y == null) return true;
else if (x == null && y != null) return false;
else return x.equals(y);
}
#Override
public int hashCode(Object x) throws HibernateException {
return x.hashCode();
}
#Override
public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws HibernateException, SQLException {
return Arrays.asList(ArrayUtils.toObject(((ARRAY) rs.getObject(names[0])).getLongArray()));
}
#Override
public void nullSafeSet(PreparedStatement st, Object value, int index) throws HibernateException, SQLException {
ARRAY array = null;
if (value != null) {
Connection nativeConn = EXTRACTOR.getNativeConnection(st.getConnection());
ArrayDescriptor descriptor =
ArrayDescriptor.createDescriptor("YOUR_CUSTOM_ARRAY_TYPE", nativeConn);
array = new ARRAY(descriptor, nativeConn, ((List<Long>) value).toArray(new Long[]{}));
}
st.setObject(1, array);
}
#Override
public Object deepCopy(Object value) throws HibernateException {
if (value == null) return null;
return new ArrayList<Long>((List<Long>) value);
}
#Override
public boolean isMutable() {
return false;
}
public Object assemble(Serializable _cached, Object _owner)
throws HibernateException {
return _cached;
}
public Serializable disassemble(Object _obj)
throws HibernateException {
return (Serializable) _obj;
}
#Override
public Object replace(Object original, Object target, Object owner) throws HibernateException {
return deepCopy(original);
}
}

I hope I'm wrong and that you find a better answer in your research, but this feature is not supported in Hibernate. Hibernate relies on standard JDBC to talk to a database and these features are not part of the standard. They are Oracle extensions.
That said, I can think of a few workarounds:
1) Implement your own UserType. With your specific user type, you'll have a chance to manipulate the values provided by the database (or about to be sent to the database). But that will only work if Oracle provides this value as one of these java.sql.Types: http://download.oracle.com/javase/1.5.0/docs/api/java/sql/Types.html
2) The other option is to use JDBC directly, through the use of a Hibernate worker. See this example of a Worker: https://github.com/hibernate/hibernate-core/blob/master/hibernate-core/src/test/java/org/hibernate/test/jdbc/GeneralWorkTest.java
That said, I think that you have to weight the solutions and re-evaluate if you really need a nested table.

Related

Generic enum JPA AttributeConverter implementation

Problem I am trying to solve
I am trying to implement enum mapping for Hibernate. So far I have researched available options, and both the #Enumerated(EnumType.ORDINAL) and #Enumerated(EnumType.STRING) seemed inadequate for my needs. The #Enumerated(EnumType.ORDINAL) seems to be very error-prone, as a mere reordering of enum constants can mess the mapping up, and the #Enumerated(EnumType.STRING) does not suffice too, as the database I work with is already full of values to be mapped, and these values are not what I would like my enum constants to be named like (the values are foreign language strings / integers).
Currently, all these values are being mapped to String / Integer properties. At the same time the properties should only allow for a restricted sets of values (imagine meetingStatus property allowing for Strings: PLANNED, CANCELED, and DONE. Or another property allowing for a restricted set of Integer values: 1, 2, 3, 4, 5).
My idea was to replace the implementation with enums to improve the type safety of the code. A good example where the String / Integer implementation could cause errors is String method parameter representing such value - with String, anything goes there. Having an Enum parameter type on the other hand introduces compile time safety.
My best approach so far
The only solution that seemed to fulfill my needs was to implement custom javax.persistence.AttributeConverter with #Converter annotation for every enum. As my model would require quite a few enums, writing custom converter for each of them started to seem like a madness really quickly. So I searched for a generic solution to the problem -> how to write a generic converter for any type of enum. The following answer was of big help here: https://stackoverflow.com/a/23564597/7024402. The code example in the answer provides for somewhat generic implementation, yet for every enum there is still a separate converter class needed. The author of the answer also continues:
"The alternative would be to define a custom annotation, patch the JPA provider to recognize this annotation. That way, you could examine the field type as you build the mapping information and feed the necessary enum type into a purely generic converter."
And that's what I think I would be interested in. I could, unfortunately, not find any more information about that, and I would need a little more guidance to understand what needs to be done and how would it work with this approach.
Current Implementation
public interface PersistableEnum<T> {
T getValue();
}
public enum IntegerEnum implements PersistableEnum<Integer> {
ONE(1),
TWO(2),
THREE(3),
FOUR(4),
FIVE(5),
SIX(6);
private int value;
IntegerEnum(int value) {
this.value = value;
}
#Override
public Integer getValue() {
return value;
}
}
public abstract class PersistableEnumConverter<E extends PersistableEnum<T>, T> implements AttributeConverter<E, T> {
private Class<E> enumType;
public PersistableEnumConverter(Class<E> enumType) {
this.enumType = enumType;
}
#Override
public T convertToDatabaseColumn(E attribute) {
return attribute.getValue();
}
#Override
public E convertToEntityAttribute(T dbData) {
for (E enumConstant : enumType.getEnumConstants()) {
if (enumConstant.getValue().equals(dbData)) {
return enumConstant;
}
}
throw new EnumConversionException(enumType, dbData);
}
}
#Converter
public class IntegerEnumConverter extends PersistableEnumConverter<IntegerEnum, Integer> {
public IntegerEnumConverter() {
super(IntegerEnum.class);
}
}
This is how I was able to achieve the partially generic converter implementation.
GOAL: Getting rid of the need to create new converter class for every enum.
Luckily, you should not patch the hibernate for this.
You can declare an annotation like the following:
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.sql.Types;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
#Target({METHOD, FIELD})
#Retention(RUNTIME)
public #interface EnumConverter
{
Class<? extends PersistableEnum<?>> enumClass() default IntegerEnum.class;
int sqlType() default Types.INTEGER;
}
A hibernate user type like the following:
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 org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.usertype.DynamicParameterizedType;
import org.hibernate.usertype.UserType;
public class PersistableEnumType implements UserType, DynamicParameterizedType
{
private int sqlType;
private Class<? extends PersistableEnum<?>> clazz;
#Override
public void setParameterValues(Properties parameters)
{
ParameterType reader = (ParameterType) parameters.get(PARAMETER_TYPE);
EnumConverter converter = getEnumConverter(reader);
sqlType = converter.sqlType();
clazz = converter.enumClass();
}
private EnumConverter getEnumConverter(ParameterType reader)
{
for (Annotation annotation : reader.getAnnotationsMethod()){
if (annotation instanceof EnumConverter) {
return (EnumConverter) annotation;
}
}
throw new IllegalStateException("The PersistableEnumType should be used with #EnumConverter annotation.");
}
#Override
public int[] sqlTypes()
{
return new int[] {sqlType};
}
#Override
public Class<?> returnedClass()
{
return clazz;
}
#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);
}
#Override
public Object nullSafeGet(ResultSet rs,
String[] names,
SharedSessionContractImplementor session,
Object owner) throws HibernateException, SQLException
{
Object val = null;
if (sqlType == Types.INTEGER) val = rs.getInt(names[0]);
if (sqlType == Types.VARCHAR) val = rs.getString(names[0]);
if (rs.wasNull()) return null;
for (PersistableEnum<?> pEnum : clazz.getEnumConstants())
{
if (Objects.equals(pEnum.getValue(), val)) return pEnum;
}
throw new IllegalArgumentException("Can not convert " + val + " to enum " + clazz.getName());
}
#Override
public void nullSafeSet(PreparedStatement st,
Object value,
int index,
SharedSessionContractImplementor session) throws HibernateException, SQLException
{
if (value == null) {
st.setNull(index, sqlType);
}
else {
PersistableEnum<?> pEnum = (PersistableEnum<?>) value;
if (sqlType == Types.INTEGER) st.setInt(index, (Integer) pEnum.getValue());
if (sqlType == Types.VARCHAR) st.setString(index, (String) pEnum.getValue());
}
}
#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 then, you can use it:
import org.hibernate.annotations.Type;
#Entity
#Table(name="TST_DATA")
public class TestData
{
...
#EnumConverter(enumClass = IntegerEnum.class, sqlType = Types.INTEGER)
#Type(type = "com.example.converter.PersistableEnumType")
#Column(name="INT_VAL")
public IntegerEnum getIntValue()
...
#EnumConverter(enumClass = StringEnum.class, sqlType = Types.VARCHAR)
#Type(type = "com.example.converter.PersistableEnumType")
#Column(name="STR_VAL")
public StringEnum getStrValue()
...
}
See also the chapter 5.3.3 Extending Hibernate with UserTypes at the excellent book "Java Persistence with Hibernate" by Bauer, King, Gregory.
Simplifying:
import com.pismo.apirest.mvc.enums.OperationType;
import com.pismo.apirest.mvc.enums.support.PersistableEnum;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
#SuppressWarnings("unused")
public interface EnumsConverters {
#RequiredArgsConstructor
abstract class AbstractPersistableEnumConverter<E extends Enum<E> & PersistableEnum<I>, I> implements AttributeConverter<E, I> {
private final E[] enumConstants;
public AbstractPersistableEnumConverter(#NonNull Class<E> enumType) {
enumConstants = enumType.getEnumConstants();
}
#Override
public I convertToDatabaseColumn(E attribute) {
return Objects.isNull(attribute) ? null : attribute.getId();
}
#Override
public E convertToEntityAttribute(I dbData) {
return fromId(dbData, enumConstants);
}
public E fromId(I idValue) {
return fromId(idValue, enumConstants);
}
public static <E extends Enum<E> & PersistableEnum<I>, I> E fromId(I idValue, E[] enumConstants) {
return Objects.isNull(idValue) ? null : Stream.of(enumConstants)
.filter(e -> e.getId().equals(idValue))
.findAny()
.orElseThrow(() -> new IllegalArgumentException(
String.format("Does not exist %s with ID: %s", enumConstants[0].getClass().getSimpleName(), idValue)));
}
}
#Converter(autoApply = true)
class OperationTypeConverter extends AbstractPersistableEnumConverter<OperationType, Integer> {
public OperationTypeConverter() {
super(OperationType.class);
}
}
}
I tried 1000 times create something same.
Generate converter for each enum on the fly - not problem, but then they will be have same class. Main problem there: org.hibernate.boot.internal.MetadataBuilderImpl#applyAttributeConverter(java.lang.Class<? extends javax.persistence.AttributeConverter>, boolean).
If converter already registered we got exception.
public void addAttributeConverterInfo(AttributeConverterInfo info) {
if ( this.attributeConverterInfoMap == null ) {
this.attributeConverterInfoMap = new HashMap<>();
}
final Object old = this.attributeConverterInfoMap.put( info.getConverterClass(), info );
if ( old != null ) {
throw new AssertionFailure(
String.format(
"AttributeConverter class [%s] registered multiple times",
info.getConverterClass()
)
);
}
}
Perhaps we can change org.hibernate.boot.internal.BootstrapContext Impl, but I'm sure it's create too complex and non-flexible code.

JDBCIO Calling Postgres Routine (Stored Proc) which takes a Custom Object Type as parameter

I'm trying to call a Postgres routine which takes a custom Object type as a parameter.
create type person_type as
(
first varchar,
second varchar,
is_real boolean
);
My routine (stored proc):
create function person_routine(person person_type)
returns void
language plpgsql
as $$
BEGIN
INSERT INTO person(first, second, is_real) VALUES
(person.first,person.second,person.is_real);
END;
$$;
Then I attempt creating a Java class to represent the custom type:
import java.sql.SQLData;
import java.sql.SQLException;
import java.sql.SQLInput;
import java.sql.SQLOutput;
public class PersonType implements SQLData {
public String first;
public String second;
public boolean is_real;
private String sql_type;
#Override
public String getSQLTypeName() throws SQLException {
return sql_type;
}
#Override
public void readSQL(SQLInput stream, String typeName) throws SQLException {
sql_type = typeName;
second = stream.readString();
first = stream.readString();
is_real = stream.readBoolean();
}
#Override
public void writeSQL(SQLOutput stream) throws SQLException {
stream.writeString(first);
stream.writeBoolean(is_real);
stream.writeString(second);
}
}
Then i attempted to execute the code like this:
.apply(JdbcIO.<Person>write()
.withDataSourceConfiguration(JdbcIO.DataSourceConfiguration.create(
"org.postgresql.Driver", configuration.GetValue("postgres_host"))
.withUsername(configuration.GetValue("postgres_username"))
.withPassword(configuration.GetValue("postgres_password")))
.withStatement("SELECT person_routine(?)")
.withPreparedStatementSetter(new JdbcIO.PreparedStatementSetter<Person>() {
public void setParameters(Person element, PreparedStatement query)
throws SQLException {
PersonType dto = new PersonType();
dto.first = element.first;
dto.second = element.second;
dto.is_real = element.is_real;
query.setObject(1, dto);
}
})
);
Unfortunately that gives me an exception:
java.lang.RuntimeException: org.postgresql.util.PSQLException: Can't infer the SQL type to use for an instance of dto.PersonType. Use setObject() with an explicit Types value to specify the type to use.
Any help would be great.
So, it's about using PGobject(). This is how i achieved it and it seems to work really well.
PGobject person = new PGobject();
person.setType("person_type");
person.setValue(String.format("(%s,%s,%s)","something","something","FALSE"));
query.setObject(1, person);

Mapping a FunctionalJava Option<Type> with Hibernate

I have a hibernate-mapped Java object, JKL, which is full of a bunch of normal hibernate-mappable fields (like strings and integers).
I'm added a new embedded field to it (which lives in the same table -- not a mapping), asdf, which is a fj.data.Option<ASDF>. I've made it an option to make it clear that this field may not actually contain anything (as opposed to having to handle null every time I access it).
How do I set up the mapping in my JKL.hbm.xml file? I'd like hibernate to automatically convert a null in the database to a none of fj.data.Option<ASDF> when it retrieves the object. It should also convert a non-null instance of ASDF to a some of fj.data.Option<ASDF>.
Is there any other trickery that I have to do?
I would suggest introducing FunctionalJava's Option in the accessors (getter and setter), while leaving Hibernate to handle a simple java field which is allowed to be null.
For example, for an optional Integer field:
// SQL
CREATE TABLE `JKL` (
`JKL_ID` INTEGER PRIMARY KEY,
`MY_FIELD` INTEGER DEFAULT NULL
)
You can map a Hibernate private field directly:
// Java
#Column(nullable = true)
private Integer myField;
You could then introduce Option at the accessor boundary:
// Java
public fj.data.Option<Integer> getMyField() {
return fj.data.Option.fromNull(myField);
}
public void setMyField(fj.data.Option<Integer> value) {
myField = value.toNull();
}
Does that work for your needs?
You can use Hibernate's custom mapping types. Documentation is here. Here is an analogous example of mapping Scala's Option to a Hibernate mapping.
Simply put, you would need to extend the org.hibernate.UserType interface. You could also create a generic-typed base class with a JKL-typed sub-type, similar to what you see in the Scala example.
I think using getter/setter is simpler, but here's an example of what I did to make it work :
(It works fine for number and string, but not for date (error with #Temporal annotation)).
import com.cestpasdur.helpers.PredicateHelper;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Optional;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import org.hibernate.HibernateException;
import org.hibernate.usertype.UserType;
import org.joda.time.DateTime;
import java.io.Serializable;
import java.sql.*;
public class OptionUserType implements UserType {
#Override
public int[] sqlTypes() {
return new int[]{
Types.NULL
};
}
#Override
public Class returnedClass() {
return Optional.class;
}
#Override
public boolean equals(Object o, Object o2) throws HibernateException {
return ObjectUtils.equals(o, o2);
}
#Override
public int hashCode(Object o) throws HibernateException {
assert (o != null);
return o.hashCode();
}
#Override
public Optional<? extends Object> nullSafeGet(ResultSet rs, String[] names, Object owner) throws HibernateException, SQLException {
return Optional.fromNullable(rs.getObject(names[0]));
}
#VisibleForTesting
void handleDate(PreparedStatement st, Date value, int index) throws SQLException {
st.setDate(index, value);
}
#VisibleForTesting
void handleNumber(PreparedStatement st, String stringValue, int index) throws SQLException {
Double doubleValue = Double.valueOf(stringValue);
st.setDouble(index, doubleValue);
}
#Override
public void nullSafeSet(PreparedStatement st, Object value, int index) throws SQLException {
if (value != null) {
if (value instanceof Optional) {
Optional optionalValue = (Optional) value;
if (optionalValue.isPresent()) {
String stringValue = String.valueOf(optionalValue.get());
if (StringUtils.isNotBlank(stringValue)) {
if (PredicateHelper.IS_DATE_PREDICATE.apply(stringValue)) {
handleDate(st, new Date(DateTime.parse(stringValue).getMillis()), index);
} else if (StringUtils.isNumeric(stringValue)) {
handleNumber(st, stringValue, index);
} else {
st.setString(index, optionalValue.get().toString());
}
} else {
st.setString(index, null);
}
} else {
System.out.println("else Some");
}
} else {
//TODO replace with Preconditions guava
throw new IllegalArgumentException(value + " is not implemented");
}
} else {
st.setString(index, null);
}
}
#Override
public Object deepCopy(Object o) throws HibernateException {
return o;
}
#Override
public boolean isMutable() {
return false;
}
#Override
public Serializable disassemble(Object o) throws HibernateException {
return (Serializable) o;
}
#Override
public Object assemble(Serializable serializable, Object o) throws HibernateException {
return serializable;
}
#Override
public Object replace(Object original, Object target, Object owner) throws HibernateException {
return original;
}
}

java.lang.ClassCastException: CommonsMultipartFile cannot be cast to oracle.sql.BFILE

I am working on a screen which will upload a file to oracle table as BFILE type. I am using spring3 and hibernate3.
The BO class look like:
#Entity
#Table(name="abc_manuals")
public class ManualBo implements Serializable {
/* Persistent Fields */
#Id
#Column(name="id", nullable=false)
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long mId;
#Column(name="guide")
#Type(type="com.bo.entity.BFILEType")
private BFILE guide;
public Long getMlId() {
return mlId;
}
public void setMId(Long manualId) {
this.mId = mId;
}
public BFILE getGuide() {
return guide;
}
public void setGuide(BFILE guide) {
guide = guide;
}
}
I have defined a BFILE userType:
public class BFILEType implements UserType, Serializable {
#SuppressWarnings("unused")
private static final String MARK_EMPTY = "<EmptyString/>";
private static final int[] TYPES = { OracleTypes.BFILE };
public int[] sqlTypes() {
return TYPES;
}
#SuppressWarnings("rawtypes")
public Class returnedClass() {
return BFILE.class;
}
public boolean equals(Object x, Object y) {
if (x==y) return true;
if (x==null || y==null) return false;
return x.equals(y);
}
public Object deepCopy(Object x) {
return x;
}
public boolean isMutable() { return false; }
public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws
HibernateException, SQLException {
BFILE bfile = (BFILE)rs.getObject(names[0]);
return bfile;
}
public void nullSafeSet(PreparedStatement st, Object value, int index) throws
HibernateException, SQLException {
if(value==null)
st.setObject(index, null);
else
st.setObject(index, value, OracleTypes.BFILE);
}
public Object assemble(Serializable arg0, Object arg1) throws HibernateException {
return deepCopy(arg0);
}
public Serializable disassemble(Object value) {
return (Serializable) deepCopy(value);
}
public int hashCode(Object arg0) throws HibernateException {
return arg0.hashCode();
}
public Object replace(Object arg0, Object arg1, Object arg2) throws
HibernateException {
return deepCopy(arg0);
}
}
Problem is when I am trying to setFile in controller
manual.setGuide((oracle.sql.BFILE) form.getFile());
it is compiling well but when I upload file from screen it is giving following exception
java.lang.ClassCastException: org.springframework.web.multipart.commons.CommonsMultipartFile cannot be cast to oracle.sql.BFILE
How to solve this?
Solved :
I tried :
public void nullSafeSet(PreparedStatement st, Object value, int index) throws HibernateException, SQLException {
if (value == null) {
st.setObject(index, null);
} else {
OracleConnection oc = (OracleConnection) st.getConnection();
OraclePreparedStatement opst = (OraclePreparedStatement) st;
OracleCallableStatement ocs = (OracleCallableStatement) oc.prepareCall("{? = call BFILENAME('" + directory + "', '"+ filename + "')}");
ocs.registerOutParameter(1, OracleTypes.BFILE);
ocs.execute();
BFILE bfile = ocs.getBFILE(1);
opst.setBFILE(index, bfile);
}
}
and its working fine.
Thanks!
java.lang.ClassCastException:
org.springframework.web.multipart.commons.CommonsMultipartFile cannot
be cast to oracle.sql.BFILE
That is correct, the two classes don't share a type hierarchy, there's no way to cast one to the other. Instead of casting the types, you should concentrate on transferring the contents.
This should be the code you need:
BFILE bfile = new BFILE();
bfile.setBytes(form.getFile().getBytes());
manual.setGuide(bfile);
UPDATE: It turns out it's not that easy, as a BFILE can't just be constructed. Here's an Oracle tutorial for working with BFILES in Java.
CommonsMultipartFile represents multipart form request, i.e. a collection of fields. You have to extract your file from the field, i.e. call getFileItem() or getInputStream() to extract the file content.

Using PostgreSQL ltree type with a Java ORM layer

I like to use ltree from PostgreSQL contrib in one of my projects, and i like to use some kind of ORM layer (Hibernate, EclipseLink) on top of the database. I didn't find anything useful about using this type with persistence. I guess i have to extend the current PostgreSQL dialect with a new type and the corresponding operators. However i don't really know where to start and what is the correct way to do this. ltree works very much like a string so guess i should start form a string representation.
Can somebody give me suggestions and/or links to examples which are doing similar things? I couldn't find a complete tutorial yet.
this:
#Column(name = "dir", nullable = false, columnDefinition = "ltree")
#Type(type = "ru.zen0n.hibernate.types.LTreeType")
private String path;
and this:
package ru.zen0n.hibernate.types;
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.usertype.UserType;
public class LTreeType implements UserType {
#Override
public int[] sqlTypes() {
return new int[] {Types.OTHER};
}
#SuppressWarnings("rawtypes")
#Override
public Class returnedClass() {
return String.class;
}
#Override
public boolean equals(Object x, Object y) throws HibernateException {
return x.equals(y);
}
#Override
public int hashCode(Object x) throws HibernateException {
return x.hashCode();
}
#Override
public Object nullSafeGet(ResultSet rs, String[] names, Object owner)
throws HibernateException, SQLException {
return rs.getString(names[0]);
}
#Override
public void nullSafeSet(PreparedStatement st, Object value, int index)
throws HibernateException, SQLException {
st.setObject(index, value, Types.OTHER);
}
#Override
public Object deepCopy(Object value) throws HibernateException {
return new String((String)value);
}
#Override
public boolean isMutable() {
return false;
}
#Override
public Serializable disassemble(Object value) throws HibernateException {
return (Serializable)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 {
// TODO Auto-generated method stub
return deepCopy(original);
}
}
Here some pointers that may help you. It is not a complete answer but it is a bit too much for a comment.
Both Hibernate and EclipseLink implement the JPA standard but also have some extensions of their own and allow for custom behavior.
I haven't done anywork with ltree's in specific but I think you could just use the java String class and add an annotation to the column to overrule the normal column type used (this can be done in standard JPA).
#Column(columnDefinition="ltree")
private String myLtreeValue;
If a String an the Java side is not sufficient you can create your own class and write a converter class for it. You can see an example of that in this question. This would be persistence provider dependent.
When performing queries with conditions involving ltree values you would probably be best of using the JPA #NamedNativeQuery annotation to define queries involving the special operators. For an example see here

Categories