TypeHandler incorrectly selects super classes/interfaces
See original GitHub issueMyBatis version
3.5.8 (any version above 3.4.2)
Database vendor and version
Any (tested on Postgres 12.7 and latest JDBC, but also fails in testing framework)
Test case or example project
https://github.com/matthewmillett-instaclustr/mybatis-issues/tree/master/gh-2427
Steps to reproduce
Run tests
Expected result
shouldGetAUser()
passes, by using the constructor within the User
class to construct the User
object
shouldGetAnAccount()
passes (regression check)
Actual result
shouldGetAUser()
fails with the exception java.lang.ClassCastException: class test.UserPrimaryKey cannot be cast to class test.User (test.UserPrimaryKey and test.User are in unnamed module of loader 'app')
shouldGetAnAccount()
passes (regression check)
Explanation
I believe that this issue was introduced with https://github.com/mybatis/mybatis-3/issues/604 (PR https://github.com/mybatis/mybatis-3/pull/859). Before 3.4.2, Mybatis would use the type User
, and find the constructor within the User
class to build the result object. However, in 3.4.2 and above, Mybatis attempts to search the super class UserPrimaryKey
, and uses the constructor within the UserPrimaryKey
class to build a User
object. It builds the UserPrimaryKey
object fine, but then hits an error when attempting to cast it to the expected result object of User
.
Sample fix
This is far from an ideal solution, but proves to show that the above explanation holds true.
Within org.apache.ibatis.type.TypeHandlerRegistry#getJdbcHandlerMapForSuperclass
, if we add the following check after the call to clazz.getSuperclass()
, the code works as intended:
if (clazz.getSuperclass() != null && clazz.getSuperclass().getName().contains("PrimaryKey")) {
return typeHandlerMap.get(clazz);
}
An alternative way of resolving this may be to check for assignability within the above function, and only if the super class is assignable to the subclass then return the typeHandlerMap
of the super class. I’m not sure how backwards compatible this would be, and/or if it would work.
Other
I’m pretty sure this is not a configuration problem, but if it is, then please let me know. If it can be resolved with the correct annotations within the mapper, I will be willing to give it a go. I couldn’t see any other related tickets with a similar issue. I know the hierarchy of User
extending UserPrimaryKey
implementing PrimaryKey
is a bit odd, but modifying our inheritance/implementation structure is not an option.
Issue Analytics
- State:
- Created 2 years ago
- Reactions:1
- Comments:5 (2 by maintainers)
Hi @matthewmillett-instaclustr ,
I’m sorry about the slow response and thank you for the PR!
When your type handler is not applicable to the subclasses of the explicitly-mapped type, the type handler should not be registered globally. Instead, you need to specify
typeHandler
attribute explicitly.The test passes if you change the mapping of
Mapper#getCompanyByName()
as follows.I took a look at #2430 , but the scope of the underlying issue is wider and the proposed fix addresses only a part of it.
Hi @harawata,
Thanks for the suggestion to use
typeHandler = ...
withinConstructorArgs
. After modifying our codebase quite a bit we’ve managed to get things mostly working, although it’s not as clean as we’d hoped.I’ve spoken with my team, and our reasoning behind the change proposed in #2430 was that when the constructor args are specified explicitly there should be no need to be searching for a typehandler. Additionally, overriding the explicitly specified constructor args doesn’t seems like desirable behaviour.
If that reasoning is incorrect or there are deeper implications of the proposed change we are happy to investigate further and work with the team to try to come up with a sound solution. We understand that changes such as these can lead to backwards compatibility issues for people who use the library; however, at the same time, resolving these issues assists with reducing technical debt, and improves the developer experience.
Let us know how the above sounds.
Thanks