學習Spring-Data-Jpa(十)---注解式方法查詢之@Query、@Modifying與派生delete


1、@Query

  對於少量的查詢,使用@NamedQuery在實體上聲明查詢是一種有效的辦法,並且可以很好的工作。由於查詢本身綁定到執行它們的java方法,實際上可以通過Spring-Data-Jpa提供的@Query注解來直接綁定它們,而不是將它們注釋到domain類。這將domain類從持久化特定信息中解放出來,並將查詢共同定位到存儲庫接口。

  1.1、@Query源碼

/**
 * 直接注解在repository方法上聲明一個查找器
 *
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@QueryAnnotation
@Documented
public @interface Query {

    /**
     * 定義在執行帶有@Query注解方法時,要執行的JPA查詢。
     */
    String value() default "";

    /**
     * 定義一個特殊的count查詢,用於分頁查詢時,查找頁面元素的總個數。如果沒有配置,將根據方法名派生一個count查詢。
     */
    String countQuery() default "";

    /**
     *定義為countQuery查詢生成的投影部分,如果沒有配置countQuery和countProjection,將根據方法名派生count查詢。
     */
    String countProjection() default "";

    /**
     * 配置給定的查詢是否是原生SQL查詢,默認是false,query中為JPQL語句,為true時,query中應寫原聲SQL語句。
     */
    boolean nativeQuery() default false;

    /**
     * named query使用,如果沒有定義,@NamedQuery的名字使用{$ domainClass}.${queryMethodName}
     */
    String name() default "";

    /**
     * 返回在執行count查詢時@NamedQuery使用的name,默認是named query name 添加后綴.count。
     */
    String countName() default "";
}

  1.2、使用時,在Repository接口的方法上添加即可。

    /**
     *  通過用戶名密碼進行查詢
     * @param username username
     * @param password password
     * @return User
     */
    @Query(value = "select u from User u where u.username = ?1 and u.password = ?2 ")
    User findByUsernameAndPassword(String username,String password);

  1.3、可以識別LIKE的分隔符字符(%),並將查詢轉換為有效的JPQL查詢(刪除%)。在執行查詢時,傳遞給方法調用的參數將使用之前識別的LIKE模式進行擴充。

    /**
     * 查詢以...開頭的phone用戶
     * @param phone phone
     * @return list
     */
    @Query(value = "select u from User u where u.phone like ?1% ")
    List<User> findByPhoneIsStartingWith(String phone);

  1.4、Spring-Data-Jpa目前不支持對nativeQuery=true時的Sort動態排序,對於原生SQL來說,它不能可靠的執行這種操作。但是可以通過指定count查詢來使用分頁。

    /**
     * 根據性別查詢並分頁,原生SQL,不能使用SEX枚舉,要使用String
     * @param sex sex
     * @param pageable pageable
     * @return page
     */
    @Query(value = "SELECT * FROM cfq_jpa_user WHERE sex = ?1 ",
            countQuery = "SELECT count(*) FROM cfq_jpa_user WHERE sex = ?1 ",
            nativeQuery = true)
    Page<User> findBySexString(String sex, Pageable pageable);

  1.5、默認情況下,Spring-Data-Jpa拒絕任何包含函數調用的Order實例,可以通過起別名或JpaSort來替代。

  接口方法;

    /**
     *  測試Order中不支持函數
     * @param sex sex
     * @param sort sort
     * @return list
     */
    @Query(value = "select u.id,length(u.email) as em_len from User u where u.sex = ?1 ")
    List<Object[]> findByAsArrayAndSort(Sex sex,Sort sort);

  測試用例:

    /**
     * Sort中不支持使用函數,可以使用別名或JpaSort替代
     */
    @Test
    void findByAsArrayAndSort(){

        //sort指向domain模型中有效的屬性,jpa會自動為我們加上表的別名,不需要自己添加,添加就會報錯哦。
        List<Object[]> result1 = userRepository.findByAsArrayAndSort(Sex.MAN, Sort.by("email"));

        //Sort不支持使用函數
        //Sort expression 'length(email): ASC' must only contain property references or aliases used in the select clause. If you really want to use something other than that for sorting, please use JpaSort.unsafe(…)!
        assertThrows(InvalidDataAccessApiUsageException.class,() -> userRepository.findByAsArrayAndSort(Sex.MAN, Sort.by("length(email)")));

        //使用JpaSort的unsafe來使用函數
        List<Object[]> result2 = userRepository.findByAsArrayAndSort(Sex.MAN, JpaSort.unsafe("length(email)"));

        //可以使用別名來進行排序
        List<Object[]> result3 = userRepository.findByAsArrayAndSort(Sex.MAN, Sort.by("em_len"));
    }

  1.6、默認情況下,Spring-Data-Jpa是基於位置的參數綁定,當重構關於參數位置的查詢方法時容易出錯,可以使用@Param指定名稱,進行名稱綁定。如果參數名稱和定義的名稱一致,可以省略@Param。

    /**
     * 使用@Param進行參數名稱綁定后,參數位置無所謂
     * @param ppp ppp
     * @param uuu uuu
     * @return list
     */
    @Query(value = "select u from User u where u.username = :username or u.phone = :phone ")
    List<User> findByPhoneOrUsername(@Param("phone")String ppp,@Param("username")String uuu);


    /**
     * 參數名稱一致時,可以省略@Param
     * @param username
     * @param phone
     * @return
     */
    @Query(value = "select u from User u where u.phone = :phone or u.username = :username ")
    List<User> findByUsernameOrPhone(String username,String phone);

  1.7、支持一個entityName的spel變量,用法是select x from #{#entityName} x,插入entityName與給定Repository的關聯的域類型。如果域類設置了@Entity的name屬性,使用name屬性的名稱,如果沒設置,使用域類型的簡單類名。也可以在參數上使用spel表達式。

    /**
     * 為了避免在查詢的字符串聲明中使用實體類名,可以使用#{#entityName}變量.
     * 根據用戶名查詢
     * @param username username
     * @return User
     */
    @Query(value = "select u from #{#entityName} u where u.username = ?1 ")
    User findUserByUsernameWithSpelEntityName(String username);

    /**
     * 使用spel表達式
     *
     * @param user user
     * @return user
     */
    @Query(value = "select u from User u where u.username = :#{#user.username} ")
    User findUserByUsernameWithSpel(User user);

  1.8、對於like條件,通常在開頭或結尾添加%,可以在參數綁定或spel上附加%。

    /**
     * like查詢可以在參數綁定或spel上追加%。
     * @param email email
     * @return list
     */
    @Query(value = "select u from User  u where u.email like %:#{[0]}% and u.email like %:email%")
    List<User> findByEmailLikeWithSpel(String email);

    /**
     * 原生sql也支持參數綁定和spel表達式
     * @param email email
     * @return list
     */
    @Query(value = "SELECT * FROM cfq_jpa_user WHERE email LIKE %:#{[0]}% AND email LIKE %:email% " ,nativeQuery = true)
    List<User> findByEmailLikeWithNativeQuery(String email);

  1.9、對於like條件中的_,%進行轉義,在spel中可以使用escape(String) 進行替換,將第二個參數中的字符作為第一個參數中_和%的前綴。escape(String)方法只能將_或%轉義,如果數據庫有其他的通配符,這無法轉義。

  1.10、轉義的字符可以通過@EnableJpaRepositories注解的escapeCharacter屬性進行設置。

    /**
     * like查詢 escape() 定義轉義字符
     * @param email email
     * @return list
     */
    @Query(value = "select u from User u where u.email like %?#{escape([0])}% escape ?#{escapeCharacter()} ")
    List<User> findByEmailLikeWithEscaped(String email);

   1.11、當 #{entityName} 與參數SPEL表達式一起使用時,參數要使用?#{進行開頭。例如:

    /**
     * 使用spel表達式
     * 當 #{#entityName} 與SPEL 一起使用時,參數要使用 ?#{
     *
     * @param user user
     * @return user
     */
//    @Query(value = "select u from User u where u.username = :#{#user.username} ")
    @Query(value = "select u from #{#entityName} u where u.username = ?#{#user.username} ")
    User findUserByUsernameWithSpel(User user);

2、@Modifying與派生delete

  2.1、@Modifying源碼

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 指示應將查詢方法視為修改查詢,因為這會更改執行查詢的方式。
 * 只有在使用@Query注解定義的查詢方法上才考慮使用此注釋。
 * 它不應該應用與自定義實現方法或從方法名派生的查詢,因為它們已經控制了底層數據庫訪問API,或者指定是否按名稱進行修改。
 *
 * 需要添加@Modifying注解的包括 INSERT、UPDATE、DELETE、DDL語句。
 * 
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Documented
public @interface Modifying {

    /**
     * 定義是否應在執行修改查詢之前刷新基礎持久性上下文。
     */
    boolean flushAutomatically() default false;

    /**
     * 定義是否應在執行修改查詢后清除基礎持久性上下文。
     */
    boolean clearAutomatically() default false;
}

  2.2、對於@Query執行INSERT、UPDATE、DELETE、DDL語句時,需要添加@Modifying注解,來改變查詢方式。

  2.3、@Modifying只有在使用@Query注解時,才考慮使用,對於自定義實現查詢和方法名派生的查詢則不需要。

  2.4、由於EntityManager在執行修改查詢后可能包含過時的實體,因此我們不會自動清除它,因為這實際上會刪除EntityManager中仍掛起的所有為刷新的修改。如果希望自動清除EntityManager,將clearAutomatically設置為true。

  2.5、Spring-Data-JPA還支持派生的delete查詢,這樣可以避免顯式聲明JPQL查詢。

  2.6、派生delete查詢會先執行select在執行delete,使用@Query和@Modifying直接執行delete。

  2.7、派生delete會觸發@PreRemove,而@Query和@Modifying則不會。

 

源碼地址:https://github.com/caofanqi/study-spring-data-jpa


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM