Комментарии 25
Есть такая штука — spring data rest. :)
Там все тоже самое, только hateoas на выходе ) но я им, если честно, не проникся и колхозил решение, как в этой статье...
How not to hate hateoas? ;)
Если я не ошибаюсь, Spring Data Rest пришивает endpoint прямо к репозиторию. Такой подход не подразумевает сервиса и контроллера. То есть, Вы можете потом именно что "наколхозить", как Вы выразились, отдельно сервис и репозиторий и пройти всю цепочку для нестандартных запросов. Но тогда у Вас будет две параллельных архитектуры, с эндпоинтами в контроллере и репозитории.
Spring Data Rest, по моему мнению, подходит в том случае, если функция сервера чисто утилитарна и сводится к функциям "взять / положить в репозиторий".
Да, я ещё забыл написать — зачем в вашей абстрактной системе сервис, который лишь вызывает метод репозитория? Не проще ли сразу реп инжектить в контроллер, раз уж пошла такая пьянка?
По поводу утилитарности — пример в статье тоже "взять/положить". Говорю же, как s-d-r, только "без крыльев" и "с перламутровыми пуговицами",
Сервис как раз затем, что, как написано в конце статьи, Вам неизбежно придётся добавлять какую-то дополнительную логику для обработки сущностей — переопределять существующие абстрактные методы и писать новые. Если, конечно, приложение шагает за рамки работы с репозиторием.
Конечно, пример в статье максимально лаконичен, чтобы как можно меньше отвлекать читателя от сути (в ресурсах он немного более развёрнут). Вы бы ещё спросили, почему я тесты не написал :)))
public abstract class AbstractController<
E extends AbstractEntity,
R extends CommonRepository<E>,
S extends CommonService<E, R>> implements CommonController<E, R, S> {
private final S service;
protected AbstractController(S service) {
this.service = service;
}
public getService() {
return this.service;
}
}
public class UserController extends AbstractController<User, UserRepository, UserService> {
public UserController(UserService service) {
super(service);
}
}
Фи-фи-фи… Но тут нет к сожалению стримов, дженериков, опшиналов, спринг дата, сервисов, интерфейсов, энтитей, наследования и копипаста сервисов, контроллеров, и репозиториев для каждой энтити. Всего одна строчка делает все, что Ваш код и еще немного)))). Хотя я не знаю как спринг сериализует ResultSet.
@RestController
@RequestMapping("/rest")
public class Rest Controller{
@RequestMapping("/{tableName}/read}")
public ResultSet read(String tableName) {
return dbConnection.prepareStatement("Select * from "+tableName.replaceAll(" ","")).executeQuery();
А что это у Вас за класс такой модный, Rest Controller?
Ждем следующую статью, как с помощью вейпа, гироскутера и смузи смыть за собой в туалете;)
Я рад тебя видеть на просторах Хабра, но очередному бесполезному спору я не рад. Продолжать его не буду. Лучше займись делом и почитай хорошую статью, вместо того, чтобы убеждать себя, почему очередная технология тебе не нужна.
Почему больше всего «прорывных идей, простых как лом», исходит от людей, которые не могут освоить даже инкапсуляцию, не говоря уже про стримы?
Кстати, ресурсы и код в примере обновился благодаря толковому предложению одного из комментаторов. Теперь в бине мы переопределяем только конструктор абстракции (которого ранее не было). Рекомендую ознакомиться с изменениями — возможно, тогда разногласия исчезнут :)
@Autowired
private final S service;
Более подробно Вы можете ознакомиться с вопросом в посте одного из контрибьюторов Spring Оливера Гирке.
Как Вы наверняка знаете, бин — это синглтон. Использовать один и тот же бин для всех сервисов при многопоточном подходе не безопасно, и Spring не рекомендует этого делать. Поэтому намного более безопасным подходом будет создать финальную копию бина и использовать её.
А что будет, если конструктор добавить, уж лучше и не проверять)))
Использование выглядит всего лишь вот так:
@Autowired @CoreDaoClass(Car.class)
private CoreDao<Car> carDao;
Аннотация:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Qualifier("coreDaoByAnnotation")
public @interface CoreDaoClass {
Class<?> value();
}
Ну и фабрика для конкретных реализаций DAO:
@Configuration
public class CoreDaoFactory {
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Lazy
public <C> CoreDao<C> coreDaoForClass(Class<C> cls) {
return new CoreDaoImpl<>(cls);
}
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Lazy
public CoreDao<?> coreDaoByAnnotation(InjectionPoint ip) {
CoreDaoClass annotation = AnnotationUtils.findAnnotation(ip.getAnnotatedElement(), CoreDaoClass.class);
if (annotation == null) {
throw new NoSuchBeanDefinitionException("CoreDao", "CoreDaoClass is not defined");
}
return new CoreDaoImpl<>(annotation.value());
}
}
Из замечаний — CrudRepository.save()
никогда не вернет null (the saved entity will never be {@literal null}.
). Поэтому Optiolal
в сервисе и map
в контроллере бессмыслены.
Для результата контроллера, который в вашем коде, есть готовый ResponseEntity.of()
.
Еще посмотрите на наследование аннотаций в контроллерах. В AbstractController
можно определять маппинг методов, а в наследуемом репозитории задавать только маппинг класса.
К сожалению, по моему опыту, это очень частный случай, в основном приходится гораздо больше кастомизировать слои и дженерики начинают больше мешать, чем помогать. Полезны только абстрактные модели: AbstractIdEntity
, AbstractNamedEntity
и связанные с ними репозитории для общих действий: например поиск по имени. А если все очень типовое — то упомянутый spring-data-rest
дает горазде меньше кода.
Абстрактный CRUD от репозитория до контроллера: что ещё можно сделать при помощи Spring + Generics