I've faced with similar issue during cascade update in hibernate 4.3.6.Final
There is Business entity and BusinessBank entity. One Business has many BusinessBank, and every BusinessBank belongs to certain Business. As primary keys we use byte[].
Code:
@Entity
@Table(name = "business")
public class Business {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY, generator = "Generator")
@GenericGenerator(name = "Generator", strategy = "org.hibernate.id.UUIDGenerator")
@Column(name = "Business_PK", unique = true, nullable = false)
private byte[] businessPk;
@Column(name = "BusinessName", nullable = false, length = 100)
private String businessName;
@OneToMany(targetEntity = BusinessBank.class, fetch = FetchType.LAZY, mappedBy = "business", cascade = {CascadeType.ALL})
private List<BusinessBank> businessBanks = new ArrayList<BusinessBank>(0);
...
}
@Entity
@Table(name = "business_bank")
public class BusinessBank {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY, generator = "Generator")
@GenericGenerator(name = "Generator", strategy = "org.hibernate.id.UUIDGenerator")
@Column(name = "BusinessBank_PK", unique = true, nullable = false)
private byte[] businessBankPk;
@ManyToOne(targetEntity = Business.class, fetch = FetchType.LAZY, cascade = CascadeType.ALL, optional = false)
@JoinColumn(name = "Business_FK", nullable = false)
private Business business;
@Column(name = "BankAccountNumber", length = 50)
private String bankName;
...
}
When Business is saved to DB, BusinessBank entities are saved also due to cascade. When initially new Business with new BusinessBank entities are saved to DB, everything works fine.
But when we make updating of more than 1 saved BusinessBank entities there is NPE:
Caused by: java.lang.NullPointerException
at org.hibernate.type.AbstractStandardBasicType.compare(AbstractStandardBasicType.java:225)
at org.hibernate.action.internal.EntityAction.compareTo(EntityAction.java:171)
at org.hibernate.engine.spi.ExecutableList.add(ExecutableList.java:222)
at org.hibernate.engine.spi.ActionQueue.addAction(ActionQueue.java:241)
at org.hibernate.event.internal.DefaultFlushEntityEventListener.scheduleUpdate(DefaultFlushEntityEventListener.java:313)
at org.hibernate.event.internal.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:160)
at org.hibernate.event.internal.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:231)
at org.hibernate.event.internal.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:102)
at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:55)
at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1222)
at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:425)
at org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.beforeTransactionCommit(JdbcTransaction.java:101)
at org.hibernate.engine.transaction.spi.AbstractTransactionImpl.commit(AbstractTransactionImpl.java:177)
at org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:77)Code:
Business business = new Business();
business.setBusinessName("Business name");
BusinessBank businessBank1 = new BusinessBank();
businessBank1.setBusiness(business);
businessBank1.setBankName("bank1");
BusinessBank businessBank2 = new BusinessBank();
businessBank2.setBusiness(business);
businessBank2.setBankName("bank2");
business.setBusinessBanks(Arrays.asList(businessBank1, businessBank2));
Session session = getSession();
try {
session.beginTransaction();
session.persist(business);
session.getTransaction().commit();
} finally {
session.close();
}
businessBank1.setBankName("Bank1 rename");
businessBank2.setBankName("Bank2 rename");
session = getSession();
try {
session.beginTransaction();
session.merge(business);
session.getTransaction().commit();
} finally {
session.close();
}
If I update just one business banks - everything is working fine. If more then one I have NPE that mentioned above.
I've looked into the source code and find out that before committing hibernate sorts entities by id. NPE occurs in this line
Code:
javaTypeDescriptor.getComparator().compare( (T) x, (T) y );
because javaTypeDescriptor.getComparator() is NULL for PrimitiveByteArrayTypeDescriptor. Comporator is set in super AbstractTypeDescriptor class and it's always NULL for byte[]:
Code:
public class PrimitiveByteArrayTypeDescriptor extends AbstractTypeDescriptor<byte[]> {
...
public PrimitiveByteArrayTypeDescriptor() {
super( byte[].class, ArrayMutabilityPlan.INSTANCE );
}
...
}
public abstract class AbstractTypeDescriptor<T> implements JavaTypeDescriptor<T>, Serializable {
...
private final Comparator<T> comparator;
...
protected AbstractTypeDescriptor(Class<T> type, MutabilityPlan<T> mutabilityPlan) {
...
this.comparator = Comparable.class.isAssignableFrom( type )
? (Comparator<T>) ComparableComparator.INSTANCE
: null;
...
}
...
}
To fix this bug I've tried to add @Type annotation under BusinessBank PK with my custom type ByteArrayType for byte[] that returns not NULL comporator.
Code:
public class BusinessBank {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY, generator = "Generator")
@GenericGenerator(name = "Generator", strategy = "org.hibernate.id.UUIDGenerator")
@Column(name = "BusinessBank_PK", unique = true, nullable = false)
@Type(type="com.model.ByteArrayType")
private byte[] businessBankPk;
...
}
Here is the implementation of ByteArrayType
Code:
import jdbm.helper.ByteArrayComparator;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.type.AbstractSingleColumnStandardBasicType;
import org.hibernate.type.VersionType;
import org.hibernate.type.descriptor.java.PrimitiveByteArrayTypeDescriptor;
import org.hibernate.type.descriptor.sql.VarbinaryTypeDescriptor;
import java.util.Comparator;
public class ByteArrayType extends AbstractSingleColumnStandardBasicType<byte[]> implements VersionType<byte[]> {
public ByteArrayType() {
super( VarbinaryTypeDescriptor.INSTANCE, CustomPrimitiveByteArrayTypeDescriptor.INSTANCE );
}
public String getName() {
return "hb_binary";
}
@Override
public String[] getRegistrationKeys() {
return new String[] { getName(), "byte[]", byte[].class.getName() };
}
@Override
public byte[] seed(SessionImplementor session) {
return null;
}
@Override
public byte[] next(byte[] current, SessionImplementor session) {
return current;
}
@Override
public Comparator<byte[]> getComparator() {
return CustomPrimitiveByteArrayTypeDescriptor.INSTANCE.getComparator();
}
private static class CustomPrimitiveByteArrayTypeDescriptor extends PrimitiveByteArrayTypeDescriptor {
public static final CustomPrimitiveByteArrayTypeDescriptor INSTANCE =
new CustomPrimitiveByteArrayTypeDescriptor();
private ByteArrayComparator comparator = new ByteArrayComparator();
@Override
public Comparator<byte[]> getComparator() {
return comparator;
}
}
}
After this business banks were updated successfully. But I don't think it's nice idea to override default BinaryType for byte[]. If someone knows better solution I would appreciate.
What about your issue, did you solve it?