How to work with SQL Views in Spring JPA? - java

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:)

Related

Select only specific columns from joined tables (Many-to-Many) in Spring Data JPA

The purpose is to select columns from joined tables (Many-to-Many).
The problem i have is to select two columns from a joined Many-to-Many table.
I'am using Springboot 2.3 and Spring data Jpa.
I have this data model, and what i want to fetch are the blue boxed fields
So the native query could look like this (if i am right ...)
SELECT bg.id, bg.name, p.name, c.name, c.short_desc FROM boardgame as bg
JOIN boardgame_category bgc on bg.id = bgc.fk_game
JOIN publisher p on bg.fk_publisher = p.id
JOIN category c on bgc.fk_category = c.id
WHERE bg.id = :id
I first tried to work with dto in JPQL statment
public class BoardgameDto {
private long id;
private String name;
private String publisherName;
private Set<CatregoryDto> categoryDto;
// setter, getter etc...
}
public class CategoryDto {
private String name;
private String shortDesc;
// setter, getter etc...
}
The JQPL query could look like this , but it doesn't work (IDE shows errors on CategoryDto)
/* THIS DOESN'T WORK */
SELECT new org.moto.tryingstuff.dto.BoardgameDto(bg.id, bg.name, p.name,
new org.moto.tryingstuff.dto.CategoryDto(c.name, c.short_desc)) FROM Boardgame as bg, Publisher as p, Category as c
Well, I think the problem I have with this way of doing is that the dto's contructor can't receive a collection as written here, and i think neither another contructor in parameter.
Then i started looking at Criteria Queries, especialy multiselect, Tuple, Dto, but it look like i had same kind of problems so i didn't dive deeper into it.
Finally i used a JpaRepository and it's findById() method like this
public interface BoardgameRepository extends JpaRepository<Boardgame, Long> {
}
// In a test or service method
Boardgame game = repository.findById(long id);
Then i filter the fields i need to keep through mappings in Service or Controller layer. So the front only received need datas.
But it feel a bit overkill,
Am I missing something, any part of the framework that would allow me to select only specific columns?
As you wrote, you can't use a collection as the parameter of a constructor expression. That's because the expression gets applied to each record in the result set. These records are a flat data structure. They don't contain any collections. Your database returns a new record for each element in that collection instead.
But your constructor expression fails for a different reason. You're trying to combine 2 constructor expressions, and that's not supported. You need to remove the 2nd expression and perform that operation within the constructor of your DTO.
So, your query should look like this:
SELECT new org.moto.tryingstuff.dto.BoardgameDto(bg.id, bg.name, p.name, c.name, c.short_desc) FROM Boardgame as bg <Your JOIN CLAUSES HERE>
And the constructor of your BoardgameDto like this:
public class BoardgameDto {
public BoardgameDto(Long id, String gameName, String publisherName, String categoryName, String description) {
this.id = id;
this.name = gameName;
this.publisherName = publisherName;
this.category = new Category(categoryName, description);
}
...
}

Spring Boot JPA: Mapping one entity to multiple (a lot) tables with same columns

I have a lot of (like 60+) tables which have the same schema and similar names:
log_2020_07_01
log_2020_07_02
... and so on.
They have the same columns: id, site, size. Each of the table contains around 2 million rows.
I've read Hibernate and tables with same data/columns but with different table names which suggests to use hibernate alone. I hope after seven years maybe there's something new we could do with JPA.
In JPA, is it possible to just write one entity class and let the code to handle which table to use?
E.G.,
for(int i=0;i<60;i++) {
// read from the i-th table.
}
First of all, we could use a middleware to make the sharding transparent to users for middle/large projects.
Here is a quick work around for my small project (and I'm the only developer working it):
Step 1, create an inteceptor:
public class MySqlInterceptor extends EmptyInterceptor {
private String entityName;
#Setter
private int tableId;
protected MySqlInterceptor() {
super();
}
public MySqlInterceptor(String entityName) {
this.entityName = entityName;
}
#Override
public String onPrepareStatement(String sql) {
// Here is the trick.
String modifiedSql = sql.replaceAll(entityName, entityName + tableId);
log.debug("{}", modifiedSql);
return modifiedSql;
}
}
Step 2, hock up the interceptor:
MySqlInterceptor mySqlInterceptor = new MySqlInterceptor("temp");
mySqlInterceptor.setTableId(tableId);
session = entityManagerFactory.unwrap(SessionFactory.class).withOptions().interceptor(mySqlInterceptor).openSession();
Explanation:
Hibernate is using JDBC to communicate with the database. The interceptor will change the table name in the sql from an entity's name (in my case it's temp) to a real table name (temp1, temp2, ...) at runtime.
P.S., be careful when you use multi-thread.

Spring data JPA query behaves unexpectedly

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.

Spring Data JPA: How can Query return Non- Entities Objects or List of Objects?

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());

Selecting fields on Spring Data

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.

Categories

Resources