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

Spring Data JDBC泛型自定义仓库如何动态获取类类型

问题:Spring Data JDBC自定义泛型仓库动态获取表名及覆盖CrudRepository方法

我正在使用Spring Data JDBC,已实现一个带泛型类型的自定义仓库。需要在自定义仓库实现中动态获取类类型以构建动态查询,且只能使用Spring Data JDBC(不能使用JPA)。

以下是我的实现代码:

自定义仓库实现类

@Slf4j
public class CrudRepositoryCustomImpl<T, ID> implements CrudRepositoryCustom<T, ID> {

    private final NamedParameterJdbcTemplate jdbcTemplate;
    
    public CrudRepositoryCustomImpl(NamedParameterJdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
    
    @Override
    public Page<T> findAll(Pageable pageable) {
        String tableName = ""; // 需要根据T动态获取表名
        String sql = "SELECT * FROM " + tableName + " WHERE IS_DELETED = FALSE LIMIT :limit OFFSET :offset";
        MapSqlParameterSource parameters = new MapSqlParameterSource();
        parameters.addValue("limit", pageable.getPageSize());
        parameters.addValue("offset", pageable.getOffset());
    
        List<Object> results = jdbcTemplate.query(sql, parameters, BeanPropertyRowMapper.newInstance(Object.class));
        String countSql = "SELECT COUNT(*) FROM " + tableName + " WHERE IS_DELETED = FALSE";
        Long total = jdbcTemplate.queryForObject(countSql, parameters, Long.class);
    
        return new PageImpl<T>((List<T>) results, pageable, total);
    }

}

自定义仓库接口

public interface CrudRepositoryCustom<T, ID> {
    Page<T> findAll(Pageable pageable);
}

继承自定义仓库的业务仓库接口

public interface MilestoneRepository extends CrudRepositoryCustom<Milestone, Long> {
}

目标

根据类类型T动态确定表名,如何在CrudRepositoryCustomImpl类中实现?或者如何将CrudRepository的方法覆盖到自定义仓库中?


解决方案

一、动态获取实体对应的表名

Spring Data JDBC内置了JdbcMappingContext来管理实体与数据库表的映射关系,结合泛型类型传递,可以动态获取表名。

步骤1:修改自定义仓库实现类

注入JdbcMappingContext并接收实体类型参数,通过映射上下文获取表名:

@Slf4j
public class CrudRepositoryCustomImpl<T, ID> implements CrudRepositoryCustom<T, ID> {

    private final NamedParameterJdbcTemplate jdbcTemplate;
    private final JdbcMappingContext mappingContext;
    private final Class<T> entityType;
    private final String tableName;

    // 构造方法注入组件与实体类型
    public CrudRepositoryCustomImpl(NamedParameterJdbcTemplate jdbcTemplate, 
                                   JdbcMappingContext mappingContext, 
                                   Class<T> entityType) {
        this.jdbcTemplate = jdbcTemplate;
        this.mappingContext = mappingContext;
        this.entityType = entityType;
        // 从映射上下文获取实体对应的表名
        this.tableName = mappingContext.getRequiredPersistentEntity(entityType).getTableName();
    }
    
    @Override
    public Page<T> findAll(Pageable pageable) {
        String sql = "SELECT * FROM " + tableName + " WHERE IS_DELETED = FALSE LIMIT :limit OFFSET :offset";
        MapSqlParameterSource parameters = new MapSqlParameterSource();
        parameters.addValue("limit", pageable.getPageSize());
        parameters.addValue("offset", pageable.getOffset());
    
        // 使用实体类型创建RowMapper,避免强制类型转换
        List<T> results = jdbcTemplate.query(sql, parameters, BeanPropertyRowMapper.newInstance(entityType));
        String countSql = "SELECT COUNT(*) FROM " + tableName + " WHERE IS_DELETED = FALSE";
        Long total = jdbcTemplate.queryForObject(countSql, parameters, Long.class);
    
        return new PageImpl<>(results, pageable, total);
    }
}

步骤2:实现自定义RepositoryFactoryBean

由于Java泛型擦除,需要通过RepositoryFactoryBean传递实体的实际类型:

public class CrudRepositoryCustomFactoryBean<T extends Repository<S, ID>, S, ID> 
        extends RepositoryFactoryBeanSupport<T, S, ID> {

    public CrudRepositoryCustomFactoryBean(Class<? extends T> repositoryInterface) {
        super(repositoryInterface);
    }

    @Override
    protected RepositoryFactorySupport createRepositoryFactory() {
        return new CrudRepositoryCustomFactorySupport(getEntityInformation().getJavaType());
    }

    private static class CrudRepositoryCustomFactorySupport<S, ID> extends RepositoryFactorySupport {

        private final Class<S> entityType;

        public CrudRepositoryCustomFactorySupport(Class<S> entityType) {
            this.entityType = entityType;
        }

        @Override
        protected Object getTargetRepository(RepositoryInformation information) {
            NamedParameterJdbcTemplate jdbcTemplate = getApplicationContext().getBean(NamedParameterJdbcTemplate.class);
            JdbcMappingContext mappingContext = getApplicationContext().getBean(JdbcMappingContext.class);
            return new CrudRepositoryCustomImpl<>(jdbcTemplate, mappingContext, entityType);
        }

        @Override
        protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
            return CrudRepositoryCustomImpl.class;
        }
    }
}

步骤3:标注仓库接口指定FactoryBean

在业务仓库接口上添加注解,指定自定义的FactoryBean:

@RepositoryFactoryBean(CrudRepositoryCustomFactoryBean.class)
public interface MilestoneRepository extends CrudRepositoryCustom<Milestone, Long> {
}

二、覆盖CrudRepository的方法

只需在自定义仓库接口中声明要覆盖的方法,再在实现类中编写自定义逻辑即可:

修改自定义仓库接口

继承CrudRepository并声明要覆盖的方法:

public interface CrudRepositoryCustom<T, ID> extends CrudRepository<T, ID> {
    // 覆盖CrudRepository的分页查询方法
    @Override
    Page<T> findAll(Pageable pageable);

    // 自定义覆盖save方法示例
    @Override
    <S extends T> S save(S entity);
}

在实现类中实现覆盖方法

@Override
public <S extends T> S save(S entity) {
    // 自定义通用save逻辑,比如自动填充创建/更新时间、删除标记等
    JdbcPersistentEntity<S> persistentEntity = (JdbcPersistentEntity<S>) mappingContext.getRequiredPersistentEntity(entity.getClass());
    
    // 编写动态insert/update逻辑(示例框架,需根据实际需求完善)
    String sql = "INSERT INTO " + persistentEntity.getTableName() + " (...) VALUES (...)";
    // 执行SQL并返回结果
    jdbcTemplate.update(sql, ...);
    
    return entity;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 14:08:11