zoukankan      html  css  js  c++  java
  • 基于SpringBoot打造在线教育系统(2)-- 深入学习JPA与Junit测试

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

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

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

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

    <!-- springboot test -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
    

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

    嗯,肯定是的。

    2. 做单元测试插入数据

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

    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,注意,包别导错了。

    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测试方法

    	@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版本的测试教程呢,我就凭借自己惊人的天赋,提前搞定了,哈哈哈。

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

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

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

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

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

    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就继承这个父类,不用再加测试的注解了。

    public class UserTest extends BaseTest{
    
    }
    

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

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

    1、调用保存实体的方法

    1)保存一个实体:repository.save(T entity)

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

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

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

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

       @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类里面的字段保持一致就行了。因为我发现,生成的表,还是用了下划线,是这样的:

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

    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去做删除,删除不是真的删除,而是逻辑删除。

    	@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吧。

    	@Test
    	public void modifyPassword(){
    		User user = new User();
    		user.setUserName("root");
    		user.setPassword("123");
    		userDao.saveAndFlush(user);
    		System.out.println("修改成功");
    	}
    

    运行,结果崩了...

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

    我太机智了。

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

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

    	@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去做查询,就可以直接写一个方法:

    public List<User> findByRoleId(String roleId);
    

    SpringData JPA方法命名规则查询

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

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

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

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

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

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

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

    public List<User> findByNickNameLike(String nickName);
    

    测试:

    	@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语句吧。

    比如,我这样写:

    @Query("select u from User u where userName = ?1 and password = ?2")
    public User findByUserNameAndPassword(String userName,String password);
    

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

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

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

  • 相关阅读:
    pydbg系列[1]
    内核参与方式
    Debugging with GDB阅读[6]
    宏技巧解读
    右键-发送到-邮件接收者没有了的解决方法
    获得文件版本信息
    解决动态生成的SQL中特殊字符的问题 QuotedStr function
    CreateFileMapping的MSDN翻译和使用心得
    关闭Windows自动播放功能
    清凉明目茶
  • 原文地址:https://www.cnblogs.com/skyblue-li/p/14226377.html
Copyright © 2011-2022 走看看