Make the variable that stores the ID an ID type instead of an int / long (Java)

Overview

When carrying an ID, it is usually stored in an int, long, String, etc., but this may cause it to become confused and difficult to understand in the following cases.

public someMethod(int hogeId, int fugaId) { ... }

If you can do this, you can prevent careless mistakes. (If you make it chiguhagu, the IDE will give you an error.)

public someMethod(HogeId hogeId, FugaId fugaId) { ... }

Attention point

While this is easier to understand, there are some points to be aware of.

  1. Must be convertible from / to JSON in Jackson
  2. What can be handled as a parameter / result in MyBatis
  3. Can be treated as a URL parameter of Spring MVC or a return value of @ RestController
  4. Since many Id classes will be created, there is no need for troublesome description individually (make the parent class implement it).
  5. Performance is not compromised

Conclusion

It can be realized by the following.

PiyoId.java


package your.package;

import your.package.LongId;
import lombok.EqualsAndHashCode;

@EqualsAndHashCode(callSuper = true)
class PiyoId extends LongId {
    public PiyoId(String id) { super(id); }
    public PiyoId(long id) { super(id); }
}

LongId.java


package your.package;

import com.fasterxml.jackson.annotation.JsonValue;
import lombok.EqualsAndHashCode;

@EqualsAndHashCode
public abstract class LongId {
    private final long id;

    public LongId(String id) {
        this.id = Long.parseLong(id);
    }

    public LongIdBase(long id) {
        this.id = id;
    }

    @JsonValue
    public long getId() {
        return this.id;
    }

    public String toString() {
        return this.getClass().getSimpleName() + "(" + this.id + ")";
    }
}

LongIdTypeHandler.java


package your.package;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import your.package.HogeId;
import your.package.LongId;
import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedTypes;

//List the ID classes here
@MappedTypes(value = {HogeId.class})
public class LongIdTypeHandler<E extends LongId> extends BaseTypeHandler<E> {
    private final Class<E> type;

    public LongIdTypeHandler(Class<E> type) {
        if (type == null) throw new IllegalArgumentException("Type argument cannot be null");
        this.type = type;
    }

    private E createInstance(boolean wasNull, long id) {
        if (wasNull) {
            return null;
        }
        try {
            return type.getDeclaredConstructor(long.class).newInstance(id);
        } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void setNonNullParameter(PreparedStatement ps, int num, E parameter, JdbcType jdbcType) throws SQLException {
        long id = rs.getLong(columnName);
        return createInstance(rs.wasNull(), id);
    }

    @Override
    public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
        long id = rs.getLong(columnName);
        return createInstance(rs.wasNull(), id);
    }

    @Override
    public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        long id = rs.getLong(columnIndex);
        return createInstance(rs.wasNull(), id);
    }

    @Override
    public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        long id = cs.getLong(columnIndex);
        return createInstance(cs.wasNull(), id);
    }
}

Details

Jackson compatible

To be able to convert seamlessly, it must be one of the following:

Convert to JSON-> Object

One of the following:

  1. Default constructor + setters
  2. Constructor with @JsonCreator
  3. Constructor with matching arguments

Object-> Convert to JSON

One of the following: 4. Judgment from getters 5. Method execution with @JsonValue

Conclusion

Correspond to 3 and 5

Supports MyBatis

Object can now be passed as a MyBatis parameter

Both of the following need to be addressed:

1. If the ID class is a parameter

--I want to specify parameterType =" com. ~ .PiyoId " and retrieve it with the description# {id}. --This is as long as the method long getId () is defined in PiyoId.

2. When receiving a class that characterizes the ID class as an argument

--I want to specify parameterType =" com. ~ .Piyo " so that it can be retrieved with the description # {id}. --This requires the definition of TypeHandler. ~~ However, only setNonNullParameter is implemented. getNullableResult cannot be used because the concrete class is unknown and new cannot be done. So that it can be converted to Object in ResultMap of MyBatis ~~ [2020/02/15 postscript] It is possible to make new by enumerating concrete classes in TypeHandler. -[2020/02/15 postscript] When getting the auto generated column, the following does not work for some reason.

It doesn't work with selectKey


<insert id="insert" parameterType="your.package.Hoge">
  INSERT INTO hoges (value1, value2)
  VALUES (#{value1}, #{value2})
  <selectKey resultType="your.package.HogeId" keyProperty="id" order="AFTER">
    select @@IDENTITY
  </selectKey>
</insert>

UseGeneratedKeys works


<insert id="insert" parameterType="your.package.Hoge" useGeneratedKeys="true" keyProperty="id">
  INSERT INTO hoges (value1, value2)
  VALUES (#{value1}, #{value2})
</insert>

Spring compatible

Spring MVC Controller parameters

--In any of the following cases, I want to receive a parameter that comes with a numerical value such as 123 as a PiyoId type. --@PathVariable ("piyoId") PiyoId piyoId --When receiving as @RequestBody PiyoForm form or @ModelAttribute PiyoForm form, and when receiving as PiyoId type in form --Normal error: Caused by: java.lang.IllegalStateException: Cannot convert value of type'java.lang.String' to required type'your.package.PiyoId': no matching editors or conversion strategy found --Conclusion: You can do this if your ID class has a constructor that accepts a String.

Spring MVC @ RestController return value

--If PiyoId is mixed, I want to convert it to JSON as a numerical value. --Conclusion: This is okay if it supports Jackson mentioned above.

Performance is not compromised

--In some cases, new was required to create the ID, and the hash value calculation became complicated, but even if it was rotated 10 million times, there was almost no difference in about 1.5 seconds between the two. If it is a long type, there is a load of Boxing, so after all it is a ton ton?

verification code


//Write later

Recommended Posts

Make the variable that stores the ID an ID type instead of an int / long (Java)
Compare the elements of an array (Java)
[Java] How to convert one element of a String type array to an Int type
[Java] Be careful of the key type of Map
Please note the division (division) of java kotlin Int and Int
A program (Java) that outputs the sum of odd and even numbers in an array
(Ruby on Rails6) Display of the database that got the id of the database
[Java] [Java EE] [Glassfish] (Continued) Style that doubts the cache of Glassfish
Initialization with an empty string to an instance of Java String type
[Java] To know the type information of type parameters at runtime
[Java] Difference between assignment of basic type variable and assignment of reference type variable