combining #NamedQuery from JPA and #Filter from Hibernate - spring

I have #NamedQuery. I want to add a lot of different filters to the query as per condition at runtime. There is another concept of #Filter in Hibernate. can this concept be a merge to have a combined result?
suppose I have
#NamedQuery(name="Users.someUsers",query="select u from Users where firstname='bob'")
suppose I want to filter the result according to some other parameter.
can I add #Filter that can do the trick?
supposed I want to an add age filer or a place filter over the existing Users.someUsers by enabling the corresponding filter on the underlying hibernate session?

I suppose you want to define named queries and filters at entity level and expect named queries to have filters which you defined.
I wrote a test for it:
#Entity
#Table(name = "DEPARTMENT")
#NamedQueries({#NamedQuery(name=DepartmentEntity.GET_DEPARTMENT_BY_ID, query=DepartmentEntity.GET_DEPARTMENT_BY_ID_QUERY),})
#FilterDef(name="deptFilter", parameters={#ParamDef( name="name", type="string")})
#Filters( {#Filter(name="deptFilter", condition=":name = name")})
public class DepartmentEntity implements Serializable {
static final String GET_DEPARTMENT_BY_ID_QUERY = "from DepartmentEntity d where d.id = :id";
public static final String GET_DEPARTMENT_BY_ID = "GET_DEPARTMENT_BY_ID";
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID", unique = true, nullable = false)
private Integer id;
#Column(name = "NAME", unique = true, nullable = false, length = 100)
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Now you can use both like this:
Session session = (Session) entityManager.getDelegate();
Filter filter = session.enableFilter("deptFilter");
filter.setParameter("name", name);
return (DepartmentEntity) session.getNamedQuery(DepartmentEntity.GET_DEPARTMENT_BY_ID)
.setParameter("id", id)
.uniqueResult();
Query generated by hibernate:
select department0_.ID as ID1_3_, department0_.NAME as NAME2_3_ from DEPARTMENT department0_ where ? = department0_.name and department0_.ID=?
You will need to add filters to session and then create named query. If this doesn't cover your use case then post example exactly what you want to acheive.

This is what CriteriaQuery is built for. It allows you to create queries at runtime.
Avoid using named queries for queries which you want to build at runtime based on user input.
Example
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<EntityType> criteria = builder.createQuery(EntityType.class);
Root<EntityType> root = criteria.from(EntityType.class);
criteria.where(
builder.equal(root.get("owner"), "something")
);
// Any conditions can be added to criteria here ar runtime based on user input
List<Topic> topics = entityManager
.createQuery(criteria)
.getResultList();
Named queries are precompiled at EntityManagerFactory startup, so they add performance benefits as long as queries are static, but for dynamic queries consider using CriteriaQueries

Related

Query syntax in URL when using Spring and #QuerydslPredicate

How can I write the HTTP request URL in order to get a query similar to:
select *
from incidents i,
jira_issues ji
where i.incident_id = ji.incident_id
and ji.external_jira_issue_id = 'ABC-123'
and ji.jira_server_id = '1'
I have the following classes:
#Entity(name = "incidents")
public class IncidentEntity {
#OneToMany(
mappedBy = "incident",
cascade = CascadeType.ALL
)
#LazyCollection(LazyCollectionOption.FALSE)
private List<JiraIssueEntity> jiraIssues;
...
}
#Entity(name = "jira_issues")
public class JiraIssueEntity {
#EmbeddedId
#EqualsAndHashCode.Include
private JiraIssueId id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "incident_id")
#ToString.Exclude
private IncidentEntity incident;
...
}
#Embeddable
public class JiraIssueId implements Serializable {
#EqualsAndHashCode.Include
private String externalJiraIssueId;
#EqualsAndHashCode.Include
private String jiraServerId;
}
This is my API method signature:
#GetMapping("")
public Page<Incident> listIncidents(
#QuerydslPredicate(root = IncidentEntity.class) Predicate predicate
);
I know that I can send something like:
/incidents/?jiraIssues.id.externalJiraIssueId=ABC-123&jiraIssues.id.jiraServerId=1"
This translates to the following query:
select *
from incidents incidenten0_
where (exists(select 1
from jira_issues jiraissues1_
where incidenten0_.incident_id = jiraissues1_.incident_id
and (lower(jiraissues1_.external_jira_issue_id) like ? escape '!')))
and (exists(select 1
from jira_issues jiraissues2_
where incidenten0_.incident_id = jiraissues2_.incident_id
and (lower(jiraissues2_.jira_server_id) like ? escape '!')))
which is not so good.
I don't know how to:
Do equals and not contains (rows with externalJiraIssueId=ABC-1234 will return as well but I don't want that).
Check that same JiraIssue has externalJiraIssueId=ABC-123 and jiraIssues.id.jiraServerId=1 and not different JiraIssues that each matches one (something like jiraIssues.id=(ABC-123, 1)
Thank you.
regarding the first problem you can make your 'repository' interface extend QuerydslPredicateExecutor and QuerydslBinderCustomizer
then you can override the 'customize' method with something like this:
#Override
default void customize(QuerydslBindings bindings, #NotNull QIncidentEntity root)
{
bindings.bind(String.class)
.first((SingleValueBinding<StringPath, String>)
StringExpression::equalsIgnoreCase);
}
this will make the query check for equals (ignoring the case) and not contains.

i wish to find a unique record which matches mutiple column values supplied at once

i have a spring application where i wish to find a unique record which matches mutiple column values supplied at once. How should i write my own custom method for it in an interface implementing CrudRepository
below is the model and the interface
#Entity
#Table(name = "tenant_subscriptions")
public class TenantSubscriptions {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "userId")
private Long userId;
#Column(name = "service_id")
private Long serviceId;
#Column(name = "feature_id")
private Long featureId;
#Column(name = "subfeature_id")
private Long subfeatureId;
#Column(name = "status")
private String Status;
#Column(name = "subscription_id")
private String SubscriptionId;
public interface TenantSubscriptionsRepository extends CrudRepository<TenantSubscriptions, Long> {
}
You don't need to write your own query if it's not something super complex.
For matching multiple column values in the same table you can use query from method name.
There is two way according to documentation and Query creation:
By deriving the query from the method name directly.
By using a manually defined query.
TenantSubscriptions findByUserIdAndServiceIdAndFeatureId(Long userId, Long serviceId, Long featureId); //Hibernate will recognize your DB object and this will work (no extra thing needs to be done)
Query:
#Query(value = "SELECT u FROM User u WHERE u.status = 'ACTIVE' AND u.creationDate <= current_date()")
List<User> findUserCandidates();
Inner join query:
#Query(value = "SELECT DISTINCT u FROM User u INNER JOIN UserAccount ua ON u.id = ua.userId WHERE ua.status = 'ACTIVE' AND ua.companyId = :companyId")
List<Bank> findBanksByCompany(Integer companyId);
You can find an entry by multiple attributes by chaining them in the interface method name. Also, Spring Data also inspects the return type of your method.
Example:
TenantSubscriptions findOneByServiceIdAndFeatureId(Long serviceId, Long featureId);
This will return the one entry that matches both attributes.
See also this answer and the Spring Data Reference Guide.

Fetch List Using DTO projections using a Constructor Expression and JPQL

Perform a search on DisabScreenRequest and fetch its child details also. Using DTO projections using a Constructor Expression and JPQL.
The parent entity with a child table.
#Entity
#Table(name = "SCREEN_REQUEST")
public class DisabScreenRequest implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private long requestId;
#Column(name = "CIVILID")
private Long civilId;
#ManyToMany()
#JoinTable(name = "_DISAB_SCREEN_REQ_DETAILS", joinColumns = {
#JoinColumn(name = "REQUEST_ID") }, inverseJoinColumns = { #JoinColumn(name = "DISABILTY_TYPE_ID") })
private Set<DisabMaster> disabilities = new HashSet<DisabMaster>();
public DisabScreenRequest() {
}
}
This is the disability table.
#Entity
#Table(name="DISAB_MASTER")
#Immutable
public class DisabMaster implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name="DIS_TYPE_ID")
private long disabilityTypeId;
#Column(name="DIS_TYPE_DESC")
private String disTypeDesc;
public DisabMaster() {
super();
}
}
Had to fetch all the requests along with the disability for each request.
Search DTO(using this I had other joins to add other than one mentioned here).
public class RequestSearchDto {
private long requestId;
private Long civilId;
private Set<DisabMaster> disabilities;
public RequestSearchDto() {
super();
}
public RequestSearchDto(long requestId, Long civilId) {
super();
this.requestId = requestId;
this.civilId = civilId;
}
public RequestSearchDto(long requestId, Long civilId, Set<DisabMaster> disabilities) {
super();
this.requestId = requestId;
this.civilId = civilId;
this.disabilities = disabilities;
}
}
This is my JPQL query
public interface ReposJPQL {
public String GET__REQUEST = "SELECT DISTINCT new org.test.RequestSearchDto "
+ "(dsr.requestId, dsr.civilId, dsr.disabilities)"
+ " FROM DisabScreenRequest dsr WHERE 1=1 ";
}
This will get an
org.hibernate.exception.SQLGrammarException: could not extract ResultSet.
What Iam I doing wrong here, how can I fetch the child table data ?
Let me know if you need any info
Stack trace :
Caused by: java.sql.SQLException: ORA-00936: missing expression
at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:113)
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:331)
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:288)
at oracle.jdbc.driver.T4C8Oall.receive(T4C8Oall.java:754)
at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:219)
at oracle.jdbc.driver.T4CPreparedStatement.executeForDescribe(T4CPreparedStatement.java:813)
at oracle.jdbc.driver.OracleStatement.executeMaybeDescribe(OracleStatement.java:1051)
at oracle.jdbc.driver.T4CPreparedStatement.executeMaybeDescribe(T4CPreparedStatement.java:854)
at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1156)
at oracle.jdbc.driver.OraclePreparedStatement.executeInternal(OraclePreparedStatement.java:3415)
at oracle.jdbc.driver.OraclePreparedStatement.executeQuery(OraclePreparedStatement.java:3460)
at com.mchange.v2.c3p0.impl.NewProxyPreparedStatement.executeQuery(NewProxyPreparedStatement.java:76)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.extract(ResultSetReturnImpl.java:60)
If you need to fetch parent entity with a collection of its nested child entities you can use this simple approach using #EntityGraph annotation or JPQL with join fetch:
#Entity
public class Parent {
//...
#OneToMany
private List<Child> children;
}
#Entity
public class Child {
//...
}
interface ParentRepo extends JpaRepository<Parent, Integer> {
// with #EntityGraph
#EntityGraph(attributePaths = "children")
#Override
List<Parent> findAll();
// or manually
#Query("select distinct p from Parent p left join fetch p.children")
List<Parent> findWithQuery();
}
Note to use distinct in your query to avoid duplicate records.
Example: duplicate-parent-entities
More info: DATAJPA-1299
AFAIK, you can't use constructor expression which take a Collection.
See the JPA 2.2 Spec, section 4.14 BNF, read about the constructor expression:
constructor_expression ::=
NEW constructor_name ( constructor_item {, constructor_item}* )
constructor_item ::=
single_valued_path_expression |
scalar_expression |
aggregate_expression |
identification_variable
This is a perfect use case for Blaze-Persistence Entity Views.
I created the library to allow easy mapping between JPA models and custom interface defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure the way you like and map attributes(getters) via JPQL expressions to the entity model. Since the attribute name is used as default mapping, you mostly don't need explicit mappings as 80% of the use cases is to have DTOs that are a subset of the entity model.
A mapping for your model could look as simple as the following
#EntityView(DisabScreenRequest.class)
interface RequestSearchDto extends Serializable {
#IdMapping
long getRequestId();
Long getCivilId();
Set<DisabMaster> getDisabilities();
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
RequestSearchDtodto = entityViewManager.find(entityManager, RequestSearchDto.class, id);
But the Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/1.4/entity-view/manual/en_US/#spring-data-features

Lazy attribute is null inside transaction after creation

I have a small example with some get/post mappings and JpaRepository calls in Spring Boot.
Firstly I have two entity Classes:
#Entity
#Table(name = "stock")
public class Stock extends BaseEntity
{
#Column(name = "value")
public String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
#Entity
#Table(name = "stock_item")
public class StockItem extends BaseEntity
{
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "stock_id", insertable = false, updatable = false)
public Stock stock;
#Column(name = "stock_id")
public Long stockId;
#Column(name = "value")
public String value;
}
I have a many-to-one association from StockItem to Stock.
I insert a Stock and have a controller as below:
#Autowired
public Controller(StockItemRepository stockItemRepository) {
this.stockItemRepository = stockItemRepository;
}
#RequestMapping("/")
#Transactional(readOnly = true)
public String get() {
List<StockItem> stockItemList = stockItemRepository.getItemsById(1L);
System.out.println("TX MANAGER: " + TransactionSynchronizationManager.isActualTransactionActive());
for (StockItem stockItem : stockItemList) {
System.out.println(stockItem.getStock().getValue());
}
return "get";
}
#RequestMapping("/fromSave")
#Transactional
public String post() {
StockItem stockItem = new StockItem();
stockItem.setStockId(1L);
stockItemRepository.saveAndFlush(stockItem);
System.out.println("saveCalled");
return get();
}
and getItemsById in the repository is defined as follows:
#Query("FROM StockItem si " +
"JOIN FETCH si.stock stk " +
"WHERE si.stockId = :id")
List<StockItem> getItemsById(#Param("id") Long id);
From my understanding, when I call the post method:
it creates a new item
sets the id of the associated attribute
saves and ends the transaction
Heres where things get strange...
I call get after the post and make the above repository call, which has a join fetch and when I call stockitem.getStock().getValue() I get a null pointer when I expect a LazyInitializationException.
If I call the get() from the mapping, outside the class, it successfully loads the associated object.
I have even removed the #Transaction annotation from the get, as well as
the join-fetch from my query and again, if I call from outside of the class it works and from the post, it crashes with a NullPointerException.
I have put the get inside of a TransactionTemplate.execute() and I still get a NullPointerException when calling from inside the class.
So the main questions are:
Why am I getting a NullPointerException instead of LazyInitializationException?
What is the transaction magic behind having no transaction but successfully fetching a lazy attribute??
The problem here is that you are misusing JPA. As you are seemingly aware judging from the comments on the other answer you have mapped the stock_id column twice. Once as a many-to-one relationship
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "stock_id", insertable = false, updatable = false)
public Stock stock;
and once as a simple column
#Column(name = "stock_id")
public Long stockId;
When you set the simple column and flush the changes as in your post() method the following happens:
the value gets set in the simple column. The reference is still null.
the value gets stored in the database. The reference is still null.
The repository call will find the id of the StockItemin the Persistence Context and return that instance, i.e. the exact same used in the post method, with the reference still null.
What is the transaction magic behind having no transaction but successfully fetching a lazy attribute??
No magic involved here. fetch specifications are only used for object traversal. JPQL queries don't honor these.
The unasked question remains: how to fix the situation?
The obvious fix is to lose the simple column and just use entity references as intended by JPA.
You don't want to do that in order to avoid DB access somewhere. But as long as you only access the id of the referenced Stock it shouldn't get initialized. So it seems that this should be possible with just Lazy Fetching.
Alternatively, I'd suggest removing the many-to-one relationship and creating a repository for Stock and manually loading it when required.
#Entity
#Table(name = "stock_item")
public class StockItem extends BaseEntity
{
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "stock_id", insertable = false, updatable = false) //here is your problem
public Stock stock;
#Column(name = "stock_id")
public Long stockId; // why explicitly define a separate column for foreign key after mapping it above
#Column(name = "value")
public String value;
}
with insertable = false and updatable = false it won't insert in your DB and neither it will allow updation, so you are getting NullPointerException. You should atleast allow insertion in order to run the query based on the foreign key stock_id
UPDATE
Change your Entity class with property-based access:
#Entity
#Table(name = "stock_item")
public class StockItem extends BaseEntity
{
private Stock stock; // variables should always be private since you have getters and setters
private String value;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "stock_id", updatable = false)
public Stock getStock() {
return stock;
}
public void setStock(Stock stock) {
this.stock = stock;
}
#Basic
#Column(name = "value")
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}

QueryDSL dynamic predicates

I need help with QueryDSL querying. I'm using this library with Spring Data JPA.
My service class:
#Service("tblActivityService")
public class TblActivityService implements AbstractService<TblActivity> {
#Resource
private TblActivityRepository tblActivityRepository;
#Override
public List<TblActivity> findAll(Predicate predicate) {
return (List<TblActivity>) tblActivityRepository.findAll(predicate);
}
}
I have dynamic list of filters:
#Entity
#Table(name = "sys_filters")
public class SysFilter implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "filter_id")
private Integer filterId;
#JoinColumn(name = "user_id", referencedColumnName = "user_id")
#ManyToOne(fetch = FetchType.EAGER)
private SysUser userId;
#Size(max = 45)
#Column(name = "table_name")
private String tableName;
#Size(max = 45)
#Column(name = "column_name")
private String columnName;
#Size(max = 45)
#Column(name = "condition")
private String condition;
#Size(max = 100)
#Column(name = "value")
private String value;
// getters & setters
}
I have column name (e.g. title)
I have condition (e.g. ==, !=, >= etc.) - I can store it as symbols or words (equals etc.)
And finally I have value.
The question is "how to dynamically generate predicate for my service?"
Table has about 25 fields.
Predicate looks like that:
public BooleanExpression buildFilteredResult(List<SysFilter> filters) {
//TODO do it!
return QTblActivity.tblActivity.title.eq("Value");
// I need to do it dynamically for each filter in the list
}
The problem is how to invoke columnName by its string value.
Do you have any suggestions?
It might be easier to use a mapping filter conditions to operators
Map<String, Operator> operators = ImmutableMap.of(
"==", Ops.EQ, "!=", Ops.NE, ">", Ops.GT, "<", Ops.LT,
">=", Ops.GOE, "<=", Ops.LOE);
Expressions.predicate(operators.get(condition),
stringPath, Expressions.constant(filterValue));
Also make sure you combine your predicates properly
predicates.and(...)
returns a new predicate and leaves predicates untouched.
Maybe BooleanBuilder is what you are after?
A newer solution was released with spring data Gosling/Fowler. If you are creating a web app, you can use the querydsl web support that does the work for you -it reads the get parameters into a predicate and then you can use this predicate from your controller - no need to manually do that -
You can customize your repository based on the search criteria (equal, like ...) needed for a particular datatype or particular entity's field.
see the documentation here
I found the solution:
public BooleanExpression buildFilteredResult(List<SysFilter> filters) {
//TODO do it!
QTblActivity qTblActivity = QTblActivity.tblActivity;
BooleanExpression expression = qTblActivity.recordState;
for (SysFilter filter : filters) {
StringPath stringPath = new StringPath(qTblActivity, filter.getColumnName());
switch (filter.getCondition()) {
case "==":
expression.and(stringPath.eq(filter.getValue()));
break;
case "!=":
expression.and(stringPath.ne(filter.getValue()));
break;
case ">":
expression.and(stringPath.gt(filter.getValue()));
break;
case "<":
expression.and(stringPath.lt(filter.getValue()));
break;
case ">=":
expression.and(stringPath.goe(filter.getValue()));
break;
case "<=":
expression.and(stringPath.loe(filter.getValue()));
break;
default:
break;
}
}
return expression;
}

Resources