custom type 有两种方式

  • 实现 BasicType 并注册
  • 实现 UserType 可以注册也可以不注册(不注册的话 @Type 中使用全名)

Implementing a BasicType

package org.example.demo.hibernate;

import java.util.Arrays;
import java.util.BitSet;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.annotations.Type;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.dialect.Dialect;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.type.AbstractSingleColumnStandardBasicType;
import org.hibernate.type.DiscriminatorType;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.AbstractTypeDescriptor;
import org.hibernate.type.descriptor.sql.VarcharTypeDescriptor;

public class Test {
    public static void main(String[] args) {
        ServiceRegistry standardRegistry = new StandardServiceRegistryBuilder()
                .applySetting("hibernate.connection.url", "jdbc:p6spy:postgresql://localhost:5432/test")
                .applySetting("hibernate.connection.username", "postgres")
                .applySetting("hibernate.connection.password", "postgres")
                .applySetting("hibernate.hbm2ddl.auto", "create").build();
        Metadata metadata = new MetadataSources(standardRegistry).addAnnotatedClass(Product.class).getMetadataBuilder()
                .applyBasicType(BitSetType.INSTANCE).build();
        SessionFactory sessionFactory = metadata.getSessionFactoryBuilder().build();
        Session session = sessionFactory.openSession();

        BitSet bitSet = BitSet.valueOf(new long[] { 1, 2, 3 });
        Product product = new Product();
        product.bitSet = bitSet;

        session.getTransaction().begin();
        session.persist(product);
        session.getTransaction().commit();

        session.clear();

        Product p = session.get(Product.class, product.id);
        // [1, 2, 3]
        System.out.println(Arrays.toString(p.bitSet.toLongArray()));

        session.close();
        sessionFactory.close();
    }

    @Entity(name = "Product")
    public static class Product {
        @Id
        @GeneratedValue
        Integer id;
        @Type(type = "bitset")
        private BitSet bitSet;
    }

    @SuppressWarnings("serial")
    public static class BitSetType extends AbstractSingleColumnStandardBasicType<BitSet> implements
            DiscriminatorType<BitSet> {
        public static final BitSetType INSTANCE = new BitSetType();

        public BitSetType() {
            super(VarcharTypeDescriptor.INSTANCE, BitSetTypeDescriptor.INSTANCE);
        }

        @Override
        public BitSet stringToObject(String xml) throws Exception {
            return fromString(xml);
        }

        @Override
        public String objectToSQLString(BitSet value, Dialect dialect) throws Exception {
            return toString(value);
        }

        @Override
        public String getName() {
            return "bitset";
        }
    }

    @SuppressWarnings("serial")
    public static class BitSetTypeDescriptor extends AbstractTypeDescriptor<BitSet> {
        private static final String DELIMITER = ",";

        public static final BitSetTypeDescriptor INSTANCE = new BitSetTypeDescriptor();

        public BitSetTypeDescriptor() {
            super(BitSet.class);
        }

        @Override
        public String toString(BitSet value) {
            StringBuilder builder = new StringBuilder();
            for (long token : value.toLongArray()) {
                if (builder.length() > 0) {
                    builder.append(DELIMITER);
                }
                builder.append(Long.toString(token, 2));
            }
            return builder.toString();
        }

        @Override
        public BitSet fromString(String string) {
            if (string == null || string.isEmpty()) {
                return null;
            }
            String[] tokens = string.split(DELIMITER);
            long[] values = new long[tokens.length];

            for (int i = 0; i < tokens.length; i++) {
                values[i] = Long.valueOf(tokens[i], 2);
            }
            return BitSet.valueOf(values);
        }

        @SuppressWarnings({ "unchecked" })
        public <X> X unwrap(BitSet value, Class<X> type, WrapperOptions options) {
            if (value == null) {
                return null;
            }
            if (BitSet.class.isAssignableFrom(type)) {
                return (X) value;
            }
            if (String.class.isAssignableFrom(type)) {
                return (X) toString(value);
            }
            throw unknownUnwrap(type);
        }

        public <X> BitSet wrap(X value, WrapperOptions options) {
            if (value == null) {
                return null;
            }
            if (String.class.isInstance(value)) {
                return fromString((String) value);
            }
            if (BitSet.class.isInstance(value)) {
                return (BitSet) value;
            }
            throw unknownWrap(value.getClass());
        }
    }
}

生成的 SQL

drop table if exists Product cascade
drop sequence if exists hibernate_sequence

create sequence hibernate_sequence start 1 increment 1
create table Product (id int4 not null, bitSet varchar(255), primary key (id))
select nextval ('hibernate_sequence')
insert into Product (bitSet, id) values ('1,10,11', 1)

select test_produ0_.id as id1_0_0_, test_produ0_.bitSet as bitSet2_0_0_ from Product test_produ0_ where test_produ0_.id=1

BasicType 接口有很多方法,可以继承 AbstractStandardBasicType 来简化实现。如果值只存储于一个 column 中,也可以继承 AbstractSingleColumnStandardBasicType

org.hibernate.type.descriptor.java.JavaTypeDescriptor 的实现可以继承 AbstractTypeDescriptor ,要实现下列方法

  • toString(T value)
  • fromString(String string)
  • unwrap(T value, Class type, WrapperOptions options) - 当作为 PreparedStatement bind parameter 时使用
  • wrap(X value, WrapperOptions options) - 当转换 JDBC column value object 到 Java 时使用

注册 BasicType 有两种方式:

  • configuration.registerTypeContributor - FIXME
  • metadataBuilder.applyBasicType()

Implementing a UserType

package org.example.demo.hibernate;

import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Objects;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.annotations.Type;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.MetadataSources;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.type.StringType;
import org.hibernate.type.descriptor.WrapperOptions;
import org.hibernate.type.descriptor.java.AbstractTypeDescriptor;
import org.hibernate.usertype.UserType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Test {
    public static void main(String[] args) {
        ServiceRegistry standardRegistry = new StandardServiceRegistryBuilder()
                .applySetting("hibernate.connection.url", "jdbc:p6spy:postgresql://localhost:5432/test")
                .applySetting("hibernate.connection.username", "postgres")
                .applySetting("hibernate.connection.password", "postgres")
                .applySetting("hibernate.hbm2ddl.auto", "create").build();
        Metadata metadata = new MetadataSources(standardRegistry).addAnnotatedClass(Product.class).getMetadataBuilder()
                .applyBasicType(BitSetUserType.INSTANCE, "bitset").build();
        SessionFactory sessionFactory = metadata.getSessionFactoryBuilder().build();
        Session session = sessionFactory.openSession();

        BitSet bitSet = BitSet.valueOf(new long[] { 1, 2, 3 });
        Product product = new Product();
        product.bitSet = bitSet;

        session.getTransaction().begin();
        session.persist(product);
        session.getTransaction().commit();

        session.clear();

        Product p = session.get(Product.class, product.id);
        // [1, 2, 3]
        System.out.println(Arrays.toString(p.bitSet.toLongArray()));

        session.close();
        sessionFactory.close();
    }

    @Entity(name = "Product")
    public static class Product {
        @Id
        @GeneratedValue
        Integer id;
        @Type(type = "bitset")
        private BitSet bitSet;
    }

    public static class BitSetUserType implements UserType {
        public static final BitSetUserType INSTANCE = new BitSetUserType();
        private static final Logger log = LoggerFactory.getLogger(BitSetUserType.class);

        @Override
        public int[] sqlTypes() {
            return new int[] { StringType.INSTANCE.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);
        }

        @Override
        public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner)
                throws HibernateException, SQLException {
            String columnName = names[0];
            String columnValue = (String) rs.getObject(columnName);
            log.debug("Result set column {} value is {}", columnName, columnValue);
            return columnValue == null ? null : BitSetTypeDescriptor.INSTANCE.fromString(columnValue);
        }

        @Override
        public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session)
                throws HibernateException, SQLException {
            if (value == null) {
                log.debug("Binding null to parameter {} ", index);
                st.setNull(index, Types.VARCHAR);
            } else {
                String stringValue = BitSetTypeDescriptor.INSTANCE.toString((BitSet) value);
                log.debug("Binding {} to parameter {} ", stringValue, index);
                st.setString(index, stringValue);
            }
        }

        @Override
        public Object deepCopy(Object value) throws HibernateException {
            return value == null ? null : BitSet.valueOf(BitSet.class.cast(value).toLongArray());
        }

        @Override
        public boolean isMutable() {
            return true;
        }

        @Override
        public Serializable disassemble(Object value) throws HibernateException {
            return (BitSet) deepCopy(value);
        }

        @Override
        public Object assemble(Serializable cached, Object owner) throws HibernateException {
            return deepCopy(cached);
        }

        @Override
        public Object replace(Object original, Object target, Object owner) throws HibernateException {
            return deepCopy(original);
        }
    }

    @SuppressWarnings("serial")
    public static class BitSetTypeDescriptor extends AbstractTypeDescriptor<BitSet> {
        private static final String DELIMITER = ",";

        public static final BitSetTypeDescriptor INSTANCE = new BitSetTypeDescriptor();

        public BitSetTypeDescriptor() {
            super(BitSet.class);
        }

        @Override
        public String toString(BitSet value) {
            StringBuilder builder = new StringBuilder();
            for (long token : value.toLongArray()) {
                if (builder.length() > 0) {
                    builder.append(DELIMITER);
                }
                builder.append(Long.toString(token, 2));
            }
            return builder.toString();
        }

        @Override
        public BitSet fromString(String string) {
            if (string == null || string.isEmpty()) {
                return null;
            }
            String[] tokens = string.split(DELIMITER);
            long[] values = new long[tokens.length];

            for (int i = 0; i < tokens.length; i++) {
                values[i] = Long.valueOf(tokens[i], 2);
            }
            return BitSet.valueOf(values);
        }

        @SuppressWarnings({ "unchecked" })
        public <X> X unwrap(BitSet value, Class<X> type, WrapperOptions options) {
            if (value == null) {
                return null;
            }
            if (BitSet.class.isAssignableFrom(type)) {
                return (X) value;
            }
            if (String.class.isAssignableFrom(type)) {
                return (X) toString(value);
            }
            throw unknownUnwrap(type);
        }

        public <X> BitSet wrap(X value, WrapperOptions options) {
            if (value == null) {
                return null;
            }
            if (String.class.isInstance(value)) {
                return fromString((String) value);
            }
            if (BitSet.class.isInstance(value)) {
                return (BitSet) value;
            }
            throw unknownWrap(value.getClass());
        }
    }
}

还要修改 logback.xml 以输出 debug 信息

<configuration>
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern>[%thread] %-5level %logger- %msg%n</pattern>
    </encoder>
  </appender>

  <logger name="org.example" level="debug" />
  <logger name="org.hibernate.SQL" level="debug" />
  <logger name="org.hibernate.type" level="trace" />

  <root level="info">
    <appender-ref ref="STDOUT" />
  </root>
</configuration>

生成的 SQL

drop table if exists Product cascade
drop sequence if exists hibernate_sequence

create sequence hibernate_sequence start 1 increment 1
create table Product (id int4 not null, bitSet varchar(255), primary key (id))
select nextval ('hibernate_sequence')
insert into Product (bitSet, id) values ('1,10,11', 1)

select test_produ0_.id as id1_0_0_, test_produ0_.bitSet as bitSet2_0_0_ from Product test_produ0_ where test_produ0_.id=1

如果第 40 行 applyBasicType(BitSetUserType.INSTANCE, "bitset") 中没有指定 key(bitset) 则第 67 行 @Type(type = "bitset") 需要改为 @Type(type = "org.example.demo.hibernate.Test$BitSetUserType") 。注意不能用 @Type(type = "org.example.demo.hibernate.Test.BitSetUserType")

当使用 JPA 而非 Hibernate bootstrap 时,只能使用指定全限定类名的方式,不能使用短名(key)。

results matching ""

    No results matching ""