问题描述
我有两个实体Questions和UserAnswers。我需要在Spring Boot中创建一个api,该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还有吗?
解决方法
这听起来像是存储库方法的清晰自定义实现。首先,我将对您实体的实施做一些假设。然后,我将提出有关如何解决您的挑战的想法。
我假设实体看起来基本上是这样的(getters,setters,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,它可能相似或相同。
休眠通常需要将属性名称用作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();
}
}
,
我开发了一个名为spring-dynamic-jpa的库,以使使用JPA实施动态查询更加容易。
您可以使用它来编写查询模板。调用该方法时,根据您的参数,在执行之前,查询模板将内置到不同的查询字符串中。