pringData JPA進階查詢—JPQL/原生SQL查詢、分頁處理、部分字段映射查詢


一、入門引導與准備

 

JPQL(JavaPersistence Query Language)是一種面向對象的查詢語言,它在框架中最終會翻譯成為sql進行查詢,如果不知JPQL請大家自行谷歌了解一下,如果你會SQL,了解這個應該不廢吹灰之力。

1.核心注解@Query介紹

 

使用SpringDataJPA進行JPQL/SQL一般查詢的核心是@Query注解,我們先來看看該注解

  1.  
    @Retention(RetentionPolicy.RUNTIME)
  2.  
    @Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
  3.  
    @QueryAnnotation
  4.  
    @Documented
  5.  
    public @interface Query {
  6.  
    String value() default "";
  7.  
    String countQuery() default "";
  8.  
    String countProjection() default "";
  9.  
    boolean nativeQuery() default false;
  10.  
    String name() default "";
  11.  
    String countName() default "";
  12.  
    }

該注解使用的注解位置為方法、注解類型,一般我們用於注解方法即可。@QueryAnnotation標識這是一個查詢注解;

 

@Query注解中有6個參數,value參數是我們需要填入的JPQL/SQL查詢語句;nativeQuery參數是標識該查詢是否為原生SQL查詢,默認為false;countQuery參數為當你需要使用到分頁查詢時,可以自己定義(count查詢)計數查詢的語句,如果該項為空但是如果要用到分頁,那么就使用默認的主sql條件來進行計數查詢;name參數為命名查詢需要使用到的參數,一般配配合@NamedQuery一起使用,這個在后面會說到;countName參數作用與countQuery相似,但是使用的是命名查詢的(count查詢)計數查詢語句;countProjection為涉及到投影部分字段查詢時的計數查詢(count查詢);關於投影查詢,待會會說到。

有了@Query基礎后,我們就可以小試牛刀一把了,對於jar包依賴,我們用的依舊是上一節的依賴,代碼如下:

2.准備實驗環境

 

 

  1.  
    <parent>
  2.  
    <groupId>org.springframework.boot</groupId>
  3.  
    <artifactId>spring-boot-starter-parent</artifactId>
  4.  
    <version>1.4.1.RELEASE</version>
  5.  
    </parent>
  6.  
     
  7.  
    <properties>
  8.  
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  9.  
    <java.version>1.8</java.version>
  10.  
    <springBoot.groupId>org.springframework.boot</springBoot.groupId>
  11.  
    </properties>
  12.  
     
  13.  
    <dependencies>
  14.  
    <!-- SpringBoot Start -->
  15.  
    <dependency>
  16.  
    <groupId>${springBoot.groupId}</groupId>
  17.  
    <artifactId>spring-boot-starter-web</artifactId>
  18.  
    </dependency>
  19.  
    <!-- jpa -->
  20.  
    <dependency>
  21.  
    <groupId>${springBoot.groupId}</groupId>
  22.  
    <artifactId>spring-boot-starter-data-jpa</artifactId>
  23.  
    </dependency>
  24.  
    <dependency>
  25.  
    <groupId>${springBoot.groupId}</groupId>
  26.  
    <artifactId>spring-boot-starter-test</artifactId>
  27.  
    </dependency>
  28.  
    <!-- mysql -->
  29.  
    <dependency>
  30.  
    <groupId>mysql</groupId>
  31.  
    <artifactId>mysql-connector-java</artifactId>
  32.  
    </dependency>
  33.  
    <dependency>
  34.  
    <groupId>junit</groupId>
  35.  
    <artifactId>junit</artifactId>
  36.  
    <version>4.12</version>
  37.  
    </dependency>
  38.  
    </dependencies>

 

項目結構如下:

JpaConfiguration配置類與上篇的相同:

 

  1.  
    @Order(Ordered.HIGHEST_PRECEDENCE)
  2.  
    @Configuration
  3.  
    @EnableTransactionManagement(proxyTargetClass=true)
  4.  
    @EnableJpaRepositories(basePackages={"org.fage.**.repository"})
  5.  
    @EntityScan(basePackages={"org.fage.**.entity"})
  6.  
    public class JpaConfiguration {
  7.  
    @Bean
  8.  
    PersistenceExceptionTranslationPostProcessor persistenceExceptionTranslationPostProcessor(){
  9.  
    return new PersistenceExceptionTranslationPostProcessor();
  10.  
    }
  11.  
    }

App類:

 

 

  1.  
    @SpringBootApplication
  2.  
    @ComponentScan("org.fage.**")
  3.  
    public class App {
  4.  
    public static void main(String[] args) throws Exception {
  5.  
    SpringApplication.run(App.class, args);
  6.  
    }
  7.  
    }

 

對於實體建模依舊用到上一篇所用的模型Department、User、Role,Department與User為一對多,User與Role為多對多,為了方便后面介紹投影,user多增加幾個字段,代碼如下:

 

  1.  
    @Entity
  2.  
    @Table(name = "user")
  3.  
    public class User implements Serializable {
  4.  
     
  5.  
    private static final long serialVersionUID = -7237729978037472653L;
  6.  
    @Id
  7.  
    @GeneratedValue(strategy = GenerationType.IDENTITY)
  8.  
    private Long id;
  9.  
    private String name;
  10.  
    private String password;
  11.  
    @Column(name = "create_date")
  12.  
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
  13.  
    @Temporal(TemporalType.TIMESTAMP)
  14.  
    private Date createDate;
  15.  
    private String email;
  16.  
    // 一對多映射
  17.  
    @ManyToOne
  18.  
    @JoinColumn(name = "department_id")
  19.  
    private Department department;
  20.  
    // 多對多映射
  21.  
    @ManyToMany @JsonBackReference
  22.  
    @JoinTable(name = "user_role", joinColumns = { @JoinColumn(name = "user_id") }, inverseJoinColumns = {
  23.  
    @JoinColumn(name = "role_id") })
  24.  
    private List<Role> roles;
  25.  
    //getter and setter .....
  26.  
    }

 

 

 

  1.  
    @Entity
  2.  
    @Table(name = "department")
  3.  
    public class Department implements Serializable {
  4.  
     
  5.  
    /**
  6.  
    *
  7.  
    */
  8.  
    private static final long serialVersionUID = 3743774627141615707L;
  9.  
    @Id
  10.  
    @GeneratedValue(strategy=GenerationType.IDENTITY)
  11.  
    private Long id;
  12.  
    private String name;
  13.  
    @OneToMany(mappedBy = "department")@JsonBackReference
  14.  
    @JsonBackReferenceprivate List <User> users;
  15.  
    //getter and setter
  16.  
    }

 

 

 

  1.  
    @Entity
  2.  
    @Table(name="role")
  3.  
    public class Role implements Serializable{
  4.  
     
  5.  
    /**
  6.  
    *
  7.  
    */
  8.  
    private static final long serialVersionUID = 1366815546093762449L;
  9.  
    @Id
  10.  
    @GeneratedValue(strategy=GenerationType.IDENTITY)
  11.  
    private Long id;
  12.  
    private String name;
  13.  
     
  14.  
    //getter and setter
  15.  
    }

 

 

建模成功時,生成的表結構如下:

對於Repository:

 

  1.  
    @Repository
  2.  
    public interface DepartmentRepository extends JpaRepository<Department, Long>{}
  1.  
    @Repository
  2.  
    public interface RoleRepository extends JpaRepository<Role, Long>{}
  1.  
    @Repository
  2.  
    public interface UserRepository extends JpaRepository<User, Long>{
  3.  
    }

 

如果以上代碼有看不懂的地方,請移步到上一篇一覽基礎篇。至此,我們已經將環境整理好了,至於表中的數據插入,希望各位參考上一篇文章進行基礎的crud操作將表中數據進行填充,接下來介紹@Query查詢

 

二、使用JPQL查詢

 

1 .核心查詢與測試樣例

 

在UserRepository中增加以下方法:

 

  1.  
    //--------------JPQL查詢展示-------------//
  2.  
     
  3.  
    //展示位置參數綁定
  4.  
    @Query(value = "from User u where u.name=?1 and u.password=?2")
  5.  
    User findByNameAndPassword(String name, String password);
  6.  
     
  7.  
    //展示名字參數綁定
  8.  
    @Query(value = "from User u where u.name=:name and u.email=:email")
  9.  
    User findByNameAndEmail(@Param("name")String name, @Param("email")String email);
  10.  
     
  11.  
    //展示like模糊查詢
  12.  
    @Query(value = "from User u where u.name like %:nameLike%")
  13.  
    List<User> findByNameLike(@Param("nameLike")String nameLike);
  14.  
     
  15.  
    //展示時間間隔查詢
  16.  
    @Query(value = "from User u where u.createDate between :start and :end")
  17.  
    List<User> findByCreateDateBetween(@Param("start")Date start, @Param("end")Date end);
  18.  
     
  19.  
    //展示傳入集合參數查詢
  20.  
    @Query(value = "from User u where u.name in :nameList")
  21.  
    List<User> findByNameIn(@Param("nameList")Collection<String> nameList);
  22.  
     
  23.  
    //展示傳入Bean進行查詢(SPEL表達式查詢)
  24.  
    @Query(value = "from User u where u.name=:#{#usr.name} and u.password=:#{#usr.password}")
  25.  
    User findByNameAndPassword(@Param("usr")User usr);
  26.  
     
  27.  
    //展示使用Spring自帶分頁查詢
  28.  
    @Query("from User u")
  29.  
    Page<User> findAllPage(Pageable pageable);
  30.  
     
  31.  
    //展示帶有條件的分頁查詢
  32.  
    @Query(value = "from User u where u.email like %:emailLike%")
  33.  
    Page<User> findByEmailLike(Pageable pageable, @Param("emailLike")String emailLike);

TestClass的代碼如下:

 

 

  1.  
    @RunWith(SpringRunner.class)
  2.  
    @SpringBootTest
  3.  
    public class TestClass {
  4.  
    final Logger logger = LoggerFactory.getLogger(TestClass.class);
  5.  
    @Autowired
  6.  
    UserRepository userRepository;
  7.  
     
  8.  
    @Test
  9.  
    public void testfindByNameAndPassword(){
  10.  
    userRepository.findByNameAndPassword( "王大帥", "123");
  11.  
    }
  12.  
     
  13.  
    @Test
  14.  
    public void testFindByNameAndEmail(){
  15.  
    userRepository.findByNameAndEmail( "張大仙", "2@qq.com");
  16.  
    }
  17.  
     
  18.  
    @Test
  19.  
    public void testFindByNameLike(){
  20.  
    List<User> users = userRepository.findByNameLike( "馬");
  21.  
    logger.info(users.size() + "----");
  22.  
    }
  23.  
     
  24.  
    @Test
  25.  
    public void testFindByCreateDateBetween() throws ParseException{
  26.  
    List<User> users = userRepository.findByCreateDateBetween( new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2018-01-01 00:00:00"), new Date(System.currentTimeMillis()));
  27.  
    logger.info(users.size() + "----");
  28.  
    }
  29.  
     
  30.  
    @Test
  31.  
    public void testFindByNameIn(){
  32.  
    List<String> list = new ArrayList<String>();
  33.  
    list.add( "王大帥");
  34.  
    list.add( "李小三");
  35.  
    userRepository.findByNameIn(list);
  36.  
    }
  37.  
     
  38.  
    @Test
  39.  
    public void testfindByNameAndPasswordEntity(){
  40.  
    User u = new User();
  41.  
    u.setName( "李小三");
  42.  
    u.setPassword( "444");
  43.  
    userRepository.findByNameAndPassword(u);
  44.  
    }
  45.  
     
  46.  
    @Test
  47.  
    public void testFindAllPage(){
  48.  
    Pageable pageable = new PageRequest(0,5);
  49.  
    Page<User> page = userRepository.findAllPage(pageable);
  50.  
    ObjectMapper mapper = new ObjectMapper();
  51.  
    String json = mapper.writeValueAsString(page); 
  52.  
    logger.info(json);
  53.  
    }
  54.  
    @Test
  55.  
    public void findByEmailLike(){
  56.  
    Pageable pageable = new PageRequest(0,5,new Sort(Direction.ASC,"id"));
  57.  
    userRepository.findByEmailLike(pageable, "@qq.com");
  58.  
    }
  59.  
    }

至此,顯示了使用JPQL進行單表查詢的絕大多數操作,當你在實體設置了fetch=FetchType.LAZY 或者EAGER時,會有不同的自動連接查詢,鼓勵大家自行嘗試。以上查詢語句有必要對其中幾個進行解釋一下;

 

對於UserRepository中的第一與第二個方法,目的是為了比較與展示位置綁定與名字綁定的區別,相信根據名稱大家就能判別是什么意思與區別了,位置綁定即是方法參數從左到右第123456...所在位置的參數與查詢語句中的第123456...進行對應。名字綁定即是查詢語句中的參數名稱與方法參數名稱一一對應;對於第三個與第四個查詢例子就不多說了;第五條查詢語句展示的是傳入集合進行in查詢;第六條查詢例子展示的是傳入bean進行查詢,該查詢使用的表達式是Spring的SPEL表達式;

2. 分頁與排序

 

最后兩條查詢語句展示的是進行分頁查詢、分頁並排序查詢,使用的計數查詢默認使用主查詢語句中的條件進行count, 當Repository接口的方法中含有Pageable參數時,那么SpringData認為該查詢是需要分頁的;org.springframework.data.domain.Pageable是一個接口,接口中定義了分頁邏輯操作,它具有一個間接實現類為PageRequest,我們最需要關注的是PageRequest這個實現類的三個構造方法:

 

  1.  
    public class PageRequest extends AbstractPageRequest {
  2.  
    ....
  3.  
    ....
  4.  
    public PageRequest(int page, int size) {
  5.  
    this(page, size, null);
  6.  
    }
  7.  
    public PageRequest(int page, int size, Direction direction, String... properties) {
  8.  
    this(page, size, new Sort(direction, properties));
  9.  
    }
  10.  
    public PageRequest(int page, int size, Sort sort) {
  11.  
    super(page, size);
  12.  
    this.sort = sort;
  13.  
    }
  14.  
    ....
  15.  
    ....
  16.  
    }

page參數為頁碼(查第幾頁,從0頁開始),size為每頁顯示多少條記錄數;
Direction則是一個枚舉,如果該參數被傳入則進行排序,常用的有Direction.ASC/Direction.DESC,即正序排與逆序排,如果排序,需要根據哪個字段排序呢?properties是一個可變長參數,傳入相應字段名稱即可根據該字段排序。還有最后一個參數Sort,Sort這個類中有一個構造方法:public Sort(Direction direction, String... properties),沒錯,我不用說相信大家都已經懂了是干什么用的了。

 

Pageable與PageRequest的關系解釋完了,那么就該介紹一下最后兩條查詢語句的返回值Page<T>是干什么用的了,讓我們看看倒數第二個測試方法返回的json串結果:

 

  1.  
    { "content": [
  2.  
    { "id": 1,"name": "王大帥","password": "123", "createDate": 1515312688000, "email": "1@qq.com","department": { "id": 1, "name": "開發部"}},
  3.  
    { "id": 2, "name": "張大仙", "password": "456", "createDate": 1515139947000, "email": "2@qq.com", "department": {"id": 1, "name": "開發部" }},
  4.  
    { "id": 3, "name": "李小三","password": "789","createDate": 1514794375000, "email": "3@qq.com","department": {"id": 1, "name": "開發部" }},
  5.  
    { "id": 4, "name": "馬上來","password": "444", "createDate": 1512116003000, "email": "4@qq.com", "department": { "id": 1,"name": "開發部" } },
  6.  
    { "id": 5, "name": "馬德華", "password": "555","createDate": 1515312825000,"email": "5@qq.com","department": { "id": 1, "name": "開發部"} }],
  7.  
    "last": true,
  8.  
    "totalPages": 1,
  9.  
    "totalElements": 5,
  10.  
    "size": 5,
  11.  
    "number": 0,
  12.  
    "sort": null,
  13.  
    "first": true,
  14.  
    "numberOfElements": 5
  15.  
    }

跟蹤源碼得到結論,Page<T>是一個接口,它的基類接口Slice<T>也是一個接口,而實現類Chunk實現了Slice,實現類PageImpl繼承了Chunk並且實現了Page接口。所以實際上Json輸出的字符串是PageImpl的擁有的所有屬性(包括其父類Chunk)。content屬性是分頁得出的實體集合,類型為List,也就是上面json串中的content。last屬性表示是否為最后一頁,totalPages表示總頁數,totalElements表示總記錄數,size為每頁記錄數大小,number表示當前為第幾頁,numberOfElements表示當前頁所擁有的記錄數,first表示當前是否第一頁,sort為排序信息。

 

到這里,Page與Pageable都了解了。

3. 關聯查詢與部分字段映射投影

 

接下來介紹使用JPQL進行關聯查詢與部分字段映射。現在的查詢需求是,查出所有用戶的名字、用戶所屬部門、用戶的email、統計用戶所擁有的角色有多少個,然后將列表結果進行給前端顯示。有的朋友說,那我把關聯到的對象都拿出來不就完了。可是,實際開發中一個表下有幾十個字段會很常見,如果全部都拿出來是沒有必要的,所以我們可以把需要的字段拿出來就可以了,下面介紹兩種方法實現這種需求。

3.1 使用VO(view object)做映射與投影

 

我們在src/main/java中增加一個org.fage.vo包,該包下存放VO對象,我們在該包下創建一個UserOutputVO:

 

  1.  
    public class UserOutputVO {
  2.  
    private String name; //用戶的名字
  3.  
    private String email; //用戶的email
  4.  
    private String departmentName; //用戶所屬的部門
  5.  
    private Long roleNum; //該用戶擁有的角色數量
  6.  
     
  7.  
    public UserOutputVO(String name, String email, String departmentName, Long roleNum) {
  8.  
    super();
  9.  
    this.name = name;
  10.  
    this.email = email;
  11.  
    this.departmentName = departmentName;
  12.  
    this.roleNum = roleNum;
  13.  
    }
  14.  
    public UserOutputVO() {
  15.  
    super();
  16.  
    }
  17.  
    //getter and setter and toString
  18.  
    ...
  19.  
    }

在UserRepository中創建查詢方法:

 

 

  1.  
    @Query(value = "select new org.fage.vo.UserOutputVO(u.name, u.email, d.name as departmentName, count(r.id) as roleNum) from User u "
  2.  
    + "left join u.department d left join u.roles r group by u.id")
  3.  
    Page<UserOutputVO> findUserOutputVOAllPage(Pageable pageable);

 

這里注意一下,VO中的構造方法參數一定要與查詢語句中的查詢字段類型相匹配(包括數量),如果不匹配就會報錯。以下是測試代碼:

 

  1.  
    @Test
  2.  
    public void testFindUserOutputVOAllPage(){
  3.  
    Pageable pageable = new PageRequest(0,5);
  4.  
    Page<UserOutputVO> page = userRepository.findUserOutputVOAllPage(pageable);
  5.  
    List<UserOutputVO> list = page.getContent();
  6.  
    for(UserOutputVO vo : list)
  7.  
    logger.info(vo.toString());
  8.  
    }

輸出結果:

 

對於連接查詢,有join、left join 、right join,與sql的類似,但是唯一需要注意的地方就是建模的關系要能連接起來,因為只有這樣才能使用“.”進行連接;就像你想的那樣,它是類似對象導航的,與sql的表連接有些使用上的不同,但是最終的連接結果是相同的。

3.2 使用projection接口做映射與投影

 
依然使用的是上面查詢VO的需求進行查詢,換成projection的方式,在org.fage.vo中創建一個接口:

 

  1.  
    public interface UserProjection {
  2.  
    String getName();
  3.  
     
  4.  
    @Value("#{target.emailColumn}")//當別名與該getXXX名稱不一致時,可以使用該注解調整
  5.  
    String getEmail();
  6.  
     
  7.  
    String getDepartmentName();
  8.  
     
  9.  
    Integer getRoleNum();
  10.  
    }

在UserRepository中創建查詢語句:

 

 

  1.  
    //故意將email別名為emailColumn,以便講解@Value的用法
  2.  
    @Query(value = "select u.name as name, u.email as emailColumn, d.name as departmentName, count(r.id) as roleNum from User u "
  3.  
    + "left join u.department d left join u.roles r group by u.id")
  4.  
    Page<UserProjection> findUserProjectionAllPage(Pageable pageable);

在TestClass中添加測試方法:

 

 

  1.  
    @Test
  2.  
    public void testFindUserProjectionAllPage(){
  3.  
    Page<UserProjection> page = userRepository.findUserProjectionAllPage( new PageRequest(0,5));
  4.  
    Collection<UserProjection> list = page.getContent();
  5.  
    for(UserProjection up : list){
  6.  
    logger.info(up.getName());
  7.  
    logger.info(up.getEmail());
  8.  
    logger.info(up.getDepartmentName());
  9.  
    logger.info(up.getRoleNum()+ "");
  10.  
    }
  11.  
    }

測試結果是成功的。在這里需要注意幾點約束,Projection接口中必須以“getXXX”來命名方法,關於“XXX”則是要與查詢語句中的別名相對應,注意觀察上面的Projection接口與查詢語句就發現了。不難發現,有一個別名為emailColumn,與Projection接口中的getEmail方法並不對應,這種時候可以使用@Value{"${target.xxx}"}注解來調整,注意其中的target不能省略,可以把target看成用別名查出來的臨時對象,這樣就好理解了。

 

兩種方式都可以,對於到底哪種方式好,這取決於你的需求。

 

 

4.命名查詢

如果以上查詢實例都弄懂了,那么命名查詢也是類似的,換湯不換葯;這里簡單的只舉兩個例子,需求變更時請大家自行嘗試。
命名查詢的核心注解是@NamedQueries 與 @NamedQuery;@NamedQueries中只有一個value參數,該參數是@NamedQuery的數組。@NamedQuery注解我們需要關注兩個參數,query參數也就是需要寫入查詢語句的地方;name參數則是給該查詢命名,命名方式一般約定為  “實體類名.實體對應的repository的查詢方法名”,如果看不懂沒關系,請看下面的例子。
在Role實體中增加以下代碼:
  1.  
    @Entity
  2.  
    @Table(name="role")
  3.  
    @NamedQueries({
  4.  
    @NamedQuery(name = "Role.findById", query = "from Role r where r.id=?1"),
  5.  
    @NamedQuery(name = "Role.findAllPage", query = "from Role r")
  6.  
    //...更多的@NamedQuery
  7.  
    })
  8.  
    public class Role implements Serializable{
  9.  
     
  10.  
    private static final long serialVersionUID = 1366815546093762449L;
  11.  
    @Id
  12.  
    @GeneratedValue(strategy=GenerationType.IDENTITY)
  13.  
    private Long id;
  14.  
    private String name;
  15.  
     
  16.  
    public Role(){
  17.  
    super();
  18.  
    }
  19.  
     
  20.  
    public Role(String name){
  21.  
    this.name = name;
  22.  
    }
  23.  
    //getter and setter
  24.  
     
  25.  
    }
對應的RoleRepository代碼:

  1.  
    @Repository
  2.  
    public interface RoleRepository extends JpaRepository<Role, Long>{
  3.  
     
  4.  
    Role findById(Long id);
  5.  
     
  6.  
    Page<Role> findAllPage(Pageable pageable);
  7.  
    }
相應的測試代碼:
 
  1.  
    @Test
  2.  
    public void testFindRoleById(){
  3.  
    roleRepository.findById( 1l);
  4.  
    }
  5.  
     
  6.  
    @Test
  7.  
    public void testFindRoleAllPage(){
  8.  
    roleRepository.findAll( new PageRequest(0,5));
  9.  
    }

以上就是命名查詢的常用方式。

 

 

5. JPQL方式總結

還是比較建議使用JPQL方式,因為SpringDataJPA各方面(比如分頁排序)、動態查詢等等都支持得比較好,Spring的SPEL表達式還可以擴展到SpringSecurity與SpringDataJPA高級的session用戶查詢方式,后續博客會有對SpringSecurity的介紹,等到那時候在一起講解。

 

三、使用原生SQL查詢

 

有些時候,JPQL使用不當會導致轉化成的sql並不如理想的簡潔與優化,所以在特定場合還是得用到原生SQL查詢的,比如當你想優化sql時等等。

 

1 .一般查詢

 

使用原生查詢時用的也是@Query注解,此時nativeQuery參數應該設置為true。我們先來看一些簡單的查詢

 

  1.  
    @Query(value = "select * from user u where u.id=:id", nativeQuery = true)
  2.  
    User findByIdNative(@Param("id")Long id);
  3.  
     
  4.  
    @Query(value = "select * from user", nativeQuery = true)
  5.  
    List<User> findAllNative();

看看測試代碼:

 

  1.  
    @Test
  2.  
    @Transactional
  3.  
    public void testFindByIdNative(){
  4.  
    User u = userRepository.findByIdNative( 1l);
  5.  
    logger.info(u.toString());
  6.  
    logger.info(u.getRoles().toString());
  7.  
    }
  8.  
     
  9.  
    @Test
  10.  
    public void testFindAllNative(){
  11.  
    List<User> list = userRepository.findAllNative();
  12.  
    for(User u : list){
  13.  
    logger.info(u.toString());
  14.  
    }
  15.  
    }

結果發現當查所有字段的時候,確實能映射成功,並且fetch快加載、懶加載自動關聯也能正常使用。接下來我們換剛才使用JPQL時的查詢需求,看看用SQL時該怎么做。

 

2.投影與映射分頁查詢

 

查詢列表的需求依舊是剛才介紹使用JPQL時使用的需求(分頁查出所有用戶的名字、用戶所屬部門、用戶的email、統計用戶所擁有的角色有多少個),在UserRepository中創建代碼片段:

 

  1.  
    //展示原生查詢
  2.  
    @Query(value = "select u.name as name, u.email as emailColumn, d.name as departmentName, count(ur.role_id) as roleNum from user u "
  3.  
    + "left join department d on d.id=u.department_id left join user_role ur on u.id=ur.user_id group by u.id limit :start,:size",
  4.  
    nativeQuery = true)
  5.  
    List<Object[]> findUserProjectionAllPageNative( @Param("start")int start, @Param("size")int size);
  6.  
     
  7.  
    //count語句
  8.  
    @Query(value = "select count(u.id) from user u", nativeQuery = true)
  9.  
    long countById();

在TestClass中創建測試代碼:

 

  1.  
    @Test
  2.  
    public void testFindUserProjectionAllPageNative(){
  3.  
    Pageable pageable = new PageRequest(0,5);
  4.  
    List<Object []> content = userRepository.findUserProjectionAllPageNative(pageable.getOffset(), pageable.getPageSize());
  5.  
    long total = userRepository.countById();
  6.  
    //查看一下查詢結果
  7.  
    logger.info(content.size() + "");
  8.  
    for(Object[] o : content){
  9.  
    logger.info( "名字:" + o[0].toString());
  10.  
    logger.info( "email:" + o[1].toString());
  11.  
    logger.info( "所屬部門" + o[2].toString());
  12.  
    logger.info( "角色數量" + o[3].toString());
  13.  
    }
  14.  
    //如果需要的話,自行封裝分頁信息
  15.  
    Page<Object[]> page = new PageImpl<Object[]>(content, pageable, total);
  16.  
    System.out.println(page);
  17.  
    }

 

解釋一下上面代碼,由於是原生查詢不支持動態分頁,Page分頁我們只能自己做了,但是依舊使用的是Spring的Page;pageable.getOffset()與pageable.getPageSize()分別對應limit ?, ?的第一與第二個問號。原生查詢得出來的List是包函一堆被封裝成Object的對象數組,每個object數組可以通過數組索引拿出值,也就與需要查的字段一一對應。如果你需要存入VO再帶回給前端,那么你可以自行封裝。對於PageImpl,我們使用了public PageImpl(List<T> content, Pageable pageable, long total) 這個構造方法,第一個參數是查詢到的結果,第二個就不用說了,第三個參數是對主sql的count查詢。當前端需要顯示分頁時,可以這樣進行手動分頁。

3.SQL方式總結

當你需要進行sql優化時,可能用原生sql方式會更好。但是一般需求時候用JPQL還是比較方便的,畢竟這樣比較省事,拿數據總是需要分頁的,有時候只需要拿幾個字段也是這樣。

 

四、總結

 

當你在接到一般需求時,使用JPQL的方式其實已經足夠用了。但是如果對sql需要優化的時候,你也可以使用SQL的方式。總而言之,需要根據需求來應變使用的策略。

如果文中有不當的地方歡迎同學們提出建議與修改方案,但是請不要謾罵與辱罵。


免責聲明!

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



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