stdcall
stdcall

Reputation: 28920

Hibernate annotation object mapping

I'm pretty new with hibernate, and I'm trying to transform a JDBC project I have into Hibernate.

I'm using annotations, and I managed to annotate the basic stuff, however, I'm stuck now with the more heavy objects, I don't know how to annotate them. Here's the Class:

@Entity
@Table(name = "person")
public class Person {

    public Person{

    }

    // THIS WILL BE SOON INJECTED BY SPRING
    private static transient PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
    private static transient EmailValidator validator = EmailValidator.getInstance();

    @Id
    @Column(name = "person_id")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;

    @Column(name = "private_name", nullable = false, length = 20)
    private String privateName;

    @Column(name = "middle_name", length = 20)
    private String middleName;

    @Column(name = "family_name", nullable = false, length = 20)
    private String familyName;

    @Column(name = "age", nullable = false)
    private int age;

    @Column(name = "address1", nullable = false)
    private String address1;

    @Column(name = "address2")
    private String address2;

    //How do I annotate this ? --> Google LIBPHONENUMBER

    private PhoneNumber phone;

    // How do I annotate this ? --> This is a normal PNG image file.
    private File image;

Edit: The File was previously mapped as a BLOB. The PhoneNumber was previously persisted as String, and was transformed using the PhoneNumber constructor to Phonenumber.

Upvotes: 1

Views: 10547

Answers (4)

Nicholas DiPiazza
Nicholas DiPiazza

Reputation: 10595

Just create a Hibernate UserType for PhoneNumber

import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;

import org.apache.commons.lang.ObjectUtils;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.type.StringRepresentableType;
import org.hibernate.usertype.UserType;

import com.google.i18n.phonenumbers.NumberParseException;
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
import com.tg.util.TGPhoneUtils;

public class PhoneNumberUserType implements UserType, StringRepresentableType<PhoneNumber>, Serializable {
    private static final long serialVersionUID = -364436436346432L;

    @Override
    public boolean equals(Object x, Object y) throws HibernateException {
        return ObjectUtils.equals(x, y);
    }

    @Override
    public int hashCode(Object object) throws HibernateException {
        return object.hashCode();
    }

    @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 (Serializable) value;
    }

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

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

    @Override
    public String toString(PhoneNumber value) throws HibernateException {
        return value.toString();
    }

    @Override
    public Class<?> returnedClass() {
        return PhoneNumber.class;
    }

    @Override
    public int[] sqlTypes() {
        return new int[] { Types.VARCHAR };
    }

    @Override
    public PhoneNumber fromStringValue(String number) throws HibernateException {
        try {
            return PhoneNumberUtil.getInstance().parse(number, "US");
        } catch (NumberParseException e) {
            throw new HibernateException(e);
        }
    }

    @Override
    public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor arg2, Object owner) throws HibernateException, SQLException {
        final String number = rs.getString(names[0]);
        if (number == null) {
            return null;
        }
        return TGPhoneUtils.parsePhoneNumber(number);
    }

    @Override
    public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor si) throws HibernateException, SQLException {
        if (value == null) {
            st.setNull(index, Types.VARCHAR);
            return;
        }
        st.setString(index, TGPhoneUtils.formatPhoneNumber((PhoneNumber)value));
    }


}

and then here is the helper class

import com.google.i18n.phonenumbers.PhoneNumberUtil;
import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;

public class TGPhoneUtils {
    public static PhoneNumber parsePhoneNumber(String phoneNum) {
        if (phoneNum == null) {
            return null;
        }
        try {
            return PhoneNumberUtil.getInstance().parse(phoneNum, "US");
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public static String formatPhoneNumber(PhoneNumber phoneNum) {
        if (phoneNum == null) {
            return null;
        }
        return PhoneNumberUtil.getInstance().format(phoneNum, PhoneNumberFormat.E164);
    }
}

Upvotes: 1

Jeff
Jeff

Reputation: 3707

The other comments about using @Lob are correct for the File type. It is also correct that if you can change the schema to not save the file data in the DB, then you probably should.

To map your PhoneNumber class to a database field, you're going to need to use a Hibernate custom UserType. It basically tells Hibernate HOW to do the object<-->db mapping for classes that it doesn't already know about. Telling the PhoneNumber field in Person to use a custom user type is easy:

@Type(type = PhoneNumberType.CLASS_NAME)
@Column
private PhoneNumber phone;

This assumes a very simple one-column storage of the phone number.

To write PhoneNumberType, you'll need to implement UserType. It looks overwhelming, with the assemble/disassemble/deepCopy, but the main part you care about is nullSetGet/Set, returnedClass and sqlTypes. You'll end up with some code like this inside your custom type:

@Override
public Class<?> returnedClass() {
    return PhoneNumber.class;
}

@Override
public int[] sqlTypes() {
    return new int[] { Types.VARCHAR };
}

@Override
public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws HibernateException, SQLException {
    final String value = rs.getString(names[0]);
    return /* PhoneNumber instance created from string. */
}

@Override
public void nullSafeSet(PreparedStatement st, Object value, int index) throws HibernateException, SQLException {
    if (value == null) {
        st.setNull(index, Types.VARBINARY);
        return;
    }

    st.setString(index, ((PhoneNumber) value).toString());
}

You can find plenty of information about how to implement the other methods via google, stackoverflow and the hibernate javadocs. It isn't that hard to do.

UPDATE: Multi-column user type

Implement CompositeUserType instead of just UserType. There are a few method changes that you care about. First you'll want to define the multiple property names and types:

public String[] getPropertyNames() {
    return new String[] { "number", "code" };
}

public Type[] getPropertyTypes() {
    return new Type[] { StandardBasicTypes.STRING,
                        StandardBasicTypes.STRING };
}

There's also getPropertyValue/setPropertyValue to implement. Your nullSafeXxxx implementations would change to read and write two properties instead of one:

@Override
public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws HibernateException, SQLException {
    // Access column in order defined in getPropertyNames()
    final String number = rs.getString(names[0]);
    final String code = rs.getString(names[1]);
    return /* PhoneNumber instance created from number and country code. */
}

Upvotes: 6

Andres Olarte
Andres Olarte

Reputation: 4380

You can annotate PhoneNumber like this:

@ManyToOne
@JoinColumn(name = "PHONE_NUMBER")
private PhoneNumber phone;

Assuming that the column PHONE_NUMBER exists and maps to the id of a phone number. The class PhoneNumber will also need to be annotated. This assumes that you want to maybe share a phone number among different entities (Many to one).

Regarding file, you probably need to decide if you want to actually store the file data in the db (normally not a good idea). Otherwise you could just store a String with a path to file.

Upvotes: 0

Dave Newton
Dave Newton

Reputation: 160291

Personally, I'd store only the filename in the object, and keep the file on the filesystem, where files belong.

Otherwise, map it as a Hibernate blob (@Lob) and you'd want it to be a byte array (would translate to a blob).

IMO this usually creates more trouble than it's worth, but that depends partially on the DB, driver revision, etc.

Upvotes: 1

Related Questions