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
  • 可读写,而不是只读

results matching ""

    No results matching ""