HHH-7870 - Fix and test
This commit is contained in:
parent
c8b20660ed
commit
0713cea180
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue