How to receive a SELECT VALUE COUNT query with Java? - spring-boot

When I use the Cosmos website to execute the query
SELECT VALUE COUNT(1) FROM c WHERE 1623736778 <= c.timestamp AND 1623736779 <= c.timestamp
I get
[
4
]
in the "Results" window. This makes sense, since executing
SELECT * FROM c WHERE 1623736778 <= c.timestamp AND 1623736779 <= c.timestamp
returns 4 entries from my database.
How do I receive the result "4" (from SELECT VALUE COUNT(1) ...) in my backend code using Java with Spring Boot? In other words, given any Cosmos repository, how does one access the return from a SELECT VALUE COUNT ... query using Java?
For context, I have a repository class that looks like this:
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import com.azure.spring.data.cosmos.repository.Query;
import com.azure.spring.data.cosmos.repository.ReactiveCosmosRepository;
import reactor.core.publisher.Flux;
import myownpackage.MyEntity;
#Repository
public interface MyRepository extends ReactiveCosmosRepository<MyEntity, String> {
...
#Query(value = "SELECT * FROM c WHERE #startTime <= c.timestamp AND c.timestamp <= #endTime")
Flux<MyEntity> findInRange(
#Param("startTime") Long startTime,
#Param("endTime") Long endTime
);
#Query(value = "SELECT VALUE COUNT(1) FROM c WHERE #startTime <= c.timestamp AND c.timestamp <= #endTime")
Flux<MyEntity> countInRange(
#Param("startTime") Long startTime,
#Param("endTime") Long endTime
);
}
In my service class I try to do
#Autowired private MyRepository repo;
int totalCount = repo.countInRange(1623736778, 1623736779).collectList().block().get(0);
so that totalCount == 4, but this causes an error saying "incompatible types: myownpackage.MyEntity cannot be converted to int." Changing the return type of countInRange to int gives "int cannot be dereferenced." There's no issue accessing results from findInRange() in this manner. I used a logger (from slf4j) to run
LOGGER.info("\n\nHere is the type we need to know: {}\n\n",
repo.countInRange(1623736778,1623736779).getClass().getCanonicalName());
which logs "Here is the type we need to know: reactor.core.publisher.FluxMap", but Googling reactor.core.publisher.FluxMap doesn't reveal any documentation similar to Flux's documentation.

The result is of primitive type int and not an Object. Dereferencing is the process of accessing the value referred to by a reference . Since, int is already a value (not a reference), it can not be dereferenced. Try changing the return type as Flux<Integer>

Related

Database Oracle, Query Date String JPA Repository Crud Repository, Java 8

NOTE: I can't put some privated code (Database or Java Code).
I have in the Database
CREATE TABLE "SCHEMA"."ENTITY"
(
"HCODFECREGISTR" DATE,
... BLABLA
)
The Entity
import java.util.Date;
public class Entity implements java.io.Serializable {
private Date hcodfecregistr;
....
}
In the Repository Interface
using
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
public interface EntityRepository extends CrudRepository<Entity, Long>,
JpaRepository<Entity, Long> {
public static final String USING_STRING
= " SELECT enti FROM Entity enti WHERE "
+ " (enti.hcodfecregistr BETWEEN TO_DATE(:stringIni,'dd/MM/yyyy hh24:mi') AND TO_DATE(:stringEnd,'dd/MM/yyyy hh24:mi'))";
#Query(value = USING_STRING)
List<Object[]> getEntityUsingString(
#Param("stringIni") String stringIni, #Param("stringEnd") String stringEnd);
public static final String USING_DATE
= " SELECT enti FROM Entity enti WHERE "
+ " (enti.hcodfecregistr BETWEEN :dateIni AND :dateIni) ";
#Query(value = USING_DATE)
List<Object[]> getEntityUsingDate(
#Param("dateIni") Date dateIni, #Param("dateEnd") Date dateEnd);
}
Now I want to perform a query.
Date fecha = //some java.util.Date
Calendar calendar = Calendar.getInstance();
calendar.setTime(fecha);
calendar.set(Calendar.HOUR, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
Date dateIni = calendar.getTime();
calendar.set(Calendar.HOUR, 23);
calendar.set(Calendar.MINUTE, 59);
calendar.set(Calendar.SECOND, 59);
Date dateEnd = calendar.getTime();
SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");
String stringIni = dateFormat.format(fecha) + " 00:00";
String stringEnd = dateFormat.format(fecha) + " 23:59";
Works!
List<Object[]> listaObjects = theRepository.getEntityUsingString(stringIni, stringEnd);
Fails! It does not bring me results (but it also does not show any error).
List<Object[]> listaObjects = theRepository.getEntityUsingDate(dateIni, dateEnd);
Question:
I want to understand Why using the same fecha (java.util.Date) the method getEntityUsingString works, but using the method getEntityUsingDate fails (
When I set the range using Date it does not bring me results, whereas when I set the range with String it does.).
In my opinion, it should yield the same result that complies with the range of dates.
What is the problem?
You did not write what error it was, so it's only my guess that the most likely error in this case is:
ORA-01861: literal does not match format string.
If this is the case, then the reason is that you are passing to the query two values of date type:
Date dateIni = calendar.getTime();
.....
Date dateEnd = calendar.getTime();
.....
theRepository.getEntityUsingDate(dateIni, dateEnd);
these values are bind to this parameters :stringIni and :stringEnd
BETWEEN TO_DATE(:stringIni,'dd/MM/yyyy hh24:mi') AND TO_DATE(:stringEnd,'dd/MM/yyyy hh24:mi')
Since TO_DATE( char, [format] ) functions expects CHAR (string) as a first parameter, but gets a date, then according to rules explained in this documentation see section: implicit conversion
The following rules govern implicit data type conversions: .... ....
When you use a SQL function or operator with an argument of a data
type other than the one it accepts, Oracle converts the argument to
the accepted data type.
it does an implicit conversion from date to string using TO_CHAR function.
This function uses the default date language from a session to format strings, so depending of this settings the date may be converted to something like 12/15/54, 19-06-11 12:23 PM, JUN 23, 2019 13:20 AM +01 etc.
This string 12/15/54 is then passed to TO_DATE(:stringIni,'dd/MM/yyyy hh24:mi'), and because this sting doesnt match format string, the error is thrown: ORA-01861: literal does not match format string

Spring Data - Custom DTO Query with filtering

I have a complexe application and I need to retrieve and filter 1000~5000 object for an xls export. Each object having multiple eager relationship (I need them for the export).
If I retrieve all the objects and their relationship as it is, I got some stackoverflow error.
Generaly when I need to make a big export, in order to make it efficient I use a DTO object with an #Query like this :
public interface myRepository extends JpaRepository<Car, Long> {
#Query("SELECT new com.blabla.myCustomObject(p.name, p.surname, c.model, c.number ...) "
+ "FROM Car c "
+ "LEFT JOIN c.person p "
+ "WHERE ... ")
List<myCustomObject> getExportCustomObject();
}
The problem is that the #Query is static and I want to add dynamic filter to my Query (Specifications, Criteria or some other system...)
How to do it ?
Specification cannot be used because this is only the where clause.
But you can use Criteria API. Here's an example. The BasicTeacherInfo is the DTO:
CriteriaQuery<BasicTeacherInfo> query = cb.createQuery(BasicTeacherInfo.class);
Root<Teacher> teacher = query.from(Teacher.class);
query.multiselect(teacher.get("firstName"),teacher.get("lastName"));
List<BasicTeacherInfo> results = em.createQuery(query).getResultList();
You can use #Param annotation to pass dynamic values to HQL, something like:
#Query("SELECT new com.blabla.myCustomObject(p.name, p.surname, c.model, c.number ...) "
+ "FROM Car c "
+ "LEFT JOIN c.person p "
+ "WHERE c.status = :status AND p.name = :name")
List<myCustomObject> getExportCustomObject(
#Param("status") Integer status,
#Param("name") String name
);
Below is one of the possible way where you can try to add offset and limit into your query you can make it dynamic with the help off placeholders.
Below is an sample pseudo code for reference:
Dao Layer:
#Query(value="SELECT e FROM tablename e WHERE condition_here ORDER BY e.id offset :offset limit:limit ")
public returnType yourMethod(String name, int offset, int limit);
Service Layer:
long count = number of records in db.
int a = // number of records to be fetched on each iterations
int num_iterations = count % a ;
int additionalrecords = count / a;
int start= 0;
while(num_iterations>0)
{
dao.yourMethod(start,a);
start = start+a;
count--;
// write your data to excel here
}
dao.yourMethod(start,additionalrecords);
Hope it is helpful.

DTO Projection of Query with #ElementCollection causes 'Unable to locate appropriate constructor on class' error

I'm writing custom query using projection to reduce amount of queries in one session, when couple of field from antity are needed and using Fetch join.
Unfortunately got stuck into a problem when one types in returned dto is a collection.
I have following class with #ElementCollection (siplified version for this purpose):
#Entity
class MyClass(
val someString: String,
#ElementCollection
#Enumerated(EnumType.STRING)
var enums: MutableSet<Enum>,
#ManyToOne
var condition: Condition
//And so on...
)
And DTO used for projection:
data class ProjectionDTO(
val aString: String,
val enumList: List<Enum>
)
But when using query:
fun query(condition: Condition): List<ProjectionDTO> =
entityManager.createQuery(
"""SELECT NEW com.xxx.ProjectionDTO( m.someString, e ) FROM MyClass m
INNER JOIN FETCH m.enums e
WHERE m.condition = :condition""", ProjectionDTO::class.java)
.setParameter("condition", condition)
.resultList
}
I get following exception:
Exception:[org.hibernate.hql.internal.ast.QuerySyntaxException: Unable to locate appropriate constructor on class [com.xxx.ProjectionDTO]. Expected arguments are: com.xxx.String, com.xxx.Enum [SELECT NEW com.xxx.ProjectionDTO( m.someString, e ) FROM MyClass m
INNER JOIN FETCH m.enums e
WHERE m.condition = :condition]]
Already tried different types of collection, additional constructors and calling field of given #ElementCollection like e.enum in query params.
Is it possible to return a list (or other collection) from this kind of query? If so, how do I tackle it?
It is not allowed to use collection path expressions in constructor query. link
Put just root entity into constructor:
SELECT NEW com.xxx.ProjectionDTO(m) WHERE m.condition = :condition
In constructor assign m.enums, m.someString to fields.

JpaRepository: using repositories in #PostPersist

I have two entities
class A {
...
Integer totalSum;
Set<B> b;
}
class B {
...
Integer value;
A container;
}
I want a.totalSum to be sum of each a.b.value;
Maybe best solution is view in db, but I want listen changes in BRepository and update A-records.
I do:
#PostPersist
#PostUpdate
#PostRemove
private void recalculateSums(B b) {
AutowireHelper.autowire(this, this.aRepository);
AutowireHelper.autowire(this, this.bRepository);
A a = b.getContainer();
a.setTotalSum(bRepository.sumByA(s));
aRepository.save(s);
}
And in BRepository:
#Query("select sum(b.value) from B b where b.container = :a")
Long sumByA(#Param("a") A a);
And I have error: org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint ["PRIMARY KEY ON PUBLIC.B(ID)"; SQL statement:
insert into b (VALUE, CONTAINER_ID, ID) values (?, ?, ?)
What I'm doing wrong?
If I do
a.setTotalSum(a.getTotalSum()+1);
aRepository.save(s);
All works, but if I do
a.setTotalSum(a.getTotalSum()+1);
aRepository.saveAndFlush(s);
I have the same error.
I can see why you might want to do this but I think it creates a whole load of potential issues around data integrity.
If you want to have the sum of B available for an A without having to load and iterate all B there are three other options which you could implement all of which would be more robust than your proposal.
As you noted, create a view. You can then Map an Entity say, SummaryData, to this view and map a one-to-one from A to SummaryData so that you can do a.getSummaryData().getTotalSum();
Alternatively, you could use the Hibernate specific #Formula annotation which will issue an inline select when the entity is loaded.
#Formula("(select sum(value) from B b inner join A a where b.a_id= a.id and a.id =id
private int totalSum;
Finally, and depending on the capabilities of your database, you could create a Virtual Column and Map a property as you would for any other field.
1 and 3 obviously require schema changes but your app would remain JPA compliant. 2 does not require any schema change but breaks strict JPA compliance if that was important.

How would I count these records using Linq to NHibernate (3.1)

I have an entity defined like so:
public class TestEntity : EntityBase
{
public string Source {get;set;}
public bool Suppressed {get;set;}
/* other stuff */
}
I want to show an HTML table that looks like:
Source Suppressed Not Suppressed
-------------------------------------------------
Source1 30 1225
Soure 7 573
My first attempt to query this was:
from e in _session.Query<TestEntity>()
group e by e.Source into g1
select new
{
Source = g1.Key,
Suppressed = g1.Sum(x=>x.Suppressed ? 1 : 0),
NotSuppressed = g1.Sum(x=>x.Suppressed ? 0 : 1),
}
But of course, Linq choked on the ternary expression when converting it to SQL. Any alternative ways to do this?
Edit: I tried Dmitry's suggestion, and it returns the same counts for both. The SQL generated by his suggestion is:
select
customer0_.SourceA as col_0_0_,
cast(count(*) as INT) as col_1_0_,
cast(count(*) as INT) as col_2_0_
from
dbo.Customers customer0_
group by
customer0_.SourceA
Which obviously isn't what I want...
What about doing something like g1.Count(x => x.Suppressed == true)?

Resources