How set where in templates using jpa in CriteriaQuery - java

I am beginner in java EE and I need know, how set a clausule where in this code of criteria
public <T> List<T> findEntity(Class<T> entityClass) {
CriteriaQuery<T> criteria = builder.createQuery(entityClass);
Root<T> entityRoot = criteria.from(entityClass);
criteria.select(entityRoot);
criteria.orderBy(order);
return em.createQuery(criteria).getResultList();
}
They will notice that I am using templates in java (<T>) to make this code work with various entities from my database.
Then I pass the sql (in postgresql) code and the entity class.
SQL:
CREATE TABLE activity
(
id integer NOT NULL,
name text NOT NULL,
_modified timestamp without time zone,
_user integer,
_enable boolean,
)
And class entity
public class activity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue( strategy = GenerationType.SEQUENCE, generator = "activity_id_activity_seq" )
#SequenceGenerator( name = "actividad_id_actividad_seq", sequenceName = "actividad_id_actividad_seq", allocationSize = 1, initialValue = 110 )
#Column(name = "id")
private Integer id;
#Column(name = "name")
private String name;
#Column(name = "_modified")
#Temporal(TemporalType.TIMESTAMP)
private Date modified;
#Column(name = "_enable")
private Boolean enable;
#Column(name = "_user")
private Integer user;
.......
}
I need to know is how to add a where clause in the code of the function findEntity using methods template.
The where clause of criteria should be matching the column _Enable, this column this column mentioned is repeated in 4 tables in my database, so you note that it is better to reuse code in that function.
thanks

Here is an example how to add where() clause:
public static <T> List<T> findEntity(Class<T> entityClass, boolean isEnabled) {
CriteriaQuery<T> criteria = builder.createQuery(entityClass);
Root<T> entityRoot = criteria.from(entityClass);
criteria.select(entityRoot);
criteria.where(builder.equal(
entityRoot.get("enable"), //path expression
builder.parameter(Boolean.class, "isEnabled")) //parameter expression
);
criteria.orderBy(order);
return em.createQuery(criteria)
.setParameter("isEnabled", isEnabled)
.getResultList();
}
The entityRoot.get("enable") statement defines Path<String> expression. This is equivalent of a.enable which uses a colon to denote a parameter in JPQL, i.e.
SELECT a FROM Activity a WHERE a.enable = :isEnabled
To build conditional expression one must also create a ParameterExpression<Boolean> which corresponds to the parameter of typed query that executes our criteria.
Using this approach you can easily extend your generic method with new parameters.
I hope it helps.

Related

Implementing complex MAX function in Spring Boot/JPA query language

Spring Boot here using JPA/Hibernate and CrudRepository impls for managing persistence to my DB tables.
I have the following MySQL table:
CREATE TABLE IF NOT EXISTS price_scarcity_configs (
price_scarcity_config_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
price_scarcity_config_ref_id VARCHAR(36) NOT NULL,
price_scarcity_config_version BIGINT NOT NULL,
price_scarcity_config_updated_on DATETIME NOT NULL,
price_scarcity_config_fizz INTEGER NOT NULL,
CONSTRAINT pk_price_scarcity_configs PRIMARY KEY (price_scarcity_config_id),
CONSTRAINT uc_price_scarcity_configs_ref_id_and_version UNIQUE (price_scarcity_config_ref_id, price_scarcity_config_version)
);
These records will be versioned and different versions of the "same" record will all share the same price_scarcity_config_ref_id. Hence 2+ records can have the same price_scarcity_config_ref_id but will have two distinct different versions.
I'm also using the following JPA/Hibernate entity to model it:
// Uses Lombok annotations to generate getters/setters, etc.
#MappedSuperclass
#Data
#EqualsAndHashCode(callSuper=false)
public abstract class BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String refId;
}
#Entity
#Table(name = "price_scarcity_configs")
#AttributeOverrides({
#AttributeOverride(name = "id", column = #Column(name = "price_scarcity_config_id")),
#AttributeOverride(name = "refId", column = #Column(name = "price_scarcity_config_ref_id"))
})
#Data
#EqualsAndHashCode(callSuper=false)
public class PriceScarcityConfiguration extends BaseEntity {
#Column(name = "price_scarcity_config_version")
private Long version;
#Column(name = "price_scarcity_config_updated_on")
private Date updatedOn;
#Column(name = "price_scarcity_config_fizz")
private Integer fizz;
}
I am now trying to write the PriceScarcityConfigurationRepository and need a fairly sophisticated query. Given a refId, I need to find the record who matches that ref id and has the highest/max version number. The raw SQL query to perform this is:
select
*
from
price_scarcity_configs pcs
inner join
(
SELECT
price_scarcity_config_ref_id,
MAX(price_scarcity_config_version) as max_ver
FROM
price_scarcity_configs
group by
price_scarcity_config_ref_id
) t
on
t.price_scarcity_config_ref_id = pcs.price_scarcity_config_ref_id
and
t.max_ver = pcs.price_scarcity_config_version;
Given my repository and using JPA/Hibernate's built-in query language/annos, how do I implement this query?
public interface PriceScarcityConfigurationRepository extends CrudRepository<PriceScarcityConfiguration,Long> {
#Query("FROM PriceScarcityConfiguration WHERE ??? HOW TO IMPLEMENT THE ABOVE QUERY HERE ???")
PriceSheetConfiguration fetchLatestVersionByRefId(#Param("refId") String refId);
}
You could use the following query instead and use setMaxResults(1)
FROM PriceScarcityConfiguration p WHERE p.refId = :refId ORDER BY p.version DESC
Or simply use the Spring Data notation
List<PriceSheetConfiguration> findFirstByRefIdOrderByVersionDesc(String refId);

different result by executing query in console and namedQuery

in my java web application i need to inquire a list of deposits from a view named VwDepositsInfo by customerNumber.
when i execute my query:
select * from VW_DEPOSIT_INFO v where v.CUSTOMER_NUMBER=:customerNo
in database console my resultList size is 2 and have something like this:
1-{depositTypeDesc="shortTerm"} {depositTypeCode="850"}
2-{depositTypeDesc="longTerm"} {depositTypeCode="2"}
but when i test my code that includes a namedQuery:
#NamedQuery(
name = "inquireAccountByCustomerNumber",
query = "select c from VWDepositInfo c where c.customerNumber=:customerNo"
)
i get a resultList with size 2 but both the same, sth like this:
1-{depositTypeDesc="shortTerm"} {depositTypeCode="850"}
2-{depositTypeDesc="shortTerm"} {depositTypeCode="850"}
when i make it nativeQuery with defining the result class:
Query query = entityManager.createNativeQuery("select * from VW_DEPOSIT_INFO v where v.CUSTOMER_NUMBER=:customerNo", VWDepositInfo.class);
again i get the wrong results.
finally i tried nativeQuery without defining the result class:
Query query = entityManager.createNativeQuery("select * from VW_DEPOSIT_INFO v where v.CUSTOMER_NUMBER=:customerNo");
and result was as i expected to be.
and this is my VwDepositsInfo.class:
#Entity
#Table(name = "VW_DEPOSIT_INFO")
#Audited(withModifiedFlag = false)
#NamedQueries(
{#NamedQuery(
name = "inquireAccountByCustomerNumber",
query = "select c from VWDepositInfo c where c.customerNumber=:customerNo"
)
}
)
public class VWDepositInfo implements Serializable {
#Id
#Column(name = "CUSTOMER_NUMBER")
private Long customerNumber;
#Column(name = "BRANCH_CODE")
private Long branchCode;
#Column(name = "DEPOSIT_TYPE_CODE")
private Long depositTypeCode;
#Column(name = "DEPOSIT_SERIAL")
private Long depositSerial;
#Column(name = "DEPOSIT_TYPE_DESC")
private String depositTypeDesc;
#Column(name = "CURRENCY_TYPE_DESC")
private String currencyTypeDesc;
#Column(name = "DEPOSIT_OPEN_DATE")
private Date depositOpenDate;
Does anyone know why this is happening???
VW = view?
You probably need to specify the master key
use #id for unique field :)
you probably need more than one field with #id for a unique row.
for example both of DEPOSIT_TYPE_CODE and customerNumber

Spring JPA native query with Projection gives "ConverterNotFoundException"

I'm using Spring JPA and I need to have a native query. With that query, I need to get only two fields from the table, so I'm trying to use Projections. It isn't working, this is the error I'm getting:
org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [org.springframework.data.jpa.repository.query.AbstractJpaQuery$TupleConverter$TupleBackedMap] to type [com.example.IdsOnly]
I tried to follow precisely the instructions of that page I linked, I tried to make my query non-native (do I actually need it to be native if I use projections, btw?), but I always get that error.
If I use an interface it works, but the results are proxies and I really need them to be "normal results" that I can turn into json.
So, here's my code. The Entity:
import lombok.Data;
import lombok.NoArgsConstructor;
#Data
#NoArgsConstructor
#Entity
#Table(name = "TestTable")
public class TestTable {
#Id
#Basic(optional = false)
#GeneratedValue(strategy = GenerationType.SEQUENCE)
#Column(name = "Id")
private Integer id;
#Column(name = "OtherId")
private String otherId;
#Column(name = "CreationDate")
#Temporal(TemporalType.TIMESTAMP)
private Date creationDate;
#Column(name = "Type")
private Integer type;
}
The class for the projection:
import lombok.Value;
#Value // This annotation fills in the "hashCode" and "equals" methods, plus the all-arguments constructor
public class IdsOnly {
private final Integer id;
private final String otherId;
}
The Repository:
public interface TestTableRepository extends JpaRepository<TestTable, Integer> {
#Query(value = "select Id, OtherId from TestTable where CreationDate > ?1 and Type in (?2)", nativeQuery = true)
public Collection<IdsOnly> findEntriesAfterDate(Date creationDate, List<Integer> types);
}
And the code that tries to get the data:
#Autowired
TestTableRepository ttRepo;
...
Date theDate = ...
List<Integer> theListOfTypes = ...
...
Collection<IdsOnly> results = ttRepo.findEntriesAfterDate(theDate, theListOfTypes);
Thanks for the help. I really don't understand what I'm doing wrong.
with spring data you can cut the middle-man and simply define
public interface IdsOnly {
Integer getId();
String getOtherId();
}
and use a native query like;
#Query(value = "Id, OtherId from TestTable where CreationDate > ?1 and Type in (?2)", nativeQuery = true)
public Collection<IdsOnly> findEntriesAfterDate(Date creationDate, List<Integer> types);
check out https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections
The query should be using a constructor expression:
#Query("select new com.example.IdsOnly(t.id, t.otherId) from TestTable t where t.creationDate > ?1 and t.type in (?2)")
And i dont know Lombok, but make sure there is a constructor that takes the two IDs as parameters.
JPA 2.1 introduces an interesting ConstructorResult feature if you want to keep it native.
You can return list of Object Array (List) as return type of the native query method in repository class.
#Query(
value = "SELECT [type],sum([cost]),[currency] FROM [CostDetails] " +
"where product_id = ? group by [type],[currency] ",
nativeQuery = true
)
public List<Object[]> getCostDetailsByProduct(Long productId);
for(Object[] obj : objectList){
String type = (String) obj[0];
Double cost = (Double) obj[1];
String currency = (String) obj[2];
}
#Query(value = "select isler.saat_dilimi as SAAT, isler.deger as DEGER from isler where isler.id=:id", nativeQuery = true)
List<Period> getById(#Param("id") Long id);
public interface Period{
Long getDEGER();
Long getSAAT();
}
as seen in the example code for native query given above, cast return values to any value like as "SAAT", "DEGER" and then define interface "period" which have getDEGER() and getSAAT(). Even if I have not understand why parameter after get must be uppercase, in lowercase scenario it didn't work properly. ie. interface with getDeger(), getSaat() does not work properly in my case.

Representing #EmbeddedId as SQL for H2 database

I am currently working on a Java project with Hibernate entities (more below). In order to test my data access object layers, I am using H2 database to populate an in-memory database and throwing queries at it. Until this point, everything is fine.
However, the problem comes when simulating the #EmbeddedId annotation.
#Entity
#Table(name = "BSCOBJ")
public class BasicObject extends AbstractDomainObject {
#EmbeddedId // This annotation here
private RestrainPK restrain;
#Embeddable
public static class RestrainPK implements Serializable {
private static final long serialVersionUID = 1L;
#Column(name = "CODI", nullable = false)
private String coDi;
#Column(name = "COGA", nullable = false)
private String coGa;
#Column(name = "TYOR", nullable = false)
private String tyOr;
public RestrainPK() {
}
... // Getters and setters
}
}
"Simply" creating the table BSCOBJ and populating it gives no value when fetching data (of course, I checked that the request would give result "normally"). How do I represent this nested class in a SQL table creation / value insertion request ? Is that even possible ?
Thanks in advance,
EDIT
As requested, here is some samples about the SQL / Hibernate ran.
Creation request:
CREATE TABLE BSCOBJ (CODI VARCHAR(5) NOT NULL, COGA VARCHAR(5) NOT NULL, TYOR VARCHAR(5) NOT NULL);
Insertion request:
INSERT INTO BSCOBJ (CODI, COGA, TYOR) VALUES
('HELLO', 'MAT', 'REF'),
('BONJ', 'SOME', 'DAIL'),
('SOPA', 'KDA', 'RATIO');
Request given by Hibernate when trying to run the test code:
select r.restrain.tyOr from mypackage.BasicObject r where r.restrain.coDi = :coDi and r.restrain.coGa = :coGa
With the following values:
coDi = "BONJ";
coGa = "SOME";
Throws a NoResultException. I am expecting DAIL, from the second line of the INSERT request.
I have used #EmbeddedId only one time, but I think that you need #AttributeOverrides under your #EmbeddedId
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name = "idpk", column = #Column(name="IDPK", nullable = false),
#AttributeOverride(name = "code", column = #Column(name="CODE")
})
and remove your #Column annotations from FormulePK

Spring JPA Join Efficiency - Create a query for each iteration

I have a simple 2 JPA entities which I have a Join Between them:
Primary entity Country:
public class Country implements Serializable {
#Id
#Column(name = "MCC")
private String mcc;
......
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "mcc", referencedColumnName = "mcc")
private List<CountryInfo> countryInfo;
Joint entity CountryInfo:
public class CountryInfo implements Serializable {
#Id
#Column(name = "id")
private Long id;
#Column(name = "mcc")
private String mcc;
#Column(name = "continent")
private String continent;
When I've turned on my configuration to dump the queries being executed, I've noticed that for each Country found, another call is done on the CountryInfo entity with the mcc specified..
This is obviously slow since rather than creating 1 call with a JOIN, it is executing N + 1 queries (where N = count of Country).
I've already seen this tutorial https://zeroturnaround.com/rebellabs/how-to-use-jpa-correctly-to-avoid-complaints-of-a-slow-application/ and changed accordingly but it is still calling N + 1 queries..
Is there a way to overcome this?
EDIT
In order to get the data I have a Repository:
#RepositoryRestResource(exported = false)
public interface CountryRepository extends JpaRepository<E212MCC, Long>,
JpaSpecificationExecutor<E212MCC> {
}
And then call with some specifications:
List<E212MCC> countries = this.countryRepository.findAll(specifications);
Since you are using Specifications you could try with specification that performs fetch join operation (I am assuming that you are using JPA meta model):
private Specification<Country> joinContryInfo() {
return (root, query, cb) -> {
root.fetch(Country_.countryInfo);
// here you can fetch more entities if you need...
return null;
};
}
And then, just add it to your specification object:
Specifications.where(joinCountryInfo())
If you are not using meta model then just replace Country_.countryInfo with "countryInfo" string.
If you are using CountryInfo fields for searching, you can omit joinContryInfo() specification and prepare join and search query in one specification:
private Specification<Country> continentEqual(String param) {
return (root, query, cb) -> {
Join<Country,CountryInfo> join = (Join) root.fetch(Country_.countryInfo);
return cb.equal(join.get(CountryInfo_.continent), addWildCards(param));;
};
}

Categories