走进Java接口测试之持久层框架Spring-data-jpa

在这里插入图片描述

一、引言

在接口测试中把Case存储至数据库中,是比较常见的“数据驱动”做法。而在实际的接口测试用例开发中,对数据库的操作无非就是“增删改查”。就为最普遍的单表操作而言,除了表和字段不同外,语句都是类似的,测试人员需要写大量类似而枯燥的语句来完成业务逻辑。
为了解决这些大量枯燥的数据库操作语句,我们第一个想到的使用ORM框架,比如:Hibernate。通过整合Hibernate之后,我们以操作Java实体的方式最终将数据改变映射到数据库表中。
为了解决抽象各个Java实体基本的“增删改查”操作,我们通常会以泛型的方式封装一个模板Dao来进行抽象简化,但是这样依然不是很方便,我们需要针对每个实体编写一个继承自泛型模板Dao的接口,再编写该接口的实现。虽然一些基础的数据访问已经可以得到很好的复用,但是在代码结构上针对每个实体都会有一堆Dao的接口和实现。

由于模板Dao的实现,使得这些具体实体的Dao层已经变的非常“薄”,有一些具体实体的Dao实现可能完全就是对模板Dao的简单代理,并且往往这样的实现类可能会出现在很多实体上。Spring-data-jpa的出现正可以让这样一个已经很“薄”的数据访问层变成只是一层接口的编写方式。

二、Spring-data-jpa介绍

1、JPA是什么?

JPA(Java Persistence API)是Sun官方提出的Java持久化规范。它为Java开发人员提供了一种对象/关联映射工具来管理Java应用中的关系数据。他的出现主要是为了简化现有的持久化开发工作和整合ORM技术,结束现在HibernateTopLinkJDOORM框架各自为营的局面。值得注意的是,JPA是在充分吸收了现有HibernateTopLinkJDOORM框架的基础上发展而来的,具有易于使用,伸缩性强等优点。

注意:JPA是一套规范,不是一套产品,那么像Hibernate,TopLink,JDO他们是一套产品,如果说这些产品实现了这个JPA规范,那么我们就可以叫他们为JPA的实现产品。

2、Spring-data-jpa

Spring-data-jpaSpring 基于 ORM 框架、JPA 规范的基础上封装的一套JPA应用框架,可使开发者用极简的代码即可实现对数据的访问和操作。它提供了包括增删改查等在内的常用功能,且易于扩展!学习并使用 Spring-data-jpa 可以极大提高开发效率!

spring data jpa让我们解脱了DAO层的操作,基本上所有CRUD都可以依赖于它来实现

三、Spring-data-jpa使用

1、基本查询

基本查询分为两种:

  • spring data默认已经实现
  • 根据查询的方法来自动解析成SQL
1.1、预先生成方法

Spring-data-jpa 默认预先生成了一些基本的CURD的方法,例如:增、删、改等等

/**
 * 继承JpaRepository,实现与数据库交互(JPA支持自动生成一些基本CURD SQL语句)
 */
public interface UserRepository extends JpaRepository<User,Integer> {
}

1.2、使用默认方法
	@Autowired
	private PersonRepository personRepository;

	@Test
	public void testBaseQuery(){
		Person person = new Person();
		personRepository.findAll();
//		personRepository.findOne();
		personRepository.save(person);
		personRepository.delete(person);
		personRepository.count();
//		personRepository.exists();
//	    ...

	}

通过方法名可以看出每个具体的用途

1.3、自定义简单查询

自定义的简单查询就是根据方法名来自动生成SQL,主要的语法是findXXBy,readAXXBy,queryXXBy,countXXBy, getXXBy 后面跟属性名称:

User findByUserName(String userName);

也使用一些加一些关键字And、 Or

User findByUserNameOrSex(String username, String sex);

修改、删除、统计也是类似语法

Long deleteById(Long id);
Long countByUserName(String userName)

基本上SQL体系中的关键词都可以使用,例如:LIKEIgnoreCaseOrderBy

List<User> findBySexLike(String sex);
User findByUserNameIgnoreCase(String userName);
List<User> findByUserNameOrderBySexDesc(String sex);

其中,相关命名规范如下:

关键字方法命名JPQL snippet
AndfindByLastnameAndFirstname… where x.lastname = ?1 and x.firstname = ?2
OrfindByLastnameOrFirstname… where x.lastname = ?1 or x.firstname = ?2
Is,EqualsfindByFirstnameIs,findByFirstnameEquals… where x.firstname = ?1
BetweenfindByStartDateBetween… where x.startDate between ?1 and ?2
LessThanfindByAgeLessThan… where x.age < ?1
LessThanEqualfindByAgeLessThanEqual… where x.age ⇐ ?1
GreaterThanfindByAgeGreaterThan… where x.age > ?1
GreaterThanEqualfindByAgeGreaterThanEqual… where x.age >= ?1
AfterfindByStartDateAfter… where x.startDate > ?1
BeforefindByStartDateBefore… where x.startDate < ?1
IsNullfindByAgeIsNull… where x.age is null
IsNotNull,NotNullfindByAge(Is)NotNull… where x.age not null
LikefindByFirstnameLike… where x.firstname like ?1
NotLikefindByFirstnameNotLike… where x.firstname not like ?1
StartingWithfindByFirstnameStartingWith… where x.firstname like ?1
EndingWithfindByFirstnameEndingWith… where x.firstname like ?1
ContainingfindByFirstnameContaining… where x.firstname like ?1 (parameter bound wrapped in %)
OrderByfindByAgeOrderByLastnameDesc… where x.age = ?1 order by x.lastname desc
NotfindByLastnameNot… where x.lastname <> ?1
InfindByAgeIn(Collection ages)… where x.age in ?1
NotInfindByAgeNotIn(Collection age)… where x.age not in ?1
TRUEfindByActiveTrue()… where x.active = true
FALSEfindByActiveFalse()… where x.active = false
IgnoreCasefindByFirstnameIgnoreCase… where UPPER(x.firstame) = UPPER(?1)

PS:Spring-data-jpa的能力远不止本文提到的这些,由于本文主要以介绍接口测试开发为主,对于Spring-data-jpa的使用只是介绍了常见的使用方式。诸如 @Modifying 操作、分页排序、原生SQL支持以及与Spring MVC的结合使用等等内容就不在本文中详细展开。

2、多数据源的支持

2.1、同源数据库的多源支持

日常接口测试中因为测试项目使用的分布式开发模式,不同的服务有不同的数据源,常常需要在一个项目中使用多个数据源,因此需要配置Spring-data-jpa对多数据源的使用,一般分一下为三步:

  • 配置多数据源
  • 不同源的实体类放入不同包路径
  • 声明不同的包路径下使用不同的数据源、事务支持
2.2、异构数据库多源支持

比如项目中,即需要对mysql的支持,也需要对mongodb的查询等。

实体类声明 @Entity 关系型数据库支持类型、声明 @Documentmongodb支持类型,不同的数据源使用不同的实体就可以了

interface PersonRepository extends Repository<Person, Long> {}

@Entity
public class Person {}

interface UserRepository extends Repository<User, Long> {}

@Document
public class User {}

但是,如果User用户既使用 mysql 也使用 mongodb 呢,也可以做混合使用

interface JpaPersonRepository extends Repository<Person, Long> {}

interface MongoDBPersonRepository extends Repository<Person, Long> {}

@Entity
@Document
public class Person {}

也可以通过对不同的包路径进行声明,比如 A 包路径下使用mysql,B 包路径下使用mongoDB

@EnableJpaRepositories(basePackages = "com.zuozewei.repositories.jpa")
@EnableMongoRepositories(basePackages = "com.zuozewei.repositories.mongo")
interface Configuration { }

四、使用示例

1、工程配置

pom.xml中添加相关依赖,加入以下内容:

<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
    </dependencies>

在application.yml中配置:

  • 数据库连接信息(如使用嵌入式数据库则不需要)
  • 自动创建表结构的设置

例如使用mysql的情况如下:

spring:
  profiles:
    active: a
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    # 手动创建db_person数据库
    url: jdbc:mysql://39.105.21.22:3306/db_person?useUnicode=true&characterEncoding=utf-8&useSSL=false
    username: zuozewei
    password: zuozewei
jpa:
  hibernate:
    ddl-auto: update
  # 显示sql 但不会显示具体的操作值 可以替换成log4jdbc
  show-sql: true

server:
  port: 8888
  servlet:
    context-path: /springboot

spring.jpa.properties.hibernate.hbm2ddl.autohibernate的配置属性,其主要作用是:自动创建、更新、验证数据库表结构。该参数的几种配置如下:

  • create:每次加载 hibernate 时都会删除上一次的生成的表,然后根据你的model类再重新来生成新表,哪怕两次没有任何改变也要这样执行,这就是导致数据库表数据丢失的一个重要原因。
  • create-drop:每次加载 hibernate 时根据 Entity 类生成表,但是sessionFactory一关闭,表就自动删除。
  • update:最常用的属性,第一次加载hibernate时根据 Entity 类会自动建立起表的结构(前提是先建立好数据库),以后加载 hibernate时根据model类自动更新表结构,即使表结构改变了但表中的行仍然存在不会删除以前的行。要注意的是当部署到服务器后,表结构是不会被马上建立起来的,是要等应用第一次运行起来后才会。
  • validate:每次加载 hibernate时,验证创建数据库表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值。

至此已经完成基础配置,如果在Spring下整合使用过它的话,相信你已经感受到Spring Boot的便利之处:JPA的传统配置在persistence.xml文件中,但是这里我们不需要。当然,最好在构建项目时候按照之前提过的最佳实践的工程结构来组织,这样以确保各种配置都能被框架扫描到。

2、创建实体

创建一个User实体,包含id(主键)、name(姓名)、age(年龄)属性,通过ORM框架其会被映射到数据库表中,由于配置了hibernate.hbm2ddl.auto,在应用启动的时候框架会自动去数据库中创建对应的表。此处使用lombok效率插件,不熟悉的同学参照走进Java接口测试之效率插件lombok

/**
 * 用户实体类
 * @author zuozwei
 *
 */

@Entity
@Data
@Table(name = "person")
public class User {
	//主键自增长
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Integer id;

	@Column(nullable = false)
	private String name;

	@Column(nullable = false)
	private Integer age;

	public User() {
	}

	public User(String name, Integer age) {
		this.name = name;
		this.age = age;
	}

}

3、创建数据访问接口

下面针对User实体创建对应的Repository接口实现对该实体的数据访问

/**
 * 数据访问类
 * @author zuozewei
 *
 */
public interface UserRepository extends JpaRepository<User,Long> {

	User findByName(String name);

	User findByNameAndAge(String name, Integer age);

	@Query("from User u where u.name=:name")
	User findUser(@Param("name") String name);
}

Spring-data-jpa中,只需要编写类似上面这样的接口就可实现数据访问。不再像我们以往编写了接口时候还需要自己编写接口实现类,直接减少了我们的文件清单。
在上例中,我们可以看到下面两个函数:

User findByName(String name)
User findByNameAndAge(String name, Integer age)

它们分别实现了按 name 查询 User 实体和按 nameage 查询 User实体,可以看到我们这里没有任何类SQL语句就完成了两个条件查询方法。这就是Spring-data-jpa的一大特性:通过解析方法名创建查询。

除了通过解析方法名来创建查询外,它也提供通过使用 @Query 注解来创建查询,只需要编写JPQL语句,并通过类似“:name”来映射@Param指定的参数,就像例子中的第三个findUser函数一样。

4、Junit单元测试

在完成了上面的数据访问接口之后,按照惯例就是编写对应的Junit单元测试来验证编写的内容是否正确。这里就不多做介绍,主要通过数据操作和查询来反复验证操作的正确性。

@RunWith(SpringRunner.class)
@SpringBootTest
public class userTest {
	@Autowired
	private UserRepository userRepository;
	
	@Test
	public void test(){

		// 创建10条记录
		userRepository.save(new User("AAA", 10));
		userRepository.save(new User("BBB", 20));
		userRepository.save(new User("CCC", 30));
		userRepository.save(new User("DDD", 40));
		userRepository.save(new User("EEE", 50));
		userRepository.save(new User("FFF", 60));
		userRepository.save(new User("GGG", 70));
		userRepository.save(new User("HHH", 80));
		userRepository.save(new User("III", 90));
		userRepository.save(new User("JJJ", 100));

		// 测试findAll, 查询所有记录
		Assert.assertEquals(10, userRepository.findAll().size());

		// 测试findByName, 查询姓名为FFF的User
		Assert.assertEquals(60, userRepository.findByName("FFF").getAge().longValue());

		// 测试findUser, 查询姓名为FFF的User
		Assert.assertEquals(60, userRepository.findUser("FFF").getAge().longValue());

		// 测试findByNameAndAge, 查询姓名为FFF并且年龄为60的User
		Assert.assertEquals("FFF", userRepository.findByNameAndAge("FFF", 60).getName());

		// 测试删除姓名为AAA的User
		userRepository.delete(userRepository.findByName("AAA"));

		// 测试findAll, 查询所有记录, 验证上面的删除是否成功
		Assert.assertEquals(9, userRepository.findAll().size());

	}
}

测试结果:
在这里插入图片描述
在这里插入图片描述

五、工程结构

在这里插入图片描述

本文源码:

  • https://github.com/zuozewei/blog-example/tree/master/Java-api-test/04-orm-framework/springboot-data-jpa-sample
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zuozewei

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值