Springboot使用JPA操作数据库

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 第七章 使用JPA操作数据库本章主要介绍如何在Spring Boot的Web应用中使用Mysq数据库,也充分展示Spring Boot的优势(尽可能少的代码和配置).

第七章 使用JPA操作数据库

本章主要介绍如何在Spring Boot的Web应用中使用Mysq数据库,也充分展示Spring Boot的优势(尽可能少的代码和配置).

数据访问层我们将使用Spring Data JPA和Hibernate(JPA的实现之一).

Maven pom.xml文件

lightsword/pom.xml

在项目中增加如下依赖文件

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

配置文件application.properties

在src/main/resources/application.properties中设置数据源和jpa配置:

#mysql

spring.datasource.url = jdbc:mysql://localhost:3306/lightsword?useUnicode=true&characterEncoding=UTF8
spring.datasource.username = root
#root@localhost ::TZaMojg3ntd
spring.datasource.password = root
spring.datasource.driverClassName = com.mysql.jdbc.Driver

spring.datasource.max-active=0
spring.datasource.max-idle=0
spring.datasource.min-idle=0
spring.datasource.max-wait=10000
spring.datasource.max-wait-millis=31536000

# Specify the DBMS
spring.jpa.database = MYSQL
# Show or not log for each sql query
spring.jpa.show-sql = true
# Hibernate ddl auto (create, create-drop, update)
spring.jpa.hibernate.ddl-auto = update
# Naming strategy
spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.ImprovedNamingStrategy

# stripped before adding them to the entity manager)
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect

全部的配置都在如上的文件中了,不需要另外的XML配置和Java配置。

上文中的数据库配置,你需要换成你的数据库的地址和用户名密码。

hibernate的ddl-auto=update配置表名,数据库的表和列会自动创建(根据Java实体类,在scala中,只要在实体类上标注@Entity,成员变量上标注@BeanProperty),这里 可以看到更多得hibernate配置。

实体类

创建一个HttpApi实体类,实体和Mysql数据库的http_api表相对应(这个表字段会在应用启动的时候,自动生成)。

package com.springboot.in.action.entity

import java.util.Date
import javax.persistence.{ Entity, GeneratedValue, GenerationType, Id }
import scala.language.implicitConversions
import scala.beans.BeanProperty

@Entity
class HttpApi {
  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  @BeanProperty
  var id: Integer = _

  @BeanProperty
  var httpSuiteId: Integer = _
  //用例名称
  @BeanProperty
  var name: String = _

  //用例状态: -1未执行 0失败 1成功
  @BeanProperty
  var state: Integer = _
  //接口
  @BeanProperty
  var url: String = _

  //方法GET,POST
  @BeanProperty
  var method: String = _

  //post参数json string
  @BeanProperty
  var paramJsonStr: String = _

  //期望输出
  @BeanProperty
  var expectOutput: String = _

  //实际输出
  @BeanProperty
  var actualOutput: String = _

  @BeanProperty
  var runTimes: Integer = _
  @BeanProperty
  var owner: String = _
  @BeanProperty
  var gmtCreate: Date = _
  @BeanProperty
  var gmtModify: Date = _

}

实体的数据访问层HttpApiDao

实体的数据访问层HttpApiDao非常简单,只需要继承CrudRespositroy即可,CrudRespositroy已经实现了save,delete,deleteAll,findOne和findAll.
(比较神奇的时这些方法其实CrudRespositroy中其实并没有实现,并且通过对dao层的方法的命名还可以实现新的方法).

当然,如果基本的CRUD方法满足不了我们稍微复杂一些的sql查询,我们可以直接定义sql查询语句,绑定dao层的方法.实例在如下代码中可以看到:

package com.springboot.in.action.dao

import java.util.List

import com.springboot.in.action.entity.HttpApi
import org.springframework.data.jpa.repository.Query
import org.springframework.data.repository.CrudRepository

import scala.language.implicitConversions

trait HttpApiDao extends CrudRepository[HttpApi, Integer] {
  def findAll(): List[HttpApi] // JavaConversions

  def save(t: HttpApi): HttpApi

  def findOne(id: Integer): HttpApi

  @Query(value = "SELECT * FROM http_api where http_suite_id = ?1", nativeQuery = true)
  def listByHttpSuiteId(id: Integer): List[HttpApi]

  @Query(value = "SELECT id FROM http_api where http_suite_id = ?1", nativeQuery = true)
  def listTestCaseId(httpSuiteId: Integer): List[Integer] // 隐式转换,直接用scala的List会报错:javax.persistence.NonUniqueResultException: result returns more than one elements] with root cause

   @Query(value = "SELECT * FROM http_api where name like %?1% ", nativeQuery = true) // like '%?%'
   def findByName(name: String): List[HttpApi]

  @Query(value = "select count(*) from http_api where http_suite_id = ?1 and state = 1", nativeQuery = true)
  def countPass(httpSuiteId: Integer): Int

  @Query(value = "select count(*) from http_api where http_suite_id = ?1 and state = 0", nativeQuery = true)
  def countFail(httpSuiteId: Integer): Int

}

重点看一下

  • @Query注解里面的value和nativeQuery=true,意思是使用原生的sql查询语句.
  • sql模糊查询like语法,我们在写sql的时候是这样写的
like '%?%'

但是在@Query的value字符串中, 这样写

SELECT * FROM http_api where name like %?1%
  • 在Springboot跟scala集成开发过程中,集合类需要使用java里面的api,直接用scala的List会报错:
javax.persistence.NonUniqueResultException: result returns more than one elements] with root cause.

可以显示声明:

import java.util.List

也可以使用隐式转换:

import scala.collection.JavaConversions._

控制器HttpApiController

新建控制器HttpApiController.scala代码

package com.springboot.in.action.controller

import java.util.Date
import java.util.concurrent.CountDownLatch

import com.alibaba.fastjson.JSON
import com.springboot.in.action.dao.{HttpApiDao, HttpReportDao, HttpSuiteDao}
import com.springboot.in.action.engine.OkHttp
import com.springboot.in.action.entity.{HttpApi, HttpReport}
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.ui.Model
import org.springframework.web.bind.annotation.{PathVariable, RequestMapping, RequestMethod, RequestParam, ResponseBody, RestController}
import org.springframework.web.servlet.ModelAndView
import scala.collection.JavaConversions._

@RestController
@RequestMapping(Array("/httpapi"))
class HttpApiController @Autowired() (
    val HttpSuiteDao: HttpSuiteDao,
    val HttpApiDao: HttpApiDao,
    val HttpReportDao: HttpReportDao) {

  @RequestMapping(value = {
    Array("", "/")
  }, method = Array(RequestMethod.GET))
  def list(model: Model) = {
    model.addAttribute("httpapis", HttpApiDao.findAll())
    new ModelAndView("/httpapi/list")
  }

  @RequestMapping(value = {
    Array("/json")
  }, method = Array(RequestMethod.GET))
  def listJson() = HttpApiDao.findAll()

  @RequestMapping(value = {
    Array("/listHttpSuiteTestCase")
  }, method = Array(RequestMethod.GET))
  def listHttpSuiteTestCase(model: Model, @RequestParam(value = "httpSuiteId") httpSuiteId: Integer) = {
    var httpapis = HttpApiDao.listByHttpSuiteId(httpSuiteId)
    model.addAttribute("httpapis", httpapis)
    model.addAttribute("httpSuiteId", httpSuiteId)
    model.addAttribute("httpSuiteName", HttpSuiteDao.findOne(httpSuiteId).name)
    new ModelAndView("/httpapi/listHttpSuiteTestCase")
  }

  @RequestMapping(value = {
    Array("/listHttpSuiteTestCaseJson")
  },
    method = Array(RequestMethod.GET))
  @ResponseBody
  def listHttpSuiteTestCaseJson(model: Model, @RequestParam(value = "httpSuiteId") httpSuiteId: Integer) = {
    HttpApiDao.listByHttpSuiteId(httpSuiteId)
  }

  @RequestMapping(Array("/newPage/{httpSuiteId}"))
  def goNewPage(@PathVariable(value = "httpSuiteId") httpSuiteId: Integer, model: Model) = {
    model.addAttribute("httpSuiteId", httpSuiteId)
    model.addAttribute("httpSuiteName", HttpSuiteDao.findOne(httpSuiteId).name)
    new ModelAndView("/httpapi/new")
  }

  /**
   * 项目下面的用例编辑
   */
  @RequestMapping(Array("/editPage/{caseId}"))
  def goEditPage(model: Model, @PathVariable(value = "caseId") caseId: Integer, @RequestParam(value = "httpSuiteId") httpSuiteId: Integer) = {
    val httpapi = HttpApiDao.findOne(caseId)
    model.addAttribute("httpapi", httpapi)
    model.addAttribute("httpSuiteId", httpSuiteId)
    model.addAttribute("httpSuiteName", HttpSuiteDao.findOne(httpSuiteId).name)
    new ModelAndView("/httpapi/edit")
  }

  @RequestMapping(Array("/copyPage/{caseId}"))
  def goCopyPage(model: Model, @PathVariable(value = "caseId") caseId: Integer, @RequestParam(value = "httpSuiteId") httpSuiteId: Integer) = {
    val httpapi = HttpApiDao.findOne(caseId)
    model.addAttribute("httpapi", httpapi)
    model.addAttribute("httpSuiteId", httpSuiteId)
    model.addAttribute("httpSuiteName", HttpSuiteDao.findOne(httpSuiteId).name)
    new ModelAndView("/httpapi/copy")
  }

  @RequestMapping(Array("/detailPage/{id}"))
  def goDetailPage(model: Model, @PathVariable(value = "id") id: Integer) = {
    val httpapi = HttpApiDao.findOne(id)
    model.addAttribute("httpapi", httpapi)
    new ModelAndView("/httpapi/detail")
  }

  @RequestMapping(value = Array("/postnew"),
    method = Array(RequestMethod.POST))
  @ResponseBody
  def newOne(@RequestParam(value = "httpSuiteId") httpSuiteId: Integer,
    @RequestParam(value = "name") name: String,
    @RequestParam(value = "url") url: String,
    @RequestParam(value = "method") method: String,
    @RequestParam(value = "paramJsonStr") paramJsonStr: String,
    @RequestParam(value = "expectOutput") expectOutput: String,
    @RequestParam(value = "actualOutput") actualOutput: String,
    @RequestParam(value = "owner") owner: String) = {
    val httpapi = new HttpApi()
    httpapi.httpSuiteId = httpSuiteId
    httpapi.name = name
    httpapi.url = url
    httpapi.method = method
    httpapi.paramJsonStr = paramJsonStr
    httpapi.expectOutput = expectOutput
    httpapi.actualOutput = actualOutput
    httpapi.runTimes = 0
    httpapi.state = -1
    httpapi.owner = owner
    httpapi.gmtCreate = new Date()
    httpapi.gmtModify = new Date()
    HttpApiDao.save(httpapi)
  }

  @RequestMapping(value = Array("/postedit"),
    method = Array(RequestMethod.POST))
  @ResponseBody
  def editOne(@RequestParam(value = "id") id: Integer,
    @RequestParam(value = "name") name: String,
    @RequestParam(value = "url") url: String,
    @RequestParam(value = "method") method: String,
    @RequestParam(value = "paramJsonStr") paramJsonStr: String,
    @RequestParam(value = "expectOutput") expectOutput: String) = {
    val httpapi = HttpApiDao.findOne(id)
    httpapi.name = name
    httpapi.url = url
    httpapi.method = method
    httpapi.paramJsonStr = paramJsonStr
    httpapi.expectOutput = expectOutput
    httpapi.gmtModify = new Date()
    HttpApiDao.save(httpapi)
  }

  /**
   * 在新建用例页面,调试用例用
   */
  @RequestMapping(value = Array("/debugTest"),
    method = Array(RequestMethod.GET))
  @ResponseBody
  def debugTest(@RequestParam(value = "url") url: String,
    @RequestParam(value = "method") method: String,
    @RequestParam(value = "paramJsonStr") paramJsonStr: String) = {
    OkHttp.run(url, method, paramJsonStr)
  }

  /**
   * 执行用例
   */
  @RequestMapping(value = Array("/runTest"),
    method = Array(RequestMethod.GET))
  @ResponseBody
  def debugTest(@RequestParam(value = "id") id: Integer) = { runTestCase(id) }

  /**
   * 回归项目全部用例,每个用例单独起一个线程跑
   */
  @RequestMapping(value = Array("/testHttpSuite"),
    method = Array(RequestMethod.GET))
  @ResponseBody
  def testProject(@RequestParam(value = "httpSuiteId") httpSuiteId: Integer) = {
    val caseIds = HttpApiDao.listTestCaseId(httpSuiteId)

    val threads = caseIds.size
    val countDownLatch = new CountDownLatch(threads)

    for (cid <- caseIds) {
      val t = new TestCaseRunner(cid, countDownLatch)
      t.start
    }
    println("回归测试开始......")
    countDownLatch.await // now waiting sub thread done.
    println("回归测试结束!")
    val HttpReport = getHttpReport(httpSuiteId)
    // 保存测试结果
    HttpReportDao.save(HttpReport)
    HttpReport
  }

  def getHttpReport(httpSuiteId: Integer) = {
    println("自动化回归测试报告:")

    val p = HttpSuiteDao.findOne(httpSuiteId)
    val httpSuiteName = p.name
    val pass = HttpApiDao.countPass(httpSuiteId)
    val fail = HttpApiDao.countFail(httpSuiteId)

    val HttpReport = new HttpReport
    HttpReport.httpSuiteId = httpSuiteId
    HttpReport.httpSuiteName = httpSuiteName
    HttpReport.pass = pass
    HttpReport.fail = fail
    HttpReport.time = new Date
    println(JSON.toJSONString(HttpReport, true))
    HttpReport
  }

  /**
   * 执行caseId这个用例
   */

  def runTestCase(id: Integer) = {
    val tc = HttpApiDao.findOne(id)
    val url = tc.url
    val method = tc.method
    val paramJsonStr = tc.paramJsonStr
    println("接口url:" + url)
    println("方法:" + method)
    println("输入参数:" + paramJsonStr)

    val result = OkHttp.run(url, method, paramJsonStr)
    //执行次数+1
    tc.runTimes = tc.runTimes + 1
    println("实际输出:" + result)
    tc.actualOutput = result
    // 结果断言
    val expectOutput = tc.expectOutput
    val contains = result.contains(expectOutput)
    tc.state = if (contains) 1 else 0
    // 执行事件
    tc.gmtModify = new Date
    HttpApiDao.save(tc)
  }

  /**
   * TestCaseRunner
   */
  class TestCaseRunner(val caseId: Integer, val countDownLatch: CountDownLatch) extends Thread {

    override def run() {
      runTestCase(caseId)
      countDownLatch.countDown
    }
  }
}

自动装配@Autowired Dao层代码,在Controller实现业务逻辑.

运行测试

启动脚本lightsword/run.sh

mvn clean scala:compile scala:run -Dlauncher=app

运行lightsword/run.sh,启动应用.

新建用例集,然后在此用例集中新建一个测试用例,如图所示:

Paste_Image.png

可以直接运行,可以看到测试结果.

也可以浏览器访问:

http://localhost:8888/httpapi/listHttpSuiteTestCaseJson?httpSuiteId=1

看到Restful接口的json返回:

[{"id":1,"httpSuiteId":1,"name":"HelloSB测试","state":1,"url":"http://localhost:8888/hello","method":"GET","paramJsonStr":"{}","expectOutput":"LightSword","actualOutput":"{\"conent\":\"Hello, LightSword! Now is: Mon Jun 27 13:23:20 CST 2016\"}","runTimes":1,"owner":"陈光剑","gmtCreate":1467004998000,"gmtModify":1467005001000}]

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
6天前
|
Java 数据库连接 API
【Java笔记+踩坑】Spring Data JPA
从常用注解、实体类和各层编写方法入手,详细介绍JPA框架在增删改查等方面的基本用法,以及填充用户名日期、分页查询等高级用法。
【Java笔记+踩坑】Spring Data JPA
|
28天前
|
安全 Java 关系型数据库
毕设项目&课程设计&毕设项目:基于springboot+jsp实现的健身房管理系统(含教程&源码&数据库数据)
本文介绍了一款基于Spring Boot和JSP技术实现的健身房管理系统。随着健康生活观念的普及,健身房成为日常锻炼的重要场所,高效管理会员信息、课程安排等变得尤为重要。该系统旨在通过简洁的操作界面帮助管理者轻松处理日常运营挑战。技术栈包括:JDK 1.8、Maven 3.6、MySQL 8.0、JSP、Shiro、Spring Boot 2.0等。系统功能覆盖登录、会员管理(如会员列表、充值管理)、教练管理、课程管理、器材管理、物品遗失管理、商品管理及信息统计等多方面。
|
26天前
|
JavaScript Java 关系型数据库
毕设项目&课程设计&毕设项目:基于springboot+vue实现的前后端分离的考试管理系统(含教程&源码&数据库数据)
在数字化时代背景下,本文详细介绍了如何使用Spring Boot框架结合Vue.js技术栈,实现一个前后端分离的考试管理系统。该系统旨在提升考试管理效率,优化用户体验,确保数据安全及可维护性。技术选型包括:Spring Boot 2.0、Vue.js 2.0、Node.js 12.14.0、MySQL 8.0、Element-UI等。系统功能涵盖登录注册、学员考试(包括查看试卷、答题、成绩查询等)、管理员功能(题库管理、试题管理、试卷管理、系统设置等)。
毕设项目&课程设计&毕设项目:基于springboot+vue实现的前后端分离的考试管理系统(含教程&源码&数据库数据)
|
1月前
|
JavaScript Java Maven
毕设项目&课程设计&毕设项目:springboot+vue实现的在线求职管理平台(含教程&源码&数据库数据)
本文介绍了一款基于Spring Boot和Vue.js实现的在线求职平台。该平台采用了前后端分离的架构,使用Spring Boot作为后端服务
毕设项目&课程设计&毕设项目:springboot+vue实现的在线求职管理平台(含教程&源码&数据库数据)
|
19天前
|
Java Spring 数据库
怎样动动手指就能实现数据操作?Spring Data JPA背后的魔法揭秘
【8月更文挑战第31天】在Java开发中,数据库交互至关重要。传统的JDBC操作繁琐且难维护,而Spring Data JPA作为集成JPA的数据访问层解决方案,提供了CRUD等通用操作接口,显著减少代码量。通过继承`JpaRepository`,开发者能轻松实现数据的增删改查,甚至复杂查询和分页也不再困难。本文将通过示例详细介绍如何利用Spring Data JPA简化数据访问层的开发,提升代码质量和可维护性。
26 0
|
29天前
|
存储 Java 数据库
|
29天前
|
存储 Java API
|
29天前
|
Java 数据库连接 数据库
Spring Data JPA 与 Hibernate 之区别
【8月更文挑战第21天】
17 0
|
1月前
|
druid Java 数据库连接
SpringBoot项目整合MybatisPlus持久层框架+Druid数据库连接池,以及实现增删改查功能
SpringBoot项目整合MybatisPlus和Druid数据库连接池,实现基本的增删改查功能。
157 0
|
SQL Java
SpringBoot整合JPA(六)下
SpringBoot整合JPA(六)
162 0
SpringBoot整合JPA(六)下