基于SpringBoot打造在线教育系统(2)-- 深入学习JP
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<T> 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吧。
【源码已经上传啦,关注公号 - java小白翻身,加群免费领取~~】