Skip to content

Commit

Permalink
MonetaryAmountType throws NullPointerException when reading a null co…
Browse files Browse the repository at this point in the history
…lumn value #465
  • Loading branch information
ugrave authored and vladmihalcea committed Aug 3, 2022
1 parent 7fc183b commit 43d76a0
Show file tree
Hide file tree
Showing 10 changed files with 915 additions and 108 deletions.
@@ -0,0 +1,328 @@
package com.vladmihalcea.hibernate.type;

import com.vladmihalcea.hibernate.type.util.Configuration;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.engine.jdbc.Size;
import org.hibernate.engine.spi.Mapping;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.type.ForeignKeyDirection;
import org.hibernate.type.Type;
import org.hibernate.type.descriptor.java.IncomparableComparator;
import org.hibernate.usertype.CompositeUserType;

import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
* Very convenient base class for implementing immutable object types using Hibernate {@link CompositeUserType}.
* <p>
* The {@link ImmutableCompositeType} implements the {@link Type} interface too, so you can pass all
* types extending the {@link ImmutableCompositeType} to the {@link org.hibernate.query.NativeQuery#addScalar(String, Type)}
* method to fix the <a href="https://vladmihalcea.com/hibernate-no-dialect-mapping-for-jdbc-type/">No Dialect mapping for JDBC type</a> issues.
*
* @author Vlad Mihalcea
*/
public abstract class ImmutableCompositeType<T> implements CompositeUserType, Type {

private final Configuration configuration;

private final Class<T> clazz;

/**
* Initialization constructor taking the {@link Class}
* and using the default {@link Configuration} object.
*
* @param clazz the entity attribute {@link Class} type to be handled
*/
protected ImmutableCompositeType(Class<T> clazz) {
this.clazz = clazz;
this.configuration = Configuration.INSTANCE;
}

/**
* Initialization constructor taking the {@link Class} and {@link Configuration} objects.
*
* @param clazz the entity attribute {@link Class} type to be handled
* @param configuration custom {@link Configuration} object.
*/
protected ImmutableCompositeType(Class<T> clazz, Configuration configuration) {
this.clazz = clazz;
this.configuration = configuration;
}

/**
* Get the current {@link Configuration} object.
*
* @return the current {@link Configuration} object.
*/
protected Configuration getConfiguration() {
return configuration;
}

/**
* Get the column value from the JDBC {@link ResultSet}.
*
* @param rs JDBC {@link ResultSet}
* @param names database column name
* @param session current Hibernate {@link org.hibernate.Session}
* @param owner current Hibernate {@link SessionFactoryImplementor}
* @return column value
* @throws SQLException in case of failure
*/
protected abstract T get(ResultSet rs, String[] names,
SharedSessionContractImplementor session, Object owner) throws SQLException;

/**
* Set the column value on the provided JDBC {@link PreparedStatement}.
*
* @param st JDBC {@link PreparedStatement}
* @param value database column value
* @param index database column index
* @param session current Hibernate {@link org.hibernate.Session}
* @throws SQLException in case of failure
*/
protected abstract void set(PreparedStatement st, T value, int index,
SharedSessionContractImplementor session) throws SQLException;

/* Methods inherited from the {@link UserType} interface */

@Override
public Object nullSafeGet(ResultSet rs, String[] names,
SharedSessionContractImplementor session, Object owner) throws SQLException {
return get(rs, names, session, owner);
}

@Override
public void nullSafeSet(PreparedStatement st, Object value, int index,
SharedSessionContractImplementor session) throws SQLException {
set(st, clazz.cast(value), index, session);
}

@Override
public Class<T> returnedClass() {
return clazz;
}

@Override
public boolean equals(Object x, Object y) {
return (x == y) || (x != null && x.equals(y));
}

@Override
public int hashCode(Object x) {
return x.hashCode();
}

@Override
public Object deepCopy(Object value) {
return value;
}

@Override
public boolean isMutable() {
return false;
}

@Override
public Serializable disassemble(Object o, SharedSessionContractImplementor session) {
return (Serializable) o;
}

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

@Override
public Object replace(Object o, Object target, SharedSessionContractImplementor session, Object owner) {
return o;
}

/* Methods inherited from the {@link Type} interface */

@Override
public boolean isAssociationType() {
return false;
}

@Override
public boolean isCollectionType() {
return false;
}

@Override
public boolean isEntityType() {
return false;
}

@Override
public boolean isAnyType() {
return false;
}

@Override
public boolean isComponentType() {
return false;
}

@Override
public int getColumnSpan(Mapping mapping) throws MappingException {
return getPropertyTypes().length;
}

@Override
public int[] sqlTypes(Mapping mapping) throws MappingException {
List<Integer> sqlTypes= new ArrayList<>();
Type[] types = getPropertyTypes();
for (int i = 0; i < types.length; i++) {
sqlTypes.addAll(
Arrays.stream(types[i].sqlTypes(mapping)).boxed().collect(Collectors.toList())
);
}
return sqlTypes.stream().mapToInt(i->i).toArray();
}

@Override
public Size[] dictatedSizes(Mapping mapping) throws MappingException {
return new Size[]{new Size()};
}

@Override
public Size[] defaultSizes(Mapping mapping) throws MappingException {
return dictatedSizes(mapping);
}

@Override
public Class getReturnedClass() {
return returnedClass();
}

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

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

@Override
public boolean isEqual(Object x, Object y, SessionFactoryImplementor factory) throws HibernateException {
return equals(x, y);
}

@Override
public int getHashCode(Object x) throws HibernateException {
return hashCode(x);
}

@Override
public int getHashCode(Object x, SessionFactoryImplementor factory) throws HibernateException {
return hashCode(x);
}

@Override
public int compare(Object x, Object y) {
return IncomparableComparator.INSTANCE.compare(x, y);
}

@Override
public final boolean isDirty(Object old, Object current, SharedSessionContractImplementor session) {
return isDirty(old, current);
}

@Override
public final boolean isDirty(Object old, Object current, boolean[] checkable, SharedSessionContractImplementor session) {
return checkable[0] && isDirty(old, current);
}

protected final boolean isDirty(Object old, Object current) {
return !isSame(old, current);
}

@Override
public boolean isModified(Object dbState, Object currentState, boolean[] checkable, SharedSessionContractImplementor session) throws HibernateException {
return isDirty(dbState, currentState);
}

@Override
public Object nullSafeGet(ResultSet rs, String name, SharedSessionContractImplementor session, Object owner) throws HibernateException, SQLException {
return get(rs, new String[]{name}, session, owner);
}

@Override
public void nullSafeSet(PreparedStatement st, Object value, int index, boolean[] settable, SharedSessionContractImplementor session) throws HibernateException, SQLException {
set(st, returnedClass().cast(value), index, session);
}

@Override
public String toLoggableString(Object value, SessionFactoryImplementor factory) throws HibernateException {
return String.valueOf(value);
}

@Override
public String getName() {
return getClass().getSimpleName();
}

@Override
public Object deepCopy(Object value, SessionFactoryImplementor factory) throws HibernateException {
return deepCopy(value);
}

@Override
public Serializable disassemble(Object value, SharedSessionContractImplementor session, Object owner) throws HibernateException {
return disassemble(value, session);
}

@Override
public void beforeAssemble(Serializable cached, SharedSessionContractImplementor session) {

}

@Override
public Object hydrate(ResultSet rs, String[] names, SharedSessionContractImplementor session, Object owner) throws HibernateException, SQLException {
return nullSafeGet(rs, names, session, owner);
}

@Override
public Object resolve(Object value, SharedSessionContractImplementor session, Object owner) throws HibernateException {
return value;
}

@Override
public Object semiResolve(Object value, SharedSessionContractImplementor session, Object owner) throws HibernateException {
return value;
}

@Override
public Type getSemiResolvedType(SessionFactoryImplementor factory) {
return this;
}

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

@Override
public Object replace(Object original, Object target, SharedSessionContractImplementor session, Object owner, Map copyCache, ForeignKeyDirection foreignKeyDirection) throws HibernateException {
return replace(original, target, session, owner);
}

@Override
public boolean[] toColumnNullness(Object value, Mapping mapping) {
return value == null ? ArrayHelper.FALSE : ArrayHelper.TRUE;
}
}

0 comments on commit 43d76a0

Please sign in to comment.