I have a query like below that doesn't work.
It throws IndexOutOfBoundsException: Index: 0, Size: 0
(sorry I can't post the whole stacktrace as it is in my workstation (remote))
This started to be an issue when I upgraded from Spring Boot 1.5.2.RELEASE to 1.5.22.RELEASE
#Transactional(readOnly = true)
#Query(value = "SELECT q.id as someId, q.name as someName from Quote q where q.id in (:quoteIds)")
List<Tuple> selectSomeThings(#Param("quoteIds") List<Long> quoteIds)
Now when I try to just select 1 column like so,
#Transactional(readOnly = true)
#Query(value = "SELECT q.id as someId from Quote q where q.id in (:quoteIds)")
List<Tuple> selectSomeThings(#Param("quoteIds") List<Long> quoteIds)
or
#Transactional(readOnly = true)
#Query(value = "SELECT q.name as someName from Quote q where q.id in (:quoteIds)")
List<Tuple> selectSomeThings(#Param("quoteIds") List<Long> quoteIds)
it works. Just doesn't work when I select 2 at the same time.
I would recommend to return a Bean instance from the repository methode.
Example here:
#Query("SELECT " +
" new com.path.to.Tuple(v.id, v.name) " +
"FROM " +
" Quote v where v.id in (:quoteIds) ")
List<Tuple> find(#Param("quoteIds") List<Long> quoteIds);
I have a many to many relationship in spring boot with User and Role. I have three tables in database (Role_utilisateur, users_roles and utilisateurs). In my repository, ireturn List of my entite called RoleEntite which have two attributes (nom and id).
#Query(value = "SELECT ROLE_UTILISATEUR.NOM, ROLE_UTILISATEUR.ROLE_ID " + "FROM ROLE_UTILISATEUR "
+ "INNER JOIN USERS_ROLES on users_roles.role_id = ROLE_UTILISATEUR.ROLE_ID "
+ "INNER JOIN UTILISATEURS on utilisateurs.utilisateur_id = USERS_ROLES.UTILISATEUR_ID "
+ "WHERE UTILISATEURS.UTILISATEUR_ID = :userId", nativeQuery = true)
List<RoleEntite> findUsersRolesNative(#Param("userId") Long userId);
When i call my function in
private List<GrantedAuthority> getGrantedAuthorities(UserEntite user) {
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
List<RoleEntite> roleList = userRepository.findUsersRolesNative(user.getId());
for (RoleEntite roleEntite : roleList) {
authorities.add(new SimpleGrantedAuthority(roleEntite.getNom()));
}
return authorities;
}
I get this error in my java eclipse
org.springframework.security.authentication.InternalAuthenticationServiceException: Failed to convert from type [java.lang.Object[]] to type [com.id.firstSpringBoot.entite.RoleEntite] for value '{ADMIN, 1}'; nested exception is org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.lang.String] to type [com.id.firstSpringBoot.entite.RoleEntite]
How can i resolve this error and get the name ADMIN in my function getGrantedAuthorities can do this
The problem is you're selecting two columns and returning a list of entities. You need to either select the entity in your query or return the list of two values in a collection.
To return entities you would need to convert your query to JPQL. The following is a JPQL representation of your native query.
I've had to do some guessing for the joins (I assume you have some JPA related entities):
#Query(value = "SELECT RoleEntite r "
+ "INNER JOIN UserRole r.user u "
+ "INNER JOIN Utilisateurs u.Utilisateur x "
+ "WHERE x.UTILISATEUR_ID = :userId")
List<RoleEntite> findUsersRolesNative(#Param("userId") Long userId);
If you go the native query route:
#Query(value = "SELECT ROLE_UTILISATEUR.NOM, ROLE_UTILISATEUR.ROLE_ID " + "FROM ROLE_UTILISATEUR "
+ "INNER JOIN USERS_ROLES on users_roles.role_id = ROLE_UTILISATEUR.ROLE_ID "
+ "INNER JOIN UTILISATEURS on utilisateurs.utilisateur_id = USERS_ROLES.UTILISATEUR_ID "
+ "WHERE UTILISATEURS.UTILISATEUR_ID = :userId", nativeQuery = true)
List<Object[]> findUsersRolesNative(#Param("userId") Long userId);
The returned list should yield:
for (Object[] obj : list) {
nom = obj[0];
roleId = obj[1];
// ... do what ever you want with the values
}
Hi what I am trying to achieve is to get SQL native query result map into my DTO in java spring jpa repository, how do I do this properly? I try several code, but it does not work, here is what I tried:
First try :
#Repository
public interface StockRepository extends RevisionRepository<Stock, Long, Integer>, JpaRepository<Stock, Long> {
#Query(value = "SELECT stock_akhir.product_id AS productId, stock_akhir.product_code AS productCode, SUM(stock_akhir.qty) as stockAkhir "
+ "FROM book_stock stock_akhir "
+ "where warehouse_code = (:warehouseCode) "
+ "AND product_code IN (:productCodes) "
+ "GROUP BY product_id, product_code, warehouse_id, warehouse_code", nativeQuery = true)
List<StockAkhirDto> findStockAkhirPerProductIn(#Param("warehouseCode") String warehouseCode, #Param("productCodes") Set<String> productCode);
}
once I execute the function, I got this error:
No converter found capable of converting from type
[org.springframework.data.jpa.repository.query.AbstractJpaQuery$TupleConverter$TupleBackedMap]
to type [com.b2bwarehouse.Dto.RequestDto.StockDto.StockAkhirDto]
Second try :
#Repository
public interface StockRepository extends RevisionRepository<Stock, Long, Integer>, JpaRepository<Stock, Long> {
#Query(value = "SELECT new com.b2bwarehouse.Dto.RequestDto.StockDto.StockAkhirDto(stock_akhir.product_id AS productId, stock_akhir.product_code AS productCode, SUM(stock_akhir.qty) as stockAkhir) "
+ "FROM book_stock stock_akhir "
+ "where warehouse_code = (:warehouseCode) "
+ "AND product_code IN (:productCodes) "
+ "GROUP BY product_id, product_code, warehouse_id, warehouse_code", nativeQuery = true)
List<StockAkhirDto> findStockAkhirPerProductIn(#Param("warehouseCode") String warehouseCode, #Param("productCodes") Set<String> productCode);
}
in second here is the error:
could not extract ResultSet; SQL [n/a]; nested exception is
org.hibernate.exception.SQLGrammarException: could not extract
ResultSet
below is my DTO:
#Data
#AllArgsConstructor
#NoArgsConstructor
public class StockAkhirDto {
private Long productId;
private String productCode;
private Integer stockAkhir;
}
How should I correct my code? So, can I get the result into my DTO?
You can define the following named native query with appropriate sql result set mapping:
import javax.persistence.NamedNativeQuery;
import javax.persistence.SqlResultSetMapping;
import javax.persistence.ConstructorResult;
import javax.persistence.ColumnResult;
#Entity
#NamedNativeQuery(
name = "find_stock_akhir_dto",
query =
"SELECT " +
" stock_akhir.product_id AS productId, " +
" stock_akhir.product_code AS productCode, " +
" SUM(stock_akhir.qty) as stockAkhir " +
"FROM book_stock stock_akhir " +
"where warehouse_code = :warehouseCode " +
" AND product_code IN :productCodes " +
"GROUP BY product_id, product_code, warehouse_id, warehouse_code",
resultSetMapping = "stock_akhir_dto"
)
#SqlResultSetMapping(
name = "stock_akhir_dto",
classes = #ConstructorResult(
targetClass = StockAkhirDto.class,
columns = {
#ColumnResult(name = "productId", type = Long.class),
#ColumnResult(name = "productCode", type = String.class),
#ColumnResult(name = "stockAkhir", type = Integer.class)
}
)
)
public class SomeEntity
{
}
and then use it:
#Repository
public interface StockRepository extends RevisionRepository<Stock, Long, Integer>, JpaRepository<Stock, Long> {
#Query(name = "find_stock_akhir_dto", nativeQuery = true)
List<StockAkhirDto> findStockAkhirPerProductIn(
#Param("warehouseCode") String warehouseCode,
#Param("productCodes") Set<String> productCode
);
}
i find a way which is not usual, but i find data type called "Tuple" when i try to use QueryDsl to solved this problem, but i won't recommend QueryDsl if you are just getting started just like me. Lets focus on how i do it with "Tuple"
i changed my return type to Tuple, here is how my repository looked like :
#Repository
public interface StockRepository extends RevisionRepository<Stock, Long, Integer>, JpaRepository<Stock, Long> {
#Query(value = "SELECT stock_akhir.product_id AS productId, stock_akhir.product_code AS productCode, SUM(stock_akhir.qty) as stockAkhir "
+ "FROM book_stock stock_akhir "
+ "where warehouse_code = (:warehouseCode) "
+ "AND product_code IN (:productCodes) "
+ "GROUP BY product_id, product_code, warehouse_id, warehouse_code", nativeQuery = true)
List<Tuple> findStockAkhirPerProductIn(#Param("warehouseCode") String warehouseCode, #Param("productCodes") Set<String> productCode);
}
and then here in my service class, since it's returned as Tuple, i have to map the column one by one manually, here is my service function looked like :
public List<StockTotalResponseDto> findStocktotal() {
List<Tuple> stockTotalTuples = stockRepository.findStocktotal();
List<StockTotalResponseDto> stockTotalDto = stockTotalTuples.stream()
.map(t -> new StockTotalResponseDto(
t.get(0, String.class),
t.get(1, String.class),
t.get(2, BigInteger.class)
))
.collect(Collectors.toList());
return stockTotalDto;
}
the column field start with 0, in this way i can keep my query neat at Repository level. But i will accept SternK answer as the accepted answer because that way worked too, i will keep my answer here if someone need something like this
Create standard native #Query
#Query(value = "select id, age, name FROM Person WHERE age=?1", nativeQuery=true)
List<PersonView> getPersonsByAge(int age);
and an interface
public interface PersonView {
Long getId();
Integer getAge();
String getName();
}
columns are matched by order (not by names).
In this way, you have a native query, no entities and not too much boiler plate code (aka many annotations).
However, resulting views (Jdk proxy etc.) are very slow in access, i had some code doing some grouping over a stream, and it's 10x !! slower than with standard DTO/Pojos !
so at the end, I don't use nativeQuery anymore, but:
SELECT new com.my_project.myDTO(p.id, p.age, p.name) .....
The second variant is pretty close. You just have to remove the aliases for the constructor expression:
new com.b2bwarehouse.Dto.RequestDto.StockDto.StockAkhirDto(
stock_akhir.product_id,
stock_akhir.product_code,
SUM(stock_akhir.qty)
)
should work.
Another valid option based on Sternk answer woul be as follows
You can define the following named native query with appropriate sql result set mapping:
resources/META-INF/orm.xml
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_2_0.xsd"
version="2.0">
<named-native-query name="find_stock_akhir_dto" result-class="com.fullyqualified.name.SomeEntity"
result-set-mapping="stock_akhir_dto">
<query><![CDATA[
SELECT
stock_akhir.product_id AS productId,
stock_akhir.product_code AS productCode,
SUM(stock_akhir.qty) as stockAkhir
FROM book_stock stock_akhir
where warehouse_code = :warehouseCode
AND product_code IN :productCodes
GROUP BY product_id, product_code, warehouse_id, warehouse_code]]></query>
</named-native-query>
</entity-mappings>
import javax.persistence.NamedNativeQuery;
import javax.persistence.SqlResultSetMapping;
import javax.persistence.ConstructorResult;
import javax.persistence.ColumnResult;
#Entity
#SqlResultSetMapping(
name = "stock_akhir_dto",
classes = #ConstructorResult(
targetClass = StockAkhirDto.class,
columns = {
#ColumnResult(name = "productId", type = Long.class),
#ColumnResult(name = "productCode", type = String.class),
#ColumnResult(name = "stockAkhir", type = Integer.class)
}
)
)
public class SomeEntity
{
}
and then use it:
#Repository
public interface StockRepository extends RevisionRepository<Stock, Long, Integer>, JpaRepository<Stock, Long> {
#Query(name = "find_stock_akhir_dto", nativeQuery = true)
List<StockAkhirDto> findStockAkhirPerProductIn(
#Param("warehouseCode") String warehouseCode,
#Param("productCodes") Set<String> productCode
);
}
I have a HQL query with a JOIN but the where clause (instrPrice.date BETWEEN :dateFrom AND :dateTo ) on the joined entity doesn't work. The query always returns all the records of instrumentPrice instead of limiting the result by the dates.
NamedQuery
#NamedQuery(name = "findAllPrices",
query = "SELECT DISTINCT taPat FROM TaPatternInstrument taPat "
+ "LEFT JOIN FETCH taPat.instrument instr "
+ "LEFT JOIN instr.instrumentPriceList instrPrice "
+ "WHERE taPat.id = :taPatternInstrumentId "
+ "AND instrPrice.date BETWEEN :dateFrom AND :dateTo ")
Service which calls the Query
public TaPatternInstrument findAllPrices(int taPatternInstrumentId, LocalDate dateFrom, LocalDate dateTo) {
TypedQuery<TaPatternInstrument> typedQuery = createNamedQuery("findAllPrices",
TaPatternInstrument.class);
typedQuery.setParameter("taPatternInstrumentId", taPatternInstrumentId);
typedQuery.setParameter("dateFrom", dateFrom);
typedQuery.setParameter("dateTo", dateTo);
return typedQuery.getSingleResult();
}
Entities
public abstract class BaseEntity implements Serializable {
#Id
#Column(name = "id")
#GeneratedValue(strategy =
GenerationType.IDENTITY)
protected int id; ...
}
public class TaPatternInstrument extends BaseEntity {
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "instrument_id", nullable = false, foreignKey = #ForeignKey(name =
"tapatterninstrument_instrument_fk"))
private Instrument instrument;
}
public class Instrument extends BaseEntity {
#OneToMany(mappedBy = "instrument", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<InstrumentPrice> instrumentPriceList;
}
Generated SQL
SELECT DISTINCT tapatterni0_.id AS id1_34_0_,
...
FROM tapatterninstrument tapatterni0_
LEFT OUTER JOIN instrument instrument1_
ON tapatterni0_.instrument_id = instrument1_.id
LEFT OUTER JOIN instrumentprice instrument2_
ON instrument1_.id = instrument2_.instrument_id
WHERE tapatterni0_.id = ?
AND ( instrument2_.date BETWEEN ? AND ? )
The solution is to add a FETCH on instrumentPriceList : LEFT JOIN FETCH instr.instrumentPriceList instrPrice
#NamedQuery(name = "findAllPrices",
query = "SELECT DISTINCT taPat FROM TaPatternInstrument taPat "
+ "LEFT JOIN FETCH taPat.instrument instr "
+ "LEFT JOIN FETCH instr.instrumentPriceList instrPrice "
+ "LEFT JOIN taPat.taPatternInstrumentPriceList taPatpr "
+ "WHERE taPat.id = :taPatternInstrumentId "
+ "AND instrPrice.date BETWEEN :dateFrom AND :dateTo ")
The FETCH forces Hibernate to retrieve the Entity (InstrumentPrice) immediately at the first DB request. And thus the where clause is taken into account.
Without FETCH, the Entity InstrumentPrice is only retrieved from the DB when the method getInstrumentPriceList of the Entity Instrument is called (an additional call to the DB is performed). And with this additional call to the DB, the where clause is not taken into account anymore, thus retrieving all records from Entity instrumentPrice.
I am using Spring Data JPA repositories (1.7.2) and I am typically facing the following scenario:
entities have lazy-loaded collections
those collections are sometimes eagerly fetched (via JPAQL fetch join)
repositories often return Page<Foo> instead of List<Foo>
I need to provide countQuery to every #Query that uses fetch joins on a repository that returns a Page. This issue has been discussed in this StackOverflow question
My typical repository method looks like this:
#Query(value = "SELECT e FROM Employee e LEFT JOIN FETCH e.addresses a " +
"WHERE e.company.id = :companyId " +
"AND e.deleted = false " +
"AND e.primaryAddress.deleted = false " +
"ORDER BY e.id, a.id",
countQuery="SELECT count(e) FROM Employee e WHERE e.companyId = :companyId AND e.deleted = false AND e.primaryAddress.deleted = false"
)
Page<Employee> findAllEmployeesWithAddressesForCompany(#Param("companyId") long companyId, Pageable pageable);
Obviously, it's not very DRY. You can tell that I am repeating all of the conditions in both value and countQuery parameters. How do I stay DRY here?
You could do something like this
public interface MyRepository extends JpaRepository {
public static final String WHERE_PART = "e.companyId = :companyId AND e.deleted = false AND e.primaryAddress.deleted = false ";
#Query(value = "SELECT e FROM Employee e LEFT JOIN FETCH e.addresses a " +
"WHERE " + MyRepository.WHERE_PART
"ORDER BY e.id, a.id",
countQuery="SELECT count(e) FROM Employee e WHERE " + MyRepository.WHERE_PART
)
Page<Employee> findAllEmployeesWithAddressesForCompany(#Param("companyId") long companyId, Pageable pageable);