MyBatis version
3.4.2
Database vendor and version
Postgres 9.6
Steps to reproduce
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;
}
Expected result
Both fields mapped.
Actual result
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.
Comment From: Hubbitus
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.
Comment From: kazuki43zoo
@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.
Comment From: kazuki43zoo
#746
is old issue number at google code. For details see https://github.com/mybatis/old-google-code-issues/issues/746.
Comment From: kazuki43zoo
@emacarron @harawata
Is this a bug or not ?
Changes are follows:
- 7ff1321d3b3a00c337db30d356feca05a5af3c2e (1st commit)
- e92c2a2f1aacce52d7c1470b9aaa524dc8e9d1e7 (2nd commit)
Comment From: harawata
@Hubbitus Could you provide a repro, please?
Comment From: RobinQu
+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.
Comment From: RobinQu
Workaround provided by @Hubbitus is working all right. But it's not DRY at all.
Comment From: RobinQu
I am afraid this also affects the new option for setting default enum handler in https://github.com/mybatis/mybatis-3/issues/970
Comment From: harawata
@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
Comment From: harawata
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.
Comment From: ArayChou
+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
Comment From: tonyfalabella
Note the issue exists in version 3.4.6 as well. Both the suggestion from @Hubbitus and @harawata worked.
Comment From: bokz53
Hi every one , I found that this issue still exists in version 3.5.2.
In my situation , I write an automatic typeHandler register function , that can build mapping between any implements of my custom interface to a generic jsonTypeHandler
.
And when I set typeHandler
attribute to a field with javaType what I didn't register , I got this issue. Because the mapping of my generic jsonTypeHandler
is exists in allTypeHandlersMap
, mybatis will use it directly instead of create a new instance of my generic jsonTypeHandler
.
What I use to fix it is , just remove the my generic jsonTypeHandler mapping in allTypeHandlersMap
by reflection ways.
I know it's not a good solution , but there are no more better way in my mind.
Now, I saw @FlyInWind1 submitted a mr (https://github.com/mybatis/mybatis-3/pull/2762) try to fix that issue, but it didn't get approved .
@harawata Sorry for the bothering, but do we have any plan on this issue?
Comment From: harawata
@bokz53 ,
I plan to update type handler implementation in version 4 which will include java.lang.reflect.Type
support, but there has been no progress as I am busy with my day job.
You didn't post any code, so I am not sure if I understand your explanation correctly. I have seen some "generic type handler" ideas, but not all of them can be supported because of type erasure, I think.
You can try my work-in-progress branch and see if it resolves your own use case. The final implementation won't be exactly the same, but the results should be close enough. https://github.com/harawata/mybatis-3/tree/type-based-handler-resolution And if it did not work as you expected, please create a (failing) test case or small demo project like these and share it on your repository.
Comment From: bokz53
@bokz53 ,
I plan to update type handler implementation in version 4 which will include
java.lang.reflect.Type
support, but there has been no progress as I am busy with my day job.You didn't post any code, so I am not sure if I understand your explanation correctly. I have seen some "generic type handler" ideas, but not all of them can be supported because of type erasure, I think.
You can try my work-in-progress branch and see if it resolves your own use case. The final implementation won't be exactly the same, but the results should be close enough. https://github.com/harawata/mybatis-3/tree/type-based-handler-resolution And if it did not work as you expected, please create a (failing) test case or small demo project like these and share it on your repository.
Thanks a lot for the reply .
I had a look for type-based-handler-resolution
just now, and thought it can work in my situation.
I will do a test in action to verify it at next few days, and report the result again.