基于SpringBoot打造在线教育系统(2)-- 深入学习JPA与Junit测试

2021-01-05 15:17:35 浏览数 (1)

1.我要添加一条用户数据

现在User表已经有了,而且对应mysql数据库里面,已经建好了用户表。

我琢磨着不是要做登录功能嘛,那就得先往User表里头添加一条数据啊。用mysql front直接添加肯定是可以的,不过前段时间正好看了兔子发在B站的SSM商城系统,里面好像有个地方能够直接用Junit Test测试的,虽然这个系统不是SSM,不过应该也可以吧。

对了,pom.xml里面不是有这么一段配置嘛:

代码语言:javascript复制
<!-- springboot test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

这玩意怎么看都像是测试用的依赖啊,这是不是意味着,我就不用跟视频里面那样,去引入jar包了??

嗯,肯定是的。

2. 做单元测试插入数据

OK,说干就干,创建一个测试包和测试类:

代码语言:javascript复制
package com.edu.test;

import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;

@RunWith(SpringRunner.class)
@SpringBootTest
@WebAppConfiguration
public class UserTest {

}

打上注解,额,对了,我TM好像还没写dao方法呢,赶紧去写个。

创建一个dao包,这个包里面都放持久层的类,现在添加一个UserDao的接口。兔子关于SpringBoot的文章里面已经写过怎么使用JPA了,这边依葫芦画瓢。

直接写一个UserDao接口,继承一下JPA,注意,包别导错了。

代码语言:javascript复制
package com.edu.dao; 

import org.springframework.data.jpa.repository.JpaRepository;
import com.edu.entity.User;

public interface UserDao  extends JpaRepository<User, String>{

}

这样就ok了,然后,回到测试类,把这个接口注入进去。

add测试方法

代码语言:javascript复制
    @Test
    public void addUser(){
        User user = new User();
        user.setUserName("root");
        user.setPassword("root");
        user.setCreateTime("20210103");
        user.setNickName("剽悍一小兔");
        user.setRoleId("1");//默认1是管理员
        user.setIsDelete("0");//默认不删除
        user.setIsLogined("0");//默认没有登录
        
        userDao.save(user);
        
        System.out.println("保存成功!");
        
    }

开始测试:

哇,真的好了嘛,赶紧看下数据库??

OK了,真的来了。

3. 单元测试优化

我叶小凡竟然也可以举一反三啦,兔子还没出这个SpringBoot版本的测试教程呢,我就凭借自己惊人的天赋,提前搞定了,哈哈哈。

兔子:“你这个单元测试还可以优化哦,亲~”

“啥情况,这不是很完美嘛?”

兔子:“这只是第一个单元测试,后面可能还会有很多其他的单元测试,你可以做一个通用的父类,这样就不用在每个测试类上打那么多注解了。”

于是,在兔子的指导下,我虽然不服,但还是照做了。

这样,在测试包下面,我们创建一个通用的测试父类。

代码语言:javascript复制
package com.edu.test;

import org.junit.After;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;

@RunWith(SpringRunner.class)
@SpringBootTest
@WebAppConfiguration
public class BaseTest {
    
    @Before
    public void init() {
        System.out.println("开始测试-----------------");
    }
 
    @After
    public void after() {
        System.out.println("测试结束-----------------");
    }

}

然后,UserTest就继承这个父类,不用再加测试的注解了。

代码语言:javascript复制
public class UserTest extends BaseTest{

}

验证一下,我们再写个测试方法,把刚才的数据删掉。

使用jpa进行update操作主要有两种方式:

1、调用保存实体的方法

代码语言:javascript复制
  1)保存一个实体:repository.save(T entity)

  2)保存多个实体:repository.save(Iterable<T> entities)

  3)保存并立即刷新一个实体:repository.saveAndFlush(T entity)

注:若是更改,entity中必须设置了主键字段,不然不能对应上数据库中的记录,变成新增(数据库自动生成主键)或报错(数据库不自动生成主键)了

2、@Query注解,自己写JPQL语句

代码语言:javascript复制
   @Modifying
   @Query("update ShopCoupon sc set sc.deleted = true where sc.id in :ids")
   public void deleteByIds(@Param(value = "ids") List<String> ids);

1)update或delete时必须使用@Modifying对方法进行注解,才能使得ORM知道现在要执行的是写操作

2)有时候不加@Param注解参数,可能会报如下异常:

org.springframework.dao.InvalidDataAccessApiUsageException: Name must not be null or empty!; nested exception i is Java.lang.IllegalArgumentException: Name must not be null or empty!

以上资料摘自百度,哈哈,我该用哪一种呢?第二种方法比较亲切,直接用sql语句了,那就使用第二种吧。

按照百度到的说法,先在dao增加一个方法,自己写jpql语句,其实我也不太懂啥叫jpql语句,估计意思就是正常写sql,但是呢,字段的名字和User类里面的字段保持一致就行了。因为我发现,生成的表,还是用了下划线,是这样的:

于是,我就不能用下划线。

代码语言:javascript复制
public interface UserDao  extends JpaRepository<User, String>{

    @Modifying
    @Query("update User u set u.isDelete = 1 where u.userName = :userName")
    public void deleteByUsername(@Param(value = "userName") String userName);
    
}

新的测试方法,我要通过userName去做删除,删除不是真的删除,而是逻辑删除。

代码语言:javascript复制
    @Test
    public void deleteUser1(){
        userDao.deleteByUsername("root");
        System.out.println("删除成功");
    }

运行,就报错了: org.springframework.dao.InvalidDataAccessApiUsageException: Executing an update/delete query; nested exception is javax.persistence.TransactionRequiredException: Executing an update/delete query at ... ...

我靠,啥情况,我百度到的啊,怎么会错呢。

算了算了,这个不行,就换另一种方法。

兔子:“小伙子,你这样可不行啊,你好歹看下报啥错啊。。。”

“额,好吧,我看下哈!咦,这个好熟悉,TransactionRequiredException,Transaction这个单词的意思好像是那个事物吧。Required是需要的意思,莫非报错的意思是,让我加一个事物,是这样嘛?”

兔子:“别问我啊,你自己试一下不就知道了嘛!”

“好吧,我就加一个事物的注解看看。奇怪了,我明明百度的文章,哎。”

兔子:“事物一般是加在service方法里面的,你别加在dao里面啊。你想直接测试dao层的方法,这个想法没有错,不过你最好还是弄个service。”

“你的意思是,我再加一个service方法,加上事物,然后调用dao的方法?”

兔子:“嗯,或者你直接把事物加在test方法,也行的。”

说罢,兔哥帮我加上了注解,然后测试,竟然通过了。

“这么说,我找的那篇文章,其实也是对的,等下奥,我翻下链接。”

兔子:“嗯,我看看。”

“就是这个...”

地址:https://blog.csdn.net/qq_33405420/article/details/89469293

兔子:“这写的没问题啊,只是别人不知道你这么菜而已,他也不知道你直接在junit测试类里面去测试dao的方法,而且还不加事物。”

“。。。好吧,那我后面建立service方法的时候,一定加上事物。”

兔子:“没事,我刚开始也这样,慢慢来就好了。对了,我已经把公众号的名字改成了【java小白翻身】,记得关注哦~”

4. 试试jpa的其他修改用法

话说这JPA还真好用,基本的增删改查我都不用写一句sql,对于一些复杂的业务逻辑,我也可以自己写jpql语句(其实还是sql语句,算是面向对象的sql语句吧)。

接下来,我就试试别的方法。刚才已经插入了一条数据,现在我再用另一种方法去修改数据,比如,我把密码改成123吧。

代码语言:javascript复制
    @Test
    public void modifyPassword(){
        User user = new User();
        user.setUserName("root");
        user.setPassword("123");
        userDao.saveAndFlush(user);
        System.out.println("修改成功");
    }

运行,结果崩了...

其他的数据全没了,看来这种更新是全量的更新,不是增量的。我还以为他会只更新userName和password呢,看来是我太天真了。不怕,我再运行一下adduser测试方法,数据不就回来了嘛。。

我太机智了。

再运行一次,数据果然回来了。

这回得小心一点了,我先根据主键userName去拿到这个用户,然后再修改密码:

代码语言:javascript复制
    @Test
    public void modifyPassword(){
        User user = new User();
        user.setUserName("root");
        
        //先找到userName为root的用户
        user = userDao.findOne("root");
        //修改密码
        user.setPassword("123");
        
        userDao.saveAndFlush(user);
        System.out.println("修改成功");
    }

这下子就成功了。

5. JPA的奇技淫巧

springDataJpa还实现了一个非常牛逼的东西,就是根据方法名自动进行sql查询。 比如,我想根据roleId去做查询,就可以直接写一个方法:

代码语言:javascript复制
public List<User> findByRoleId(String roleId);
SpringData JPA方法命名规则查询

顾名思义,方法命名规则查询就是根据方法的名字,就能创建查询。

只需要按照Spring Data JPA提供的方法命名规则定义方法的名称,就可以完成查询工作。

Spring Data JPA在程序执行的时候会根据方法名称进行解析,并自动生成查询语句进行查询

按照Spring Data JPA 定义的规则,查询方法以findBy开头,涉及条件查询时,条件的属性用条件关键字连接,

要注意的是:条件属性首字母需大写。框架在进行方法名解析时,会先把方法名多余的前缀截取掉,然后对剩下部分进行解析。

我们多造一点测试数据,用addUser方法。

比如,我们现在要查询nickName里面带有“剽悍”的,就用like。

代码语言:javascript复制
public List<User> findByNickNameLike(String nickName);

测试:

代码语言:javascript复制
    @Test
    public void userQuery(){
        List<User> users = userDao.findByNickNameLike("%剽悍%");
        for (int i = 0; i < users.size(); i  ) {
            System.out.println(users.get(i).getNickName());
        }
    }

结果:

这个算是jpa里面一个很有意思的用法了,但是我感觉这样心里好没底啊,哈哈。最稳妥的办法,还是直接写JPQL语句吧。

比如,我这样写:

代码语言:javascript复制
@Query("select u from User u where userName = ?1 and password = ?2")
public User findByUserNameAndPassword(String userName,String password);

这样的好处就是,你想写什么查询就写什么查询,是最放心的。

JPA里面最常用的两种传参方式,就是这两种。

其实,jpa里面还有很多其他的拼接方法,但是我看来看去,都太麻烦了,还是直接用JPQL最省事。对于简单的查询,就直接用默认的方法即可,复杂的查询,就老老实实自己写sql吧。

0 人点赞