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
相关产品推荐
相关产品推荐

