Using Criteria API or java.util.List contains() method? - performance

Lets assume the following situation: I have two entity classes Person and Comment.
//Person class
#Entity
public class Person extends AbstractBusinesObject{
#OneToMany(mappedby ="owner")
private List<Comment> comments;
//Comment class
#Entity
public class Comment extends AbstractBusinesObject{
#ManyToOne
private Person owner;
I would like to know which approach is better (performance, memory-efficiency) from the following two alternatives to determine whether a specific comment belongs to a specific person or not.
public boolean isUsersComment(Person p, Comment c){
return p.getComments().contains(c);
}
or
public boolean isUsersComment(Person p, Comment c){
CriteriaBuilder cb = getEntitymanager().getCriteriaBuilder();
CriteriaQuery<Comment> cq = cb.createQuery(Comment.class);
Root<Comment> root = cq.from(Comment.class);
cq.select(root).where(cb.and(cb.equal(root, c), cb.isTrue(root.in(p.getComments())));
try{
getEntityManager().createQuery(cq).getSingleResult();
return true;
} catch (NoResultException ex){
return false;
} catch (NoUniqueResultException ex){
throw new IllegalStateException(message);
}
}
As far as i know criteria api generates a database query, while the first solution searches in a collection.
Thank you for your answer.
ps: I am new to Criteria API, so I also accept fixes/suggestions.
Edit
Ok, I have found out it was a stupid question; As I declared a bidirectional relationship, the solution is very simple:
return c.getOwner.equals(p);
Anyway, what is the approach in case of unidirectional relationship?

Depends if the original Collection is already loaded, no? If the Collection is loaded then its a straight contains() call, otherwise it likely would be the equivalent of what a query (Criteria or JPQL) would do.
If the Collection was large then I'd go through a query always (since I wouldn't want to load the whole collection). Otherwise I'd use Collection.contains

Related

Trying to lazy load and return a list inside a #transactional annotated method and getting a no session error

this works, but when I try to remove the courses.size i get no session error.
#Transactional
public List<Course> initiateCourses(Long id) {
Instructor instructor = instructorRepository.findById(2L).get();
List<Course> courses = instructor.getCourses();
courses.size();
return courses;
}
It works, but it feels like a hack.
also I found another way to load lazy collections.
#Repository
public interface InstructorRepository extends CrudRepository<Instructor, Long> {
#Query("SELECT p FROM Instructor p LEFT JOIN FETCH p.courses WHERE p.id = ?1")
Optional<Instructor> findByIdAndFetchCourseEagerly(Long id);
}
which one should I use performance wise? or is there a better way of fetching lazy initialized objects.
You can work with #EntityGraph to specify which parts of the object hierarchy should be fetched:
#Repository
public interface InstructorRepository extends CrudRepository<Instructor, Long> {
#EntityGraph(type = EntityGraphType.FETCH, attributePaths = {"courses"})
Optional<Instructor> findById(Long id);
}
See: EntityGraph (Spring Data JPA)
This proxy will not be initialized till you retrieve element from it or call size() method.
There are several ways to solve your problem without "magic":
Explicitly call Hibernate.init() and pass in it your collection-proxy before returning it from method.
Use entity graph as Peter Walser suggested

Dynamic JPA query

I have two entities Questions and UserAnswers. I need to make an api in spring boot which returns all the columns from both the entities based on some conditions.
Conditions are:
I will be give a comparator eg: >, <, =, >=, <=
A column name eg: last_answered_at, last_seen_at
A value of the above column eg: 28-09-2020 06:00:18
I will need to return an inner join of the two entities and filter based on the above conditions.
Sample sql query based on above conditions will be like:
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
The problem I am facing is that the column name and the comparator for the query needs to be dynamic.
Is there an efficient way to do this using spring boot and JPA as I do not want to make jpa query methods for all possible combinations of columns and operators as it can be a very large number and there will be extensive use of if else?
I have developed a library called spring-dynamic-jpa to make it easier to implement dynamic queries with JPA.
You can use it to write the query templates. The query template will be built into different query strings before execution depending on your parameters when you invoke the method.
This sounds like a clear custom implementation of a repository method. Firstly, I will make some assumptions about the implementation of your entities. Afterwards, I will present an idea on how to solve your challenge.
I assume that the entities look basically like this (getters, setters, equals, hachCode... ignored).
#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;
}
I will write the code with the knowledge about the JPA of Hibernate. For other JPAs, it might work similarly or the same.
Hibernate often needs the name of attributes as a String. To circumvent the issue of undetected mistakes (especially when refactoring), I suggest the module hibernate-jpamodelgen (see the class names suffixed with an underscore). You can also use it to pass the names of the attributes as arguments to your repository method.
Repository methods try to communicate with the database. In JPA, there are different ways of implementing database requests: JPQL as a query language and the Criteria API (easier to refactor, less error prone). As I am a fan of the Criteria API, I will use the Criteria API together with the modelgen to tell the ORM Hibernate to talk to the database to retrieve the relevant objects.
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();
}
}

Efficient way to fetch list size

I have an entity like below. When I need to list comment size of company I'm calling totalComments() method. For this does hibernate go to the database and fetch entire comment data or just querying with count(*)? If hibernate fetch entire comment what is the efficient way for getting comment size?
#Entity
#Table(name = "companies")
public class Company extends ItemEntity {
#OneToMany(fetch = FetchType.LAZY)
#JoinTable(name="companies_comments",
joinColumns=#JoinColumn(name="company_id"),
inverseJoinColumns=#JoinColumn(name="comment_id"))
private Set<Comment> comments = new HashSet<>();
public void addComment(Comment comment) {
this.comments.add(comment);
}
public int totalComments() {
return this.comments.size();
}
}
You should drop the own method counter and create a specific (business) query to retrieve the size of the list, such as
public long getCommentsCount(Company c) {
String query = "SELECT COUNT(cm) FROM Company AS c JOIN c.comments AS cm WHERE c = :company";
return entityManager.createQuery(q, Long.class).setParameter("company", c).getSingleResult();
}
Some persistence provider may optimize performance when this kind of query is loaded as a #NamedQuery on entity, or when using CriteriaQuery API.
Depending on your database, you may need to change the return class to Number.class and convert to long.
If you want to tune even more your performance, use createNativeQuery method and write your own pure SQL, but keep in mind that changes on db schema requires to review theses queries.
I found the answer. If we don't adjust for getting collection size of entity hibernate loads every comment. We can solve this performance issue in two ways.
We can use #LazyCollection(LazyCollectionOption.EXTRA) like below. By LazyCollectionOption.EXTRA .size() and .contains() won't initialize the whole collection.
#OneToMany(fetch = FetchType.LAZY)
#LazyCollection(LazyCollectionOption.EXTRA)
#JoinTable(name="companies_comments",
joinColumns=#JoinColumn(name="company_id"),
inverseJoinColumns=#JoinColumn(name="comment_id"))
private Set<Comment> comments = new HashSet<>();
Or we can use #Formula annotation.
#Formula(SELECT COUNT(*) FROM companies_comments cc WHERE cc.company_id = id)
private int numberOfComments;
Edit after 8 months: For simplicity and performance perspective, we should create a JPA Query Method like below.
#Repository
public interface CommentRepository extends JpaRepository<Comment, Long> {
int countAllByCompany(Company company);
}
We should never use getComments().size() for this purpose, because this way all comments are loaded into memory and this may be cause performance issues.
It is also true when adding comments to the collection. We shouldn't use getComments().add(newComment). When we have OneToMany relation, all we have to do is set the company field of the comment like as newComment.setCompany(company), and perform the persist operation. Therefore, it is recommended to define OneToMany relationships bidirectional.

LazyInitializationException with graphql-spring

I am currently in the middle of migrating my REST-Server to GraphQL (at least partly). Most of the work is done, but i stumbled upon this problem which i seem to be unable to solve: OneToMany relationships in a graphql query, with FetchType.LAZY.
I am using:
https://github.com/graphql-java/graphql-spring-boot
and
https://github.com/graphql-java/graphql-java-tools for the integration.
Here is an example:
Entities:
#Entity
class Show {
private Long id;
private String name;
#OneToMany(mappedBy = "show")
private List<Competition> competition;
}
#Entity
class Competition {
private Long id;
private String name;
#ManyToOne(fetch = FetchType.LAZY)
private Show show;
}
Schema:
type Show {
id: ID!
name: String!
competitions: [Competition]
}
type Competition {
id: ID!
name: String
}
extend type Query {
shows : [Show]
}
Resolver:
#Component
public class ShowResolver implements GraphQLQueryResolver {
#Autowired
private ShowRepository showRepository;
public List<Show> getShows() {
return ((List<Show>)showRepository.findAll());
}
}
If i now query the endpoint with this (shorthand) query:
{
shows {
id
name
competitions {
id
}
}
}
i get:
org.hibernate.LazyInitializationException: failed to lazily initialize
a collection of role: Show.competitions, could not initialize proxy -
no Session
Now i know why this error happens and what it means, but i don't really know were to apply a fix for this. I don't want to make my entites to eagerly fetch all relations, because that would negate some of the advantages of GraphQL. Any ideas where i might need to look for a solution?
Thanks!
My prefered solution is to have the transaction open until the Servlet sends its response. With this small code change your LazyLoad will work right:
import javax.servlet.Filter;
import org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter;
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
/**
* Register the {#link OpenEntityManagerInViewFilter} so that the
* GraphQL-Servlet can handle lazy loads during execution.
*
* #return
*/
#Bean
public Filter OpenFilter() {
return new OpenEntityManagerInViewFilter();
}
}
I solved it and should have read the documentation of the graphql-java-tools library more carefully i suppose.
Beside the GraphQLQueryResolver which resolves the basic queries i also needed a GraphQLResolver<T> for my Showclass, which looks like this:
#Component
public class ShowResolver implements GraphQLResolver<Show> {
#Autowired
private CompetitionRepository competitionRepository;
public List<Competition> competitions(Show show) {
return ((List<Competition>)competitionRepository.findByShowId(show.getId()));
}
}
This tells the library how to resolve complex objects inside my Showclass and is only used if the initially query requests to include the Competitionobjects. Happy new Year!
EDIT 31.07.2019: I since stepped away from the solution below. Long running transactions are seldom a good idea and in this case it can cause problems once you scale your application. We started to implement DataLoaders to batch queries in an async matter. The long running transactions in combination with the async nature of the DataLoaders can lead to deadlocks: https://github.com/graphql-java-kickstart/graphql-java-tools/issues/58#issuecomment-398761715 (above and below for more information). I will not remove the solution below, because it might still be good starting point for smaller applications and/or applications which will not need any batched queries, but please keep this comment in mind when doing so.
EDIT: As requested here is another solution using a custom execution strategy. I am using graphql-spring-boot-starter and graphql-java-tools:
Create a Bean of type ExecutionStrategy that handles the transaction, like this:
#Service(GraphQLWebAutoConfiguration.QUERY_EXECUTION_STRATEGY)
public class AsyncTransactionalExecutionStrategy extends AsyncExecutionStrategy {
#Override
#Transactional
public CompletableFuture<ExecutionResult> execute(ExecutionContext executionContext, ExecutionStrategyParameters parameters) throws NonNullableFieldWasNullException {
return super.execute(executionContext, parameters);
}
}
This puts the whole execution of the query inside the same transaction. I don't know if this is the most optimal solution, and it also already has some drawbacks in regards to error handling, but you don't need to define a type resolver that way.
Notice that if this is the only ExecutionStrategy Bean present, this will also be used for mutations, contrary to what the Bean name might suggest. See https://github.com/graphql-java-kickstart/graphql-spring-boot/blob/v11.1.0/graphql-spring-boot-autoconfigure/src/main/java/graphql/kickstart/spring/web/boot/GraphQLWebAutoConfiguration.java#L161-L166 for reference. To avoid this define another ExecutionStrategy to be used for mutations:
#Bean(GraphQLWebAutoConfiguration.MUTATION_EXECUTION_STRATEGY)
public ExecutionStrategy queryExecutionStrategy() {
return new AsyncSerialExecutionStrategy();
}
For anyone confused about the accepted answer then you need to change the java entities to include a bidirectional relationship and ensure you use the helper methods to add a Competition otherwise its easy to forget to set the relationship up correctly.
#Entity
class Show {
private Long id;
private String name;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "show")
private List<Competition> competition;
public void addCompetition(Competition c) {
c.setShow(this);
competition.add(c);
}
}
#Entity
class Competition {
private Long id;
private String name;
#ManyToOne(fetch = FetchType.LAZY)
private Show show;
}
The general intuition behind the accepted answer is:
The graphql resolver ShowResolver will open a transaction to get the list of shows but then it will close the transaction once its done doing that.
Then the nested graphql query for competitions will attempt to call getCompetition() on each Show instance retrieved from the previous query which will throw a LazyInitializationException because the transaction has been closed.
{
shows {
id
name
competitions {
id
}
}
}
The accepted answer is essentially
bypassing retrieving the list of competitions through the OneToMany relationship and instead creates a new query in a new transaction which eliminates the problem.
Not sure if this is a hack but #Transactional on resolvers doesn't work for me although the logic of doing that does make some sense but I am clearly not understanding the root cause.
For me using AsyncTransactionalExecutionStrategy worked incorrectly with exceptions. E.g. lazy init or app-level exception triggered transaction to rollback-only status. Spring transaction mechanism then threw on rollback-only transaction at the boundary of strategy execute, causing HttpRequestHandlerImpl to return 400 empty response. See https://github.com/graphql-java-kickstart/graphql-java-servlet/issues/250 and https://github.com/graphql-java/graphql-java/issues/1652 for more details.
What worked for me was using Instrumentation to wrap the whole operation in a transaction: https://spectrum.chat/graphql/general/transactional-queries-with-spring~47749680-3bb7-4508-8935-1d20d04d0c6a
I am assuming that whenever you fetch an object of Show, you want all the associated Competition of the Show object.
By default the fetch type for all collections type in an entity is LAZY. You can specify the EAGER type to make sure hibernate fetches the collection.
In your Show class you can change the fetchType to EAGER.
#OneToMany(cascade=CascadeType.ALL,fetch=FetchType.EAGER)
private List<Competition> competition;
You just need to annotate your resolver classes with #Transactional. Then, entities returned from repositories will be able to lazily fetch data.

SpringBoot: Is this correct way to save a new entry which has ManyToOne relationship?

I have two entities Person and Visit
Person has OneToMany relationship with Visit.
I was wondering if I want to save an new entry of Visit, and interm of using RestController. Is my approach correct? Or is there another way which is more efficient?
So I have the following controller which takes a VisitModel from the RequestBody, is it a correct way to call it like so?
VisitModel has the ID of person, and the needed properties for the Visit entity. I use the ID of person to look up in the personRepository for the related Person entry, whereafter I issue it to a new instance of Visit and then use the visitRepository to save it.
#RequestMapping(value="", method=RequestMethod.POST)
public String checkIn(#RequestBody VisitModel visit) {
Person person = personRepository.findById(visit.personId);
Visit newVisit = new Visit(visit.getCheckIn, person);
visitRepository.save(newVisit);
return "success";
}
The Visit entity looks as following
#Entity
public class Visit {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
#JsonProperty("check_in")
private Date checkIn;
#JsonProperty("check_out")
private Date checkOut;
#ManyToOne
#JoinColumn(name="personId")
private Person person;
public Visit(Date checkIn, Person person) {
this.checkIn = checkIn;
this.person = person;
}
public Date getCheckIn() {
return checkIn;
}
public void setCheckIn(Date checkIn) {
this.checkIn = checkIn;
}
public Date getCheckOut() {
return checkOut;
}
public void setCheckOut(Date checkOut) {
this.checkOut = checkOut;
}
public Person getPerson() {
return person;
}
}
I want to know of the following approach is correct. Or is there another way which is better?
You don't need to get a Person from the database to associate it with a Visit, of course. Because of, you need to have only id of a Person to save it in the foreign key column personId.
If you use JPA EntityManager
Person person = entityManager.getReference(Person.class, visit.personId);
for Hibernate Session
Person person = session.load(Person.class, visit.personId);
This methods just create a proxy and don't do any database requests.
With Hibernate Session I used new Person(personId) as #MadhusudanaReddySunnapu suggested. Everything worked fine.
What is the difference between EntityManager.find() and EntityManger.getReference()?
Hibernate: Difference between session.get and session.load
Yes, that seems to me to be the standard way to map a bidirectional relationship. EDIT: The personId column points to the "id" field of the Person entity.Eg:
#Id
private Long id;
UPDATE: 1: The VisitModel is a 'DTO' or Data Transfer Object. Any separate package is fine. You could consider putting them into a separate jar, so that anyone using your API (with java) can use the jar to create the data before making the call. 2) The way you save it is fine as far as I can see.

Resources