How to persist an AtomicInteger in hibernate instead of Integer?

2.8k Views Asked by At

I try to persist an object that have an AtomicInteger variable instead of an Integer using the hibernate java framework (I need use the object in a thread safe scenario after save it) but when i try to save my object java throws:

java.lang.ClassCastException: java.util.concurrent.atomic.AtomicInteger cannot be cast to java.lang.Integer

Is there any way to map AtomicInteger to integer? There is an example of object:

public class Statistics implements java.io.Serializable {
  private AtomicInteger id;
  private AtomicInteger totalErrors;

  public Statistics() {
  }


  public AtomicInteger getTotalErrors() {
    return this.totalErrors;
  }

  public void seTotalErrors(AtomicInteger totalErrors) {
    this.totalErrors= totalErrors;
  }
}

And the respective POJO xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
   <class name="Statistics" table="statistics" catalog="example" optimistic-lock="version">
      <id name="id" type="java.lang.Integer">
         <column name="id" />
         <generator class="identity" />
      </id>
      <property name="totalErrors" type="java.lang.Integer">
         <column name="total_errors" />
      </property>
   </class>
</hibernate-mapping>

And there is the hibernate version:

    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-core</artifactId>
        <version>5.2.10.Final</version>
    </dependency>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-hikaricp</artifactId>
        <version>5.2.10.Final</version>
    </dependency>
2

There are 2 best solutions below

2
On BEST ANSWER

Finally the better solution (because is the more standard hibernate converter https://docs.jboss.org/hibernate/orm/4.2/manual/en-US/html/ch06.html), is to create an hibernate UserType class. I dont know what is the reason because the AttributeConverter not work (because it is in the hibernate documentation to).

This works in my case with Hibernate 5.2. Create an AtomicIntegerType that implements hibernate UserType:

import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;
import org.hibernate.HibernateException;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.usertype.UserType;

public class AtomicIntegerType implements UserType {

    private static final Logger logger = Logger.getLogger("AtomicInteger");

    /**
     * Returns the object from the 2 level cache
     */
    @Override
    public Object assemble(final Serializable cached, final Object owner)
            throws HibernateException {
        //would work as the AtomicInteger.class is Serializable, 
        //and stored in cache as it is - see disassemble
        return cached;
    }

    /**
     * Used to create Snapshots of the object
     */
    @Override
    public Object deepCopy(Object value) throws HibernateException {
        //return value; -> if AtomicInteger.class was immutable we could return the object as it is
        final AtomicInteger recievedParam = (AtomicInteger) value;
        final AtomicInteger atomicInteger = new AtomicInteger(recievedParam.get());
        return atomicInteger;
    }

    /**
     * method called when Hibernate puts the data in a second level cache. The
     * data is stored in a serializable form
     */
    @Override
    public Serializable disassemble(final Object value) throws HibernateException {
        //For this purpose the AtomicInteger.class must implement serializable
        return (Serializable) value;
    }

    /**
     * Used while dirty checking - control passed on to the
     * {@link AtomicInteger}
     */
    @Override
    public boolean equals(final Object o1, final Object o2) throws HibernateException {
        boolean isEqual = false;
        if (o1 == o2) {
            isEqual = true;
        }
        if (null == o1 || null == o2) {
            isEqual = false;
        } else {
            isEqual = o1.equals(o2);
        }
        return isEqual;
        //for this to work correctly the equals() 
        //method must be implemented correctly by AtomicInteger class
    }

    @Override
    public int hashCode(final Object value) throws HibernateException {
        return value.hashCode();
        //for this to work correctly the hashCode() 
        //method must be implemented correctly by AtomicInteger class

    }

    /**
     * Helps hibernate apply certain optimizations for immutable objects
     */
    @Override
    public boolean isMutable() {
        return true; //The audit fields can be modified
    }

    /**
     * This method retrieves the property value from the JDBC resultSet
     */
    @Override
    public Object nullSafeGet(ResultSet resultSet, String[] names, SharedSessionContractImplementor ssci, Object owner) throws HibernateException, SQLException {
        //owner here is class from where the call to retrieve data was made.
        //In this case the Test class
        AtomicInteger atomicInteger = null;
        if (!resultSet.wasNull()) {
            atomicInteger = new AtomicInteger(resultSet.getInt(names[0]));            
        }
        return atomicInteger;
    }

    /**
     * The method writes the property value to the JDBC prepared Statement
     *
     */
    @Override
    public void nullSafeSet(final PreparedStatement statement,
            final Object value, final int index, SharedSessionContractImplementor ssci) throws HibernateException,
            SQLException {
        if (null == value) {
            statement.setNull(index, java.sql.Types.INTEGER);
        } else {
            AtomicInteger atomicInteger = (AtomicInteger) value;
            if (null != atomicInteger) {
                statement.setInt(index , atomicInteger.get());
            } else {
                statement.setNull(index, java.sql.Types.INTEGER);
            }

        }
    }

    /**
     * Method used by Hibernate to handle merging of detached object.
     */
    @Override
    public Object replace(final Object original, final Object target,
            final Object owner)
            throws HibernateException {
        //return original; // if immutable use this
        //For mutable types at bare minimum return a deep copy of first argument
        return this.deepCopy(original);

    }

    /**
     * Method tells Hibernate which Java class is mapped to this Hibernate Type
     */
    @SuppressWarnings("rawtypes")
    @Override
    public Class returnedClass() {
        return AtomicInteger.class;
    }

    /**
     * Method tells Hibernate what SQL columns to use for DDL schema generation.
     * using the Hibernate Types leaves Hibernate free to choose actual SQl
     * types based on database dialect. (Alternatively SQL types can also be
     * used directly)
     */
    @Override
    public int[] sqlTypes() {
        //createdBy, createdDate,modifiedBy,modifiedDate
        return new int[]{java.sql.Types.INTEGER};
    }

}

And in the hibernate mapping file change Integer to AtomicIntegerType as follows:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<!-- Generated 12-sep-2017 13:14:50 by Hibernate Tools 4.3.1 -->
<hibernate-mapping>
   <class name="classs.location.Statistics" table="statistics" catalog="CATALOG" optimistic-lock="version">
      <id name="id" type="java.lang.Integer">
         <column name="id" />
         <generator class="identity" />
      </id>
      <property name="totalErrors" type="class.location.AtomicIntegerType">
         <column name="total_errors" />
      </property>
   </class>
</hibernate-mapping>
7
On

One way to do it is to write a JPA 2.1 AttributeConverter that converts from an Integer to an AtomicInteger like below

@Converter
public class AtomicIntConverter implements AttributeConverter<AtomicInteger, Integer> {


 @Override
 public Integer convertToDatabaseColumn(AtomicInteger attribute) {
  return attribute.get();
 }

 @Override
 public AtomicInteger convertToEntityAttribute(Integer dbData) {
  return new AtomicInteger(dbData);
 }

}

Normally you can then use the JPA @Convert annotation on your @Entity class fields like this:

@Convert(converter = AtomicIntConverter.class)
private AtomicInteger totalErrors;

You can read more about it in the Hibernate 5.2 documentation here.

But as you are using a hibernate mapping file, make sure to set the FQN of the Converter class as the type of the field instead of using the @Converter annotation.

<property name="totalErrors" type="fully.qualified.name.of.AtomicIntConverter">
     <column name="total_errors" />
 </property>