I'm trying to find information about how to select only certain fields of an entity using Spring Data (I'm using JPA). I want to select only specific information of an entity, the repository interfaces gives you the ways to return the information of the WHOLE entity!. Some times I only need 2 or 3 fields of an entity and returning 20,30, ...100.. fields may be a little overkill.
This kind of functionality is something that I would do using Hibernate Criteria Projections, or even JPA "SELECT NEW ...." queries. Don't know if it is possible with Spring Data.
Thanks.
What you can do is return a List<Object[]> from repository. Then in your service class iterate over this list and manually create the object you need. Sample repository method
#Query("select el.moduleId, el.threadId from ExceptionLog el")
public List<Object[]> tempQuery();
I think you can also do it in this way
SomeDataPOJO{
required col1
required col2
}
and then write query like this
#Query("select new SomeDataPOJO from requiredTable where xyz="abc")
public List<SomeDataPoJO> tempQuery()
Its not plain Spring Data but did you consider using Springs JdbcTemplate? Its also in the Context if you use Spring Boots Autoconfiguration and has several handlers for transforming results of the Query.
For Example for the Query SELECT a, b FROM EMPLOYEE WHERE ID = ? you could use
String query = "SELECT a, b FROM EMPLOYEE WHERE ID = ?";
List<Pair<String,Integer>> employees = jdbcTemplate.queryForObject(
query, new Object[] { id }, new ExampleRowMapper());
Where the ExampleRowMapper transforms each row from the result into your given Return type (Pair<String, Integer> in this case) and could look like
public class ExampleRowMapper implements RowMapper<Pair<String, Integer>> {
#Override
public Pair<String, Integer> mapRow(ResultSet rs, int rowNum) throws SQLException {
return Pair.of(rs.getString(1), rs.getString(2));
}
}
Example adapted from https://www.baeldung.com/spring-jdbc-jdbctemplate where you find more information.
Of course its not as typesafe as a JPQL as the Query is "raw" or "native" SQL but at least the response is again typesafe and I like it more than to return Object[] or something.
Related
I am writing an application with Spring 4 and Hibernate 5.1. I need to be able to interoperate with a legacy system that saves SQL queries using the native table and column names. I need to be able to add a Selection object to my Tuple query which uses the original column name rather then the entity field name.
I tried doing this using Hibernate with Projections.sqlProjection(column_name... and that sort of worked, but other issues are preventing me from continuing in this direction.
#Transactional(propagation = Propagation.MANDATORY)
public List<Object[]> testQuery() {
Class<?> rootClass = getEntityClassByTable("pm_project");
List<String> columnList = new ArrayList<>();
columnList.add("pm_project.pm_project_id");
columnList.add("pm_project.pm_project_name");
columnList.add("pm_project.pm_project_title");
columnList.add("pm_project.parent_id");
columnList.add("pm_project.pm_project_from_date");
CriteriaQuery<Tuple> query = criteriaBuilder.createTupleQuery();
Root<?> root = query.from(rootClass);
List<Selection> selectionList = new ArrayList<>();
for (String column : columnList) {
Selection s = nativeSelection(column);
selectionList.add(s);
}
query.multiselect(selectionList.toArray());
List<Tuple> resultList = em.createQuery(query).getResultList();
return resultList;
}
I need 'NativeSelection', something that produces a Selection for a native column name, not an entity attribute name. It would be really good if it took any sql that could be a field, because some JSON_VALUE fields might pop up.
The values of the native sql are dynamic, and I am not receiving a full SQL, only column and table names, with possible JSON_VALUE calls. I would very much like not to have to generate native sql, as I want to leverage other JPA features in my query.
If you are using native query then no need to specify entity attribute names. All you have to do is to set the value of the nativeQuery attribute to true and define the native SQL query in the value attribute of the annotation
#Query(
value = "SELECT * FROM pm_project p WHERE p.pm_project_id= 1",
nativeQuery = true)
Collection<Project> findProjectById();
You can also do it without using native query by simple JPA
List<Project> findAllByProjectId(Integer id);
I have a table "Signal" with id, volume and object_id columns.
Object_id is a foreign key. I need to retrieve each signal that has a particular object_id.
Im trying to use this Query
public interface SignalRepository extends JpaRepository<Signal, Integer> {
#Query("select s from Signal s where s.object = ?1")
Optional<List<Signal>> findSignalByObjectId(Integer objectId);
}
It doens't work. If I change "?1" to 1 it gets the hardcoded value. If I try to query the "volume", it works fine.
I get this error:
Blockquote
nested exception is java.lang.IllegalArgumentException: Parameter value [1] did not match expected type
I'd recommend you omit the query and let spring data generate one for you. So your case may be represented somehow like that (in case of proper relation mapping defined):
public interface SignalRepository extends JpaRepository<Signal, Integer> {
Optional<Signal> findByObject(YourObjectType object);
}
If you provide more info e.g. your entities - you can get more help.
You can use Spring data to generate the underlying query like this :
public interface SignalRepository extends JpaRepository<Signal, Integer> {
List<Signal> findSignalByObjectId(Integer objectId);
}
or you can write the query with this return type and parameter:
public interface SignalRepository extends JpaRepository<Signal, Integer> {
#Query("select s from Signal s where s.object = :id")
List<Signal> findSignalByObjectId(#Param("id") Integer objectId);
}
I'm currently working on a project with Spring and HANA DB.
I have already an Webservice which is working properly, but I need to select a View from my HANA DB(created outside of the Spring Webservice).
My view is made from 2 tables, 4 of its fields are SUM of one column from one of the tables(which counts how much of each of each 4 possible values and shows that as a number in each of the 4 fields).
I read about in some Spring JPA posts that it should work properly with a simple select, my questions are:
I usually make selects like this:
TypedQuery<name_of_the_model_class> query = em.createQuery("SELECT c FROM NameOfTheTable ORDER BY c.some_field DESC WHERE c.name =?1 ", name_of_the_model_class.class);
query.setParameter(1, name_to_search);
And then after that I just return the list to the Controller and the Controller shows that in a JSON.
The problem is, I have created this View in Eclipse, using directly SQL code in HANA DB, without creating it at the Webservice.
I would like to know, is there a way to select like I showed above, returning it without a model class? It would just need a class with exactly the same fields like a model class but without #Entity annotation on it?
OR is there a way to create a view like a model class in Spring JPA? If there is a way, what are the annotations to make it work?
Spring JPA would not accept me to return it without a class would it?
I read plenty posts but didn't found it, even in the Manual it doesn't give clear specification or examples.
Thanks a lot for your time, if I didn't explained it well pls tell me, I'm really stuck on this.
More info(my latest try):
My entire search function at the DAO:
public List<IncidentStatusResultsUser> getIncidentStatusByUser(String userID) throws ParseException {
List<IncidentStatusResultsUser> resultList = em.createQuery("SELECT c FROM USERSTATUS ORDER BY c.uploadDateTime DESC WHERE c.userID=?1 ").setMaxResults(48).getResultList();
Collections.reverse(resultList);
return resultList;
}
I have created a class to just receive the data with the same fields of the view:
public class IncidentStatusResultsUser {
private Date uploadDateTime ;
private int status1;
private int status2;
private int status3;
private int status4;
//All gets and sets ommited
public IncidentStatusResultsUser (Date uploadDateTime, long status1, long status2, long status3, long status4) {
this.uploadDateTime = uploadDateTime;
this.status1= status1;
this.status2= status2;
this.status3= status3;
this.status4= status4;
}
}
EDIT-2: Added public constructor inside the class.
Best Regards,
Famibica
You should be able to map your view to a JPA object as long as it has a primary key (or composite key). You can then map to a Spring JPA Reposiry so your query would go from -
TypedQuery<name_of_the_model_class> query = em.createQuery("SELECT c FROM NameOfTheTable ORDER BY c.some_field DESC WHERE c.name =?1 ", name_of_the_model_class.class);
query.setParameter(1, name_to_search);
To
findBySomeFieldOrderBySomeFieldDesc(String name_to_search)
If you want JPA returning a list of object directly, you need a DTO just like the IncidentStatusResultsUser you created. make sure you have a constructor taking all fields, something like:
public IncidentStatusResultsUser(Date uploadDateTime, int status1, int status2, int status3, int status4)
Then in your query String dtoQuery, you can do:
SELECT new IncidentStatusResultsUser(c.uploadDateTime, c.status1,c.status2,c.status3,c.status4)
FROM NameOfTheTable
ORDER BY c.some_field DESC
WHERE c.name =?1
Then you should be able to call your stuff:
List<IncidentStatusResultsUser> resultList = em.createQuery(dtoQuery).setMaxResults(48).getResultList();
Hope it works for you:)
I have these entities:
public class Order_status_sas {
private Order_sas order;
private Date lastModified;
...
}
public class Order_sas {
private long id;
...
}
My CrudRepository:
public interface StatusesWareHouseRepository extends CrudRepository<Order_status_sas, Long> {
Order_status_sas findFirstByOrderIdOrderByLastModifiedDesc(long id);
}
I expect that method findFirstByOrderIdOrderByLastModifiedDesc would return first row from table Order_status_sas, where order.id = <some_id> sorted by field lastModified, but in log I see this query:
Hibernate: select ...
from order_status_sas a
left outer join orders_sas b
on a.order_id=b.id
where b.id=?
order by a.last_modified desc
This query does not return me one row, but returns a list of rows. It seems that Spring Data do not look at word First in my method name. Also, I get an Exception:
org.springframework.dao.IncorrectResultSizeDataAccessException:
result returns more than one elements; nested exception is javax.persistence.NonUniqueResultException: result returns more than one elements
Please, tell me what I am doing wrong and how can I achieve my purpose?
EDITED:
I edited my StatusesWareHouseRepository with custom query:
#Query("select s from Order_status_sas s where s.order.id = ?1 order by s.lastModified desc limit 1")
Order_status_sas findFirstByOrderIdOrderByLastModifiedDesc(long id);
but the query, executed by Hibernate, haven't changed. It looks like this:
select ...
from order_status_sas s
where s.order_id=?
order by s.last_modified desc
OK, I understood #PriduNeemre point. Lets leave the DB model and come back to the JPA question. Here is another example:
#Entity
public class Client {
....
}
public interface ClientRepository extends CrudRepository<Client, Integer> {
Client findFirstByOrderByNameDesc();
}
Hibernate query still looks like this:
select ...
from clients c
order by c.name desc
Have you tried adding a #Query annotation (see here) on top of your findFirstByOrderIdOrderByLastModifiedDesc(..) method to specify the expected behaviour by hand? A (non-related) example on how this could work:
public interface InvoiceRepository extends JpaRepository<Invoice, Long> {
#Query("SELECT I FROM Invoice I JOIN I.customer C JOIN C.user U WHERE
U.username = :username")
public List<Invoice> findInvoicesByUsername(#Param("username")
String username);
}
Note that the query language used in the annotation body is in fact JPQL, not SQL. For more examples on the #Query annotation, see the Spring Data docs here.
PS: I'm also having conflicted feelings about your domain object structure, i.e. whether an instance of Order_sas should really be stored in an instance of Order_status_sas - shouldn't it be the other way around? Normally you would want to store the reference objects in your main domain object, not vice versa. (There's a slight possibility that I'm just not getting it right, though.)
EDIT: I would even go as far as to say that considering your current domain model, Hibernate is doing everything right except missing a LIMIT 1 clause to limit the expected resultset to one single row. The query itself is extremely inefficient, though, and could be improved by fixing your skewed domain model.
I am using spring data JPA in my project. I am playing with millions of records. I have a requirement where I have to fetch data for various tables and build a object and then paint it on a UI. Now how to achieve this my Spring data Repositories. I have read that it can be achieved by Named native queries.
If the named native query does not return an entity or a list of
entities, we can map the query result to a correct return type by
using the #SqlResultSetMapping annotation.
But when I am trying to use #SqlResultSetMapping it is taking another entityResult. Mean what I understand is that it is just transformation some query result to entity result set only, but I want a result set of non - entities objects.
#SqlResultSetMapping(
name="studentPercentile",
entities={
#EntityResult(
entityClass=CustomStudent.class,
fields={
#FieldResult(name="id", column="ID"),
#FieldResult(name="firstName", column="FIRST_NAME"),
#FieldResult(name="lastName", column="LAST_NAME")
}
)
}
)
#NamedNativeQuery(
name="findStudentPercentile",
query="SELECT * FROM STUDENT",
resultSetMapping="studentPercentile")
In above example I am just trying to get a results from student Entity into another pojo 'CustomStudent' which is not a entity. (This example I am trying to execute just for POC purpose, actual usecase is much complicated, with complicated query returning different resultset).
How to achieve above usecase? Is there any other way besides using name query that my repository method returning Non - Entities objects?
You can do something like
#NamedQuery(name="findWhatever", query="SELECT new path.to.dto.MyDto(e.id, e.otherProperty) FROM Student e WHERE e.id = ?1")
Then the MyDto object would just need a constructor defined with the correct fields i.e.
public MyDto(String id, String otherProperty) { this.id = id; this.otherProperty = otherProperty; }
I was deeply surprised when I came accross this for the first time but, yes, you can map query results using #SqlResultSetMapping only to scalars and managed entities.
The best you can do, I guess, is to skip automatic mapping. Query without mapping would return List<Object[]> and you can map it the way you need.
Another approach would be to use #MappedSuperclass. The class denoted as #MappedSuperclass (CustomStudent in your case) can be (not sure 100%, though) used in #SqlResultSetMapping. but you need to introduce inheritance hierarchy, that is your Student entity must extend CustomStudent. That would suck most of the time from the proper OO design, because inheritance would be a little bit artificial...
How about JPA 2.1 ConstructorResult ?
#SqlResultSetMapping(
name="studentPercentile",
classes={
#ConstructorResult(
targetClass=CustomStudent.class,
columns={
#ColumnResult(name="ID"),
#ColumnResult(name="FIRST_NAME"),
#ColumnResult(name="LAST_NAME")
}
)
}
)
#NamedNativeQuery(name="findStudentPercentile", query="SELECT * FROM STUDENT", resultSetMapping="studentPercentile")
We can also parse using JSON help.
Class level declaration.
#Autowired
private EntityManager em;
private ObjectMapper mapper = new ObjectMapper();
Main Code.
Query query = em.createNativeQuery(argQueryString);
NativeQueryImpl nativeQuery = (NativeQueryImpl) query;
nativeQuery.setResultTransformer(AliasToEntityMapResultTransformer.INSTANCE);
List<Map<String,Object>> result = nativeQuery.getResultList();
List<CustomStudent> resultList = result.stream()
.map(o -> {
try {
return
mapper.readValue(mapper.writeValueAsString(o),CustomStudent.class);
} catch (Exception e) {
ApplicationLogger.logger.error(e.getMessage(),e);
}
return null;
}).collect(Collectors.toList());