HHH-7870 - Fix and test

This commit is contained in:
Lukasz Antoniak 2013-01-10 14:38:11 +01:00
parent c8b20660ed
commit 0713cea180
5 changed files with 329 additions and 1 deletions

View File

@ -67,7 +67,7 @@ public class SinglePropertyMapper implements PropertyMapper, SimpleMapperBuilder
boolean dbLogicallyDifferent = true;
if ((session.getFactory().getDialect() instanceof Oracle8iDialect) && (newObj instanceof String || oldObj instanceof String)) {
// Don't generate new revision when database replaces empty string with NULL during INSERT or UPDATE statements.
dbLogicallyDifferent = !(StringTools.isEmpty((String) newObj) && StringTools.isEmpty((String) oldObj));
dbLogicallyDifferent = !(StringTools.isEmpty(newObj) && StringTools.isEmpty(oldObj));
}
return dbLogicallyDifferent && !Tools.objectsEqual(newObj, oldObj);
}

View File

@ -32,6 +32,10 @@ public class StringTools {
return s == null || "".equals(s);
}
public static boolean isEmpty(Object o) {
return o == null || "".equals(o);
}
/**
* @param s String, from which to get the last component.
* @return The last component of the dot-separated string <code>s</code>. For example, for a string

View File

@ -0,0 +1,146 @@
package org.hibernate.envers.test.integration.customtype;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.usertype.UserType;
/**
* Custom type used to persist binary representation of Java object in the database.
* Spans over two columns - one storing text representation of Java class name and the second one
* containing binary data.
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
public class ObjectUserType implements UserType {
private static final int[] TYPES = new int[] { Types.VARCHAR, Types.BLOB };
@Override
public int[] sqlTypes() {
return TYPES;
}
@Override
public Class returnedClass() {
return Object.class;
}
@Override
public boolean equals(Object x, Object y) throws HibernateException {
if ( x == y ) return true;
if ( x == null || y == null ) return false;
return x.equals( y );
}
@Override
public int hashCode(Object x) throws HibernateException {
return x.hashCode();
}
@Override
public Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner)
throws HibernateException, SQLException {
final String type = rs.getString( names[0] ); // For debug purpose.
return convertInputStreamToObject( rs.getBinaryStream( names[1] ) );
}
@Override
public void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session)
throws HibernateException, SQLException {
if ( value == null ) {
st.setNull( index, TYPES[0] );
st.setNull( index + 1, TYPES[1] );
}
else {
st.setString( index, value.getClass().getName() );
st.setBinaryStream( index + 1, convertObjectToInputStream( value ) );
}
}
private InputStream convertObjectToInputStream(Object value) {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = null;
try {
objectOutputStream = new ObjectOutputStream( byteArrayOutputStream );
objectOutputStream.writeObject( value );
objectOutputStream.flush();
return new ByteArrayInputStream( byteArrayOutputStream.toByteArray() );
}
catch ( IOException e ) {
throw new RuntimeException( e );
}
finally {
closeQuietly( objectOutputStream );
}
}
private Object convertInputStreamToObject(InputStream inputStream) {
ObjectInputStream objectInputStream = null;
try {
objectInputStream = new ObjectInputStream( inputStream );
return objectInputStream.readObject();
}
catch ( Exception e ) {
throw new RuntimeException( e );
}
finally {
closeQuietly( objectInputStream );
}
}
private void closeQuietly(OutputStream stream) {
if ( stream != null ) {
try {
stream.close();
}
catch ( IOException e ) {
}
}
}
private void closeQuietly(InputStream stream) {
if ( stream != null ) {
try {
stream.close();
}
catch ( IOException e ) {
}
}
}
@Override
public Object deepCopy(Object value) throws HibernateException {
return value; // Persisting only immutable types.
}
@Override
public boolean isMutable() {
return false;
}
@Override
public Serializable disassemble(Object value) throws HibernateException {
return (Serializable) value;
}
@Override
public Object assemble(Serializable cached, Object owner) throws HibernateException {
return cached;
}
@Override
public Object replace(Object original, Object target, Object owner) throws HibernateException {
return original;
}
}

View File

@ -0,0 +1,94 @@
package org.hibernate.envers.test.integration.customtype;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import org.hibernate.annotations.Columns;
import org.hibernate.annotations.Type;
import org.hibernate.envers.Audited;
/**
* Entity encapsulating {@link Object} property which concrete type may change during subsequent updates.
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
@Entity
@Audited
public class ObjectUserTypeEntity implements Serializable {
@Id
@GeneratedValue
private int id;
private String buildInType;
@Type(type = "org.hibernate.envers.test.integration.customtype.ObjectUserType")
@Columns(columns = { @Column(name = "OBJ_TYPE"), @Column(name = "OBJ_VALUE") })
private Object userType;
public ObjectUserTypeEntity() {
}
public ObjectUserTypeEntity(String buildInType, Object userType) {
this.buildInType = buildInType;
this.userType = userType;
}
public ObjectUserTypeEntity(int id, String buildInType, Object userType) {
this.id = id;
this.buildInType = buildInType;
this.userType = userType;
}
@Override
public boolean equals(Object o) {
if ( this == o ) return true;
if ( ! ( o instanceof ObjectUserTypeEntity ) ) return false;
ObjectUserTypeEntity that = (ObjectUserTypeEntity) o;
if ( id != that.id ) return false;
if ( buildInType != null ? !buildInType.equals( that.buildInType ) : that.buildInType != null ) return false;
if ( userType != null ? !userType.equals( that.userType ) : that.userType != null ) return false;
return true;
}
@Override
public int hashCode() {
int result = id;
result = 31 * result + ( buildInType != null ? buildInType.hashCode() : 0 );
result = 31 * result + ( userType != null ? userType.hashCode() : 0 );
return result;
}
@Override
public String toString() {
return "ObjectUserTypeEntity(id = " + id + ", buildInType = " + buildInType + ", userType = " + userType + ")";
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getBuildInType() {
return buildInType;
}
public void setBuildInType(String buildInType) {
this.buildInType = buildInType;
}
public Object getUserType() {
return userType;
}
public void setUserType(Object userType) {
this.userType = userType;
}
}

View File

@ -0,0 +1,84 @@
package org.hibernate.envers.test.integration.customtype;
import java.util.Arrays;
import java.util.Map;
import javax.persistence.EntityManager;
import org.junit.Assert;
import org.junit.Test;
import org.hibernate.dialect.Oracle8iDialect;
import org.hibernate.envers.test.BaseEnversJPAFunctionalTestCase;
import org.hibernate.envers.test.Priority;
import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.TestForIssue;
/**
* @author Lukasz Antoniak (lukasz dot antoniak at gmail dot com)
*/
@TestForIssue(jiraKey = "HHH-7870")
@RequiresDialect(Oracle8iDialect.class)
public class ObjectUserTypeTest extends BaseEnversJPAFunctionalTestCase {
private int id;
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] { ObjectUserTypeEntity.class };
}
@Override
protected void addConfigOptions(Map options) {
super.addConfigOptions( options );
options.put( "org.hibernate.envers.store_data_at_delete", "true" );
}
@Test
@Priority(10)
public void initData() {
EntityManager em = getEntityManager();
// Revision 1 - add
em.getTransaction().begin();
ObjectUserTypeEntity entity = new ObjectUserTypeEntity( "builtInType1", "stringUserType1" );
em.persist( entity );
em.getTransaction().commit();
id = entity.getId();
// Revision 2 - modify
em.getTransaction().begin();
entity = em.find( ObjectUserTypeEntity.class, entity.getId() );
entity.setUserType( 2 );
entity = em.merge( entity );
em.getTransaction().commit();
// Revision 3 - remove
em.getTransaction().begin();
entity = em.find( ObjectUserTypeEntity.class, entity.getId() );
em.remove( entity );
em.getTransaction().commit();
em.close();
}
@Test
public void testRevisionCount() {
Assert.assertEquals(
Arrays.asList( 1, 2, 3 ),
getAuditReader().getRevisions( ObjectUserTypeEntity.class, id )
);
}
@Test
public void testHistory() {
ObjectUserTypeEntity ver1 = new ObjectUserTypeEntity( id, "builtInType1", "stringUserType1" );
ObjectUserTypeEntity ver2 = new ObjectUserTypeEntity( id, "builtInType1", 2 );
Assert.assertEquals( ver1, getAuditReader().find( ObjectUserTypeEntity.class, id, 1 ) );
Assert.assertEquals( ver2, getAuditReader().find( ObjectUserTypeEntity.class, id, 2 ) );
Assert.assertEquals(
ver2,
getAuditReader().createQuery().forRevisionsOfEntity( ObjectUserTypeEntity.class, true, true ).getResultList().get( 2 )
); // Checking delete state.
}
}