In the database, the column "status" is integer.
xml mybatis
<resultMap id="TaskStatus" type="ru....domain.Task$Status">
<result typeHandler="org.apache.ibatis.type.EnumTypeHandler"
property="id" column="status"/>
</resultMap>
<select id="selectStatus" resultMap="TaskStatus">
select id, status
from task
where id = #{id}
</select>
my enum class
public class Task{
@Getter
@AllArgsConstructor
public enum Status {
CREATED(1),
RUNNING(2),
PAUSED(3),
FINISHED(4),
ARCHIVED(5),
MODERATION_READY(6),
MODERATING(7),
REJECTED(8);
private final Integer id;
}
....
}
I want to put a column in enum class.
Error
The default EnumTypeHandler
maps enum's name (e.g. "CREATED"
, "RUNNING"
), so the column type must be one of text types like VARCHAR
[1].
As MyBatis knows nothing about the id
property, you have to write a custom type handler.
Here is an example implementation.
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedTypes;
import org.apache.ibatis.type.TypeHandler;
@MappedTypes(Status.class)
public class StatusTypeHandler implements TypeHandler<Status> {
@Override
public void setParameter(PreparedStatement ps,
int i, Status parameter, JdbcType jdbcType) throws SQLException {
if (parameter == null) {
ps.setNull(i, Types.INTEGER);
} else {
ps.setInt(i, parameter.getId());
}
}
@Override
public Status getResult(ResultSet rs, String columnName) throws SQLException {
return getStatus(rs.getInt(columnName));
}
@Override
public Status getResult(ResultSet rs, int columnIndex) throws SQLException {
return getStatus(rs.getInt(columnIndex));
}
@Override
public Status getResult(CallableStatement cs, int columnIndex) throws SQLException {
return getStatus(cs.getInt(columnIndex));
}
private static Status getStatus(int id) {
if (id == 0) {
return null;
}
for (Status status : Status.values()) {
if (id == status.getId()) {
return status;
}
}
throw new IllegalArgumentException("Cannot convert " + id + " to Status");
}
}
You should register this type handler globally.
Then it's unnecessary to specify typeHandler
explicitly in most cases.
If you use mybatis-spring-boot, specifying mybatis.type-handlers-package
in the config may be the easiest way to register type handlers globally.
If you use XML config, add the following.
<typeHandlers>
<typeHandler
handler="xxx.yyy.StatusTypeHandler" />
</typeHandlers>
If the Status
is the only enum in your project, you can stop reading.
But, what if there are many enums that have id
property and you don't want to write a similar custom type handler for each of them?
If your enums implement a common interface like below, you can write a type handler that can map all of them.
public interface HasId {
Integer getId();
}
Here is an example type handler implementation.
Note that it has a constructor that takes java.lang.Class
as its argument.
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.MappedTypes;
import org.apache.ibatis.type.TypeHandler;
@MappedTypes(HasId.class)
public class HasIdTypeHandler<E extends Enum<E> & HasId> implements TypeHandler<E> {
private Class<E> type;
private final E[] enums;
public HasIdTypeHandler(Class<E> type) {
if (type == null)
throw new IllegalArgumentException("Type argument cannot be null");
this.type = type;
this.enums = type.getEnumConstants();
if (!type.isInterface() && this.enums == null)
throw new IllegalArgumentException(type.getSimpleName()
+ " does not represent an enum type.");
}
@Override
public void setParameter(PreparedStatement ps,
int i, E parameter, JdbcType jdbcType) throws SQLException {
if (parameter == null) {
ps.setNull(i, Types.INTEGER);
} else {
ps.setInt(i, parameter.getId());
}
}
@Override
public E getResult(ResultSet rs, String columnName) throws SQLException {
return getEnum(rs.getInt(columnName));
}
@Override
public E getResult(ResultSet rs, int columnIndex) throws SQLException {
return getEnum(rs.getInt(columnIndex));
}
@Override
public E getResult(CallableStatement cs, int columnIndex) throws SQLException {
return getEnum(cs.getInt(columnIndex));
}
private E getEnum(int id) {
if (id == 0) {
return null;
}
for (E e : enums) {
if (id == e.getId()) {
return e;
}
}
throw new IllegalArgumentException("Cannot convert " +
id + " to " + type.getSimpleName());
}
}
Note that if you try to specify this type handler in a mapper, it might not work properly. There is a known issue.
[1] FYI, there is another built-in type handler for enums :EnumOrdinalTypeHandler
maps enum's ordinal.