I recently saw the custom type of hibernate. I have never been exposed to it before. I will record it here as a consolidation of my knowledge and let friends who have never been exposed to learn and study it together.
1) Custom types, as the name suggests, are of course types that are implemented by themselves because the internal types do not meet the needs. There are not many such situations, but we still need to learn it. If we have more skills, we won’t suppress our body. Also learn how others consider how to think about extensibility when making frameworks.
There are two ways to implement custom types, one is to implement UserType, the other is to implement CompositeUserType, and there may be some methods, but I haven't used it for the time being, so I won't talk about it for now.
I'm only using UserType for the time being, let's first look at the definition of the UserType interface:
public interface UserType { /** * Return the SQL type codes for the columns mapped by this type. The * codes are defined on <tt>java.sql.Types</tt>. */ public int[] sqlTypes(); /** * The class returned by <tt>nullSafeGet()</tt>. */ public Class returnedClass(); /** * Compare two instances of the class mapped by this type for persistence "equality". * Equality of the persistent state. */ public boolean equals(Object x, Object y) throws HibernateException; /** * Get a hashcode for the instance, consistent with persistence "equality" */ public int hashCode(Object x) throws HibernateException; /** * Retrieve an instance of the mapped class from a JDBC resultset. Implementors * should handle possibility of null values. */ public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws HibernateException, SQLException; /** * Write an instance of the mapped class to a prepared statement. Implementors * should handle possibility of null values. A multi-column type should be written * to parameters starting from <tt>index</tt>. */ public void nullSafeSet(PreparedStatement st, Object value, int index) throws HibernateException, SQLException; /** * Return a deep copy of the persistent state, stopping at entities and at * collections. It is not necessary to copy immutable objects, or null * values, in which case it is safe to simply return the argument. */ public Object deepCopy(Object value) throws HibernateException; /** * Are objects of this type mutable? * * @return boolean */ public boolean isMutable(); /** * Transform the object into its cacheable representation. At the very least this * method should perform a deep copy if the type is mutable. That may not be enough * for some implementations, however; for example, associations must be cached as * identifier values. (optional operation) * * @param value the object to be cached * @return a cacheable representation of the object * @throws HibernateException */ public Serializable disassemble(Object value) throws HibernateException; /** * Reconstruct an object from the cacheable representation. At the very least this * method should perform a deep copy if the type is mutable. (optional operation) */ public Object assemble(Serializable cached, Object owner) throws HibernateException; /** * During merge, replace the existing (target) value in the entity we are merged to * with a new (original) value from the detached entity we are merging. For immutable * objects, or null values, it is safe to simply return the first parameter. For * mutable objects, it is safe to return a copy of the first parameter. For objects * with component values, it might make sense to recursively replace component values. */ public Object replace(Object original, Object target, Object owner) throws HibernateException; } In fact, you can understand it in English in general, so I won’t explain it more. Here, the main thing we are to implement the nullSafeSet() method. This method mainly uses saving this type of value to the database. This time we will learn how to use it first, and then we will slowly study how it is implemented internally.
2) The examples I wrote when I was studying were referring to Xia Xin's example, so it is definitely the same as most of the online ones. Let's just analyze it roughly:
Below is the User class
package org.hibernate.tutorial.domain; import java.io.Serializable; import java.util.List; public class User implements Serializable{ public Long id; private String name; private List emails; omit Get/Set method} Next is the custom EmailList class:
package org.hibernate.tutorial.domain; import java.io.Serializable; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Sql.Types; import java.util.ArrayList; import java.util.List; import org.hibernate.Hibernate; import org.hibernate.HibernateException; import org.hibernate.usertype.UserType; public class EmailList implements UserType { private static final char SPLITTER = ';'; private static final int[] TYPES = new int[] {Types.VARCHAR}; private String assemble(List emailList) { StringBuilder strBuf = new StringBuilder(); for (int i = 0; i < emailList.size() - 1; i++){ strBuf.append(emailList.get(i)).append(SPLITTER); } strBuf.append(emailList.get(emailList.size()-1)); return strBuf.toString(); } private List parse(String value) { String[] strs = org.hibernate.util.StringHelper.split(value,String.valueOf(SPLITTER)); List emailList = new ArrayList(); for (int i = 0;i < strs.length; i++) { emailList.add(strs[i]); } return emailList; } public Object deepCopy(Object value) throws HibernateException { List sourceList = (List)value; List targetList = new ArrayList(); targetList.add(sourceList); return targetList; } public Serializable disassemble(Object value) throws HibernateException { return null; } public boolean equals(Object x, Object y) throws HibernateException { if (x == y) return true; System.out.println("X:"+x+"Y:"+y); if (x != null && y != null) { List xList = (List)x; List yList = (List)y; if(xList.size() != yList.size()) return false; for (int i = 0; i < xList.size(); i++) { String str1 = (String)xList.get(i); String str2 = (String)yList.get(i); if (!str1.equals(str2)) return false; } return true; } return false; } public boolean isMutable() { return false; } public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws HibernateException, SQLException { String value = (String)Hibernate.STRING.nullSafeGet(rs, names[0]); if (value != null) { return parse(value);//Paste List; split} else{ return null; } } public void nullSafeSet(PreparedStatement st, Object value, int index) throws HibernateException, SQLException { System.out.println("Set Method Executed!"); System.out.println("value:" + value); if (value != null){ String str = assemble((List)value);//Use strings; splice Hibernate.STRING.nullSafeSet(st, str, index); } else { Hibernate.STRING.nullSafeSet(st, value, index); } } public Class returnedClass() { return List.class; } public int[] sqlTypes() { return TYPES; } //Omit other methods that do not require modification} The methods implemented in the class are methods that need to be modified, and other methods that do not need to be modified for the time being have not been written out, but they still need to be implemented.
3) Next is the mapping file of the User class:
<class name="User" table="USER"> <id name="id" column="USER_ID" type="java.lang.Long"> <generator /> </id> <property name="name" type="string" column="USER_NAME"/> <property name="emails" type="org.hibernate.tutorial.domain.EmailList" column="emails"/> </class>
I believe everyone knows how to modify it, and I won't explain it here. It mainly changes the type of emails and changes it to the EmailList class we just defined.
4) Finally, let’s write a test class:
import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.ArrayList; import junit.framework.TestCase; import org.hibernate.EntityMode; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.Transaction; import org.hibernate.cfg.Configuration; import org.hibernate.tutorial.domain.User; public class HibernateTest extends TestCase{ private Session session = null; protected void setUp() throws Exception { Configuration cfg = new Configuration().configure(); SessionFactory sessionFactory = cfg.buildSessionFactory(); session = sessionFactory.openSession(); } public void testInsert(){ Transaction tran = null; try{ tran = session.beginTransaction(); User user = new User(); user.setName("shun"); List list = new ArrayList(); list.add("[email protected]"); list.add("[email protected]"); user.setEmails(list); session.save(user); tran.commit(); } catch (Exception ex) { ex.printStackTrace(); if (tran != null){ tran.rollback(); } } protected void tearDown() throws Exception { session.close(); } } There may be a problem here. When we only save one email, it will have an exception. The email field in the database is empty. When we have two codes like the above code, there will be no problem. The result in the database is as shown in the figure:
And when we save only one, the exception is as follows:
java.lang.ClassCastException: java.util.ArrayList cannot be cast to java.lang.String
It occurs in the equals method of EmailList, String str1 = (String)xList.get(i); In this code, after checking, it becomes a List of List when inserting data and passing it to EmailList's nullSafeSet method, that is,
Value:[[[email protected], [email protected]]]] This form will cause problems when comparing. It always has only one value, but it is different when comparing.
if(xList.size() != yList.size()) return false;
So there will be problems when casting.
After inspection, the equals method:
X:[[[email protected], [email protected]]]Y:[[email protected], [email protected]]
This result is very strange. The Internet did not talk about why this situation occurred. Let me propose it here: the hibernate version I use is Hibernate 3.3.2.GA. I don’t know if it’s a version issue or another issue, let’s study it tomorrow. If anyone knows why, I hope I will tell me.