我有一堆表,需要为其提供标准的CRUD接口。每次必须公开一个新表时,我都会遵循以下模式。
public interface EntityWithId<TDbEntity> extends Serializable {
public TDbEntity entityId();
}
@Entity
public class DbEntityName implements EntityWithId<Long> {
@Id private Long id;
@Override public Long entityId() {return id;}
// other fields follow
}
public class EntityName {
private Long id;
// other fields follow
// create Entity from DbEntity
public EntityName(DbEntityName dbItem) { ... }
// get DbEntity from Entity
public DbEntityName toDb() { ... }
}
@Repository
public interface DbEntityNameRepository extends CrudRepository<DbEntityName, Long> { }
public interface CrudService<TDbEntity extends EntityWithId<ID>, ID> {
CrudRepository<TDbEntity, ID> getCrudRepository();
// provide default implementation of all CRUD operations here like the below one
default TDbEntity save(TDbEntity entity) { return getCrudRepository().save(entity); }
}
public interface DbEntityNameService extends CrudService<DbEntityName, Long> {
}
@Service
public class DbEntityNameServiceImpl implements DbEntityNameService {
@lombok.Getter @Autowired DbEntityNameRepository crudRepository;
}
@RestController
@RequestMapping("/api/v1/dbservice")
public class EntityNameController {
@Autowired DbEntityNameService dbService;
@PostMapping("/{EntityName}") // this should be replaced by the actual name of the entity
public Long save(@RequestBody EntityName msg) {
return dbService.save(msg.toDb()).entityId();
}
// implement other CRUD end points
}
EntityWithId<T>
接口和CrudService<TDbEntity extends EntityWithId<ID>, ID>
仅为系统定义一次。它们提供了在访问存储库时消除重复代码的机制。
您将注意到,只需完成真正的代码即可在Entity和DB Entity中添加字段以及它们的转换。另外,我需要为每个新表推出一个新的Controller。
问题:如何构造控制器代码,以便可以从基本CRUD控制器继承功能。
请注意,在我的真实代码中,并非所有实体都适用于简单的CRUD,并且当前结构提供了扩展服务的简便方法
简而言之,我正在寻找某种模式来帮助我提供如下所示的内容,其中有一个通用的Base类,并且我可以用最少的代码创建一个子类来暴露控制器的端点。不用说,下面的代码将无法按原样提供我想要的功能。class BaseController<TEntity, TDbEntity, TId> {
CrudService<TDbEntity, TId> dbService;
@GetMapping("/{TEntity}/{id}")
public TEntity getById(@PathVariable TId id) {
return new TEntity(dbService.getById(id));
}
@PostMapping("/{TEntity}")
public Long save(@RequestBody TEntity msg) {
return dbService.save(msg.toDb()).entityId();
}
}
class EntityNameController : BaseController<EntityName, DbEntityName, Long> {
}
也随时提供其他建议。我的目的是减少控制器中的重复代码-主要是创建CRUD函数,将其与CRUD端点关联,然后调用基础服务来完成实际工作。
编辑:我了解我可以编写一个自定义注释处理器来生成标准的CRUD函数(几乎类似于CrudRepository
的工作方式),但这不是我想要的方向。
仅需澄清一下,此处的意图是可以在将公开它的基本控制器中对标准功能(如CRUD)进行一次性编码,从而释放子控制器以处理其他非标准工作。
答案 0 :(得分:1)
我认为对于dbService,您可以使用类似的
public interface CrudService<T, ID> {
T findByName(String name);
Set<T> findAll();
T findById(ID id);
T save(T object);
void delete(T object);
void deleteById(ID id);
}
public interface EntityNameService extends CrudService<EntityName, Long> {
}
public class EntityNameServiceImpl implements EntityNameService {
@Inject
private DbEntityNameRepository repository;
// implement all your repo code here
}
您的基本控制器可以像这样启动
public class BaseController {
@Autowired
private EntityNameService service;
public String getEntityName(String name) {
service.findByName(name);
}
@PostMapping("/{EntityName}") // this should be replaced by the actual name of the entity
public Long save(@PathVariable String EntityName) {
getEntityName(EntityName);
return dbService.save(msg.toDb()).entityId();
}
}
答案 1 :(得分:0)
这是为了摆脱一些锅炉板的尝试。想法是,业务逻辑将位于服务中,而不位于RestController或存储库中。 服务可以被重用并且单元测试良好。
使用SpringData的QueryDSL是您的朋友:
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
</dependency>
基本仓库。
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.PagingAndSortingRepository;
@NoRepositoryBean
public interface BaseRepo<T , ID > extends PagingAndSortingRepository<T, ID>, QuerydslPredicateExecutor<T> {}
可以访问非常大的表的真实存储库:
import com.querydsl.core.types.Predicate;
import static java.lang.System.out;
import refactor.BaseRepo;
public interface MyEntityRepository extends BaseRepo<MyEntity, String> {
@Override
default long count(Predicate predicate){
//counts on very large tables take forever. Optionally add this
return 0;
}
@Override
default long count(){
//counts on very large tables take forever. Optionally add this
return 0;
}
}
基本服务:
import com.querydsl.core.types.Predicate;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
@RequiredArgsConstructor
public class BaseService<T, ID> {
final BaseRepo<T, ID> repo;
public Page<T> findAll(Predicate predicate, Pageable pageable) {
return repo.findAll(predicate, pageable);
}
public Iterable<T> findAllWithoutMeta(Predicate predicate, Pageable pageable) {
return repo.findAll(predicate, pageable);
}
public Iterable<T> findAll() {
return repo.findAll();
}
public T save(T vendor) {
return repo.save(vendor);
}
public T update(T vendor) {
return repo.save(vendor);
}
public void delete(ID id) {
repo.deleteById(id);
}
public boolean exists(ID id) {
return repo.findById(id).isPresent();
}
public Optional<T> getById(ID id) {
return repo.findById(id);
}
}
真正的服务
import com.querydsl.core.types.Predicate;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
@Service
public class MyService extends BaseService<MyEntity, String>{
public MyService(MyEntityRepository repo) {
super(repo);
}
@Override
public Page<MyEntity> findAll(Predicate predicate, Pageable pageable) {
return super.findAll(predicate, pageable);
}
}
我决定不使RestContoller通用化,只写我需要的CRUD操作所需的代码。 (例如,在某些情况下,不需要或不需要删除和放置操作) 这是HATEOAS RESTful API的实现。投资HATEOAS设计并不适合每个人和每个应用程序。可以用一个普通的休息控制器代替。
获取此处信息可以过滤存储库中的所有字段。这样您就可以获得http://localhost/api/v1/myapi?name=YourName&age=30
import com.querydsl.core.types.Predicate;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.springframework.data.domain.Pageable;
import org.springframework.data.querydsl.binding.QuerydslPredicate;
import org.springframework.hateoas.EntityLinks;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.MediaTypes;
import org.springframework.hateoas.Resource;
import org.springframework.hateoas.Resources;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(path = "/api/v1/myapi", produces = MediaTypes.HAL_JSON_VALUE)
public class MyApiController {
private final MyService service;
private final EntityLinks eLinks;
MyApiController(MyService service, EntityLinks eLinks) {
this.service = service;
this.eLinks = eLinks;
}
@GetMapping
@Transactional(readOnly = true)
ResponseEntity<Resources<Resource<MyEntity>>> findAll(@QuerydslPredicate(root = MyEntity.class) Predicate predicate, Pageable pageable) {
return new ResponseEntity(toResources(service.findAllWithoutMeta(predicate,pageable)), HttpStatus.OK);
}
@GetMapping(value = "/{id}")
ResponseEntity<Resource<MyEntity>> findOne(@PathVariable String id) {
final Optional<MyEntity> findById = service.getById(id);
if (!findById.isPresent()) {
return null;//fixme ResponseEntity.notFound(assembler.);
}
return ResponseEntity.ok(toResource(findById.get()));
}
private Resources<Resource<MyEntity>> toResources(Iterable<MyEntity> customers) {
List<Resource<MyEntity>> customerResources = new ArrayList<>();
for (MyEntity l : customers) {
customerResources.add(toResource(l));
}
return new Resources<>(customerResources);//, selfLink);
}
private Resource<MyEntity> toResource(MyEntity customer) {
Link selfLink = linkTo(methodOn(CallLoggingController.class).findOne(customer.getId())).withSelfRel();
return new Resource<>(customer, selfLink);
}
}
我的建议是不要沉迷于通用代码。复制和粘贴优于超级通用代码imho。