Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Handling Column Mismatches and Custom Enum Types in MyBatis XML

Tech May 17 2

To enhance maintainability, database connection properties should be decoupled from the core MyBatis configuration file. Instead of hardcoding values, we can utilize a dedicated properties file. First, create a file named database.properties in your resources directory: ``` jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/app_db jdbc.user=admin jdbc.pass=secretPassword


Next, reference this file within your `mybatis-config.xml`. Its important to adhere to the DTD order when defining elements; the `<properties>` tag must appear before `<settings>` and `<typeAliases>` to avoid parsing errors. ```
<configuration>
    <properties resource="database.properties">
        <!-- Optional default values if properties file is missing keys -->
        <property name="jdbc.user" value="dev_user"/>
    </properties>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.user}"/>
                <property name="password" value="${jdbc.pass}"/>
            </dataSource>
        </environment>
    </environments>
</configuration>

Repeatedly using fully qualified class names (FQCN) in XML mappers increases verbosity and reduces readability. MyBatis allows you to define aliases for Java types. You can register aliases individually: ``` <typeAliases> <typeAlias type="com.app.persistence.model.Account" alias="Account"/> </typeAliases>


Alternatively, to automaticaly register all classes in a package with their simple class name as the alias, specify the package name: ```
<typeAliases>
    <package name="com.app.persistence.model"/>
</typeAliases>

In many scenarios, database column names (e.g., snake_case like user_name) do not match Java property names (e.g., camelCase like userName). Additionally, handling Enumerations requires specific strategies. #### 1. Using ResultMaps for Mismatched Names

The <resultMap> element is the primary tool for mapping columns to properties explicitly. This is essential for SELECT operations where automatic mapping fails. ``` <resultMap type="Account" id="accountResultMap"> </resultMap>


#### 2. Built-in Enum Handlers

MyBatis provides two default handlers for Enum types: - `EnumTypeHandler`: Stores the Enum name (e.g., "ACTIVE") as a String.
- `EnumOrdinalTypeHandler`: Stores the Enum's ordinal index (e.g., 0, 1, 2) as an Integer.

Example usage in an `<insert>` statement: ```
<insert id="insertAccount">
    INSERT INTO accounts(username, status) 
    VALUES(#{username}, #{status, typeHandler=org.apache.ibatis.type.EnumOrdinalTypeHandler})
</insert>

However, for SELECT operations, the resultMap must specify the type handler to ensure correct conversion back to the Enum type. ``` <resultMap type="Account" id="accountWithStatus"> <result column="status" property="status" typeHandler="org.apache.ibatis.type.EnumOrdinalTypeHandler"/> </resultMap>


#### 3. Creating a Generic Custom Type Handler

Often, database codes for enums are specific integers (e.g., 10 for Bronze, 20 for Silver) rather than ordinals or names. To handle this cleanly across multiple Enum types without creating a handler for each one, we can use a generic approach. First, define a common interface for your enums: ```
package com.app.persistence.model;

public interface PersistableEnum {
    int getCode();
}

Implement this interface in your Enum class: ``` package com.app.persistence.model;

public enum MembershipLevel implements PersistableEnum { BRONZE(10), SILVER(20), GOLD(30);

private final int code;

MembershipLevel(int code) {
    this.code = code;
}

@Override
public int getCode() {
    return code;
}

}


Now, implement the generic `BaseTypeHandler`. This handler will cache the mapping between database codes and Enum constants to optimize performance. ```
package com.app.persistence.handler;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;

import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import com.app.persistence.model.PersistableEnum;

public class GenericEnumHandler<E extends Enum<E>&PersistableEnum> extends BaseTypeHandler<E> {

    private final Class<E> type;
    private final Map<Integer, E> codeToEnumMap = new HashMap<>();

    public GenericEnumHandler(Class<E> type) {
        if (type == null) {
            throw new IllegalArgumentException("Type argument cannot be null");
        }
        this.type = type;
        E[] enumConstants = type.getEnumConstants();
        if (enumConstants == null) {
            throw new IllegalArgumentException(type.getSimpleName() + " does not represent an enum type.");
        }
        for (E e : enumConstants) {
            codeToEnumMap.put(e.getCode(), e);
        }
    }

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
        ps.setInt(i, parameter.getCode());
    }

    @Override
    public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
        Integer code = rs.getInt(columnName);
        if (rs.wasNull()) {
            return null;
        }
        return codeToEnumMap.get(code);
    }

    @Override
    public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        Integer code = rs.getInt(columnIndex);
        if (rs.wasNull()) {
            return null;
        }
        return codeToEnumMap.get(code);
    }

    @Override
    public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        Integer code = cs.getInt(columnIndex);
        if (cs.wasNull()) {
            return null;
        }
        return codeToEnumMap.get(code);
    }
}

Finally, register this handler in your ResultMap. You need to specify the fully qualified class name of the Enum as the Java type for the handler. ``` <resultMap type="Account" id="accountWithMembership"> <result column="membership_level" property="membership" typeHandler="com.app.persistence.handler.GenericEnumHandler"/> </resultMap>


</div>

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

SBUS Signal Analysis and Communication Implementation Using STM32 with Fus Remote Controller

Overview In a recent project, I utilized the SBUS protocol with the Fus remote controller to control a vehicle's basic operations, including movement, lights, and mode switching. This article is aimed...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.