Spring Data JPA @Query

översikt

Spring Data ger många sätt att definiera en fråga som vi kan utföra. En av dessa är @Query-anteckningen.

i denna handledning visar vi hur du använder @Query-anteckningen i Spring Data JPA för att utföra både JPQL och native SQL-frågor.

vi visar också hur man bygger en dynamisk fråga när @Query-anteckningen inte räcker.

vidare läsning:

härledda frågemetoder i Spring Data JPA Repositories

utforska frågederiveringsmekanismen i Spring Data JPA.
Läs mer →

Spring Data JPA @modifiera Annotation

skapa DML-och DDL-frågor i Spring Data JPA genom att kombinera @Query och @ modifiera annoteringar
Läs mer →

Välj Query

för att definiera SQL att köra för en Fjäderdatalagringsmetod kan vi kommentera metoden med @Query — annotationen-dess värdeattribut innehåller JPQL eller SQL att köra.

@Query-anteckningen har företräde framför namngivna frågor, som kommenteras med @NamedQuery eller definieras i en orm.xml-fil.

det är ett bra sätt att placera en frågedefinition precis ovanför metoden i förvaret snarare än inuti vår domänmodell som namngivna frågor. Förvaret är ansvarigt för uthållighet, så det är en bättre plats att lagra dessa definitioner.

2.1. JPQL

som standard använder frågedefinitionen JPQL.

Låt oss titta på en enkel lagringsmetod som returnerar aktiva användarenheter från databasen:

@Query("SELECT u FROM User u WHERE u.status = 1")Collection<User> findAllActiveUsers();

2.2. Native

vi kan också använda native SQL för att definiera vår fråga. Allt vi behöver göra är att ställa in värdet på attributet nativeQuery till true och definiera den inbyggda SQL-frågan i anteckningens värdeattribut:

@Query( value = "SELECT * FROM USERS u WHERE u.status = 1", nativeQuery = true)Collection<User> findAllActiveUsersNative();

definiera ordning i en fråga

vi kan skicka en ytterligare parameter av typen Sort till en Fjäderdatametoddeklaration som har @Query-anteckningen. Det kommer att översättas till ORDER BY-klausulen som skickas till databasen.

3.1. Sortering för JPA tillhandahålls och härledda metoder

för de metoder vi får ut ur rutan som findAll (Sortera) eller de som genereras genom att analysera metodsignaturer, kan vi bara använda objektegenskaper för att definiera vår sort:

userRepository.findAll(Sort.by(Sort.Direction.ASC, "name"));

föreställ dig nu att vi vill sortera efter längden på en namnegenskap:

userRepository.findAll(Sort.by("LENGTH(name)"));

när vi kör ovanstående kod får vi ett undantag:

org.springframework.data.kartläggning.PropertyReferenceException: ingen egenskap längd (namn) hittades för Typ användare!

3.2. JPQL

när vi använder JPQL för en frågedefinition, kan Spring Data hantera sortering utan problem – allt vi behöver göra är att lägga till en metodparameter av typ Sortera:

@Query(value = "SELECT u FROM User u")List<User> findAllUsers(Sort sort);

vi kan ringa den här metoden och skicka en Sorteringsparameter, som beställer resultatet med namnet egenskapen för användarobjektet:

userRepository.findAllUsers(Sort.by("name"));

och eftersom vi använde @ Query-anteckningen kan vi använda samma metod för att få den sorterade listan över användare med längden på deras namn:

userRepository.findAllUsers(JpaSort.unsafe("LENGTH(name)"));

det är viktigt att vi använder JpaSort.osäker () för att skapa en Sorteringsobjektinstans.

när vi använder:

Sort.by("LENGTH(name)");

då får vi exakt samma undantag som vi såg ovan för findAll () – metoden.

när Spring Data upptäcker den osäkra sorteringsordningen för en metod som använder @Query — anteckningen, lägger den bara till sorteringsklausulen till frågan-den hoppar över att kontrollera om egenskapen att Sortera efter tillhör domänmodellen.

3.3. Native

när @Query-anteckningen Använder native SQL är det inte möjligt att definiera en Sort.

om vi gör det får vi ett undantag:

org.springframework.data.jpa.Arkiv.fråga.InvalidJpaQueryMethodException: Det går inte att använda inbyggda frågor med dynamisk sortering och/eller paginering

som undantaget säger stöds inte sorteringen för inbyggda frågor. Felmeddelandet ger oss en antydan om att paginering också kommer att orsaka ett undantag.

det finns dock en lösning som möjliggör paginering, och vi täcker den i nästa avsnitt.

paginering

paginering tillåter oss att returnera bara en delmängd av ett helt resultat på en sida. Detta är till exempel användbart när du navigerar genom flera sidor med data på en webbsida.

en annan fördel med paginering är att mängden data som skickas från server till klient minimeras. Genom att skicka mindre data kan vi i allmänhet se en förbättring av prestanda.

4.1. JPQL

att använda paginering i jpql-frågedefinitionen är enkelt:

@Query(value = "SELECT u FROM User u ORDER BY id")Page<User> findAllUsersWithPagination(Pageable pageable);

vi kan skicka en PageRequest-parameter för att få en sida med data.

Pagination stöds också för inbyggda frågor men kräver lite extra arbete.

4.2. Native

vi kan aktivera paginering för infödda frågor genom att förklara en ytterligare attribut countQuery.

detta definierar SQL att köra för att räkna antalet rader i hela resultatet:

@Query( value = "SELECT * FROM Users ORDER BY id", countQuery = "SELECT count(*) FROM Users", nativeQuery = true)Page<User> findAllUsersWithPagination(Pageable pageable);

4.3. Spring Data JPA versioner före 2.0.4

ovanstående lösning för infödda frågor fungerar bra för Spring Data JPA versioner 2.0.4 och senare.

före den versionen, när vi försöker utföra en sådan fråga, får vi samma undantag som vi beskrev i föregående avsnitt om sortering.

vi kan övervinna detta genom att lägga till en ytterligare parameter för paginering i vår fråga:

@Query( value = "SELECT * FROM Users ORDER BY id \n-- #pageable\n", countQuery = "SELECT count(*) FROM Users", nativeQuery = true)Page<User> findAllUsersWithPagination(Pageable pageable);

i exemplet ovan lägger vi till ”\n– #pageable\n” som platshållare för pagineringsparametern. Detta berättar Spring Data JPA hur man tolka frågan och injicera sidbar parameter. Denna lösning fungerar för H2-databasen.

vi har täckt hur man skapar enkla select-frågor via JPQL och native SQL. Därefter visar vi hur du definierar ytterligare parametrar.

indexerade frågeparametrar

det finns två möjliga sätt att vi kan skicka metodparametrar till vår fråga: indexerade och namngivna parametrar.

i det här avsnittet täcker vi indexerade parametrar.

5.1. JPQL

för indexerade parametrar i JPQL kommer Vårdata att skicka metodparametrar till frågan i samma ordning som de visas i metoddeklarationen:

@Query("SELECT u FROM User u WHERE u.status = ?1")User findUserByStatus(Integer status);@Query("SELECT u FROM User u WHERE u.status = ?1 and u.name = ?2")User findUserByStatusAndName(Integer status, String name);

för ovanstående frågor tilldelas parametern status method till frågeparametern med index 1 och parametern name method tilldelas frågeparametern med index 2.

5.2. Native

indexerade parametrar för de ursprungliga frågorna fungerar exakt på samma sätt som för JPQL:

@Query( value = "SELECT * FROM Users u WHERE u.status = ?1", nativeQuery = true)User findUserByStatusNative(Integer status);

i nästa avsnitt visar vi ett annat tillvägagångssätt: passerar parametrar via namn.

namngivna parametrar

vi kan också skicka metodparametrar till frågan med namngivna parametrar. Vi definierar dessa med hjälp av @Param-anteckningen i vår repository method declaration.

varje parameter som kommenteras med @Param måste ha en värdesträng som matchar motsvarande jpql-eller SQL-frågeparameternamn. En fråga med namngivna parametrar är lättare att läsa och är mindre felbenägen om frågan behöver refactored.

6.1. JPQL

som nämnts ovan använder vi @ Param-anteckningen i metoddeklarationen för att matcha parametrar definierade med namn i JPQL med parametrar från metoddeklarationen:

@Query("SELECT u FROM User u WHERE u.status = :status and u.name = :name")User findUserByStatusAndNameNamedParams( @Param("status") Integer status, @Param("name") String name);

Observera att i ovanstående exempel definierade vi våra SQL-frågor och metodparametrar för att ha samma namn, men det krävs inte så länge värdesträngarna är desamma:

@Query("SELECT u FROM User u WHERE u.status = :status and u.name = :name")User findUserByUserStatusAndUserName(@Param("status") Integer userStatus, @Param("name") String userName);

6.2. Native

för den ursprungliga frågedefinitionen är det ingen skillnad i hur vi skickar en parameter via namnet till frågan i jämförelse med JPQL-vi använder @ Param-anteckningen:

@Query(value = "SELECT * FROM Users u WHERE u.status = :status and u.name = :name", nativeQuery = true)User findUserByStatusAndNameNamedParamsNative( @Param("status") Integer status, @Param("name") String name);

7. Samlingsparameter

låt oss överväga fallet när where-klausulen i vår JPQL – eller SQL-fråga innehåller in (eller inte IN) nyckelordet:

SELECT u FROM User u WHERE u.name IN :names

i det här fallet kan vi definiera en frågemetod som tar samling som en parameter:

@Query(value = "SELECT u FROM User u WHERE u.name IN :names")List<User> findUserByNameList(@Param("names") Collection<String> names);

eftersom parametern är en samling kan den användas med List, HashSet etc.

därefter visar vi hur du ändrar data med @Modifying annotation.

uppdatera frågor med @Modifying

vi kan använda @Query annotation för att ändra databasens tillstånd genom att också lägga till @Modifying annotation till förvarsmetoden.

8.1. JPQL

förvarsmetoden som ändrar data har två skillnader i jämförelse med select-frågan – den har @ Modifying-anteckningen och naturligtvis använder JPQL-frågan uppdatering istället för select:

@Modifying@Query("update User u set u.status = :status where u.name = :name")int updateUserSetStatusForName(@Param("status") Integer status, @Param("name") String name);

returvärdet definierar hur många rader exekveringen av frågan uppdateras. Både indexerade och namngivna parametrar kan användas i uppdateringsfrågor.

8.2. Native

vi kan ändra databasens tillstånd också med en inbyggd fråga. Vi behöver bara lägga till @ modify annotation:

@Modifying@Query(value = "update Users u set u.status = ? where u.name = ?", nativeQuery = true)int updateUserSetStatusForNameNative(Integer status, String name);

8.3. Infogar

för att utföra en insert-operation måste vi både tillämpa @Modifying och använda en inbyggd fråga eftersom INSERT inte är en del av JPA-gränssnittet:

@Modifying@Query( value = "insert into Users (name, age, email, status) values (:name, :age, :email, :status)", nativeQuery = true)void insertUser(@Param("name") String name, @Param("age") Integer age, @Param("status") Integer status, @Param("email") String email);

dynamisk fråga

ofta möter vi behovet av att bygga SQL-satser baserat på villkor eller datamängder vars värden endast är kända vid körning. Och i dessa fall kan vi inte bara använda en statisk fråga.

9.1. Exempel på en dynamisk fråga

låt oss till exempel föreställa oss en situation där vi måste välja alla användare vars e-post är som en från en uppsättning definierad vid körning-email1, email2,…, emailn:

SELECT u FROM User u WHERE u.email LIKE '%email1%' or u.email LIKE '%email2%' ... or u.email LIKE '%emailn%'

eftersom uppsättningen är dynamiskt konstruerad kan vi inte veta vid kompileringstid hur många liknande klausuler som ska läggas till.

i det här fallet kan vi inte bara använda @Query-anteckningen eftersom vi inte kan tillhandahålla ett statiskt SQL-uttalande.

istället, genom att implementera ett anpassat kompositförråd, kan vi utöka bas JpaRepository-funktionaliteten och ge vår egen logik för att bygga en dynamisk fråga. Låt oss ta en titt på hur man gör det här.

9.2. Anpassade Repositories och JPA Criteria API

lyckligtvis för oss, Spring ger ett sätt att utöka basförvaret genom användning av anpassade fragment gränssnitt. Vi kan sedan länka dem tillsammans för att skapa ett sammansatt arkiv.

vi börjar med att skapa ett anpassat fragmentgränssnitt:

public interface UserRepositoryCustom { List<User> findUserByEmails(Set<String> emails);}

och då ska vi genomföra det:

public class UserRepositoryCustomImpl implements UserRepositoryCustom { @PersistenceContext private EntityManager entityManager; @Override public List<User> findUserByEmails(Set<String> emails) { CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery<User> query = cb.createQuery(User.class); Root<User> user = query.from(User.class); Path<String> emailPath = user.get("email"); List<Predicate> predicates = new ArrayList<>(); for (String email : emails) { predicates.add(cb.like(emailPath, email)); } query.select(user) .where(cb.or(predicates.toArray(new Predicate))); return entityManager.createQuery(query) .getResultList(); }}

som visas ovan utnyttjade vi JPA Criteria API för att bygga vår dynamiska fråga.

vi måste också se till att inkludera Impl postfix i klassnamnet. Våren kommer att söka userrepositorycustom genomförandet som UserRepositoryCustomImpl. Eftersom fragment inte är förvar i sig, förlitar Spring sig på denna mekanism för att hitta fragmentimplementeringen.

9.3. Utöka det befintliga arkivet

Lägg märke till att alla frågemetoder från avsnitt 2 till Avsnitt 7 finns i Användarrepository.

så nu integrerar vi vårt fragment genom att utöka det nya gränssnittet i Användarrepository:

public interface UserRepository extends JpaRepository<User, Integer>, UserRepositoryCustom { // query methods from section 2 - section 7}

9.4. Med hjälp av förvaret

och slutligen kan vi ringa vår dynamiska frågemetod:

Set<String> emails = new HashSet<>();// filling the set with any number of itemsuserRepository.findUserByEmails(emails);

vi har framgångsrikt skapat ett sammansatt arkiv och kallat vår anpassade metod.

slutsats

i den här artikeln täckte vi flera sätt att definiera frågor i Spring Data JPA repository-metoder med hjälp av @Query-anteckningen.

vi lärde oss också hur man implementerar ett anpassat arkiv och skapar en dynamisk fråga.

som alltid är de fullständiga kodexemplen som används i den här artikeln tillgängliga över på GitHub.

Lämna ett svar

Din e-postadress kommer inte publiceras.