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)。