The Tale of an Enum and a BeanPropertyRowMapper: A Spring Developer's Quest

The Tale of an Enum and a BeanPropertyRowMapper: A Spring Developer's Quest

It was a quiet afternoon in my coding world when a peculiar bug showed up. I was working on a Spring-based web application, mapping database records to Java objects using BeanPropertyRowMapper. Everything seemed routine—until an enum property decided to throw an exception.

The Problem Unveiled

The issue started with a simple class, representing an order in my application:

public class Order {
    private Long id;
    private OrderStatus status;

    // Getters and setters
}

And here was the culprit, the OrderStatus enum:

public enum OrderStatus {
    PENDING(1),
    SHIPPED(2),
    DELIVERED(3),
    CANCELLED(4);

    private final int value;

    OrderStatus(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }
}

The database stored the OrderStatus as number (1, 2, 3, 4), but when BeanPropertyRowMapper tried to map them, it expected the ordinal values instead.

The Bug That Wouldn't Budge

I watched in frustration as my application spat out error after error, complaining about mismatched types. The issue was clear: Spring's BeanPropertyRowMapper had no idea how to handle the custom OrderStatus mapping. It was mapping enums based on ordinal values by default, which wasn't what I needed.

For a moment, I thought about hacking the database layer to store enum ordinals. But that idea made my inner developer cringe—what if the enum order changed later? No, the solution had to be clean, reusable, and future-proof.

The Eureka Moment

After diving deep into countless blogs, documentation pages, and forum threads, the solution finally began to take shape: I needed to extend BeanPropertyRowMapper and teach it how to handle enums like OrderStatus.

The first step was to enhance the OrderStatus enum by adding a static fromValue method. This method maps the database-stored string codes to the corresponding enum values. Here’s how the enum was updated:

public enum OrderStatus {
    PENDING(1),
    SHIPPED(2),
    DELIVERED(3),
    CANCELLED(4);

    private final int value;

    OrderStatus(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }

    public static OrderStatus fromValue(int value) {
        for (OrderStatus status : values()) {
            if (status.value == value) {
                return status;
            }
        }
        throw new IllegalArgumentException("Unknown enum value: " + value);
    }
}

And Here’s how I crafted my custom RowMapper:

import org.springframework.jdbc.core.BeanPropertyRowMapper;

import java.beans.PropertyDescriptor;
import java.sql.ResultSet;
import java.sql.SQLException;

public class OrderRowMapper extends BeanPropertyRowMapper<Order> {

    public OrderRowMapper(Class<Order> mappedClass) {
        super(mappedClass);
    }

    @Override
    protected Object getColumnValue(ResultSet rs, int index, PropertyDescriptor pd) throws SQLException {
        if ("status".equalsIgnoreCase(pd.getName()) && pd.getPropertyType().isEnum()) {
            int statusValue = rs.getInt(index);
            return OrderStatus.fromValue(statusValue);
        }

        return super.getColumnValue(rs, index, pd);
    }
}

The Test of Courage

Armed with my new OrderRowMapper, I was ready to face the database again. I updated my repository to use the custom mapper:

import org.springframework.jdbc.core.JdbcTemplate;

import java.util.List;

public class OrderRepository {

    private final JdbcTemplate jdbcTemplate;

    public OrderRepository(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public List<Order> findAllOrders() {
        String sql = "SELECT id, status FROM orders";
        return jdbcTemplate.query(sql, new OrderRowMapper(Order.class));
    }
}

When I ran the application, everything just... worked. The database codes (1, 2, 3, 4) were correctly mapped to their respective OrderStatus values. It felt magical, almost as if my application was thanking me for giving it the gift of clarity.

Lessons Learned

This little adventure taught me several important lessons:

  1. Don't settle for defaults: While BeanPropertyRowMapper works great out of the box, customizing it for specific use cases can make your application more robust.

  2. Enums are powerful allies: The fromValue method in my enum not only solved this bug but also made the code more expressive and maintainable.


What about you? Have you faced any similar challenges with BeanPropertyRowMapper or enums in your projects? Share your stories in the comments—I’d love to hear how you tackled them!