3.4.2
Postgres 9.6
I have result map with 2 Enums in it for handling in sumulationaly like that:
<resultMap id="groupPlotFactMap" type="ru.rlh.egais.portal.api.dto.bo.PlotFact">
<id column="pf_document_base_fkey" property="documentId"/>
<id column="pf_unit" property="unit" typeHandler="EnumTypeHandler"/>
<id column="pf_meta" property="meta" typeHandler="EnumTypeHandler"/>
</resultMap>
And class:
public class PlotFact {
private Unit unit;
private FactMeta meta;
}
Both fields mapped.
Exception:
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.executor.result.ResultMapException: Error attempting to get column 'pf_meta' from result set. Cause: java.lang.IllegalArgumentException: No enum constant ru.rlh.egais.portal.api.dto.enumeration.Unit.GROUP
at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:77)
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:446)
at com.sun.proxy.$Proxy1029.selectList(Unknown Source)
at org.mybatis.spring.SqlSessionTemplate.selectList(SqlSessionTemplate.java:230)
at org.apache.ibatis.binding.MapperMethod.executeForMany(MapperMethod.java:137)
at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:75)
at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:59)
at com.sun.proxy.$Proxy1115.search(Unknown Source)
at ru.rlh.egais.portal.backend.service.PlotFactService.lambda$find$0(PlotFactService.java:82)
at ru.rlh.egais.portal.backend.util.SearchUtils.makeSearch(SearchUtils.java:38)
at ru.rlh.egais.portal.backend.service.PlotFactService.find(PlotFactService.java:81)
Indeed, database value GROUP belongs to secong enum ru.rlh.egais.portal.api.dto.enumeration.FactMeta instead of ru.rlh.egais.portal.api.dto.enumeration.Unit.
I start debugging and found what org.apache.ibatis.type.EnumTypeHandler instantiated once. And placed into registry by handler ignore handled target class. In source code even comment about that:
// javaType ignored for injected handlers see issue #746 for full detail
TypeHandler> handler = typeHandlerRegistry.getMappingTypeHandler(typeHandlerType);
But issue https://github.com/mybatis/mybatis-3/issues/746 redirect to pull request https://github.com/mybatis/mybatis-3/pull/746 which seams irrelevant.
So could you please clarify why so?
If it desired I could try provide pull request for change caching.
When (as workaround) I create new noop handler just to be different in cached map:
public class FactMetaTypeHandler extends EnumTypeHandler<FactMeta> {
public FactMetaTypeHandler(Class<FactMeta> type) {
super(type);
}
}
it works as expected.
@Hubbitus Thanks for your reporting.
I will propose to remove the typeHandler attribute from your result mapping definitions as follow:
<resultMap id="groupPlotFactMap" type="ru.rlh.egais.portal.api.dto.bo.PlotFact">
<id column="pf_document_base_fkey" property="documentId"/>
<id column="pf_unit" property="unit" />
<id column="pf_meta" property="meta" />
</resultMap>
If you use the EnumTypeHandler(default type handler for enum type), you can omit the typeHandler attribute. Please try this.
Thanks.
#746 is old issue number at google code. For details see https://github.com/mybatis/old-google-code-issues/issues/746.
@emacarron @harawata
Is this a bug or not ?
Changes are follows:
@Hubbitus Could you provide a repro, please?
+1
I wrote a custom enum handler. It's created multiple times for each result column in xml.
However, in select queries, BaseBuilder resolves to the last created handler because it fetches a typehandler only by class type regardless of java type of related property. As result, an exception arises when it sets a property field of one Enum class with a mismatch enum value from another Enum class.
@MappedJdbcTypes(value = {JdbcType.TINYINT}, includeNullJdbcType = true)
public class GenericEnumCodeHandler extends BaseTypeHandler<GenericEnum> {
private final GenericEnum[] enums;
public GenericEnumCodeHandler(Class<GenericEnum> type) {
if (type == null) {
throw new IllegalArgumentException("Type argument cannot be null");
}
this.enums = type.getEnumConstants();
if (this.enums == null) {
throw new IllegalArgumentException(type.getSimpleName()
+ " does not represent an enum type.");
}
}
@Override
public void setNonNullParameter(PreparedStatement ps, int i,
GenericEnum parameter, JdbcType jdbcType) throws SQLException {
ps.setInt(i, parameter.getCode());
}
@Override
public GenericEnum getNullableResult(ResultSet rs, String columnName)
throws SQLException {
int i = rs.getInt(columnName);
if (rs.wasNull()) {
return null;
} else {
return locateEnumStatus(i);
}
}
@Override
public GenericEnum getNullableResult(ResultSet rs, int columnIndex)
throws SQLException {
int i = rs.getInt(columnIndex);
if (rs.wasNull()) {
return null;
} else {
return locateEnumStatus(i);
}
}
@Override
public GenericEnum getNullableResult(CallableStatement cs, int columnIndex)
throws SQLException {
int i = cs.getInt(columnIndex);
if (cs.wasNull()) {
return null;
} else {
return locateEnumStatus(i);
}
}
private GenericEnum locateEnumStatus(int code) {
for (GenericEnum status : enums) {
if (status.getCode() == code) {
return status;
}
}
return null;
}
}
Enum A
public enum StatusA implements GenericEnum {
DEPRECATED(-1, "deprecated"), NORMAL(1, "normal");
private int code;
private String label;
StatusA(int code, String label) {
this.code = code;
this.label = label;
}
@Override
public String getLabel() {
return label;
}
@Override
public int getCode() {
return code;
}
}
Enum B
public enum StatusB implements GenericEnum {
Type1(1, "new"),
Type2(2, "old");
private int code;
private String label;
StatusB(int code, String label) {
this.code = code;
this.label = label;
}
@Override
public int getCode() {
return code;
}
@Override
public String getLabel() {
return label;
}
}
In resultMap:
<result property="statusA" column="status_a" typeHandler="GenericEnumCodeHandler"></result>
<result property="statusB" column="status_b" typeHandler="GenericEnumCodeHandler"></result>
And I believe the built-in EnumHandler and EnumOrdinalHandler have similar issues.
Workaround provided by @Hubbitus is working all right. But it's not DRY at all.
I am afraid this also affects the new option for setting default enum handler in https://github.com/mybatis/mybatis-3/issues/970
@RobinQu ,
Could you wrap it up as an executable project?
We have to deal with many reports in our spare time and it should be easier for the reporter than for us to create a repro including configuration.
There is a project template for MyBatis-Spring as well.
Thank you,
Iwao
That error occurs when you specify typeHandler attribute when it is not necessary.
Please try removing typeHandler attribute from <result /> as @kazuki43zoo suggested.
If you use Map as resultType, you may need to add javaType="YourEnum" instead of typeHandler attribute.
I plan to overhaul type handler related code in future, but for now, this should be a reasonable workaround.
If it does not work, please provide an executable example.
+1
I have the same problem. I write a demo code, execute method com.gmail.aray.chou.mybatis.enumeration.type.bug.demo.Demo#main to start the demo.
In this branch: https://github.com/ArayChou/mybatis-enumeration-handler-demo/tree/works_version. I register com.gmail.aray.chou.mybatis.enumeration.type.bug.demo.CodedEnumHandler to mybatis configuration (line 39-40 in com.gmail.aray.chou.mybatis.enumeration.type.bug.demo.Demo), and do NOT explicit specific typeHander in Mapper (com.gmail.aray.chou.mybatis.enumeration.type.bug.demo.BlogMapper), It works fine.
In this branch: https://github.com/ArayChou/mybatis-enumeration-handler-demo/tree/not-work--explicit-type-handler. I just explicit specific typeHander in the Mapper. and I get the same problem.
in this branch: https://github.com/ArayChou/mybatis-enumeration-handler-demo/tree/not-work--explicit-type-handler--without-register-handler , I explicit specific typeHander in the mapper, and do NOT explicit register handler (comment out line 39-40 in com.gmail.aray.chou.mybatis.enumeration.type.bug.demo.Demo). it does NOT work. and get a deferent error. mybatis call
constructor com.gmail.aray.chou.mybatis.enumeration.type.bug.demo.CodedEnumHandler#CodedEnumHandler to get an instance, but the paramter type is java.lang.Object.class, instead of the actual enum type.
I think every mybatis user who use customer enum type handler may get confusing. We'd better to make all the 3 branch works.
Thanks
Note the issue exists in version 3.4.6 as well. Both the suggestion from @Hubbitus and @harawata worked.
Most helpful comment
That error occurs when you specify
typeHandlerattribute when it is not necessary.Please try removing
typeHandlerattribute from<result />as @kazuki43zoo suggested.If you use Map as
resultType, you may need to addjavaType="YourEnum"instead oftypeHandlerattribute.I plan to overhaul type handler related code in future, but for now, this should be a reasonable workaround.
If it does not work, please provide an executable example.