2021-10-11

Spring Data JPA(四):Query注解从学习到测试


前言

前面的章节讲述了 Spring Data JPA一些基本的操作,尤其是Query注解的一些语法操作,相对而言上手速度快且简单易操作。JPA实现查询操作就是通过将 @Query 注解在继承 repository 的接口类方法上 。今天我们要写一遍完整的JPA查询流程,并通过测试去验证查询流程是否正确。


一、源码分析

上次我们只是讲到如何利用Query注解去进行查询,今天我们来看一下注解的底层是如何实现的。

public @interface Query {
    /**
    * 指定 JPQL 的查询语句。(nativeQuery = true)是原生的 SQL 语句.
	*/
    String value() default "";
	/**
	* 指定 count 的 JPQL 语句,如果不指定将根据 query 自动生成。
	* (nativeQuery = true 的时候,是原生查询的 SQL 语句)
	*/
    String countQuery() default "";
    /**
    *根据那个字段来 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 "";
}

上面有6个方法,我们经常用到的是 String value() default " ",我们通过这个可以调节是否是原生SQL查询。

二、案例流程

1.完成pom.xml、application.properties中配置

2.创建实体类

@Entity
@Table(name = "t_user")
public class User implements Serializable {

    private static final long serialVersionUID = 1L;
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "u_name")
    private String name;

    @Column(name ="u_age")
    private Integer age;
    
    @Column(name ="u_email")
    private String email;

    // 省略构造器 set/get	当然我们也可以使用Lombok来简化构造器代码,将Lombok的相关注解加在类上	

}

3.查询实现

使用@Query注解

public interface UserQueryRepository extends JpaRepository<User, Long> {

    /**
     *  语句中 User 查询数据表的类名,?1 括号代表第一个参数
     */
    @Query(name = "select  * from User where name = ?1")
    List<User> findByName(String name);

     /**
     * Sort 排序
     *  根据姓名模糊查询排序
     */
    @Query("select u from User u where u.name like ?1%")
    List<User> findByAndSort(String name, Sort sort);
    
    /**
     * @Transactional 事务的支持 ,@Modifying 用于修改查询
     * @param name 对应 ?1
     * @param id 对应 ?2
     * @return
     */
    @Transactional
    @Modifying
    @Query("update User u set u.name = ?1 where u.id = ?2")
    int updateById(String  name, Long id);


}

上面是最基本的查询,当我们直接使用@Query注解时,容易出现下列异常:

org.springframework.dao.InvalidDataAccessApiUsageException: For queries with named parameters you need to use provide names for method parameters. 
Use @Param for query method parameters, or when on Java 8+ use the javac flag -parameters.; 
nested exception is java.lang.IllegalStateException: For queries with named parameters you need to use provide names for method parameters. 
Use @Param for query method parameters, or when on Java 8+ use the javac flag -parameters.

上面异常的意思就是:需要使用@Param指定参数名称是什么,或者当你的编译器版本大于1.8的时候要对编译参数加上-parameters。后者我们很好满足,我们需要了解前者是如何实现的。

@Param用法

  /**
     *  param 对象
     * @param name
     * @param age
     * @return
     */
    @Query(value = "select  u from User u where u.name = :name and u.age = :age")
    List<User> queryParamByNameAndAge(@Param("name") String name,@Param("age") Integer age);


    /**
     *  传一个对象
     * @param user
     * @return
     */
    @Query(value = "select  u from User u  where u.name = :#{#user.name} and u.age = :#{#user.age}")
    List<User> queryObjectParamByNameAndAge(@Param("user") User user);

其中:name 对应 @Param中的 name。
age 对应 @Param中的 age。
#{#user.name} : 对象中的参数使用方法。

我们来看一个错误实例就能够很好地理解为什么要使用@Param注解了。


public interface StudentRepository extends CrudRepository<Student, Integer> {
  @Query(
      "select new com.alphathur.jpademo.model.vo.StudentVo(name, age) from Student where id = :id ")
  List<StudentVo> findStudentVo(Double id);
}

在这种情况下就会报错,我们对比这上面的使用方法,就可以发现这个接口使用了‘ :参数名’ 来绑定参数,也就是where id = :id ,且没有对id使用@Param参数,所以报错了。正确修改如下:

public interface StudentRepository extends CrudRepository<Student, Integer> {
  @Query(
      "select new com.alphathur.jpademo.model.vo.StudentVo(name, age) from Student where id = :id ")
  List<StudentVo> findStudentVo(@Param("id") Double id);
}

原生 SQL用法

   @Query(value = "select * from t_user  where u_name = :name",nativeQuery = true)
    List<User> queryNativeByName(@Param("name") String name);

其实就是将整个的SQL语句嵌入到value中,这也是很方便的写法,而且对于初学者来说,这种写法更具有可读性。


测试

在完成了上述操作之后,我们就能够进行测试了,在repository层下面我们写一个测试类:

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserQueryRepositoryTest {

    /**
     * ⽇志对象
     */
    private Logger logger = LoggerFactory.getLogger(UserQueryRepositoryTest.class);

    @Autowired
    private UserQueryRepository userQueryRepository;


    @Before
    public void save() {
        logger.info("新增数据 result = {}", userQueryRepository.save(new User("小米", 9, "a@qq.com")));
        logger.info("新增数据 result = {}", userQueryRepository.save(new User("张三", 16, "b@qq.com")));
        logger.info("新增数据 result = {}", userQueryRepository.save(new User("三哥", 12, "c@qq.com")));
        logger.info("新增数据 result = {}", userQueryRepository.save(new User("米二", 13, "e@qq.com")));
        logger.info("新增数据 result = {}", userQueryRepository.save(new User("阿三", 12, "f@qq.com")));
        logger.info("新增数据 result = {}", userQueryRepository.save(new User("张三", 12, "g@qq.com")));
        logger.info("新增数据 result = {}", userQueryRepository.save(new User("米二", 8, "h@qq.com")));
    }

    /**
     * 基本使用
     */
    @Test
    public void test() {
        logger.info("@query 查询张三 result = {}", userQueryRepository.findByName("张三"));
        logger.info("根据姓名模糊查询排序 result = {}", userQueryRepository.findByNameAndSort("米", new Sort(Sort.Direction.ASC,"age")));
        logger.info("修改 id = 1 的name ,result ={ }", userQueryRepository.updateById("红米", 1L));
    }

    /**
     *  param 参数使用
     */
    @Test
    public void paramTest(){
        logger.info("@param 使用方法  result = {}",userQueryRepository.queryParamByNameAndAge("张三", 12));
        User user = new User();
        user.setName("张三");
        user.setAge(12);
        logger.info("@Param 对象 result = {}", userQueryRepository.queryObjectParamByNameAndAge(user));
    }


    /**
     * 原生查询
     */
    @Test
    public void nativeTest(){
        logger.info("原生查询 使用方法  result = {}",userQueryRepository.queryNativeByName("张三"));

    }
}

这就是一个整体的过程,我们推荐使用原生查询的方式,这种方式更加具有可读性,而且易于初学者的理解。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值