You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在AbstractDAO中使用特定RowMapper?DAO结构优化咨询

这是个很典型的DAO层复用与定制化的问题,我来给你梳理几个可行的方案,顺便聊聊怎么优化现有结构:

解决方案一:构造函数注入RowMapper(推荐,符合依赖注入原则)

核心思路是把RowMapper的控制权从父类AbstractDAO转移到子类,让子类在初始化时传入自己需要的Mapper实例,父类只负责通用逻辑。

首先改造AbstractDAO,不再硬编码默认的TypeAMapper,而是通过构造函数接收RowMapper,同时提供一个默认构造函数保持原有行为:

public abstract class AbstractDAO<T> {
    protected final RowMapper<T> rowMapper;
    // 假设这里有JdbcTemplate等通用依赖,省略初始化逻辑

    // 默认构造,保留原有默认使用TypeAMapper的逻辑
    protected AbstractDAO() {
        this.rowMapper = new TypeAMapper<>();
    }

    // 子类可通过此构造函数传入自定义Mapper
    protected AbstractDAO(RowMapper<T> rowMapper) {
        this.rowMapper = Objects.requireNonNull(rowMapper, "RowMapper cannot be null");
    }

    // 通用方法,使用rowMapper处理结果
    public T getOne(Object id) {
        String query = getOneQuery();
        // 执行查询,用rowMapper映射结果
        return jdbcTemplate.queryForObject(query, new Object[]{id}, rowMapper);
    }

    public List<T> getAll() {
        String query = getAllQuery();
        return jdbcTemplate.query(query, rowMapper);
    }

    // 抽象方法,子类实现具体查询语句
    protected abstract String getOneQuery();
    protected abstract String getAllQuery();
}

然后TypeBDAO在初始化时,传入配置好的TypeBMapper:

public class TypeBDAO extends AbstractDAO<TypeB> {
    // 提前初始化带特定配置的TypeBMapper,避免重复创建
    private static final RowMapper<TypeB> CUSTOM_TYPEB_MAPPER = new TypeBMapper.Builder()
            .mapColumn("b_id", "id")
            .mapColumn("b_status", "status", StatusEnum::valueOf)
            .ignoreColumn("b_unused")
            .build();

    public TypeBDAO() {
        // 调用父类带参数的构造函数,传入自定义Mapper
        super(CUSTOM_TYPEB_MAPPER);
    }

    @Override
    protected String getOneQuery() {
        return "SELECT * FROM type_b WHERE b_id = ?";
    }

    @Override
    protected String getAllQuery() {
        return "SELECT * FROM type_b WHERE b_status != 'DELETED'";
    }
}

这种方式的好处是:父类不需要知道子类用什么Mapper,完全由子类决定,符合开闭原则,新增其他类型的DAO时也不需要修改AbstractDAO。

解决方案二:模板方法模式

如果不想修改构造函数,可以在AbstractDAO里定义一个获取RowMapper的模板方法,子类重写这个方法返回自己的Mapper:

public abstract class AbstractDAO<T> {
    // 默认返回TypeAMapper
    protected RowMapper<T> getRowMapper() {
        return new TypeAMapper<>();
    }

    public T getOne(Object id) {
        String query = getOneQuery();
        // 调用模板方法获取Mapper
        return jdbcTemplate.queryForObject(query, new Object[]{id}, getRowMapper());
    }

    // 其他通用方法和抽象方法...
}

然后TypeBDAO重写getRowMapper()方法:

public class TypeBDAO extends AbstractDAO<TypeB> {
    // 缓存配置好的Mapper实例
    private static final RowMapper<TypeB> CUSTOM_TYPEB_MAPPER = new TypeBMapper.Builder()
            .mapColumn("b_id", "id")
            .build();

    @Override
    protected RowMapper<TypeB> getRowMapper() {
        return CUSTOM_TYPEB_MAPPER;
    }

    // 实现getOneQuery和getAllQuery...
}

这种方式更灵活,子类可以动态决定返回的Mapper(比如根据不同场景返回不同配置的Mapper),但要注意如果Mapper是有状态的,每次调用getRowMapper()创建新实例可能有性能问题,所以最好把Mapper实例缓存起来。

结构优化建议

  1. 依赖抽象而非具体实现:确保AbstractDAO依赖的是RowMapper接口,而不是具体的TypeAMapper/TypeBMapper,这样后续新增任何类型的Mapper都不需要修改父类代码,完全符合开闭原则。
  2. 抽离Mapper配置逻辑:把TypeBMapper的特定配置(比如字段映射、类型转换器)抽成单独的Builder类或配置类,不要把配置代码写在DAO里,这样配置逻辑可以复用,也让DAO代码更简洁。
  3. 统一DAO创建入口:如果项目里有很多DAO类,可以编写一个DAO工厂类,负责创建不同类型的DAO并注入对应的RowMapper,避免每个DAO都重复写构造函数或重写模板方法的代码。比如:
public class DAOFactory {
    private static final JdbcTemplate JDBC_TEMPLATE = new JdbcTemplate(dataSource);

    public static TypeADAO createTypeADAO() {
        return new TypeADAO(JDBC_TEMPLATE);
    }

    public static TypeBDAO createTypeBDAO() {
        RowMapper<TypeB> mapper = new TypeBMapper.Builder()
                .mapColumn("b_id", "id")
                .build();
        return new TypeBDAO(JDBC_TEMPLATE, mapper);
    }
}

这样外部使用DAO时只需要调用工厂方法,不需要关心内部的Mapper配置和依赖初始化。

内容的提问来源于stack exchange,提问作者M06H

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 08:04:23