如何用单个@RestController和通用JPARepository处理80+ lookup表CRUD?
通用Lookup表CRUD实现方案(Spring Boot + JPA)
1. 核心思路
利用Spring JPA的泛型特性+统一实体基类+动态实体映射,实现单个Controller和通用Repository处理所有Lookup表的CRUD操作,避免重复编写80余套代码。
2. 具体实现步骤
2.1 定义通用实体基类
所有Lookup表的实体继承该基类,统一JSON格式中的通用字段(比如id、code、name等):
@MappedSuperclass public abstract class BaseLookupEntity implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String code; private String name; // 可添加其他通用字段(如status、createTime) // getter/setter 省略 }
单个Lookup表实体只需继承基类并指定表名:
@Entity @Table(name = "country_lookup") public class CountryLookup extends BaseLookupEntity { // 无额外字段,直接复用基类属性 }
2.2 通用Repository定义
创建泛型Repository作为父接口,避免重复编写CRUD方法:
@NoRepositoryBean public interface GenericLookupRepo<T extends BaseLookupEntity, ID extends Serializable> extends JpaRepository<T, ID> { }
@NoRepositoryBean用于标记该接口仅作为父类,Spring不会为其生成实例。
如果不想为每个实体创建空的Repository子类,也可以直接用EntityManager实现通用操作(灵活性更高):
@PersistenceContext private EntityManager entityManager; // 示例:通用查询所有数据 public <T> List<T> findAll(Class<T> entityClass) { CriteriaQuery<T> query = entityManager.getCriteriaBuilder().createQuery(entityClass); query.from(entityClass); return entityManager.createQuery(query).getResultList(); }
2.3 通用RestController实现
通过路径变量(如/lookups/{entityName})区分不同Lookup表,实现LIST/GET/POST/PUT操作:
@RestController @RequestMapping("/lookups") public class GenericLookupController { private final ApplicationContext context; private final ObjectMapper objectMapper; public GenericLookupController(ApplicationContext context, ObjectMapper objectMapper) { this.context = context; this.objectMapper = objectMapper; } // 查询列表 @GetMapping("/{entityName}") public List<?> list(@PathVariable String entityName) { Class<?> entityClass = getEntityClass(entityName); GenericLookupRepo<?, Long> repo = getRepo(entityClass); return repo.findAll(); } // 查询单条 @GetMapping("/{entityName}/{id}") public Object get(@PathVariable String entityName, @PathVariable Long id) { Class<?> entityClass = getEntityClass(entityName); GenericLookupRepo<?, Long> repo = getRepo(entityClass); return repo.findById(id).orElseThrow(() -> new RuntimeException("数据不存在")); } // 新增数据 @PostMapping("/{entityName}") public Object create(@PathVariable String entityName, @RequestBody Map<String, Object> body) { Class<?> entityClass = getEntityClass(entityName); GenericLookupRepo<?, Long> repo = getRepo(entityClass); BaseLookupEntity entity = objectMapper.convertValue(body, entityClass); return repo.save(entity); } // 更新数据 @PutMapping("/{entityName}/{id}") public Object update(@PathVariable String entityName, @PathVariable Long id, @RequestBody Map<String, Object> body) { Class<?> entityClass = getEntityClass(entityName); GenericLookupRepo<?, Long> repo = getRepo(entityClass); BaseLookupEntity existing = (BaseLookupEntity) repo.findById(id) .orElseThrow(() -> new RuntimeException("数据不存在")); objectMapper.updateValue(existing, body); return repo.save(existing); } // 辅助方法:实体名称与实体类映射 private Class<?> getEntityClass(String entityName) { // 用枚举维护映射,避免硬编码switch return LookupEntityEnum.getByEntityName(entityName); } // 辅助方法:获取对应实体的Repository实例 private <T> GenericLookupRepo<T, Long> getRepo(Class<T> entityClass) { return context.getBean(GenericLookupRepo.class, entityClass); } }
2.4 实体名称映射枚举
用枚举统一维护entityName与实体类的对应关系,便于管理:
public enum LookupEntityEnum { COUNTRY("country", CountryLookup.class), STATE("state", StateLookup.class), // 依次添加剩余80个Lookup表的映射 ; private final String entityName; private final Class<?> entityClass; LookupEntityEnum(String entityName, Class<?> entityClass) { this.entityName = entityName; this.entityClass = entityClass; } public static Class<?> getByEntityName(String entityName) { for (LookupEntityEnum item : values()) { if (item.entityName.equals(entityName)) { return item.entityClass; } } throw new RuntimeException("未知实体类型:" + entityName); } }
3. 关键注意事项
- 类型转换:用
ObjectMapper处理JSON到实体的转换,自动适配不同字段类型,避免手动复制属性的繁琐。 - 异常处理:可添加全局异常处理器(
@RestControllerAdvice),统一返回404/400等HTTP状态码。 - 权限控制:若需细粒度权限,可在Controller中根据
entityName添加权限校验逻辑。 - 性能优化:批量操作或大数据量查询时,可添加分页支持(
Pageable参数)。
内容的提问来源于stack exchange,提问作者Jugs
相关产品推荐
相关产品推荐

