Implementing pagination in MVC - Servlets + JSP - java

[MVC, Servlets + JSP, JPA, MySQL]
I am working on simple Blog application. I am using JPA to map entities to MySQL tables. Here is code excerpt from entities in question:
Entity Post:
#NamedQueries({
#NamedQuery(name = "getNewestPosts", query = "SELECT p FROM Post p ORDER BY p.date DESC"), // getting resultList ordered by date
#NamedQuery(name = "getMostVisitedPosts", query = "SELECT p FROM Post p ORDER BY p.visitors DESC") // ordered by most visited
})
#Entity
#Table(name = "post")
public class Post implements Serializable {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "post_id", unique = true, nullable = false)
private Integer id;
#Column(name = "post_title", length=300, unique = false, nullable = false)
private String title;
#Column(name = "post_date", unique = false, nullable = false)
private Date date;
#Column(name = "post_summary", length=1000, unique = false, nullable = true)
private String summary;
#Column(name = "post_content", length=50000, unique = false, nullable = false)
private String content;
#Column(name = "post_visitors", unique = false, nullable = false)
private Integer visitors;
#OneToMany(cascade = { ALL }, fetch = LAZY, mappedBy = "post")
private Set<Comment> comments = new HashSet<Comment>();
...
Entity Comment:
#Entity
#Table(name = "comment")
public class Comment implements Serializable {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "comment_id", unique = true, nullable = false)
private Integer id;
#Column(name = "comment_title", length=300, unique = false, nullable = false)
private String title;
#Column(name = "comment_date", unique = false, nullable = false)
private Date date;
#Column(name = "comment_content", length=600, unique = false, nullable = false)
private String content;
#ManyToOne
#JoinColumn (name = "post_id", referencedColumnName="post_id", nullable = false)
private Post post; ...
Blog home page should contain summaries of 10 newest posts. So, in PostDAO object I have defined next method (returns all posts from db ordered by date):
public List<Post> getNewestPosts(){
Query q = em.createNamedQuery("getNewestPosts");
List<Post> resultList = (List<Post>) q.getResultList();
if (resultList.isEmpty())
return null;
else
return resultList;
}
I would like to implement pagination in some simple way, probably passing certain request parameters and reading data in jsp using jstl (i'm not yet familiar with jquery). Now, how to approach to implementing pagination in MVC? Which parameters I need to be attaching to request? How should I approach to implementing page navigation links (previous, page numbers, next) in JSP?

I think you can use the setMaxResults and the setFirstResult methods on the namedQuery. Keep passing the First result as a function of the number of records to be displayed on the page and the page number.
If you use Spring MVC there is already a way to do it and you can take a look at the PageListHolder api documentation. I havent used this but i have stumbled upon the API.

Related

Circumventing Attribute Limitations of Many-to-Many Relations in JPA

I am using PostgreSQL 12.11, JPA 3.1.0, and Hibernate 5.6.10. This might become important because I am doing things that apparently do not work with JPA 2.0.
My goal is to add an attribute to a many-to-many relationship. I found this posting. #Mikko Maunu states that "There is no concept of having additional persistent attribute in relation in JPA (2.0)." To me, this sounds like what I want to do is not possible. However, the answer is rather old and might not be complete anymore.
Beside the time gap and the version gap, this is, in my opinion, a new question because I am doing something that is probably questionable and not part of the original thread.
What I did is this:
Create a #ManyToMany relationship in JPA and specify a #JoinTable.
Manually define an entity with identical table name to the table specified in 1. For this table, I chose a composite primary key using #IdClass. I also added my attribute.
Inside one of the n:m-connected entities, create a #OneToMany relationship to the connection-table-entity created in 2. However, I did not create a corresponding #ManyToOne relationship as that would have created an error.
As a result, I can access the original entities and their relation as many-to-many, but also the relation itself, which is not an entity in the original ERM, but it is for JPA. First tests show this seems to be working.
I am aware, however, that I basically access the same part of the persistence (the PostgreSQL database) through two different ways at the same time.
Now my questions are:
Is this a valid way to do it? Or will I get in bad trouble at one point?
Is there a situation where I will need to refresh to prevent trouble?
Is this something new in JPA > 2.0, or just an extension to the original answer?
This should help.
Here is how I do it:
#Entity
#Table(name = "person", schema = "crm")
public final class Person implements Serializable {
#Id
#Column(name = "id", unique = true, nullable = false, updatable = false, columnDefinition = "bigserial")
private Long id;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "person", orphanRemoval = true)
private Set<PersonEmail> emails = new HashSet<>();
}
#Entity
#Table(name = "email", schema = "crm")
public final class Email implements Serializable {
#Id
#Column(name = "id", unique = true, nullable = false, updatable = false, columnDefinition = "bigserial")
private Long id;
#Column(name = "email", nullable = false, length = 64, columnDefinition = "varchar(64)")
private String localPart;
#Column(name = "domain", nullable = false, length = 255, columnDefinition = "varchar(255)")
private String domain;
}
#Entity
#Table(name = "person_email", schema = "crm")
public final class PersonEmail implements Serializable {
#EmbeddedId
private PersonEmailId id;
// The mapped objects are fetched lazily.
// This is a choice.
#ToString.Exclude
#MapsId("personId")
#ManyToOne(fetch = FetchType.LAZY, optional = false)
private Person person;
#ToString.Exclude
#MapsId("emailId")
#ManyToOne(fetch = FetchType.LAZY, optional = false)
private Email email;
// Here's an extra column.
#Column(name = "type", nullable = false, columnDefinition = "email_type_t")
#Convert(converter = EmailType.EmailTypeConverter.class)
private EmailType type;
public final void setPerson(final Person person) {
this.person = person;
id.setPersonId(this.person.getId());
}
public final void setEmail(final Email email) {
this.email = email;
id.setEmailId(this.email.getId());
}
#Embeddable
public static final class PersonEmailId implements Serializable {
#Column(name = "person_id", nullable = false, insertable = false, updatable = false, columnDefinition = "bigint")
private Long personId;
#Column(name = "email_id", nullable = false, insertable = false, updatable = false, columnDefinition = "bigint")
private Long emailId;
}

How to add JPA specifications conditionally to query?

I am trying to write the following query outlined here on sqlfiddle in JPA. I first tried using the #query annotation with native = true and that does work, but my issue is that I want the query to be more dynamic, because it could be the case where I don't want to add the clause to filter by name or by account.
My entities look something like this:
#Entity
#Table(name = "INSTRUCTION")
public class Instruction {
#Id
#Column(name = "ID", nullable = false, unique = true)
public Long id;
#Column(name = "ACCOUNT", nullable = false)
public String account;
#Column(name = "NAME", nullable = false)
public String name;
#OneToMany(cascade = CascadeType.All, fetch = FetchType.EAGER)
#JoinColumn(name = "INSTRUCTION_ID", referenceColumnName = "ID")
#OrderBy("lastUpdated")
private List<Audit> auditItems = new ArrayList<>();
//Getters & Setters
}
.
#Entity
#Table(name = "AUDIT")
public class Audit {
#Id
#Column(name = "ID", nullable = false, unique = true)
public Long id;
#Column(name = "INSTRUCTION_STATUS", nullable = false)
public InstructionStatus status;
#Column(name = "LAST_UPDATED", nullable = false)
public LocalDateTime lastUpdated;
#Column(name = "LAST_UPDATED_BY", nullable = false)
public String lastUpdatedBy;
//Getters & Setters
}
I had looked into using specifications to do this, and I managed to break my query into different specifications like so:
private Specification<Instruction> hasAccount(String account) {
return (root, query, criteriaBuilder) -> criteriaBuilder.in(root.get("account")).value(account);
}
private Specification<Instruction> havingStatus(List<String> status) {
return (root, query, criteriaBuilder) -> {
List<Predicate> predicates = new ArrayList<>();
final Subquery<Audit> auditSubquery = query.subquery(Audit.class);
final Root<Audit> audit = auditSubquery.from(Audit.class);
//select instruction id from audit where status is not in {status}
auditSubquery.select(audit.get("instruction").get("id"));
auditSubquery.where(criteriaBuilder.trim(audit.get("status")).in(status).not());
//Select instruction from table where
predicates.add(root.get("id").in(auditSubquery).not());
return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
};
}
// Other specifications....
And these work fine when called like so:
final List<Instruction> instructions = this.instructionRepository.findAll(
where(havingStatus(statuses)
.and(hasAccount(account))));
But my goal is have it so that for example I could check if account == null then do not include the hasAccount specification, and so on for other fields that may be null. Is there a way I can do this?
This should do the trick.
Specification spec = where(null);
if (statuses != null) {
spec = spec.and(havingStatus(statuses))
}
if (account != null) {
spec = spec.and(hasAccount(account))
}
final List<Instruction> instructions = this.instructionRepository.findAll(spec);

Hibernate CriteriaQuery How to Find list of integer having two conditions

I got two entity class one:
#Table(name = "package")
public class Package {
#Id
#Column(name = "package_id", insertable = false, nullable = false)
private Long packageId;
#Column(name = "timestamp", nullable = false, updatable = false)
#Temporal(TemporalType.TIMESTAMP)
private Date timestamp;
#Column(name = "name", nullable = false)
private String name;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "queue_id",foreignKey=#ForeignKey(name = "package_queue_id_fk"))
private Queue Queue;
#Column(name = "file_number", nullable = true)
private Integer fileNumber;
And
#Table(name = "queue")
public class Queue {
#Id
#Column(name = "queue_id", insertable = false, nullable = false)
private Integer queue;
#Column(name = "description", nullable = true)
private String description;
#Column(name = "name", nullable = false)
private String name;
#OneToMany(mappedBy = "Queue", fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.REFRESH })
#MapKeyColumn(name = "package_id")
private Set<Package> packages;
And I would like to find List of fileNumbers depending on package.name and package.queue.queue_id
So currently I got only one condition (name) and it looks like this:
public List<Integer> getAllFileNumbers(String fileName, Integer queueId) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Integer> query = cb.createQuery(Integer.class);
Root<Package> package = query.from(package.class);
query.select(package.get("fileNumber")).where(cb.equal(package.get("name"), fileName));
return em.createQuery(query).getResultList();
}
Anyone could help me add another one, on top of thet remamber that the value need to be from another entity.
Edit:
So after #Leviand hint I did it like this:
Predicate filenamePred= cb.equal(package.get("name"), fileName);
Predicate queueIdPred = cb.equal(package.get("queue_id"), queueId);
query.select(package.get("fileNumber")).where(cb.and(filenamePred, queueIdPred ));
I got error:
Caused by: java.lang.IllegalArgumentException: Unable to locate Attribute with the the given name [queue_id] on this ManagedType
You need to add the two predicates ( you can ofc write it all inline, I'm splitting so it's clearer) in a or condition, for example:
Predicate filename= cb.equal(package.get("name"), fileName);
Predicate queueId = cb.equal(package.get("queue"), queueId);
//then use them in a or condition
query.select(root).where(cb.and(filename, queueId ));
queue_id is the name of the column. You have to use the name of the field, which is Queue, and you have to get it's id field (queue) to compare.
This can be made easier and more type-safe if you use the metamodel-generator.

Mapping three identical tables in one entity

i'm needing your help.
i have 3 tables on my database running on postgresql. they are exactly the same structure. so i thought if i could mapp them in one entity.
i try this:
#Entity
#Table(name = "stock_tierra")
#SecondaryTables({
#SecondaryTable(name = "stock_bebelandia"),
#SecondaryTable(name = "stock_libertador")
})
public class Stock implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id_stock", unique = true, nullable = false)
private int idStock;
#Column(name = "cantidad", nullable = false)
private int cantidad;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "id_producto", nullable = false)
private Producto idProducto;
#Column(name = "estado", nullable = false)
private boolean estado;
#Column(name = "usuario_creacion", nullable = false)
private int usuarioCreacion;
#Column(name = "usuario_modificacion")
private Integer usuarioModificacion;
#Temporal(TemporalType.DATE)
#Column(name = "fecha_creacion", nullable = false, length = 13)
private Date fechaCreacion;
#Temporal(TemporalType.DATE)
#Column(name = "fecha_modificacion", length = 13)
private Date fechaModificacion;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "id_sucursal", nullable = false)
private Sucursal idSucursal;
but when i try to look in to one table i only get data from the first stock_tierra
String stockSucursal = null;
switch (sucursal) {
case 1:
stockSucursal = "stock_tierra";
break;
case 2:
stockSucursal = "stock_bebelandia";
break;
case 3:
stockSucursal = "stock_libertador";
break;
}
Criteria criteria = getSession().createCriteria(Stock.class, stockSucursal);
criteria.add(Restrictions.eq("estado", true));
criteria.addOrder(Order.asc("idStock"));
List<Stock> list = criteria.list();
return list;
some idea what i'm doing wrong?
#SecondaryTables is used to denote one entity being spread over multiple tables in terms of columns, not as a union.
The only thing I can think of right now is using a view which does a union over all the tables, but I am not sure whether postgres can handle writable views, or how you declare them (if you even need a write interface.)

Hibernate One To Many criteria doesn't work

I have the following entities:
#Entity
#Table(name = "author")
public class Author implements Serializable {
private static final long serialVersionUID = 12345L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "author_id")
private int authorId;
#Column(name = "author_bio")
private String authorBio;
#Column(name = "author_email")
private String authorEmail;
#Column(name = "author_favourite_section")
private String authorFavouriteSection;
#Column(name = "author_password")
private String authorPassword;
#Column(name = "author_username")
private String authorUsername;
#OneToOne(mappedBy = "author", fetch = FetchType.LAZY)
private Blog blog;
#OneToMany(mappedBy = "author", fetch = FetchType.LAZY)
private List<Post> posts;
// getters and setters
#Entity
#Table(name = "blog")
public class Blog implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "blog_id")
private int blogId;
#Column(name = "blog_title")
private String blogTitle;
#OneToOne(optional = false, fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "blog_author_id", unique = true)
private Author author;
#OneToMany(mappedBy = "blog", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<Post> posts;
// getters and setters
#Entity
#Table(name = "post")
public class Post implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "post_id")
private int postId;
#Column(name = "post_subject")
private String postSubject;
#Column(name = "post_body")
private String postBody;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "blog_id")
private Blog blog;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "post_author_id")
private Author author;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "post_tag", joinColumns = {
#JoinColumn(name = "post_id", nullable = false, updatable = false)},
inverseJoinColumns = {#JoinColumn(name = "tag_id",
nullable = false, updatable = false)})
private Set<Tag> tags = new HashSet<Tag>();
// getters and setters
#Entity
#Table(name = "tag")
public class Tag implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "tag_id")
private int tagId;
#Column(name = "tag_name")
private String tagName;
#ManyToMany(mappedBy = "tags", fetch = FetchType.LAZY)
private Set<Post> posts = new HashSet<Post>();
// getters and setters
The following data is presented in db:
author-blog-post-tag-AND-post_tag-tables
THE MAIN GOAL TO ACHIEVE IS: Find all authors which have written posts that contain appropriate tags.
I can do it using a SQL query:
SELECT a.author_id, a.author_bio, p.post_id, p.post_subject, t.tag_id, t.tag_name from author a
join blog b
on a.author_id = b.blog_author_id
join post p
on p.post_author_id = a.author_id
join post_tag pt
on p.post_id = pt.post_id
join tag t
on t.tag_id = pt.tag_id
where t.tag_name in ('Football', 'Basketball')
And the correct result is returned with author, filtered posts and tags.
But I need to do it using hibernate.
So using hibernate I want to find all authors which have written posts that contain appropriate tags.
And all those authors with ONLY those posts which contain indicated tags (see above - 'Football', 'Basketball') have to be returned.
I wrote this code:
final DetachedCriteria authorCriteria = DetachedCriteria.forClass(Author.class, "author");
authorCriteria.createAlias("author.posts", "post");
authorCriteria.createAlias("post.tags", "tag");
Criterion football = Restrictions.eq("tag.tagName", "Football");
Criterion basketball = Restrictions.eq("tag.tagName", "Basketball");
authorCriteria.add(Restrictions.or(football, basketball));
authorCriteria
.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
final List<Author> result = (List<Author>)getConfiguredHibernateTemplate().findByCriteria(authorCriteria);
and I expected to receive:
Author(author_id = 54) with only one Post (post_id = 26) and this post containing two tags ('Football' and 'Basketball') as I received it using above SQL query.
But the actual result is that I receive Author(author_id = 54) with ALL HIS POSTS PRESENTED IN DB (ERROR AND PROBLEM HERE !!!) and each post contains all tags which also presented in db.
intellij-idea-debug-result
Hibernate generated the following queries:
select this_.author_id as author_i1_0_2_, this_.author_bio as author_b2_0_2_, this_.author_email as author_e3_0_2_, this_.author_favourite_section as author_f4_0_2_, this_.author_password as author_p5_0_2_, this_.author_username as author_u6_0_2_, post1_.post_id as post_id1_2_0_, post1_.post_author_id as post_aut4_2_0_, post1_.blog_id as blog_id5_2_0_, post1_.post_body as post_bod2_2_0_, post1_.post_subject as post_sub3_2_0_, tags5_.post_id as post_id1_2_, tag2_.tag_id as tag_id2_3_, tag2_.tag_id as tag_id1_4_1_, tag2_.tag_name as tag_name2_4_1_ from author this_ inner join post post1_ on this_.author_id=post1_.post_author_id inner join post_tag tags5_ on post1_.post_id=tags5_.post_id inner join tag tag2_ on tags5_.tag_id=tag2_.tag_id where (tag2_.tag_name=? or tag2_.tag_name=?)
select blog0_.blog_id as blog_id1_1_0_, blog0_.blog_author_id as blog_aut3_1_0_, blog0_.blog_title as blog_tit2_1_0_ from blog blog0_ where blog0_.blog_author_id=?
select posts0_.post_author_id as post_aut4_0_0_, posts0_.post_id as post_id1_2_0_, posts0_.post_id as post_id1_2_1_, posts0_.post_author_id as post_aut4_2_1_, posts0_.blog_id as blog_id5_2_1_, posts0_.post_body as post_bod2_2_1_, posts0_.post_subject as post_sub3_2_1_ from post posts0_ where posts0_.post_author_id=?
How do I achieve the expected and correctly filtered result using hibernate?
You asked for an author which writes a blog about Football or BaketBall:
DetachedCriteria.forClass(Author.class, "author");
It happens that this author also wrote blogs about something else. So you get what you've asked for. In your sql statement you ask for a projection whereas with hibernate you ask the ORM to get the object (author) with its posts collection.
I tried using projection ( authorCriteria.setResultTransformer(CriteriaSpecification.PROJECTION )
final DetachedCriteria authorCriteria = DetachedCriteria.forClass(Author.class, "author");
authorCriteria.createAlias("author.posts", "post");
authorCriteria.createAlias("post.tags", "tag");
final Criterion football = Restrictions.eq("tag.tagName", "Football");
final Criterion basketball = Restrictions.eq("tag.tagName", "Basketball");
authorCriteria.add(Restrictions.or(football, basketball));
authorCriteria.setResultTransformer(CriteriaSpecification.PROJECTION);
final List<Author> result = (List<Author>) getConfiguredHibernateTemplate().findByCriteria(authorCriteria);
and the following result I see in debugger:
Ok that is correct and I can analyze those data and sorted them to Author->List of Posts -> with Tags to specific Post. But this is extra work in code.
I assume maybe hibernate has more elegant way to return filtered data I need or NOT. If no way then I became disappointed of hibernate. Then it is more convenient to use some spring jdbc template or mybatis or something like that.
Using approach "You could then select Post as your root entity" involves extra queries to db. Why we need to do so extra work ? Looks like hibernate is not flexible and useful suffice if it can not run query which I want and return results in way I prefer.

Categories