如何在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实例缓存起来。
结构优化建议
- 依赖抽象而非具体实现:确保AbstractDAO依赖的是
RowMapper接口,而不是具体的TypeAMapper/TypeBMapper,这样后续新增任何类型的Mapper都不需要修改父类代码,完全符合开闭原则。 - 抽离Mapper配置逻辑:把TypeBMapper的特定配置(比如字段映射、类型转换器)抽成单独的Builder类或配置类,不要把配置代码写在DAO里,这样配置逻辑可以复用,也让DAO代码更简洁。
- 统一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
相关产品推荐
相关产品推荐

