How to prevent sorting on a given column? - spring-data-jpa

My repository allows for sorting on any column when retrieving a list of users:
public interface UserRepository extends JpaRepository<User, Long>, UserRepositoryCustom {
#Query("SELECT u FROM User u")
public Page<User> all(Pageable page);
}
The trouble is that a user has some properties that cannot offer sorting, like his confirmedEmail property.
#Entity
#Table(name = "user_account")
#SequenceGenerator(name = "id_generator", sequenceName = "user_account_id_seq", allocationSize = 10)
public class User extends AbstractEntity {
#Column(nullable = false)
private String firstname;
#Column(nullable = false)
private String lastname;
#Column(nullable = false, unique = true)
private EmailAddress email;
#Column(nullable = false)
private boolean confirmedEmail;
}
How can I prevent the Pageable argument from sorting on this boolean confirmedEmail property ?
I stumbled upon this issue when I sorted by clicking on the Confirmed header in my Angular data table.
This front-end event triggered the following request:
SELECT u FROM com.thalasoft.user.data.jpa.domain.User u order by u.confirmed asc
I know I can make this data table column not sortable, and I did. But I'd like to also have a server side safety in place.
As a side note, I wonder if I could also provide a default sorting if none is specified in the client request.
UPDATE: I created the utility method:
public static final Sort stripColumnsFromSorting(Sort sort, Set<String> nonSortableColumns) {
return Sort.by(sort.stream().filter(order -> {
return !nonSortableColumns.contains(order.getProperty());
}).collect(Collectors.toList()));
}
which I call like:
Set<String> nonSortableColumns = new HashSet<String>(Arrays.asList("id", "confirmedEmail"));
public ResponseEntity<PagedResources<UserResource>> all(#PageableDefault(sort = { "lastname", "firstname" }, direction = Sort.Direction.ASC) Pageable pageable, Sort sort,
PagedResourcesAssembler<User> pagedResourcesAssembler, UriComponentsBuilder builder) {
sort = CommonUtils.stripColumnsFromSorting(sort, nonSortableColumns);
userService.addSortToPageable(pageable, sort);
But it is still invoking the sorting on the non sortable column:
Invoking 'com.thalasoft.user.rest.controller.UserController.all' with arguments [Page request [number: 0, size 5, sort: confirmedEmail: DESC], confirmedEmail: DESC, org.springframework.data.web.MethodParameterAwarePagedResourcesAssembler#78817e7e, org.springframework.web.servlet.support.ServletUriComponentsBuilder#3e49647a]

I think you can check Pageable argument of your controller method and then remove from it unnecessary fields, something like this:
public ResponseEntity<?> myControllerMethod(..., Pageable page) {
Sort newSort = Sort.by(page.getSort()
.get()
.filter(order -> !order.getProperty().equals("confirmedEmail"))
.collect(Collectors.toList()));
PageRequest newPage = PageRequest.of(page.getPageNumber(), page.getPageSize(), newSort);
// using newPage instead of page...
}
To specify a default order you can use #PageableDefault annotation, for example:
public ResponseEntity<?> myControllerMethod(..., #PageableDefault(sort = "lastname", direction = ASC) Pageable page) {
//...
}

Related

Spring Data Specification orderBy subquery

On my MySql project I got this particular model with 3 entities: Prodotto with many childs QuotaIngrediente, that in turn is Many-to-One child of Ingrediente too. All my relationships are bi-directional.
All of them got an autogenerated integer Id and other fields removed to focus on the interesting ones.
#Entity
public class Prodotto {
private List<QuotaIngrediente> listaQuoteIng = new ArrayList<QuotaIngrediente>();
#OneToMany(mappedBy = "prodotto", cascade = CascadeType.ALL, orphanRemoval = true)
public List<QuotaIngrediente> getListaQuoteIng() {
return listaQuoteIng;
}
#Entity
public class QuotaIngrediente{
private Prodotto prodotto;
private Ingrediente ing;
private Double perc_ing;
#ManyToOne
#JoinColumn(name = "prodotto")
public Prodotto getProdotto() {
return prodotto;
}
#ManyToOne
#JoinColumn(name = "ing")
public Ingrediente getIng() {
return ing;
}
#Entity
public class Ingrediente {
private Set<QuotaIngrediente> quoteIng = new HashSet<QuotaIngrediente>();
#OneToMany(mappedBy = "ing", cascade = CascadeType.ALL, orphanRemoval = true)
public Set<QuotaIngrediente> getQuoteIng() {
return quoteIng;
}
I'm using SpringData Specification and I can build a query to get Prodotto based on Ingrediente criteria, this way:
public static Specification<Prodotto> getProdottoByIngSpec (String ing) {
if (ing != null) {
return (root, query, criteriaBuilder) -> {
query.groupBy(root.get(Prodotto_.id));
return criteriaBuilder.like(((root.join(Prodotto_.listaQuoteIng))
.join(QuotaIngrediente_.ing))
.get(Ingrediente_.nome), "%"+ing+"%");
};
It works as expected, but now I want to sort it by the QuotaIngrediente perc_ing field OF THAT SPECIFIC INGREDIENTE.
Obviously I'm asking how to do it on DB, not in business logic.
I was struggling with a false problem due to a wrong assumption of mine. Solution was the simplest. Just sort by orderBy CriteriaQuery method. The query I used to search already filtered the QuotaIngrediente returning just the lines that match my search criteria. Then this is the only line I had to add to my Specification:
query.orderBy(builder.desc((root.join(Prodotto_.listaQuoteIng))
.get(QuotaIngrediente_.perc_ing)));

Spring JPA query using specification and projection

I used spring jpa specification to build dynamically an entity query.
It's working perfect but the query returns all entity fields which makes the performance slower.
I want to fetch specific entity fields only and not fetching all entity fields and dependencies which I don't want and I will not use.
I search on the web, I tried some scenarios but without any lack.
Can anyone suggest any solution on this?
Thanks in advance
Here is what I have.I'm using spring boot 2.2.4
public class Concert {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String name;
#Column
private String code;
#Column
private double totalIncome;
#Column
private double totalExpenses;
#Column
private double totalBudget;
#ManyToOne(targetEntity = Orchestra.class, fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "orchestra_id")
private Orchestra orchestra;
#ManyToOne(targetEntity = ConcertStatus.class, fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "concert_status_id")
private ConcertStatus status;
/* other fields */
}
Specification:
public class ConcertSpecification implements Specification<Concert> {
#Override
public Predicate toPredicate(Root<Concert> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
List<Predicate> predicates = new ArrayList<>();
//add add criteria to predicates
for (Criterion criteria : criteriaList) {
/* predicates builder here */
}
return builder.and(predicates.toArray(new Predicate[0]));
}
}
Repository:
public interface ConcertDao extends JpaRepository<Concert, Long>, JpaSpecificationExecutor<Concert>, PagingAndSortingRepository<Concert, Long> { }
ConcertService:
public interface ConcertService {
Page<Concert> findAll(#Nullable Specification<Concert> spec, Pageable pageable);
}
ConcertServiceImpl:
#Service(value = "concertService")
public class ConcertServiceImpl implements ConcertService {
public Page<Concert> findAll(#Nullable Specification<Concert> spec, Pageable pageable){
List<Concert> list = new ArrayList<>();
concertDao.findAll(spec).iterator().forEachRemaining(list::add);
return new PageImpl<Concert>(list);
}
}
Usage of projections with specifications are not supported and there is a PR for it that has been hanging for over five years.

How to filter a OneToMany field using Spring Data JPA?

I'm trying to filter out posts associated with a category depending on whether the post is set as hidden or not.
I can do this with a post-query filter just fine (see below) but I was wondering if it's possible to construct the query using the JPA methods? (Specifically the query building methods like FindAllBy..., I'm hoping to keep database agnostic by sticking to these types of queries)
I could also probably call FindAllByCategory on the PostRepository and construct the return that way but it feels hacky and backwards.
So to summarize I'd like to find a way to declare FindAllAndFilterPostsByIsHidden(boolean isHidden)
Category Class
#Entity
public class Category {
public Category(String name, Post... posts) {
this.name = name;
this.posts = Stream.of(posts)
.collect(Collectors.toSet());
this.posts.forEach(post -> post.setCategory(this));
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
#OneToMany(mappedBy = "category")
private Set<Post> posts;
}
Post Class (stripped to basics for brevity )
#Entity
public class Post {
public Post(Category category, boolean isHidden) {
this.category = category;
this.isHidden = isHidden
}
#ManyToOne
#JoinColumn
private Category category;
private boolean isHidden;
}
Right now I'm doing this to filter the posts associated with categories in the CategoryController
#GetMapping
public List<Category> list(Authentication authentication) {
boolean canViewHidden = securityService.hasAuthority(Permissions.Post.VIEWHIDDEN, authentication.getAuthorities());
List<Category> categories = categoryRepository.findAll();
categories.forEach(
category -> {
Set<Post> filteredPosts = category.getPosts().stream()
.filter(post -> canViewHidden || !post.isHidden())
.collect(Collectors.toSet());
category.setPosts(filteredPosts);
}
);
return categories;
}
I'd try using a custom query in your JPA-Repository for the Post Class like this:
#Query(value = "SELECT p FROM Post p INNER JOIN Category c ON p.id = c.post.id "
+ "WHERE p.hidden = false AND c.id = :id")
List<Post> findViewablePostsByCategory(#Param("id") Long categoryId);
I know this might not be the exact approach you were looking for but as K.Nicholas pointed out there is no way to use joins with the query building methods of JPA-Repositories.

Exception when selecting specific columns using Hibernate and Spring Data JPA

I have a table that has a bytea column (named 'pdf') and I don't want to always select it, specially when I'm returning a list from the database, due to performance issues.
I use native queries with spring data inside the repository to solve these types of situations before (when I used eclipselink), but with Hibernate, if I don't write all the columns in the query, it throws an exception.
For test purposes, I'm trying to select only the id from the User and I still get the exception.
Example: "SELET user.id FROM user WHERE user.id = '1'"
It throws an exception saying that it did not find name in the ResultSet, if I put name in the SQL, it then says age was not found and so on, until I have to write all the columns in the SQL.
Thanks in advance for any help.
What I have tried already:
Updating/Downgrading Hibernate and Spring Data with no luck.
Creating a new entity with only the columns I need, works, but it's a messy solution for me.
Maybe the problem is the combination of the frameworks I use and the way I use them, if someone wants, I could try to upload my whole project structure.
My code:
Entity
#Entity
#Table(name = "user", schema = "portal")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id")
private Integer id;
#Column(name = "pdf")
private byte[] pdf;
#Column(name = "name")
private String name;
#Column(name = "age")
private Integer age;
public User() {
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public byte[] getPdf() {
return pdf;
}
public void setPdf(byte[] pdf) {
this.pdf = pdf;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
#Override
public int hashCode() {
int hash = 0;
hash += (id != null ? id.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof Anexo)) {
return false;
}
Anexo other = (Anexo) object;
if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
return false;
}
return true;
}
#Override
public String toString() {
return "br.gov.to.secad.portal.domain.User[ id=" + id + " ]";
}
}
Service
#Service
#Transactional(readOnly = true)
public class UserService implements Serializable {
private static final long serialVersionUID = 1L;
#Autowired
private IUserRepository userRepository;
public UserService() {
}
public User findOne() {
return userRepository.findOneSQL();
}
}
Repository
public interface IUserRepository extends JpaRepository<User, Serializable>, JpaSpecificationExecutor {
#Query(value = "SELECT user.id FROM user WHERE user.id = '1'", nativeQuery = true)
public User findOneSQL();
}
The exception:
org.postgresql.util.PSQLException: The column name name was not found in this ResultSet.
Solution
The solution is using an array of Object when I want to select anything less than what I've mapped on my Entity class, thats the limitation of Hibernate that I now understand.
So basically, the method will return Object[] and then I can iterate each position and instantiate a new entity of User with these values.
Example:
#Query(value = "SELECT user.id FROM user WHERE user.id = '1'", nativeQuery = true)
public Object[] findOneSQL();
I have faced the same problem, I know it is late but well there is a solution that I found elegant.
By the Spring documentation you can declare an interface and from here take the fields you want, in my case it has been something similar to this.
The interface to minimize the fields:
public interface CountryMinify {
String getName();
String getNameTranslation();
}
And my JpaRepository
public interface PlanetRepository extends JpaRepository<Planet, Long> {
#Query(value = "select p.name_country as name, p.name_country_translation as nameTranslation from vm_planet p where gid = ?1", nativeQuery = true)
CountryMinify findByCode(String codeCountry);
}
Keep in mind that the columns should be called the same as gos getter. For example: column name_country -> AS name and the getter of the interface is getName()
Try this
#Query(value = "SELECT user.id FROM user WHERE user.id = '1'", nativeQuery = true)
Integer findOneSQL();
Call the method like so
Integer user = userRepository.findOneSQL();
Edit 1 :
Since you are using native query you wont be able to use Projections which is a great way of accessing only certain entity fields. There is a JIRA ticket which is still under investigation.
Solution
Return List from your repository like so
#Query(value = "SELECT user.id, user.name FROM user WHERE user.id = '1'", nativeQuery = true)
List<Object[]> findOneSQL();
Iterate over the list of Objects and get your specific columns.
List<Object[]> userNative = userRepository.findOneSQL();
for (Object[] obj : userNative) {
System.out.println("User id : " + obj[0]);
System.out.println("User Name : " + obj[1]);
}

Hibernate Envers - custom RevisionEntity - how to get record

I have written my custom RevisionEntity class to store additional data (for example username), like below:
#Entity
#RevisionEntity(AuditListener.class)
#Table(name = "REVINFO", schema = "history")
#AttributeOverrides({
#AttributeOverride(name = "timestamp", column = #Column(name = "REVTSTMP")),
#AttributeOverride(name = "id", column = #Column(name = "REV")) })
public class AuditEntity extends DefaultRevisionEntity {
private static final long serialVersionUID = -6578236495291540666L;
#Column(name = "USER_ID", nullable = false)
private Long userId;
#Column(name = "USER_NAME")
private String username;
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
I can see that all rows in database are correctly stored, REVINFO table contains also username.
I would like to query database to get detailed information from my custom RevisionEntity, like username.
How can I do it? Is there any supported API to get it?
Lets assume you know the identifier of the entity you're interested in the revision entity metadata for, you can easily query that information using the following approach:
final AuditReader auditReader = AuditReaderFactory.get( session );
List<?> results = auditReader.createQuery()
.forRevisionsOfEntity( YourEntityClass.class, false, false )
.add( AuditEntity.id().eq( yourEntityClassId ) )
.getResultList();
The returned results will contain an Object array, e.g. Object[] where results[1] will hold the revision entity instance which contains the pertinent information your wanting.
For more details, you can see the java documentation comments here
If you only have the revision number, you can access just the revision entity instance directly by:
// I use YourAuditEntity here because AuditEntity is actually an Envers class
YourAuditEntity auditEntity = auditReader
.findRevision( YourAuditEntity.class, revisionId );
For more details on the AuditReader interface, you can see the java documentation here