Spring-Data-JPA parameterised NativeQuery argument error? - spring-data-jpa

I have this JPA entity in a spring-boot v1.5.1 application
#Entity
#Table(name = "score")
public class Score implements Serializable {
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
private Long id;
#Column(name = "user_id",nullable = false)
private Long user;
#Column(name = "message_id",nullable = false)
private Long message;
#Column(nullable = false)
private Long value;
#Column(nullable = false)
private Date date;
which records a bunch of user scores by date. I have a simple SQL query which returns a grouped report
SELECT (SELECT fullname from user where id=s.user_id) as name,SUM(s.value) as total FROM score s
WHERE date >= '2017/03/01''
GROUP BY user_id ORDER BY total desc
To recreate this using the spring-data-jpa interface, I've defined this ScoreRepository which is using a Projection interface as a return type.
public interface ScoreRepository extends JpaRepository<Score, Long> {
#Query(value = "SELECT new c.b.a.service.datastore.model.LeaderScore(" +
"(SELECT fullname from beuser where id=s.user_id) as name,SUM(s.value) as total) FROM score s " +
"WHERE date >= '?1' GROUP BY user_id ORDER BY total desc",
nativeQuery = true)
List<LeaderScore> getLeaderBoard(Date from);
My nativeQuery returns the exact field names as per my LeaderScore projection interface
public interface LeaderScore {
String getName();
Long getTotal();
}
I get this error, which seems to indicate a Spel mapping error
java.lang.IllegalArgumentException: Parameter with that position [1] did not exist
at org.hibernate.jpa.spi.BaseQueryImpl.findParameterRegistration(BaseQueryImpl.java:502) ~[hibernate-entitymanager-5.0.11.Final.jar:5.0.11.Final]
at org.hibernate.jpa.spi.BaseQueryImpl.setParameter(BaseQueryImpl.java:692) ~[hibernate-entitymanager-5.0.11.Final.jar:5.0.11.Final]
at org.hibernate.jpa.spi.AbstractQueryImpl.setParameter(AbstractQueryImpl.java:181) ~[hibernate-entitymanager-5.0.11.Final.jar:5.0.11.Final]
at org.hibernate.jpa.spi.AbstractQueryImpl.setParameter(AbstractQueryImpl.java:32) ~[hibernate-entitymanager-5.0.11.Final.jar:5.0.11.Final]
at org.springframework.data.jpa.repository.query.ParameterBinder.bind(ParameterBinder.java:140) ~[spring-data-jpa-1.11.0.RELEASE.jar:na]
at org.springframework.data.jpa.repository.query.StringQueryParameterBinder.bind(StringQueryParameterBinder.java:61) ~[spring-data-jpa-1.11.0.RELEASE.jar:na]
at org.springframework.data.jpa.repository.query.ParameterBinder.bind(ParameterBinder.java:100) ~[spring-data-jpa-1.11.0.RELEASE.jar:na]
at org.springframework.data.jpa.repository.query.SpelExpressionStringQueryParameterBinder.bind(SpelExpressionStringQueryParameterBinder.java:69) ~[spring-data-jpa-1.11.0.RELEASE.jar:na]
at org.springframework.data.jpa.repository.query.ParameterBinder.bindAndPrepare(ParameterBinder.java:160) ~[spring-data-jpa-1.11.0.RELEASE.jar:na]
at org.springframework.data.jpa.repository.query.ParameterBinder.bindAndPrepare(ParameterBinder.java:151) ~[spring-data-jpa-1.11.0.RELEASE.jar:na]
at org.springframework.data.jpa.repository.query.AbstractStringBasedJpaQuery.doCreateQuery(AbstractStringBasedJpaQuery.java:81) ~[spring-data-jpa-1.11.0.RELEASE.jar:na]
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.createQuery(AbstractJpaQuery.java:190) ~[spring-data-jpa-1.11.0.RELEASE.jar:na]
at org.springframework.data.jpa.repository.query.JpaQueryExecution$CollectionExecution.doExecute(JpaQueryExecution.java:121) ~[spring-data-jpa-1.11.0.RELEASE.jar:na]
at org.springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.java:85) ~[spring-data-jpa-1.11.0.RELEASE.jar:na]
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:116) ~[spring-data-jpa-1.11.0.RELEASE.jar:na]
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:106) ~[spring-data-jpa-1.11.0.RELEASE.jar:na]
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:483) ~[spring-data-commons-1.13.0.RELEASE.jar:na]
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:461) ~[spring-data-commons-1.13.0.RELEASE.jar:na]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.6.RELEASE.jar:4.3.6.RELEASE]
See this similar issue :: Modifying native query cannot have named parameter bindings?

Related

JPARepository - delete using date comparison with derived query

I'm trying to use JPARepository in Spring Boot to delete records that are less than a certain date, for for a given userid
Should be something like this Delete * from [table] where expiration_date < [date] and userid = [userid]
I thought I should be able to use one of the automatically generated methods
int deleteByExpiryDateBeforeAndUser(Date date, User user);
But this is generating a Select and not a Delete. What am I doing wrong?
Update
Entity class
#Getter
#Setter
#ToString
#Entity(name = "refresh_token")
public class RefreshToken {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#OneToOne
#JoinColumn(name = "user_id", referencedColumnName = "id")
private User user;
#Column(nullable = false, unique = true)
private String token;
#Column(nullable = false)
private Date expiryDate;
public RefreshToken() {
}
}
Repository class
#Repository
public interface RefreshTokenRepository extends JpaRepository<RefreshToken, Long> {
Optional<RefreshToken> findByToken(String token);
#Modifying
void deleteByUserIdAndExpiryDateBefore(Long userId, Date expiryDate);
int deleteByUser(User user);
}
Here's how I'm calling it
#Transactional
public void deleteExpiredTokens(User user) {
refreshTokenRepository.deleteByUserIdAndExpiryDateBefore(user.getId(), new Date());
}
You see a select statement because Spring Data first loads entities by condition.
Then once entities became 'managed' Spring Data issues a delete query for each entity that was found.
If you want to avoid redundant SQL query - you have to consider #Query annotation.
Then your code will look like this:
#Repository
public interface RefreshTokenRepository extends JpaRepository<RefreshToken, Long> {
// ...
#Query(value = "DELETE FROM refresh_token WHERE user_id =:userId AND expiry_date < :expiryDate", nativeQuery = true)
#Modifying
void deleteByUserIdAndExpiryDateBefore(Long userId, Date expiryDate);
//...
}

How to return a count column not exists in table by JPA

I want find a way to get extra column that count my records and return it in 1 mapping entity with extra filed.
I tried #transient on field but it will not return value when query.
Then I remove #transient but get an exception when save.
Also I tried #Formula but received null pointer exception.
Here's my repository code:
#Query(value = "select id,account,session_id,create_time,count from query_history a join " +
"(select session_id sessionId,max(create_time) createTime,count(*) count from query_history group by session_id) b " +
"on a.session_id = b.sessionId and a.create_time = b.createTime where account = ?1 order by create_time desc",
countQuery = "select count(distinct(session_id)) from query_history where account = ?1",
nativeQuery = true)
Page<QueryHistory> findByNtAndGroupBySessionAndAction(String account, Pageable pageable);
entity code:
#Entity
#Table(name = "query_history")
#Data
public class QueryHistory {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column
private String account;
#Column
private Long sessionId;
#Column
private long createTime;
#Transient
private Integer count;
}
Sorry about my English and thanks a lot for any advice.
I solved the problem by projections spring-data-projections, in fact I tried this before but in my sql:
select id,account,session_id,create_time,count
which should be:
select id,account,session_id sessionId,create_time createTime,count
PS:
projection interface:
public interface QueryHistoryWithCountProjection {
Long getId();
String getAccount();
Long getSessionId();
long getCreateTime();
Integer getCount();
}

Composite key query in JPQL not generated correctly

I use spring data jpa and I use composite key
#Entity
#IdClass(SamplesPK.class)
public class Samples extends BaseEntity {
#Id
private String sampleLetter;
#Embedded
private TestSamples testSamples;
#Id
#ManyToOne(optional=false)
#JoinColumns({
#JoinColumn(name = "sampling_id", referencedColumnName = "id"),
#JoinColumn(name = "sampling_year", referencedColumnName = "year")})
private Samplings sampling;
//get set
}
public class SamplesPK implements Serializable {
private SamplingsPK sampling;
private String sampleLetter;
public SamplesPK(SamplingsPK sampling, String sampleLetter) {
this.sampling = sampling;
this.sampleLetter = sampleLetter;
}
//get set
}
#Entity
#IdClass(SamplingsPK.class)
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class Samplings {
#Id
private Integer year;
#Id
#GeneratedValue
private Integer id;
#OneToMany(mappedBy = "sampling", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Samples> samples = new ArrayList<>();
//get set
}
public class SamplingsPK implements Serializable {
private int year;
private Integer id;
public SamplingsPK(int year, Integer id) {
this.id = id;
this.year = year;
}
}
I try to do search sample with composite key
#Query(value = "select s from Samples s Join fetch s.sampling sp Join fetch sp.product p Join fetch p.productType Join Fetch s.testSamples.compressionTest where s.id=:id and s.year=:year and s.sampleLetter=:sampleLetter and sp.id=:id and sp.year=:year")
public Samples findSamplesWithFullProductAndTest(#Param("id") Integer id, #Param("year") Integer year, #Param("sampleLetter") String sampleLetter);
I get this error
java.lang.IllegalArgumentException: Parameter value [2] did not match
expected type [com.lcm.model.SamplesPK (n/a)]
Edit,
I modified query to
select s from Samples s Join fetch s.sampling sp Join fetch sp.product p Join fetch p.productType Join Fetch s.testSamples.compressionTest where s.sampling.id=:id and s.sampling.year=:year and s.sampleLetter=:sampleLetter and sp.id=:id and sp.year=:year
but I get this error
org.postgresql.util.PSQLException: ERROR: operator does not exist:
record = integer Indice : No operator matches the given name and
argument type(s). You might need to add explicit type casts.
Generated query is
select
samples0_.sample_letter as sample_l1_20_0_,
samples0_.sampling_id as sampling0_20_0_,
samplings1_.id as id2_21_1_,
samplings1_.year as year3_21_1_,
products2_.id as id2_15_2_,
producttyp3_.id as id1_16_3_,
compressio4_.id as id1_3_4_,
samples0_.sampling_id as samplin27_20_0_,
samples0_.sampling_year as samplin28_20_0_,
samples0_.compression as compres18_20_0_,
samples0_.compression_number as compres19_20_0_,
samples0_.compression_test_id as compres29_20_0_,
samplings1_.available_for_test as availabl4_21_1_,
samplings1_.dtype as dtype1_21_1_,
products2_.created_at as created_3_15_2_,
products2_.updated_at as updated_4_15_2_,
products2_.name_en as name_en5_15_2_,
products2_.dtype as dtype1_15_2_,
producttyp3_.created_at as created_2_16_3_,
producttyp3_.updated_at as updated_3_16_3_,
producttyp3_.name_en as name_en4_16_3_,
compressio4_.created_at as created_2_3_4_,
compressio4_.updated_at as updated_3_3_4_
from
permacon.samples samples0_
inner join
permacon.samplings samplings1_
on samples0_.sampling_id=samplings1_.id
and samples0_.sampling_year=samplings1_.year
inner join
permacon.products products2_
on samplings1_.product_id=products2_.id
inner join
permacon.product_types producttyp3_
on products2_.product_type_id=producttyp3_.id
inner join
permacon.compressions compressio4_
on samples0_.compression_test_id=compressio4_.id
where
(
samples0_.sampling_id, samples0_.sampling_year
)=?
and samplings1_.year=?
and samples0_.sample_letter=?
and samplings1_.id=?
and samplings1_.year=?
Edit 2
#MappedSuperclass
public abstract class BaseEntity {
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
//get set...
}
Inheritance is used because I have two table who extends Samplings
you year field is a int put int instead of Integer in your
findSamplesWithFullProductAndTest
method

Hibernate Filter being ignored

My application uses Hibernate 5.02 and Wildfly 10 with a PostgreSQL 9.5 database. I'm trying to enable a filter on a #OneToMany collection held within an entity that is constructed via a NamedQuery. Unfortunately, it seems as if the filter is just ignored. Here are the different components, redacted for ease of reading.
#NamedNativeQueries({
#NamedNativeQuery(
name = "getAnalystProcess",
query = "SELECT * FROM analysis.analystprocess WHERE id = :processId",
resultClass = AnalystProcessEntity.class
)})
#FilterDef(
name = "analystProcessUnanalyzedMsgsFilter",
parameters = { #ParamDef(name = "processIds", type = "integer"), #ParamDef(name = "analystIds", type = "integer") })
#Filter(name = "analystProcessUnanalyzedMsgsFilter", condition = "analystprocess_id IN (:processIds) AND id NOT IN (SELECT msg_id FROM analysis.analyzedmsg WHERE analyst_id IN (:analystIds) AND analystprocess_id IN (:processIds)) ORDER BY process_msg_id")
#Entity
#Table(name = "analystprocess", schema = "analyst")
public class AnalystProcessEntity implements JPAEntity {
public static final String GET_PROCESS = "getAnalystProcess";
public static final String MSG_FILTER = "analystProcessUnanalyzedMsgsFilter";
public static final String MSG_FILTER_PROC_ID_PARAM = "processIds";
public static final String MSG_FILTER_ANALYST_ID_PARAM = "analystIds";
private static final long serialVersionUID = 1L;
...
#OneToMany(fetch = FetchType.LAZY, orphanRemoval = true, mappedBy = "process")
#OrderColumn(name = "process_msg_id")
#LazyCollection(LazyCollectionOption.EXTRA)
private List<MsgEntity> msgList;
#Entity
#Table(name = "msg", schema = "analyst")
public class MsgEntity implements JPAEntity {
...
#ManyToOne(cascade = CascadeType.ALL, optional = false)
#JoinColumn(name = "analystprocess_id", referencedColumnName = "id")
private AnalystProcessEntity process;
#Column(name = "process_msg_id")
private Integer processMsgId;
private void buildAnalystProcess() {
LOG.info("Building AnalystProcessEntity");
analystUser.getJdbcSession().enableFilter(AnalystProcessEntity.MSG_FILTER)
.setParameter(AnalystProcessEntity.MSG_FILTER_PROC_ID_PARAM, analystProcessId)
.setParameter(AnalystProcessEntity.MSG_FILTER_ANALYST_ID_PARAM, analystUser.getId());
Query query = analystUser.getJdbcSession().getNamedQuery(AnalystProcessEntity.GET_PROCESS)
.setParameter("processId", analystProcessId);
// Query query = analystUser.getJdbcSession().createNativeQuery("SELECT * FROM analysis.analystprocess WHERE id = :processId")
// .setParameter("processId", analystProcessId)
// .addEntity(AnalystProcessEntity.class);
analystProcess = (AnalystProcessEntity) query.getSingleResult();
CREATE TABLE analysis.analystprocess (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
description TEXT,
created_date TIMESTAMP NOT NULL DEFAULT now(),
...
);
CREATE TABLE analysis.msg (
id SERIAL PRIMARY KEY,
analystprocess_id INTEGER NOT NULL REFERENCES analysis.analystprocess(id) ON DELETE CASCADE ON UPDATE CASCADE,
process_msg_id INTEGER NOT NULL,
constraint tbl_statusid_analystprocessid unique(status_id, analystprocess_id)
);
As seen above, I have also tried the filter on constructing the AnalystProcessEntity class via createNativeQuery instead of getNamedQuery and no luck.
I also added a defaultCondition with hardcoded values into the #FilterDef just to see if it would execute the default condition and it still didn't.
I've tried the #Filter above the entity definition as well as above the class definition. I even came across a blog post which made it sound like the condition references entity fields (variable names) and not table fields (column names). Trying to stick to Java naming conventions in the Entity and Postgres naming conventions in the table, so I tried switching the references in the condition and to no avail.
I have sql logging turned on in Hibernate and the condition doesn't show up anywhere, as if it's just simply being ignored.
Any help would be greatly appreciated!
So, the problem was that I had the #FilterDef applied to the wrong class. It was my presumption that because I was constructing the AnalystProcessEntity which holds the MsgEntity collection (which I am trying to filter), that the #FilterDef would be applied to the AnalystProcessEntity class. Instead, it needs to be applied to the entity that it's actually filtering (hindsight being 20/20, that's pretty obvious).
Also, the actual condition needed to be modified to use complete references within the sub-select query.
I hope this helps someone at some point...
#NamedNativeQueries({
#NamedNativeQuery(
name = "getAnalystProcess",
query = "SELECT * FROM analysis.analystprocess WHERE id = :processId",
resultClass = AnalystProcessEntity.class
)})
#Filter(name = "analystProcessUnanalyzedMsgsFilter", condition = "id NOT IN (SELECT amsg.msg_id FROM analysis.analyzedmsg amsg WHERE amsg.analyst_id IN (:analystIds) AND amsg.analystprocess_id IN (:processIds))")
#Entity
#Table(name = "analystprocess", schema = "analyst")
public class AnalystProcessEntity implements JPAEntity {
public static final String GET_PROCESS = "getAnalystProcess";
public static final String MSG_FILTER = "analystProcessUnanalyzedMsgsFilter";
public static final String MSG_FILTER_PROC_ID_PARAM = "processIds";
public static final String MSG_FILTER_ANALYST_ID_PARAM = "analystIds";
private static final long serialVersionUID = 1L;
...
#OneToMany(fetch = FetchType.LAZY, orphanRemoval = true, mappedBy = "process")
#OrderColumn(name = "process_msg_id")
#LazyCollection(LazyCollectionOption.EXTRA)
private List<MsgEntity> msgList;
#FilterDef(
name = "analystProcessUnanalyzedMsgsFilter",
parameters = { #ParamDef(name = "processIds", type = "integer"), #ParamDef(name = "analystIds", type = "integer") })
#Entity
#Table(name = "msg", schema = "analyst")
public class MsgEntity implements JPAEntity {
...
#ManyToOne(cascade = CascadeType.ALL, optional = false)
#JoinColumn(name = "analystprocess_id", referencedColumnName = "id")
private AnalystProcessEntity process;
#Column(name = "process_msg_id")
private Integer processMsgId;
Additionally, I ran into another problem with null's appearing in the collection, despite the fact that I am using an #OrderColumn, which I thought fixed that issue. It seems that with the use of the #Filter, null's are inserted in place of what ended up being filtered OUT (excluded).

JPA The state field path cannot be resolved to a valid type

I need assistance in troubleshooting a relationship / query with EclipseLink 2.5.x provider.
The relationship from ThreePhaseMotorInput to ValidationMessage is supposed to be a uni-directional OneToMany, i.e. each motor can have 0..n messages and in Java object graph ValidationMessage does not have a reference back to ThreePhaseMotorInput.
I am getting an error that JPA can't find the attributes that are part of the ValidationMessage class when accessed via ThreePhaseMotor. (See error text below)
Thanks for thinking about my question!
Query
select msg.validationMsg, COUNT(m.id) from ThreePhaseMotorInput AS m JOIN m.valMessages AS msg GROUP BY msg.validationMsg
Error
org.eclipse.persistence.exceptions.JPQLException:
Exception Description: Problem compiling [select msg.validationMsg, COUNT(m.id) from ThreePhaseMotorInput AS m JOIN m.valMessages AS msg GROUP BY msg.validationMsg].
[7, 24] The state field path 'msg.validationMsg' cannot be resolved to a valid type.
[71, 84] The collection-valued path 'm.valMessages' cannot be resolved to a valid association field.
[119, 136] The state field path 'msg.validationMsg' cannot be resolved to a valid type.
ThreePhaseMotorInput class
#Entity
#Table(name = "three_phase_motor_input")
public class ThreePhaseMotorInput implements IThreePhaseMotorInput, Serializable {
private static final long serialVersionUID = 8084370807289186987L;
#Transient
private final PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Version
private Integer version;
private Integer status;
#Transient
private Integer numMessages;
#OneToOne(cascade = CascadeType.ALL, optional = true, targetEntity = UnapprovedThreePhaseMotor.class)
#JoinColumn(name = "unapproved_id")
private IThreePhaseMotor unapprovedMotor;
#OneToOne(cascade = CascadeType.ALL, optional = true, targetEntity = ApprovedThreePhaseMotor.class)
#JoinColumn(name = "approved_id")
private IThreePhaseMotor approvedMotor;
#OneToMany(orphanRemoval = true, cascade = CascadeType .ALL, fetch = FetchType.LAZY, targetEntity = ValidationMessage.class)
#JoinColumn(name = "input_id", referencedColumnName = "id", nullable = false)
#OrderColumn(name = "idx")
private List<IValidationMessage> valMessages;
ValidationMessage class
#Entity
#Table(name = "validation_message")
public class ValidationMessage implements Serializable, IValidationMessage {
private static final long serialVersionUID = 8765213112015434057L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "record_id")
private Long recordId;
#Column(name = "field_name")
private String fieldName;
#Column(name = "validation_msg")
private String validationMsg;
private Integer status;
#Column(name = "fail_field")
private String failField;
#Column(name = "error_source")
private Integer errorSource;
The problem seems to be in the following query: select m.approvedMotor, m.valMessages, m.valMessages.validationMsg, count(m.valMessages.id) from ThreePhaseMotorInput m group by m.valMessages.validationMsg.
That query should be a JPQL query, i.e a query where you specify entities and their Java properties. Also you must use JOINs if you want to jump to another entity's properties: m.valMessages.validationMsg is not correct, but INNER JOIN m.valMessages msg GROUP BY msg is correct.
So try the following query:
select m, COUNT(msg) from ThreePhaseMotorInput AS m LEFT JOIN m.valMessages AS msg GROUP BY msg.validationMsg
You can't use a path expression with a Collection value association.
The documentation says: JPQL Path Expressions
It is syntactically illegal to compose a path expression from a path expression that evaluates to a collection.
In your query, m.valMessages is illegal because it references a collection of ValidationMessages.
In the other hand, m.approvedMotor is legal because it is a single value association.
As suggested in Andrei response, you need to modify your query to add another path expression:
select msg.validationMsg, COUNT(m.id) from ThreePhaseMotorInput m JOIN m.valMessages msg GROUP BY msg.validationMsg
you should use JOINs if you want to jump to another entity's properties. try the following JPQL query
select m, COUNT(msg) from ThreePhaseMotorInput AS m LEFT JOIN m.valMessages AS msg GROUP BY msg.validationMsg