Hibernate 允许使用 @org.hibernate.annotations.ColumnTransformer 自定义读写 column 值时的 SQL 。如果有多个 column 要自定义读写时的 SQL 则可以使用 @ColumnTransformers
@ColumnTransformer example
package org.example.demo.hibernate;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Persistence;
import org.hibernate.annotations.ColumnTransformer;
public class Test {
public static void main(String[] args) throws Exception {
EntityManagerFactory factory = Persistence.createEntityManagerFactory("test");
EntityManager em = factory.createEntityManager();
Employee employee = new Employee();
employee.password = "hello world";
em.getTransaction().begin();
em.persist(employee);
em.getTransaction().commit();
em.clear();
Employee e = em.find(Employee.class, employee.id);
// hello world
System.out.println(e.password);
em.close();
factory.close();
}
@Entity(name = "Employee")
public static class Employee {
@Id
@GeneratedValue
private Long id;
@Column(name = "pswd")
@ColumnTransformer(read = "convert_from(decode(pswd, 'base64'), 'UTF8')", write = "encode(?::bytea, 'base64')")
private String password;
}
}
生成 SQL
drop table if exists Employee cascade
drop sequence if exists hibernate_sequence
create sequence hibernate_sequence start 1 increment 1
create table Employee (id int8 not null, pswd varchar(255), primary key (id))
select nextval ('hibernate_sequence')
insert into Employee (pswd, id) values (encode('hello world'::bytea, 'base64'), 1)
select test_emplo0_.id as id1_0_0_, convert_from(decode(test_emplo0_.pswd, 'base64'), 'UTF8') as pswd2_0_0_ from Employee test_emplo0_ where test_emplo0_.id=1
@ColumnTransformer forColumn attribute usage
如果 property 映射到多个 column 还可以用 @ColumnTransformer.forColumn 指定 column
write expression 必须有且仅有一个 "?"
package org.example.demo.hibernate;
import java.io.Serializable;
import java.math.BigDecimal;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Currency;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Persistence;
import org.hibernate.HibernateException;
import org.hibernate.annotations.ColumnTransformer;
import org.hibernate.annotations.Columns;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.Type;
import org.hibernate.usertype.CompositeUserType;
public class Test {
public static void main(String[] args) throws Exception {
EntityManagerFactory factory = Persistence.createEntityManagerFactory("test");
EntityManager em = factory.createEntityManager();
Savings savings = new Savings();
savings.wallet = new MonetaryAmount(new BigDecimal(100), Currency.getInstance("CNY"));
em.getTransaction().begin();
em.persist(savings);
em.getTransaction().commit();
em.clear();
Savings s = em.find(Savings.class, savings.id);
// 100.0000000000000000
System.out.println(s.wallet.amount);
// CNY
System.out.println(s.wallet.currency);
em.close();
factory.close();
}
@Entity(name = "Savings")
public static class Savings {
@Id
@GeneratedValue
private Long id;
@org.hibernate.annotations.Type(type = "org.example.demo.hibernate.Test$MonetaryAmountUserType")
@Columns(columns = { @Column(name = "money"), @Column(name = "currency") })
@ColumnTransformer(forColumn = "money", read = "money / 100", write = "? * 100")
private MonetaryAmount wallet;
}
@SuppressWarnings("serial")
public static class MonetaryAmount implements Serializable {
private BigDecimal amount;
private Currency currency;
public MonetaryAmount(BigDecimal amount, Currency currency) {
this.amount = amount;
this.currency = currency;
}
public BigDecimal getAmount() {
return amount;
}
public void setAmount(BigDecimal amount) {
this.amount = amount;
}
public Currency getCurrency() {
return currency;
}
public void setCurrency(Currency currency) {
this.currency = currency;
}
}
public static class MonetaryAmountUserType implements CompositeUserType {
public String[] getPropertyNames() {
return new String[] { "amount", "currency" };
}
public Type[] getPropertyTypes() {
return new Type[] { StandardBasicTypes.BIG_DECIMAL, StandardBasicTypes.CURRENCY };
}
public Object getPropertyValue(Object component, int property) throws HibernateException {
MonetaryAmount ma = (MonetaryAmount) component;
return property == 0 ? ma.getAmount() : ma.getCurrency();
}
public void setPropertyValue(Object component, int property, Object value) throws HibernateException {
MonetaryAmount ma = (MonetaryAmount) component;
if (property == 0) {
ma.setAmount((BigDecimal) value);
} else {
ma.setCurrency((Currency) value);
}
}
public Class<?> returnedClass() {
return String.class;
}
public boolean equals(Object x, Object y) throws HibernateException {
if (x == y)
return true;
if (x == null || y == null)
return false;
MonetaryAmount mx = (MonetaryAmount) x;
MonetaryAmount my = (MonetaryAmount) y;
return mx.getAmount().equals(my.getAmount()) && mx.getCurrency().equals(my.getCurrency());
}
public int hashCode(Object x) throws HibernateException {
return ((MonetaryAmount) x).getAmount().hashCode();
}
public Object nullSafeGet(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner)
throws HibernateException, SQLException {
BigDecimal amt = StandardBasicTypes.BIG_DECIMAL.nullSafeGet(rs, names[0], session);
Currency cur = StandardBasicTypes.CURRENCY.nullSafeGet(rs, names[1], session);
if (amt == null)
return null;
return new MonetaryAmount(amt, cur);
}
public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session)
throws HibernateException, SQLException {
MonetaryAmount ma = (MonetaryAmount) value;
BigDecimal amt = ma == null ? null : ma.getAmount();
Currency cur = ma == null ? null : ma.getCurrency();
StandardBasicTypes.BIG_DECIMAL.nullSafeSet(st, amt, index, session);
StandardBasicTypes.CURRENCY.nullSafeSet(st, cur, index + 1, session);
}
public Object deepCopy(Object value) throws HibernateException {
MonetaryAmount ma = (MonetaryAmount) value;
return new MonetaryAmount(ma.getAmount(), ma.getCurrency());
}
public boolean isMutable() {
return true;
}
public Serializable disassemble(Object value, SharedSessionContractImplementor session)
throws HibernateException {
return (Serializable) deepCopy(value);
}
public Object assemble(Serializable cached, SharedSessionContractImplementor session, Object owner)
throws HibernateException {
return deepCopy(cached);
}
public Object replace(Object original, Object target, SharedSessionContractImplementor session, Object owner)
throws HibernateException {
return deepCopy(original);
}
}
}
生成 SQL
drop table if exists Savings cascade
drop sequence if exists hibernate_sequence
create sequence hibernate_sequence start 1 increment 1
create table Savings (id int8 not null, money numeric(19, 2), currency varchar(255), primary key (id))
select nextval ('hibernate_sequence')
insert into Savings (money, currency, id) values (100 * 100, 'CNY', 1)
select test_savin0_.id as id1_0_0_, test_savin0_.money / 100 as money2_0_0_, test_savin0_.currency as currency3_0_0_ from Savings test_savin0_ where test_savin0_.id=1
这种方式与 @Formula 有两个不同:
- 有实际的 column
- 可读写,而不是只读