I am trying to do "sql interpreter" in my web-app, only for CRUD. Everything work fine, I am using method prepareStatement() to execute query. But I have problem with operation select :
When I use the select operation only for 1 field, then parsing to a string gives a fairly good result:
for(String x: resultList){
System.out.println(x);
}
Is there any way to execute:
SELECT field_1, field_2, field_3 FROM table;
and print result in console, with some neat form without use Entites?
Well if it isn't possible, is there any way to generate entity "on the fly"? I mean generate Entities using java code.
You could use a native query and explicitly specify which columns you want to select:
String sql = "SELECT field_1, field_2, field_3 FROM table";
Query q = em.createNativeQuery(sql);
List<Object[]> results = q.getResultList();
for (Object[] r : results) {
System.out.println("(field_1, field_2, field_3) = (" + r[0] + ", " + r[1] + ", " + r[2] + ")");
}
With Spring Data JPA Projections
If you already use some entities and Spring Repository then you can add this code to one of them. Thanks Spring Data JPA Projections.
public interface SomeEntityRepository extends Repository<SomeEntity, Long> {
#Query(value = "SELECT field_1, field_2, field_3 FROM table", nativeQuery = true)
List<TableDto> getFromTable();
}
Where TableDto:
public interface TableDto{
Long getField_1();
String getField_2();
String getField_3();
}
With Spring JdbcTemplate
Or use Spring JdbcTemplate:
String query = "SELECT field_1, field_2, field_3 FROM table where id = ?";
List<TableDto> resluts = jdbcTemplate.queryForObject(
query, new Object[] { id }, new TableDtoRowMapper());
public class TableDtoRowMapper implements RowMapper<TableDto> {
#Override
public TableDtomapRow(ResultSet rs, int rowNum) throws SQLException {
TableDto dto = new TableDto();
dto.setField_1(rs.getString("field_1"));
dto.setField_2(rs.getString("field_2"));
dto.setField_3(rs.getString("field_3"));
return dto;
}
}
In this example TableDto is real class with getters and setters.
Related
I have these 3 independent table i.e Student, Teacher and Subject. Independent here refers that there is no relation in these tables.
I want the count of all these tables . SQL query looks like -
SELECT
(SELECT COUNT(*) FROM Student as ST,
(SELECT COUNT(*) FROM Teacher as TE,
(SELECT COUNT(*) FROM Subject as SU
Now I want to map this result into dto .
The DTO looks like
public class CountDto{
Integer student;
Integer teacher;
Integer subject;
}
The repository call looks like -
#Query(value = "SELECT\r\n"
+ " (SELECT COUNT(*) FROM Student) as ST, \r\n"
+ " (SELECT COUNT(*) FROM Teacher) as TE,\r\n"
+ " (SELECT COUNT(*) FROM Subject) as SU", nativeQuery = true)
public CountDto getCount();
While calling this function I get following error stating
"message": "Failed to convert from type [java.lang.Object[]] to type [com.rbl.mdm.dto.CountDto ] for value '{16, 16 , 34}'; nested exception is org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.lang.Integer] to type [com.rbl.mdm.dto.CountDto]"
How should I convert my response to desired DTO ?
You can declare CountDto as a public interface and it should work. It's called a Projection in terms of Spring. Or you can use SqlResultSetMapping or ConstructorResult along with your class.
you don't have to implement it by any entity class, just create it like an independent interface even within the repository file:
public interface StudentRepository extends CrudRepository<Student, Long> {
#Query(value = "SELECT\r\n"
+ " (SELECT COUNT(*) FROM Student) as ST, \r\n"
+ " (SELECT COUNT(*) FROM Teacher) as TE,\r\n"
+ " (SELECT COUNT(*) FROM Subject) as SU", nativeQuery = true)
Counts getCount();
public static interface Counts {
Integer getST();
Integer getTE();
Integer getSU();
}
}
So here is the answer to do -
The DTO will look like
public CountDto{
private Integer studentTotal;
private Integer teacherTota;
private Integer subjectTotal;
}
The repository call -
#Query(value = "SELECT\r\n"
+ " (SELECT COUNT(*) FROM Student) as ST, \r\n"
+ " (SELECT COUNT(*) FROM Teacher) as TE,\r\n"
+ " (SELECT COUNT(*) FROM Subject) as SU", nativeQuery = true)
public Map<String,Integer> getCount();
Finally the serviceImpl--
public CountDto getCount{
CountDto CountValue = new CountDto();
Map<String,Integer> map = repository.getCount();
for (Map.Entry<String,Integer> entry : map.entrySet()) {
if(entry.getKey().equals("ST"))
CountValue.setStudentTotal( entry.getValue());
if(entry.getKey().equals("TE"))
CountValue.setTeacherTotal( entry.getValue());
if(entry.getKey().equals("SU"))
CountValue.setSubjectTotal( entry.getValue());
}
return CountValue ;
}
But the solution seems quite complex to me. Any simpler approach required.
In pure Hibernate/JPA usage this is a simple dynamic-instantiation query (what JPA calls a "constructor result"):
select new CountDTO(
(SELECT COUNT(*) FROM Student) as ST,
...
)
No idea here about Spring, though a word of warning.. in trying to be useful, it often "gets in the way". Not sure that is the case here... Have you tried straight Hibernate/JPA?
I am trying to setup a repository call to retrieve the ID's of a list of test results ids used in the GROUP_BY. I can get this to work using createNativeQuery but I am unable to get this to work using Spring's JPA with the FUNCTION call.
FUNCTION('string_agg', FUNCTION('to_char',r.id, '999999999999'), ',')) as ids
I am using Spring Boot 1.4, hibernate and PostgreSQL.
Question
If someone can please help me out to setup the proper function call
shown below in the JPA example it would be much appreciated.
Update 1
After implementing the custom dialect it looks like its trying to cast the function to a long. Is the Function code correct?
FUNCTION('string_agg', FUNCTION('to_char',r.id, '999999999999'), ','))
Update 2
After looking into the dialect further it looks like you need to register the return type for your function otherwise it will default to a long. See below for a solution.
Here is my code:
DTO
#Data
#NoArgsConstructor
#AllArgsConstructor
public class TestScriptErrorAnalysisDto {
private String testScriptName;
private String testScriptVersion;
private String checkpointName;
private String actionName;
private String errorMessage;
private Long count;
private String testResultIds;
}
Controller
#RequestMapping(method = RequestMethod.GET)
#ResponseBody
public ResponseEntity<Set<TestScriptErrorAnalysisDto>> getTestScriptErrorsByExecutionId(#RequestParam("executionId") Long executionId) throws Exception {
return new ResponseEntity<Set<TestScriptErrorAnalysisDto>>(testScriptErrorAnalysisRepository.findTestScriptErrorsByExecutionId(executionId), HttpStatus.OK);
}
Repository trying to use Function Not working
#Query(value = "SELECT new com.dto.TestScriptErrorAnalysisDto(r.testScriptName, r.testScriptVersion, c.name, ac.name, ac.errorMessage, count(*) as ec, FUNCTION('string_agg', FUNCTION('to_char',r.id, '999999999999'), ',')) "
+ "FROM Action ac, Checkpoint c, TestResult r " + "WHERE ac.status = 'Failed' " + "AND ac.checkpoint = c.id " + "AND r.id = c.testResult " + "AND r.testRunExecutionLogId = :executionId "
+ "GROUP by r.testScriptName, r.testScriptVersion, c.name, ac.name, ac.errorMessage " + "ORDER by ec desc")
Set<TestScriptErrorAnalysisDto> findTestScriptErrorsByExecutionId(#Param("executionId") Long executionId);
Repository using createNativeQuery working
List<Object[]> errorObjects = entityManager.createNativeQuery(
"SELECT r.test_script_name, r.test_script_version, c.name as checkpoint_name, ac.name as action_name, ac.error_message, count(*) as ec, string_agg(to_char(r.id, '999999999999'), ',') as test_result_ids "
+ "FROM action ac, checkpoint c, test_result r " + "WHERE ac.status = 'Failed' " + "AND ac.checkpoint_id = c.id "
+ "AND r.id = c.test_result_id " + "AND r.test_run_execution_log_id = ? "
+ "GROUP by r.test_script_name, r.test_script_version, c.name, ac.name, ac.error_message " + "ORDER by ec desc")
.setParameter(1, test_run_execution_log_id).getResultList();
for (Object[] obj : errorObjects) {
for (Object ind : obj) {
log.debug("Value: " + ind.toString());
log.debug("Value: " + ind.getClass());
}
}
Here was the documents I found on FUNCTION
4.6.17.3 Invocation of Predefined and User-defined Database Functions
The invocation of functions other than the built-in functions of the Java Persistence query language is supported by means of the function_invocation syntax. This includes the invocation of predefined database functions and user-defined database functions.
function_invocation::= FUNCTION(function_name {, function_arg}*)
function_arg ::=
literal |
state_valued_path_expression |
input_parameter |
scalar_expression
The function_name argument is a string that denotes the database function that is to be invoked. The arguments must be suitable for the database function that is to be invoked. The result of the function must be suitable for the invocation context.
The function may be a database-defined function or a user-defined function. The function may be a scalar function or an aggregate function.
Applications that use the function_invocation syntax will not be portable across databases.
Example:
SELECT c
FROM Customer c
WHERE FUNCTION(‘hasGoodCredit’, c.balance, c.creditLimit)
In the end the main piece which was missing was defining the functions by creating a new class to extend the PostgreSQL94Dialect. Since these functions were not defined for the dialect they were not processed in the call.
public class MCBPostgreSQL9Dialect extends PostgreSQL94Dialect {
public MCBPostgreSQL9Dialect() {
super();
registerFunction("string_agg", new StandardSQLFunction("string_agg", new org.hibernate.type.StringType()));
registerFunction("to_char", new StandardSQLFunction("to_char"));
registerFunction("trim", new StandardSQLFunction("trim"));
}
}
The other issue was that a type needed to be set for the return type of the function on registration. I was getting a long back because by default registerFunction returns a long even though string_agg would return a string in a sql query in postgres.
After updating that with new org.hibernate.type.StringType() it worked.
registerFunction("string_agg", new StandardSQLFunction("string_agg", new org.hibernate.type.StringType()));
I would like to fetch serveral rows of data from a table.
I have a dao, sql and some parameters.
public static final String SELECT = ""
+ "SELECT NAME, SURNAME, AGE, LEVEL"
+ "FROM MYTABLE "
+ "WHERE "
public static final String CONDITION = ""
+ "SURNAME = :" + PARAM_SURNAME
+ "AND LEVEL = :" + PARAM_LEVEL;
For retrieval of a signle object I would do something like that:
public MyObject getMyThing(
final String surname,
final Integer level) {
final MapSqlParameterSource parameters = new MapSqlParameterSource()
.addValue(PARAM_SURNAME, surname)
.addValue(PARAM_LEVEL, level);
final String query = SELECT + CONDITION;
return myNamedTemplate.query(query, parameters, new MyObjectRowMapper());
}
And it is be just fine. However I would also like to use one SQL to retrieve several objects from databse. I know about the "IN" clause but it is not good enough as its usage will affect performance in the bad way.
What I would want is something that results in a following query:
SELECT STUFF FROM TABLE WHERE
//and the part I am interested in:
(SURNAME = :PARAM_SURNAME
AND LEVEL = :PARAM_LEVEL)
OR
(SURNAME = :PARAM_SURNAME
AND LEVEL = :PARAM_LEVEL)
OR
(SURNAME = :PARAM_SURNAME
AND LEVEL = :PARAM_LEVEL)
//and so on...
So the question:
How can I achieve this using JDBC Named Template in Spring? I cannot figure out how to do parameter mapping. Desired method in DAO could be something like that:
public List<MyObject> getMyThings(
final List<String> surnames,
final List<Integer> levels) {
final MapSqlParameterSource parameters = // magic
final String query = // more magic
return myNamedTemplate.query(query, parameters, new MyObjectRowMapper());
}
This is my function to get all rows from table player.
public List<Player> getAllPlayer() {
String sql = "SELECT * FROM PLAYER";
List<Player> lstPlayers = getJdbcTemplate().query(
sql, new BeanPropertyRowMapper(Player.class)
);
return lstPlayers;
}
I want to get data from another table 'student' using this same function.
Is it possible to get data from both table using same function by changing the
above code something like below ?
public List<Object> getData(String TableName) {
String sql = "SELECT * FROM " + TableName;
List<Object> lstPlayers = getJdbcTemplate().query(
sql, new BeanPropertyRowMapper(Object.class)
);
return lstPlayers;
}
You could do something like the following
List<Player> players = getData(Player.class);
List<Student> students = getData(Student.class);
public List<T> getData(Class<T> clazz) {
String sql = "SELECT * FROM " + clazz.getSimpleName();
List<T> lstPlayers = getJdbcTemplate().query(
sql,
new BeanPropertyRowMapper(clazz));
return lstPlayers;
}
However in real life there's not much advantage. It's not that common to want to load all the contents of a database table at once due to performance and other reasons, so this method won't be as helpful as you think.
Following up on a question I posted yesterday: How to populate POJO class from custom Hibernate query?
Can someone show me an example of how to code the following SQL in Hibernate, and get the results correctly?
SQL:
select firstName, lastName
from Employee
What I'd like to do, if it's possible in Hibernate, is to put the results in their own base class:
class Results {
private firstName;
private lastName;
// getters and setters
}
I believe it's possible in JPA (using EntityManager), but I haven't figured out how to do it in Hibernate (using SessionFactory and Session).
I'm trying to learn Hibernate better, and even this "simple" query is proving confusing to know what form Hibernate returns the results, and how to map the results into my own (base) class. So at the end of the DAO routine, I'd do:
List<Results> list = query.list();
returning a List of Results (my base class).
select firstName, lastName from Employee
query.setResultTransformer(Transformers.aliasToBean(MyResults.class));
You can't use above code with Hibernate 5 and Hibernate 4 (at least Hibernate 4.3.6.Final), because of an exception
java.lang.ClassCastException: com.github.fluent.hibernate.request.persistent.UserDto cannot be cast to java.util.Map
at org.hibernate.property.access.internal.PropertyAccessMapImpl$SetterImpl.set(PropertyAccessMapImpl.java:102)
The problem is that Hibernate converts aliases for column names to upper case — firstName becomes FIRSTNAME. And it try to find a getter with name getFIRSTNAME(), and setter setFIRSTNAME() in the DTO using such strategies
PropertyAccessStrategyChainedImpl propertyAccessStrategy = new PropertyAccessStrategyChainedImpl(
PropertyAccessStrategyBasicImpl.INSTANCE,
PropertyAccessStrategyFieldImpl.INSTANCE,
PropertyAccessStrategyMapImpl.INSTANCE
);
Only PropertyAccessStrategyMapImpl.INSTANCE suits, in opinion of Hibernate, well. So after that it tries to do conversion (Map)MyResults.
public void set(Object target, Object value, SessionFactoryImplementor factory) {
( (Map) target ).put( propertyName, value );
}
Don't know, it is a bug or feature.
How to solve
Using aliases with quotes
public class Results {
private String firstName;
private String lastName;
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
String sql = "select firstName as \"firstName\",
lastName as \"lastName\" from Employee";
List<Results> employees = session.createSQLQuery(sql).setResultTransformer(
Transformers.aliasToBean(Results.class)).list();
Using a custom result transformer
Another way to solve the problem — using a result transformer that ignores method names case (treat getFirstName() as getFIRSTNAME()). You can write your own or use FluentHibernateResultTransformer. You will not need to use quotes and aliases (if you have column names equal to DTO names).
Just download the library from the project page (it doesn't need additional jars): fluent-hibernate.
String sql = "select firstName, lastName from Employee";
List<Results> employees = session.createSQLQuery(sql)
.setResultTransformer(new FluentHibernateResultTransformer(Results.class))
.list();
This transformer can be used for nested projections too: How to transform a flat result set using Hibernate
See AliasToBeanResultTransformer:
Result transformer that allows to transform a result to a user specified class which will be populated via setter methods or fields matching the alias names.
List resultWithAliasedBean = s.createCriteria(Enrolment.class)
.createAlias("student", "st")
.createAlias("course", "co")
.setProjection( Projections.projectionList()
.add( Projections.property("co.description"), "courseDescription" )
)
.setResultTransformer( new AliasToBeanResultTransformer(StudentDTO.class) )
.list();
StudentDTO dto = (StudentDTO)resultWithAliasedBean.get(0);
Your modified code:
List resultWithAliasedBean = s.createCriteria(Employee.class, "e")
.setProjection(Projections.projectionList()
.add(Projections.property("e.firstName"), "firstName")
.add(Projections.property("e.lastName"), "lastName")
)
.setResultTransformer(new AliasToBeanResultTransformer(Results.class))
.list();
Results dto = (Results) resultWithAliasedBean.get(0);
For native SQL queries see Hibernate documentation:
13.1.5. Returning non-managed entities
It is possible to apply a ResultTransformer to native SQL queries, allowing it to return non-managed entities.
sess.createSQLQuery("SELECT NAME, BIRTHDATE FROM CATS")
.setResultTransformer(Transformers.aliasToBean(CatDTO.class))
This query specified:
the SQL query string
a result transformer
The above query will return a list of CatDTO which has been instantiated and injected the values of NAME and BIRTHNAME into its corresponding properties or fields.
You need to use a constructor and in the hql use new. I let you the code example taken from this question: hibernate HQL createQuery() list() type cast to model directly
class Result {
private firstName;
private lastName;
public Result (String firstName, String lastName){
this.firstName = firstName;
this.lastName = lastName;
}
}
then your hql
select new com.yourpackage.Result(employee.firstName,employee.lastName)
from Employee
and your java (using Hibernate)
List<Result> results = session.createQuery("select new com.yourpackage.Result(employee.firstName,employee.lastName) from Employee").list();
YMMV but I've found that the key factor is you must make sure to alias every field in your SELECT clause with the SQL "AS" keyword. I've never had to use quotes around the alias names. Also, in your SELECT clause use the case and punctuation of the actual columns in your database and in the aliases use the case of the fields in your POJO. This has worked for me in Hibernate 4 and 5.
#Autowired
private SessionFactory sessionFactory;
...
String sqlQuery = "SELECT firstName AS firstName," +
"lastName AS lastName from Employee";
List<Results> employeeList = sessionFactory
.getCurrentSession()
.createSQLQuery(sqlQuery)
.setResultTransformer(Transformers.aliasToBean(Results.class))
.list();
If you have multiple tables you can use table aliases in the SQL as well. This contrived example with an additional table named "Department" uses more traditional lower case and underscores in database field names with camel case in the POJO field names.
String sqlQuery = "SELECT e.first_name AS firstName, " +
"e.last_name AS lastName, d.name as departmentName" +
"from Employee e, Department d" +
"WHERE e.department_id - d.id";
List<Results> employeeList = sessionFactory
.getCurrentSession()
.createSQLQuery(sqlQuery)
.setResultTransformer(Transformers.aliasToBean(Results.class))
.list();
java.lang.ClassCastException: "CustomClass" cannot be cast to java.util.Map.
This issue appears when the columns specified in SQL Query doesn't match with the columns of the mapping class.
It may be due to:
Non-matching casing of column name or
The column names are not matching or
column exist in query but missing in class.
JPQL case from hibernate 5.4:
Query<Employee> queryList = session.createQuery("select new xxx.xxx.Employee(e.firstName,e.lastName) from Employee e", Employee.class);
List<Employee> list = queryList.list();
Query<Long> queryCount = session.createQuery("select count(*) from Employee", Long.class);
Long count = queryCount.getSingleResult();
The select statement in JPQL is exactly the same as for HQL except that JPQL requires a select_clause, whereas HQL does not.
setResultTransformer #Deprecated It should not be used
more on Hibernate_User_Guide.html#hql-select
In case you have a native query, all answers here use deprecated methods for newer versions of Hibernate, so if you are using 5.1+ this is the way to go:
// Note this is a org.hibernate.query.NativeQuery NOT Query.
NativeQuery query = getCurrentSession()
.createNativeQuery(
"SELECT {y.*} , {x.*} from TableY y left join TableX x on x.id = y.id");
// This maps the results to entities.
query.addEntity("x", TableXEntity.class);
query.addEntity("y", TableYEntity.class);
query.list()
Below is a result transformer that ignores case:
package org.apec.abtc.dao.hibernate.transform;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
import org.hibernate.HibernateException;
import org.hibernate.property.access.internal.PropertyAccessStrategyBasicImpl;
import org.hibernate.property.access.internal.PropertyAccessStrategyChainedImpl;
import org.hibernate.property.access.internal.PropertyAccessStrategyFieldImpl;
import org.hibernate.property.access.internal.PropertyAccessStrategyMapImpl;
import org.hibernate.property.access.spi.Setter;
import org.hibernate.transform.AliasedTupleSubsetResultTransformer;
/**
* IgnoreCaseAlias to BeanResult Transformer
*
* #author Stephen Gray
*/
public class IgnoreCaseAliasToBeanResultTransformer extends AliasedTupleSubsetResultTransformer
{
/** The serialVersionUID field. */
private static final long serialVersionUID = -3779317531110592988L;
/** The resultClass field. */
#SuppressWarnings("rawtypes")
private final Class resultClass;
/** The setters field. */
private Setter[] setters;
/** The fields field. */
private Field[] fields;
private String[] aliases;
/**
* #param resultClass
*/
#SuppressWarnings("rawtypes")
public IgnoreCaseAliasToBeanResultTransformer(final Class resultClass)
{
if (resultClass == null)
{
throw new IllegalArgumentException("resultClass cannot be null");
}
this.resultClass = resultClass;
this.fields = this.resultClass.getDeclaredFields();
}
#Override
public boolean isTransformedValueATupleElement(String[] aliases, int tupleLength) {
return false;
}
/**
* {#inheritDoc}
*/
#Override
public Object transformTuple(final Object[] tuple, final String[] aliases)
{
Object result;
try
{
if (this.setters == null)
{
this.aliases = aliases;
setSetters(aliases);
}
result = this.resultClass.newInstance();
for (int i = 0; i < aliases.length; i++)
{
if (this.setters[i] != null)
{
this.setters[i].set(result, tuple[i], null);
}
}
}
catch (final InstantiationException | IllegalAccessException e)
{
throw new HibernateException("Could not instantiate resultclass: " + this.resultClass.getName(), e);
}
return result;
}
private void setSetters(final String[] aliases)
{
PropertyAccessStrategyChainedImpl propertyAccessStrategy = new PropertyAccessStrategyChainedImpl(
PropertyAccessStrategyBasicImpl.INSTANCE,
PropertyAccessStrategyFieldImpl.INSTANCE,
PropertyAccessStrategyMapImpl.INSTANCE
);
this.setters = new Setter[aliases.length];
for (int i = 0; i < aliases.length; i++)
{
String alias = aliases[i];
if (alias != null)
{
for (final Field field : this.fields)
{
final String fieldName = field.getName();
if (fieldName.equalsIgnoreCase(alias))
{
alias = fieldName;
break;
}
}
setters[i] = propertyAccessStrategy.buildPropertyAccess( resultClass, alias ).getSetter();
}
}
}
/**
* {#inheritDoc}
*/
#Override
#SuppressWarnings("rawtypes")
public List transformList(final List collection)
{
return collection;
}
#Override
public boolean equals(Object o) {
if ( this == o ) {
return true;
}
if ( o == null || getClass() != o.getClass() ) {
return false;
}
IgnoreCaseAliasToBeanResultTransformer that = ( IgnoreCaseAliasToBeanResultTransformer ) o;
if ( ! resultClass.equals( that.resultClass ) ) {
return false;
}
if ( ! Arrays.equals( aliases, that.aliases ) ) {
return false;
}
return true;
}
#Override
public int hashCode() {
int result = resultClass.hashCode();
result = 31 * result + ( aliases != null ? Arrays.hashCode( aliases ) : 0 );
return result;
}
}
Writing (exist this type of Challenges working with hibernate)
Custom Queries
Custom Queries with Optional Parameters
Mapping Hibernate Custom query results to Custom class.
I am not saying about custom EntityRepository interface which extends JpaRepository on SpringBoot which you can write custom Query with #Query -> here you can't write query with optional params
e.g. if param is null don't append it in query string. And you can use Criteria api of hibernate but it not recommended in their documentation because of performance issue...
But exist simple and error prone and performance good way...
Write your own QueryService class which are methods will get
string(answer for first and second problem) sql and will map result to
Custom class (third problem) with it's any association #OneToMany, #ManyToOne ....
#Service
#Transactional
public class HibernateQueryService {
private final Logger log = LoggerFactory.getLogger(HibernateQueryService.class);
private JpaContext jpaContext;
public HibernateQueryService(JpaContext jpaContext) {
this.jpaContext = jpaContext;
}
public List executeJPANativeQuery(String sql, Class entity){
log.debug("JPANativeQuery executing: "+sql);
EntityManager entityManager = jpaContext.getEntityManagerByManagedType(Article.class);
return entityManager.createNativeQuery(sql, entity).getResultList();
}
/**
* as annotation #Query -> we can construct here hibernate dialect
* supported query and fetch any type of data
* with any association #OneToMany and #ManyToOne.....
*/
public List executeHibernateQuery(String sql, Class entity){
log.debug("HibernateNativeQuery executing: "+sql);
Session session = jpaContext.getEntityManagerByManagedType(Article.class).unwrap(Session.class);
return session.createQuery(sql, entity).getResultList();
}
public <T> List<T> executeGenericHibernateQuery(String sql, Class<T> entity){
log.debug("HibernateNativeQuery executing: "+sql);
Session session = jpaContext.getEntityManagerByManagedType(Article.class).unwrap(Session.class);
return session.createQuery(sql, entity).getResultList();
}
}
Use case - you can write any type condition about query params
#Transactional(readOnly = true)
public List<ArticleDTO> findWithHibernateWay(SearchFiltersVM filter){
Long[] stores = filter.getStores();
Long[] categories = filter.getCategories();
Long[] brands = filter.getBrands();
Long[] articles = filter.getArticles();
Long[] colors = filter.getColors();
String query = "select article from Article article " +
"left join fetch article.attributeOptions " +
"left join fetch article.brand " +
"left join fetch article.stocks stock " +
"left join fetch stock.color " +
"left join fetch stock.images ";
boolean isFirst = true;
if(!isArrayEmptyOrNull(stores)){
query += isFirst ? "where " : "and ";
query += "stock.store.id in ("+ Arrays.stream(stores).map(store -> store.toString()).collect(Collectors.joining(", "))+") ";
isFirst = false;
}
if(!isArrayEmptyOrNull(brands)){
query += isFirst ? "where " : "and ";
query += "article.brand.id in ("+ Arrays.stream(brands).map(store -> store.toString()).collect(Collectors.joining(", "))+") ";
isFirst = false;
}
if(!isArrayEmptyOrNull(articles)){
query += isFirst ? "where " : "and ";
query += "article.id in ("+ Arrays.stream(articles).map(store -> store.toString()).collect(Collectors.joining(", "))+") ";
isFirst = false;
}
if(!isArrayEmptyOrNull(colors)){
query += isFirst ? "where " : "and ";
query += "stock.color.id in ("+ Arrays.stream(colors).map(store -> store.toString()).collect(Collectors.joining(", "))+") ";
}
List<Article> articles = hibernateQueryService.executeHibernateQuery(query, Article.class);
/**
* MapStruct [http://mapstruct.org/][1]
*/
return articles.stream().map(articleMapper::toDto).collect(Collectors.toList());
}