Spring Boot的@Query注解

摘要

@Query说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@QueryAnnotation
@Documented
public @interface Query {
// 指定JPA Query语句,当nativeQuery=true时是原生的sql语句
String value() default "";
//指定count的JPA Query语句,不指定则自动生成,当nativeQuery=true时是原生的sql语句,countQuery主要用来配合 value实现分页功能
String countQuery() default "";
//依据哪个字段来count,如果未配置countQuery()或countProjection(),将从原始查询派生count查询
String countProjection() default "";
// 默认是false,表示value 里面是不是原生的Sql 语句
boolean nativeQuery() default false;
//指定一个query 的名字,必须是唯一的。 如果不指定,默认的生成规则是:{$domainClass}.${queryMethodName}。使用命名查询时会用到。
String name() default "";
//指定一个count 的query 名字,必须是唯一的。如果不指定,默认的生成规则是:{$domainClass}.${queryMethodName}.count
String countName() default "";
}

基本用法示例

  • @Entity

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    @Data
    @Entity
    @Table(name = "tbl_country")
    public class Country implements Serializable{
    private static final long serialVersionUID = 1L;

    /*
    * 主键自增
    */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name="id")
    private Long id;

    /*
    * 中文简称
    */
    @Column(name="name_zh")
    private String nameZh;

    /*
    * 英文简称
    */
    @Column(name="name_en")
    private String nameEn;

    /*
    * 英文全称
    */
    @Column(name="name_en_full")
    private String nameEnFull;

    /*
    * 两字母代码
    */
    @Column(name="code_two")
    private String codeTwo;

    /*
    * 三字母代码
    */
    @Column(name="code_three")
    private String codeThree;

    /*
    * 数字代码
    */
    @Column(name="num_code")
    private String numCode;

    /*
    * 备注
    */
    @Column(name="remark")
    private String remark;


    public Country(Long id, String nameZh, String nameEn) {
    this.id = id;
    this.nameZh = nameZh;
    this.nameEn = nameEn;
    }

    public Country() {
    }

  • Dto

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    @Data
    public class CountryDto implements Serializable {
    private static final long serialVersionUID = 1L;

    /*
    * 主键自增
    */
    private Long id;

    /*
    * 中文简称
    */
    private String nameZh;

    /*
    * 英文简称
    */
    private String nameEn;

    /*
    * 英文全称
    */
    private String nameEnFull;

    /*
    * 两字母代码
    */
    private String codeTwo;

    /*
    * 三字母代码
    */
    private String codeThree;

    /*
    * 数字代码
    */
    private String numCode;

    /*
    * 备注
    */
    private String remark;

    public CountryDto() {
    }

    public CountryDto(Long id, String nameZh, String nameEn) {
    this.id = id;
    this.nameZh = nameZh;
    this.nameEn = nameEn;
    }


    }
  • Jpa

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public interface CountryJpaRepository extends BaseJpaRepository<Country, Long> {
    //sql查询,只有SELECT * 时才可以直接返回@Entity对象,但要有无参构造方法,page要指定countQuery
    @Query(value = "SELECT * FROM tbl_country WHERE id > ?1", countQuery = "SELECT count(*) FROM tbl_country WHERE id > ?1", nativeQuery = true)
    Page<Country> findByIdAfter(Long id, Pageable pageable);

    //sql查询,只有SELECT * 时才可以直接返回@Entity对象
    @Query(value = "SELECT * FROM tbl_country WHERE name_zh = ?1",nativeQuery = true)
    Country findCountrySqlByName(String name);

    //hql查询部分属性时,返回@Entity对象,要在@Entity中创建对应的构造方法
    @Query(value = "SELECT new Country(id,nameZh,nameEn) FROM Country WHERE nameZh = ?1")
    Country findCountryHqlByName(String name);

    //hql查询部分属性时,返回非@Entity对象,要创建对应的构造方法,且必须使用全路径
    @Query(value = "SELECT new com.example.demo.CountryDto(id,nameZh,nameEn) FROM Country WHERE nameZh = ?1")
    CountryDto findCountryDtoNewHqlByName(String name);

    }

说明

  • 返回@Entity对象,查询全部属性时,sql和hql都支持直接转换
  • 返回@Entity对象,查询部分属性时,sql不支持直接转换,hql时,实体要有对应的构造方法
  • hql也可以返回Dto对象,查询部分属性时,同样需要Dto有对应的构造方法,注意Dto必须使用全路径

sql查询部分属性直接转换成对象的方法

  • 方法1,前一篇文章Spring Boot自定义JpaRepository基类中通过自定义JpaRepository基类,转换@Entity对象和Dto对象都支持
  • 方法2,注入自定义的结果转换器,只能转换为Dto对象,下面说明如何实现

注入自定义的结果转换器

  • 无论是sql还是hql,查询部分属性时,jpa输出的结果都会被转化为Map对象,我们只需要注入一个能将Map转换为指定对象的转换器就可以了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* <h1>注入自定义查询结果转换器</h1>
*/
@Slf4j
@Configuration
public class JpaDtoConfig {

@Autowired
private ApplicationContext applicationContext;

//这里用到了上文“Spring Boot自定义JpaRepository基类”中介绍过的JpaUtil,通过json作为中间媒介
@Autowired
private JpaUtil jpaUtil;


/**
* 初始化注入@JpaDto对应的Converter
*/
@PostConstruct
public void init() {
Map<String, Object> map = applicationContext.getBeansWithAnnotation(JpaDto.class);
for (Object o : map.values()) {
Class c = o.getClass();
log.info("Jpa添加Converter,class={}", c.getName());
DefaultConversionService defaultConversionService = ((DefaultConversionService) DefaultConversionService.getSharedInstance());
//添加Map对象的转换器,将Map对象转换为指定的对象类型
defaultConversionService.addConverter(Map.class, c, m -> {
try {
return jpaUtil.mapToObject(m, c, false);
} catch (Exception e) {
throw new FatalBeanException("Jpa结果转换出错,class=" + c.getName(), e);
}
});
}
}

}

  • 定义标记注解

    1
    2
    3
    4
    5
    6
    7
    8
    9
    /**
    * <h1>注解标记,声明在Dto对象上,表示该对象作为结果对象时,可以被自定义结果转换器转化</h1>
    */
    @Documented
    @Component
    @Target(value = {ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface JpaDto {
    }
  • 此时Dto对象上要加上@JpaDto注解

    1
    2
    3
    4
    5
    6
    @JpaDto
    @Data
    public class CountryDto implements Serializable {
    …………………………
    …………………………
    }
  • JpaRepository示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public interface CountryJpaRepository extends BaseJpaRepository<Country, Long> {
    //hql查询部分属性时,一定有使用as别名,这里返回值不能是@Entity,并且其要标注@JpaDto注解
    @Query(value = "SELECT id as id,nameZh as nameZh,nameEn as nameEn FROM Country WHERE nameZh = ?1")
    CountryDto findCountryDtoHqlByName(String name);

    //sql查询部分属性时,一定有使用as别名,这里返回值不能是@Entity,并且其要标注@JpaDto注解
    @Query(value = "SELECT id,name_zh as nameZh,name_en as nameEn FROM tbl_country WHERE name_zh = ?1",nativeQuery = true)
    CountryDto findCountryDtoSqlByName(String name);

    //sql查询部分属性时,一定有使用as别名,这里返回值不能是@Entity,并且其要标注@JpaDto注解,page要指定countQuery
    @Query(value = "SELECT id,name_zh as nameZh,name_en as nameEn FROM tbl_country WHERE id > ?1", countQuery = "SELECT count(*) FROM tbl_country WHERE id > ?1", nativeQuery = true)
    Page<CountryDto> findByIdAfterDto(Long id, Pageable pageable);
    }

说明

  • 返回注解了@JpaDto注解的对象时,hql和sql都支持部分字段查询,但要注意字段要加as别名,别名与Dto对象属性名称要一致;
  • 部分字段查询时,Dto对象中不必创建对应的构造方法;
  • 查询部分属性时,不支持返回@Entity对象,即使在@Entity类上增加@JpaDto注解也不好使,说明其返回的不是Map对象
  • hql返回部分属性时,如果直接返回@Entity对象,会抛异常Failed to convert from type [java.lang.Object[]] to type [com.example.demo.Country],提示类型转换失败,因其返回类型是java.lang.Object[],所以也说明为什么要用构造方法的形式接收返回数据,就是要与数组位置一一对应上,所以hql返回部分属性,就要使用构造方法的形式;
    1
    2
    3
    4
    5
    6
    7
    //错误
    @Query(value = "SELECT id as id,nameZh as nameZh,nameEn as nameEn FROM Country WHERE nameZh = ?1")
    Country findCountryHqlByNameError(String name);

    //正确
    @Query(value = "SELECT new Country(id,nameZh,nameEn) FROM Country WHERE nameZh = ?1")
    Country findCountryHqlByNameOk(String name);
  • sql返回部分属性时,如果直接返回@Entity对象,会抛异常No such column: 'code_three'.,提示缺少字段。说明该形式在封装@Entity对象时需要遍历其所有@Column字段并为其赋值,此时可以返回Dto或者Map,也可以使用自定义JpaRepository的方式,还有一种方法就是使用命名查询,就是@NamedNativeQuery和@SqlResultSetMapping;
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //错误
    @Query(value = "SELECT id,name_zh as nameZh,name_en as nameEn FROM tbl_country WHERE name_zh = ?1",nativeQuery = true)
    Country findCountrySqlByNameError(String name);

    //正确,之后可以将map转换为@Entity对象,如通过json的方式
    @Query(value = "SELECT id,name_zh as nameZh,name_en as nameEn FROM tbl_country WHERE name_zh = ?1",nativeQuery = true)
    Map findCountrySqlByNameOk(String name);

    //正确
    @Query(value = "SELECT id,name_zh as nameZh,name_en as nameEn FROM tbl_country WHERE name_zh = ?1",nativeQuery = true)
    Object[] findCountrySqlByNameOk2(String name);

    //正确,CountryDto要加上@JpaDto注解
    @Query(value = "SELECT id,name_zh as nameZh,name_en as nameEn FROM tbl_country WHERE name_zh = ?1",nativeQuery = true)
    CountryDto findCountrySqlByNameOk3(String name);

命名查询

  • @NamedQuery,用于执行hql

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @NamedQueries({
    @NamedQuery(
    //指定名称,必须全局唯一
    name = "HQL_FIND_ALL_COUNTRY",
    //要指定的hql
    query = "from Country"
    )
    })
    @Data
    @Entity
    @Table(name = "tbl_country")
    public class Country implements Serializable {
    …………
    }
  • @NamedNativeQuery和@SqlResultSetMapping,用于执行sql,可以指定结果类型,不指定结果类型时返回java.lang.Object[]

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    @NamedNativeQueries({
    @NamedNativeQuery(
    //指定名称,必须全局唯一
    name = "SQL_FIND_ALL_COUNTRY",
    //要指定的sql
    query = "select * from tbl_country",
    //指定返回类型
    resultClass = Country.class
    ),
    @NamedNativeQuery(
    //指定名称,必须全局唯一
    name = "SQL_FIND_ALL_COUNTRY2",
    //要指定的sql
    query = "select * from tbl_country",
    //通过@SqlResultSetMapping封装返回类型,这里是@SqlResultSetMapping的名称
    resultSetMapping = "SQL_RESULT_COUNTRY_ALL"
    ),
    @NamedNativeQuery(
    //指定名称,必须全局唯一
    name = "SQL_FIND_SOME_FIELD_COUNTRY",
    //要指定的sql
    query = "select id,name_zh as nameZh,name_en as nameEn from tbl_country",
    //通过@SqlResultSetMapping封装返回类型,这里是@SqlResultSetMapping的名称
    resultSetMapping = "SQL_RESULT_COUNTRY_SOME_FIELD"
    )
    })
    @SqlResultSetMappings({
    @SqlResultSetMapping(
    //指定名称,必须全局唯一
    name = "SQL_RESULT_COUNTRY_ALL",
    //指定返回类的@Entity类型
    entities = {
    @EntityResult(
    //指定返回类的@Entity类型
    entityClass = Country.class
    )
    }

    ),
    @SqlResultSetMapping(
    //指定名称,必须全局唯一
    name = "SQL_RESULT_COUNTRY_SOME_FIELD",
    //查询部分属性时,可以通过构造方法封装结果数据
    classes = {
    @ConstructorResult(
    //指定返回类的@Entity类型
    targetClass = Country.class,
    //指定调用的构造方法
    columns = {
    @ColumnResult(name = "id", type = Long.class),
    @ColumnResult(name = "nameZh"),
    @ColumnResult(name = "nameEn")
    }
    )
    }
    )
    })
    @Data
    @Entity
    @Table(name = "tbl_country")
    public class Country implements Serializable {
    …………
    public Country(Long id, String nameZh, String nameEn) {
    this.id = id;
    this.nameZh = nameZh;
    this.nameEn = nameEn;
    }

    public Country() {
    }
    }

说明

  • 命名查询的注解只能声明在对应的@Entity类型上
  • @NamedNativeQuery名称必须全局唯一,可以通过entityManager.createNamedQuery(String queryName)进行调用
  • sql查询部分字段时@Entity一定要有对应的构造方法
  • 返回类型必须是@Entity对象
  • @SqlResultSetMapping可以单独使用,其功能就是将sql的查询结果转换为指定的类型,entityManager.createNativeQuery(String sqlString, String resultSetMapping)

使用方法

  • 通过entityManager的方法进行调用

    1
    2
    3
    List list = entityManager.createNamedQuery("SQL_FIND_ALL_COUNTRY").getResultList();

    List list = entityManager.createNativeQuery("select id,name_zh as nameZh,name_en as nameEn from tbl_country","SQL_RESULT_COUNTRY_SOME_FIELD").getResultList();
  • 通过在JpaRepository声明指定的@Query方法,并指定其name属性为对应的@NamedNativeQuery的name值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public interface CountryJpaRepository extends BaseJpaRepository<Country, Long> {
    @Query(name = "HQL_FIND_ALL_COUNTRY")
    List<Country> nameHqlFindAll();

    @Query(name = "SQL_FIND_ALL_COUNTRY")
    List<Country> nameSqlFindAll();

    @Query(name = "SQL_FIND_ALL_COUNTRY2")
    List<Country> nameSqlFindAll2();

    @Query(name = "SQL_FIND_SOME_FIELD_COUNTRY")
    List<Country> nameSqlFindSomeField();
    }