我有一个休息控制器,其中包含一个根据“标题”和“作者”参数查找图书的方法。
你能否给我一些提示如何摆脱if-else的建设? 现在它并不复杂,但将来可能会增加参数的数量,从而导致混乱。
map $arg_amp $x {
1 foo;
2 bar;
default baz;
}
...
location / {
add_header X-Foo $x;
return 204;
}
答案 0 :(得分:2)
由于您的计划最终会支持更多参数,因此最好的选择可能是调查Hibernate's Criteria
class。这允许您动态构造查询。它不会避免使用if
语句,但将避免使用else
语句,并且很容易支持新参数。在您的存储库/ DAO级别:
Criteria criteria = session.createCriteria(Book.class);
if (author != null && !author.isEmpty()) {
criteria.add(Restriction.eq("author", author));
}
if (title != null && !title.isEmpty()) {
criteria.add(Restriction.eq("title", title));
}
criteria.addOrder(Order.asc("publishDate"));
return (List<Book>) criteria.list();
这有一些显着的好处:
要支持新参数,您只需将参数添加到控制器,然后将参数传递到存储库,并将参数添加到此块。
您最终可以对您的排序进行配置,例如:
?sort=title:asc&author=Bobby%20Tables
但是,有一些缺点,最明显的是它依赖于字符串值来引用您的属性。如果您的属性名称更改(请注意这是POJO属性,而不是数据库列名称),则此代码将需要更改。但是,我认为这是一种非常罕见的情况,除了最新的数据库模式仍在不断变化的新项目中,并且在数据库模式建立之后,这种缺点很少会引起问题。
另一个提示,一旦你点击了一定数量的传入参数(例如4-5),创建a parameter object就可以将你的参数包装成一个可以传递的好的对象。
答案 1 :(得分:1)
我会推荐Specification or Querydsl 它受到域驱动设计/模型的启发,实现依赖于JPA Criteria,可以灵活地构建查询。
这是一个例子(没有经过测试,但你应该得到一般的想法)。
书籍规范
用于动态创建规范的工厂类。
public class BookSpecifications {
private BookSpecifications(){
}
public static Specification<Book> withAuthor(String author) {
return new Specification<Book>() {
public Predicate toPredicate(Root<Book> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
return cb.equal(root.get("author"), author);
}
};
}
public static Specification<Book> withTitle(String title) {
return new Specification<Book>() {
public Predicate toPredicate(Root<Book> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
return cb.equal(root.get("title"), title);
}
};
}
}
请注意,使用从Entity类在编译时生成的常量值是一种更好的方法。
Yo可以使用Book.author
或Book.title
来引用实体的属性,而不是使用在编译时未检查的某些String
与实际的实体模型(如“作者”或“标题”)。
BookController
在控制器端,您应该避免以最小的逻辑减少逻辑,并且有利于将处理委托给服务类,该服务类将创建所需的规范并将其传递给存储库。
public class BookController {
@GetMapping
public ResponseEntity<List<Book>> searchBooksByTitleAndAuthor(
@RequestParam(value = "title", required = false) String title,
@RequestParam(value = "author", required = false) String author) {
List<Book> books = bookService.findAll(title, author);
if (books.isEmpty()) {
log.info("No books with this specification ");
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
return new ResponseEntity<>(books, HttpStatus.OK);
}
}
BookService
整个逻辑在这里(单元测试应该集中在这里)。
public class BookService {
private BookRepository bookRepository;
public BookService(BookRepository bookRepository){
this.bookRepository = bookRepository;
}
public List<Book> findAll(String title, String author) {
if (Stream.of(title, author)
.filter(s-> s == null || s.equals(""))
.count() == 0) {
return new ArrayList<>();
}
Specification<Book> specification = createSpecification(specification, title, () -> BookSpecifications.withTitle(title));
specification = createSpecification(specification, author, () -> BookSpecifications.withAuthor(author));
List<Book> books = bookRepository.findAll(specification);
return books;
}
Specification<Book> createSpecification(Specification<Book> currentSpecification, String arg, Callable<Specification<Book>> callable) {
// no valued parameter so we return
if (arg == null) {
return currentSpecification;
}
try {
// Specification instance already created : reuse it
if (currentSpecification != null) {
return currentSpecification.and(callable.call());
}
// Specification instance not created yes : create a new one
return callable.call();
} catch (Exception e) {
// handle the exception... if any
}
}
}
在搜索中添加新的实体属性非常简单
在方法和流中添加参数,以测试是否至少填充了条件搜索,然后将新调用链接到createSpecification()
:
// existing
Specification<Book> specification = createSpecification(specification, title, () -> BookSpecifications.withTitle(title));
specification = createSpecification(specification, author, () -> BookSpecifications.withAuthor(author));
// change below !
specification = createSpecification(specification, anotherColumn, () -> BookSpecifications.withAnotherColumn(anotherColumn));
BookRepository
最后一步:让您的BookRepository
界面扩展JpaSpecificationExecutor
,以便能够调用接受Repository
参数的Specification<T>
方法,例如:
List<T> findAll(@Nullable Specification<T> spec);
那应该没问题:
@Repository
public interface BookRepository extends JpaRepository<Book, Long> , JpaSpecificationExecutor<Book> {
}
请注意,如果可能要求许多实体属性,则使用更动态的方法可能会很有趣:
public class BookSpecifications {
private BookSpecifications(){
}
public static Specification<Book> withAttribute(String name, T value) {
return new Specification<Book>() {
public Predicate toPredicate(Root<Book> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
return cb.equal(root.get(name), value);
}
};
}
}
但推迟检测编码错误有其缺点 实际上,规范构建中的错误(例如:值类型不兼容)只能在运行时检测到。
答案 2 :(得分:0)
我只想写这个:
@GetMapping
public List<Book> searchBooksByTitleAndAuthor(@RequestParam String title, @RequestParam String author) {
return bookService.getBooksByTitleAndAuthor(title, author);
}
我会将ResponseEntity
创建和HttpStatus
管理留给Spring。对于null
或空值管理,我将其留给后面的服务或数据库查询。
此外,您在RequestParam注释中编写的参数是默认值,可以简化。
最后,为什么要记录所有查询?重点是什么?如果您的目标是生产,那么您的信息日志将被大量查询发送垃圾邮件,而商业信息无论如何都不属于技术日志。
答案 3 :(得分:0)
没有很好的解决方案。如果我是你,我会将其重构为:
@GetMapping
public ResponseEntity<List<Book>> searchBooksByTitleAndAuthor(
@RequestParam(value = "title", required = false) String title,
@RequestParam(value = "author", required = false) String author) {
List<Book> books;
if (StringUtils.isBlank(title) && StringUtils.isBlank(author)) {
log.info("Empty request");
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
} else if (!StringUtils.isBlank(title) && !StringUtils.isBlank(author)) {
books = bookService.getBooksByTitleAndAuthor(title, author);
} else if (StringUtils.isBlank(author)) {
books = bookService.getBooksByTitle(title);
} else {
books = bookService.getBooksByAuthor(author);
}
if (books.isEmpty()) {
log.info("No books found with title = " + title + " and author = " + author);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
return new ResponseEntity<>(books, HttpStatus.OK);
}
使用此解决方案,您将在响应后进行处理。
一个提示
如果没有发送参数,则返回错误请求,说明此服务至少需要一个参数。
答案 4 :(得分:0)
一点点筑巢可能会有所帮助。 这将最小化重复测试。 一些代码:
if (StringUtils.isNotBlank(blam) || StringUtils.isNotBlank(kapow))
{
if (StringUtils.isBlank(blam))
{
// kapow is not blank.
}
else if (StringUtils.isBlank(kapow))
{
// blam is not blank.
}
else
{
// neither kapow nor blam is blank.
}
}
else
{
// both empty. error.
}
我喜欢Brian的回答, 但我永远不会建议使用Hibernate。 MyBatis还支持条件where子句。
答案 5 :(得分:0)
这是一个想法。代码可能需要一些工作,特别是我不知道你是否可以使用静态地图,但是对于Spring,你可能还有一个单例。
private static long toKey(Object ... args) {
long key = 0L;
for(int i = 0; i < args.length; i++) {
if(args[i] != null) {
key |= (1L << i);
}
}
return key;
}
private static interface BookFinder {
ResponseEntity<List<Book>> search(String title, String author);
}
private static Map<Long, BookFinder> _keyToFinderMap = new HashMap<>();
static {
_keyToFinderMap.put(toKey(null, null), new BookFinder() {
public ResponseEntity<List<Book>> search(String title, String author) {
log.info("Empty request");
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
});
_keyToFinderMap.put(toKey("", null), new BookFinder() {
public ResponseEntity<List<Book>> search(String title, String author) {
log.info("Retrieving books by title");
List<Book> books = bookService.getBooksByTitle(title);
if (books.isEmpty()) {
log.info("No books with this title");
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
return new ResponseEntity<>(books, HttpStatus.OK);
}
});
// Other cases
};
@GetMapping
public ResponseEntity<List<Book>> searchBooksByTitleAndAuthor(
@RequestParam(value = "title", required = false) String title,
@RequestParam(value = "author", required = false) String author) {
return _keyToFinderMap.get(toKey(title, author)).search(title, author);
}
如果添加新参数,只需添加新的书籍查找器即可。不,如果声明。我会将NO_CONTENT
移到searchBooksByTitleAndAuthor
,但我不想更改您的日志记录语句。否则,这将简化发现者。
toKey
方法当然可以改变,实施并不重要。它必须将输入组合映射到唯一键。建议的方法最多可处理64个参数,这些参数可以是null / not null。