Search code examples
spring-bootmybatisspring-mybatis

Why doesn't Mybatis map a simple ENUM correctly?


I'm not doing anything out of the ordinary from what I can tell. I have a spring boot application using mybatis:

implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.1'

I have an application.properties config for mybatis that is pretty simple:

## MyBatis ##
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.configuration.default-statement-timeout=30

My database table looks like this:

CREATE TABLE workspace_external_references (
    id CHAR(36) PRIMARY KEY,

    workspace_id CHAR(36) NOT NULL,
    site VARCHAR(255) NOT NULL,
    external_id VARCHAR(255) NOT NULL,

    created_at DATETIME(6) NOT NULL DEFAULT NOW(6),
    updated_at DATETIME(6) NOT NULL DEFAULT NOW(6),

    FOREIGN KEY (workspace_id) REFERENCES workspaces (id) ON DELETE CASCADE
)

With just a single entry like this:

'a907c0af-216a-41e0-b16d-42107a7af05f', 'e99e4ab4-839e-405a-982b-08e00fbfb2d4', 'ABC', '6', '2020-06-09 00:19:20.135822', '2020-06-09 00:19:20.135822'

In my mapper file I'm doing a select of all references like this:

@Select("SELECT * FROM workspace_external_references WHERE workspace_id = #{workspaceId}")
List<WorkspaceExternalReference> findByWorkspace(@Param("workspaceId") final UUID workspaceId);

And the java object that this is supposed to map to looks like this:

public class WorkspaceExternalReference {
    private UUID id;
    private UUID workspaceId;
    private Sites site;

    private String externalId;

    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;

    public WorkspaceExternalReference(
            final Sites site,
            final UUID workspaceId,
            final String externalId) {
        this.site = site;
        this.workspaceId = workspaceId;
        this.externalId = externalId;
    }
}

public enum Sites {
   ABC, XYZ;
}

Sooooo why doesn't this work? I get this error back:

Caused by: org.apache.ibatis.executor.result.ResultMapException: Error attempting to get column 'id' from result set.  Cause: java.lang.IllegalArgumentException: No enum constant com.acme.Sites.a907c0af-216a-41e0-b16d-42107a7af05f

Solution

  • When there is no default constructor, you need to let MyBatis know which columns to pass to the constructor explicitly (in most cases).

    With annotations, it would look as follows.
    You can use <resultMap> and <constructor> in XML mapper.

    @ConstructorArgs({
      @Arg(column = "site", javaType = Sites.class),
      @Arg(column = "workspace_id", javaType = UUID.class),
      @Arg(column = "external_id", javaType = String.class)
    })
    @Select("SELECT * FROM workspace_external_references WHERE workspace_id = #{workspaceId}")
    List<WorkspaceExternalReference> findByWorkspace(@Param("workspaceId") final UUID workspaceId);
    

    Other columns (i.e. id, created_at, updated_at) will be auto-mapped via setters (if there are) or reflection.

    Alternatively, you can just add the default (no-arg) constructor to the WorkspaceExternalReference class. Then all columns will be auto-mapped after the class is instantiated.

    Note: To make it work, there needs to be a type handler registered for UUID, but you seem to have done it already (otherwise the parameter mapping wouldn't work).