Spring JPA Criteria Builder specific columns - java

I'm using JPA criteria builder to query Database for the records. However, I want to return specific columns from a table using JPA criteria API.
Currently, my implementation returns full object. But, I want to return only specific columns and those columns could be dynamic. Below code will query on Person table and returns Full-Object of Person.
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Person> query = cb.createQuery(Person.class);
Root<Person> root = query.from(Person.class);
query.select(cb.equal(root.get("id"), 1));
TypedQuery<Person> result = em.createQuery(query);
List<Person> personList = result.getResultList();
I know we can create a minified class like PersonMini which will include only a specific field like below but, In my case, the columns could be dynamic and based on the user's preference.
class PersonMini {
private long id;
private String name;
// .. constructor and getter/setter
}
It's not feasible to create a separate mini-classes of Person. Has anyone solved such issue before or could you please suggest another approach if that would solve my problem.
EDIT:
As mentioned in here.
If the type of the criteria query is CriteriaQuery for some user-defined class X (i.e., a criteria query object created by passing a X class argument to the createQuery method), the elements of the list passed to the multiselect method will be passed to the X constructor and an instance of type X will be returned for each row.
If I use multiselect method and use PersonMini Class then I will need a constructor for each and every combination of the fields. Because the selection of the fields is done by Users. selection of fields is not static.
Suppose, in PersonMini class I have 3 fields. Id, Name, and City then User could choose Id and Name, Name and City, City(alone), Id(alone), Id and City. For each combination I need constructor otherwise it will throw error runtime.
For two-three field, it is feasible to have a different combination of constructor but for 15-20 field it is not.

Related

Hibernate: Fetching columns with their aliases

Consider this trivial query:
SELECT 1 as first, 2 as second
When using Hibernate we can then do something like:
em.createNativeQuery(query).fetchResultList()
However, there seem to be no way of getting the aliases (or column names). This would be very helpful for creating List<Map<String, Object>> where each map would be a row with their aliases, for instance in this case: [{first: 1, second: 2}].
Is there a way to do something like that?
I would suggest a bit different approach which may meet your needs.
In JPA 2.1 there is a feature called "result set mapping".
Basically you have to define a POJO class which would hold the result values (all the values must be passed using the constructor):
public class ResultClass{
private String fieldOne;
private String fieldTwo;
public ResultClass(String fieldOne, String fieldTwo){
this.fieldOne = fieldOne;
this.fieldTwo = fieldTwo;
}
}
Then you have to declare the mapping on one of your entities (does not matter on which, it just has to be a declated #Entity):
#SqlResultSetMapping(name="ResultMapping", classes = {
#ConstructorResult(targetClass = ResultClass.class,
columns = {#ColumnResult(name="columnOne"), #ColumnResult(name="columnTwo")})
})
The columnOne and columnTwo are aliases as declared in the select clause of the native query.
And finally use in the query creation:
List<ResultClass> results = em.createNativeQuery(query, "ResultMapping").getResultList();
In my opinion this is more elegant and "a level above" solution as you are not working with a generic Map key/values pairs but with a concrete POJO class.
You can use ResultTransformer interface . Implement custom mapper for mapping values with aliases.
here is example https://vladmihalcea.com/why-you-should-use-the-hibernate-resulttransformer-to-customize-result-set-mappings/
with ResultTransformer you can easy customize result set type , especially if you need aliases

VO and DAO, How to design classes and execute query

I have a 3 tables:
Item: item_id (pk), short_description, ...
SupplierItem: item_id (fk), supplier_id (fk), vendor_product_number, ...
Supplier: item_supplier (pk), name, ...
Relation between Item and Supplier is many to many. SupplierItem is
intermediate table.
I want use VO and DAO.
How to design this in VO (Java)?
After, How can I do the following query in java code.
select i.item_id, i.short_description, s.vendor_product_number as FONUA_PRODUCT_CODE
from item i
left join supplier_item s
on i.item_id=s.item_id
where ((i.item_id=:item_id) OR :item_id IS NULL)
and i.parent_item_id is null
order by vendor_product_number DESC"
I still do not understand the concept in use VO and DAO.
Thanks
DAO would be the java file where you define your query and get the results of the query call on the database.
The results from the query will be saved by setting them as values to the properties of a particular VO.
For Example:
Your query returns ,
item_id, short_description, vendor_product_number
So let us say that you will have to create another java file say ItemVO.java and declare the particular properties of the ItemVO object,
For Ex:
private String itemId;
private String shortDescription;
private String vendorShortNum;
/*Define your getters and setters*/
In the DAO file you will have to map the query results to the VO file's objects.
ItemVO itemVO= new ItemVO();
itemVO.setItemId(/*the particular column value from the query result*/);
itemVO.setShortDescription(/*the particular column value from the query result*/);
itemVO.setVendorShortNum(/*the particular column value from the query result*/);

Is it possible to add native query in hibernate for using as entity select?

When a class annotated with #Entity annotaion hibernate uses queries like SELECT * FROM class_name. But for some reason it is needed to run custom select query for class. Is there a way to provide such query?
Yes, according to the official hibernate reference, you can do it like:
sess.createSQLQuery("SELECT * FROM CATS").addEntity(Cat.class);
or:
sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE FROM CATS").addEntity(Cat.class);
Criteria cr = getCurrentSession().createCritiera(class_name.class)
.setProjection(Projections.projectionList()
.add(Projections.property("id"), "id")
.add(Projections.property("name"), "name"))
.setResultTransformer(Transformers.aliasToBean(class_name.class));
List<class_name> list = cr.list();
Act as select id,name from class_name this will return List of class_name containing objects only have id and name and the rest of variables in class_name will be null (as you only selected id and name)
I would recommend you getHibernatTemplate() from HibernateDaoSupport. Afterwards you can call find("from Class where....") and you get back an object which you can cast to your java class easily.
i.e.
List < User > users = (List < User > )getHibernateTemplate("from User");

set table name with hibernate name parameters

I need to set a table name dynamically so that I use query.setText(tname,abc)
e.g: select a.name from :tname where a.id = '2'
I used setText() because when I use setString() it says "tname is a invalid parameter" because I assume that Hibernate adds '' when setting string parameters.
But even setText() does not help and gives the same exception.
How can I set the table name dynamically?
Reply to PSR:
So you mean replace table name as a java string replacement. But then we can not take support of sql injections prevention etc from hibernate right? Also How we bind parameters in hibernate in a situation where like statement,
Eg: name like "%:name%"
This also gives me Illegal argument exception: Parameter does not exist as a named parameter when i try to bind it using query.setString(name,"def");
Hibernate will not do this for you, because it works with PreparedStatements, and you can't prepare a statement where the table being queried isn't known yet.
I don't see why you would be exposing table names to end users, so preventing SQL injection doing a regular string substitution should be easy. You use some sort of business logic to determine the correct table from a list that only you know. The table name isn't coming from user input at all.
Depending on your choice of RDBMS, you may find a discriminator column, or table inheritance with partitioning to be a better way of handling a situation where identical queries are made against different tables.
It is not possible to set table name dynamically.You can set dynamically column names.it is not possible to set table name
try like this
select a.name from '+table name+'where a.id = '2'
In my opinion, There are 2 ways to resolve this issue:
1- If you are using Spring and Hibernate together, you could use SpEL and it would be like #{#entityName} as it is described here
#Entity
public class User {
#Id
#GeneratedValue
Long id;
String lastname;
}
public interface UserRepository extends JpaRepository<User,Long> {
#Query("select u from #{#entityName} u where u.lastname = ?1")
List<User> findByLastname(String lastname);
}
2-You could use CriteriaBuilder like
CriteriaQuery<YourEntity> cr = cb.createQuery(YourEntity.class);
Root<YourEntity> root = cr.from(YourEntity.class);
cr.select(root);
I copied the source codes from the provided links and they are described there much better

How to selectively fetch items from a certain table via JPA

Environment: JPA 1, Hibernate 3.3.x
I have an JPA entity class (User), how do I selectively fetch member variables say (first_name, last_name) instead of fetching all user attributes using the JPA api.
Do you mean something like this (and in that case, the result of your query will be an Object[]):
SELECT u.firstName, u.lastName FROM User u
Alternatively, you could use a constructor expression in the SELECT clause:
SELECT NEW com.acme.example.UserDetails(u.firstName, u.lastName) FROM User u
The class used in the NEW is not necessarily an Entity, it just has to provide a proper constructor.
If you query for more columns you'll get an object result which you'll have to cast to an object array to retrieve values. Better is to create a viewObject class in which you directly store the results:
select new full.package.name.UserView(u.firstName, u.LastName) from User u
where UserView looks like:
class UserView {
String firstName, String lastName;
// getters, setters/constuctor
}

Categories