spring - 动态 JPA 查询
问题描述
我有两个实体问题和用户答案。我需要在 spring boot 中创建一个 api,它根据某些条件返回两个实体的所有列。
条件是:
- 我将给出一个比较器,例如:>、<、=、>=、<=
- 列名,例如:last_answered_at、last_seen_at
- 上列的值 例如:28-09-2020 06:00:18
我将需要返回两个实体的内部连接并根据上述条件进行过滤。
基于上述条件的示例 sql 查询将如下所示:
SELECT q,ua from questions q INNER JOIN
user_answers ua on q.id = ua.question_id
WHERE ua.last_answered_at > 28-09-2020 06:00:18
我面临的问题是查询的列名和比较器需要是动态的。
有没有一种有效的方法可以使用spring boot和JPA来做到这一点,因为我不想为所有可能的列和运算符组合创建jpa查询方法,因为它可能是一个非常大的数字并且会广泛使用if else?
解决方案
这听起来像是存储库方法的清晰自定义实现。首先,我将对您的实体的实施做出一些假设。之后,我将介绍如何解决您的挑战的想法。
我假设实体看起来基本上是这样的(getter、setter、equals、hachCode...被忽略)。
@Entity
@Table(name = "questions")
public class Question {
@Id
@GeneratedValue
private Long id;
private LocalDateTime lastAnsweredAt;
private LocalDateTime lastSeenAt;
// other attributes you mentioned...
@OneToMany(mappedBy = "question", cascade = CascadeType.ALL, orphanRemoval = true)
private List<UserAnswer> userAnswers = new ArrayList();
// Add and remove methods added to keep bidirectional relationship synchronised
public void addUserAnswer(UserAnswer userAnswer) {
userAnswers.add(userAnswer);
userAnswer.setQuestion(this);
}
public void removeUserAnswer(UserAnswer userAnswer) {
userAnswers.remove(userAnswer);
userAnswer.setQuestion(null);
}
}
@Entity
@Table(name = "user_answers")
public class UserAnswer {
@Id
@GeneratedValue
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "task_release_id")
private Question question;
}
我将使用 Hibernate 的 JPA 知识编写代码。对于其他 JPA,它的工作方式可能类似或相同。
Hibernate 通常需要将属性名称作为String
. 为了规避未检测到的错误问题(尤其是在重构时),我建议使用该模块hibernate-jpamodelgen
(请参阅带下划线后缀的类名)。您还可以使用它将属性名称作为参数传递给存储库方法。
存储库方法尝试与数据库通信。在 JPA 中,实现数据库请求的方式有多种:JPQL 作为查询语言和 Criteria API(更容易重构,不易出错)。由于我是 Criteria API 的粉丝,我将使用 Criteria API 和 modelgen 来告诉 ORM Hibernate 与数据库对话以检索相关对象。
public class QuestionRepositoryCustomImpl implements QuestionRepository {
@PersistenceContext
private EntityManager entityManager;
@Override
public List<Question> dynamicFind(String comparator, String attribute, String value) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Question> cq = cb.createQuery(Question.class);
// Root gets constructed for first, main class in the request (see return of method)
Root<Question> root = cq.from(Question.class);
// Join happens based on respective attribute within root
root.join(Question_.USER_ANSWER);
// The following ifs are not the nicest solution.
// The ifs check what comparator String contains and adds respective where clause to query
// This .where() is like WHERE in SQL
if("==".equals(comparator)) {
cq.where(cb.equal(root.get(attribute), value));
}
if(">".equals(comparator)) {
cq.where(cb.gt(root.get(attribute), value));
}
if(">=".equals(comparator)) {
cq.where(cb.ge(root.get(attribute), value));
}
if("<".equals(comparator)) {
cq.where(cb.lt(root.get(attribute), value));
}
if("<=".equals(comparator)) {
cq.where(cb.le(root.get(attribute), value));
}
// Finally, query gets created and result collected and returned as List
// Hint for READ_ONLY is added as lists are often just for read and performance is better.
return entityManager.createQuery(cq).setHint(QueryHints.READ_ONLY, true).getResultList();
}
}
推荐阅读
- python - 如何处理由另一个模块引起的错误
- reactjs - TypeError:无法仅在 ReactJS 中读取已部署版本的未定义属性“地图”?
- python - 如何在 Pandas pd.merge 中跟踪哪些行用 .fillna(value=median) 填充?
- javascript - 如何验证大型响应体是否包含赛普拉斯测试中的预期值?
- c# - Visual Studio 在发布时将 .dll 复制到 Bin 文件夹
- javascript - HERE URL 的 SSL 证书即将到期
- javascript - D3 数据可视化
- sql - 在雪花上运行的流氓任务?
- node.js - Tedious.js 不支持的协议
- linqpad - LinqPad - 从 LinqPad 执行的 RazorLight 抛出“RazorLight.RazorLightException:无法加载元数据引用”