文章目录
本页将介绍Spring Boot
CrudRepository
的例子。Spring Boot Data
默认启用JPA
仓库支持。
CrudRepository
为特定类型的存储库提供通用的CRUD
操作。CrudRepository
是一个Spring
数据接口,要使用它,我们需要通过扩展CrudRepository
来创建我们的接口。
Spring
在运行时自动提供CrudRepository
实现类。它包含的方法有:save
, findById
, delete
, count
等。
如果该仓库接口的包与@SpringBootApplication
注释的类相同或为其子包,Spring Boot
就会自动检测我们的仓库。
当Spring Boot
在类路径中扫描Spring Data JPA
时,它提供了默认的数据库配置。
Spring Boot
使用spring-boot-starter-data-jpa
启动器来配置spring JPA
。
对于数据源,我们需要在application.properties
中配置以spring.datasource.*
开头的数据源属性。
在Spring Boot 2.0
版本中,默认的数据库池技术已经从Tomcat Pool
切换到HikariCP
。在Spring Boot
中,首先是HikariCP
,然后是Tomcat Pooling
,最后是Commons DBCP2
(基于可用性)。
在这个页面上,我们将为CRUD
操作创建一个Spring Boot
Rest Web
服务。CRUD
操作将由CrudRepository
执行。
现在请看完整的例子。
1. 演示工具版本
- Java 9
- Spring 5.0.5.RELEASE
- Spring Boot 2.0.1.RELEASE
- Maven 3.5.2
- MySQL 5.5
- Eclipse Oxygen
2. 项目中使用的 Maven 文件
找到我们的例子中使用的pom.xml
。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.concretepage</groupId>
<artifactId>spring-boot-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>spring-demo</name>
<description>Spring Boot Demo Project</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
<relativePath/>
</parent>
<properties>
<java.version>9</java.version>
</properties>
<dependencies>
<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>
<version>6.0.5</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3. CrudRepository 接口
CrudRepository
是一个接口,扩展了Spring data
的Repository
接口。
CrudRepository
为特定类型的存储库提供通用的CRUD
操作。它有通用的方法用于CRUD
操作。
为了使用CrudRepository
,我们必须创建我们的接口并扩展CrudRepository
。
我们不需要实现我们的接口,它的实现将在运行时被自动创建。找到一些CrudRepository
的方法。
<S extends T> S save(S entity): 保存和更新当前实体并返回该实体。
Optional<T> findById(ID primaryKey): 返回指定id
的实体。
Iterable<T> findAll(): 返回所有实体。
long count(): 返回数据的数量。
void delete(T entity): 删除指定的实体。
boolean existsById(ID primaryKey): 检查指定id
的实体是否存在。
CrudRepository
的扩展接口是PagingAndSortingRepository
,它提供了额外的方法来使用分页和排序的抽象来检索实体。
4. 使用 CrudRepository 的步骤
Spring boot
默认启用JPA
仓库支持。为了在我们的Spring data
应用程序中使用CrudRepository
,我们需要创建一个实现CrudRepository
的接口,然后就可以使用它了。让我们逐步讨论如何在Spring data
应用程序中使用CrudRepository
。
4.1 创建一个扩展 CrudRepository 的接口
在我们的例子中,我们将对文章数据进行CRUD
操作来演示。所以我将为文章创建一个扩展CrudRepository
的接口,如下所示。
public interface ArticleRepository extends CrudRepository<Article, Long> {
}
我们不需要创建它的实现类。Spring
会在运行时自动创建其实现类。
4.2 JPA 存储库的自动检测
如果该接口的包与@SpringBootApplication
注释的类相同或为其子包,Spring boot
可以自动检测我们的存储库,如果不是,我们需要使用@EnableJpaRepositories
与@SpringBootApplication
注释。让我们通过例子来理解。
假设我们在com.concretepage
包中有一个用@SpringBootApplication
注释的类,如下所示。
package com.concretepage;
------
@SpringBootApplication
public class MyApplication {
------
}
现在,如果我们有一个存储库ArticleRepository
,并且它存放在com.concretepage
包或其子包中,如com.concretepage.repository
,那么Spring boot
将自动检测到我们的存储库,所以不需要使用@EnableJpaRepositories
注解。
如果我们为仓库选择的包既不是相同的包,也不是用@SpringBootApplication
注解的类的包的子包,那么Spring boot
将无法默认检测仓库类。在这种情况下,我们需要使用@EnableJpaRepositories
注解和@SpringBootApplication
。使用@EnableJpaRepositories
,我们将配置资源库类所在的包名。假设我们的资源库类的包是com.cp.repository
,我们将使用@EnableJpaRepositories
如下。
package com.concretepage;
------
@SpringBootApplication
@EnableJpaRepositories("com.cp.repository")
public class MyApplication {
------
}
如果我们想配置特定的类,那么我们需要使用@EnableJpaRepositories
注解的basePackageClasses
属性。假设我们在com.cp.repository
包中有一个ArticleRepository
类,那么我们可以使用basePackageClasses
配置repository
,如下所示。
package com.concretepage;
-----
import com.cp.repository.ArticleRepository;
@SpringBootApplication
@EnableJpaRepositories(basePackageClasses= {ArticleRepository.class})
public class MyApplication {
------
}
4.3 实例化并使用 CrudRepository
为了实例化我们扩展了CrudRepository
的ArticleRepository
,我们可以使用依赖注入。
public class ArticleService {
@Autowired
private ArticleRepository articleRepository;
------
}
现在我们准备使用CrudRepository
的方法。找到它的一些方法的例子。
a. Create and Update:
Article savedArticle = articleRepository.save(article);
b. Read:
Article obj = articleRepository.findById(articleId).get();
Iterable<Article> articles = articleRepository.findAll();
c. Delete:
articleRepository.delete(article);
5. 自定义存储库方法
CrudRepository
提供了通用的CRUD
操作方法,如果我们想在扩展了CrudRepository
的接口中添加自定义方法,我们可以通过以下方式添加。
a. 我们可以用find...By
、read...By
、query...By
、count...By
和get...By
开始我们的查询方法名称。在By
之前,我们可以添加表达式,如Distinct
。在By
之后,我们需要添加我们实体的属性名称。
b. 为了在一个以上的属性基础上获得数据,我们可以在创建方法名称时使用And
和Or
将属性名称连接起来。
c. 如果我们想为我们的方法使用完全自定义的名称,我们可以使用@Query
注解来编写查询。
查找使用上述场景的示例方法名称的代码段。
public interface ArticleRepository extends CrudRepository<Article, Long> {
List<Article> findByTitle(String title);
List<Article> findDistinctByCategory(String category);
List<Article> findByTitleAndCategory(String title, String category);
@Query("SELECT a FROM Article a WHERE a.title=:title and a.category=:category")
List<Article> fetchArticles(@Param("title") String title, @Param("category") String category);
}
上述方法的实现类将由Spring
在运行时自动创建。
6. 在 CrudRepository 上使用 @Transactional 注解
CrudRepository
的CRUD
方法默认是事务性的。它们在运行时被@Transactional
注解和实现类的默认设置所注解。对于读操作,readOnly
标志被设置为true
。
要覆盖任何CrudRepository
方法的默认事务性设置,我们需要在我们的接口中覆盖该方法,并使用所需的配置用@Transactional
注解。找到这个例子。
public interface ArticleRepository extends CrudRepository<Article, Long> {
@Override
@Transactional(timeout = 8)
Iterable<Article> findAll();
}
在这里,我们将超时配置为8
秒,以便在findAll()
方法中执行没有readOnly
标志的查询。
7. 在 application.properties 文件中配置属性
数据源、JPA
属性和日志等需要在位于Spring boot
应用程序类路径中的application.properties
文件中进行配置。这些属性将被Spring boot
自动读取。
application.properties
spring.datasource.url=jdbc:mysql://localhost:3306/concretepage
spring.datasource.username=root
spring.datasource.password=cp
spring.datasource.hikari.connection-timeout=20000
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.maximum-pool-size=12
spring.datasource.hikari.idle-timeout=300000
spring.datasource.hikari.max-lifetime=1200000
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQLDialect
spring.jpa.properties.hibernate.id.new_generator_mappings = false
spring.jpa.properties.hibernate.format_sql = true
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
Spring-boot-starter-jdbc
和spring-boot-starter-data-jpa
默认解决HikariCP
依赖,spring.datasource.type
属性默认值为HikariDataSource
。
以spring.datasource.*
开头的数据源属性将被Spring boot
的JPA
自动读取。
要改变Hibernate
属性,我们将使用前缀spring.jpa.properties.*
与Hibernate
属性名称。
在给定的数据源URL
的基础上,Spring boot
可以自动识别数据源驱动类。所以我们不需要配置驱动类。
spring.data.jpa.repositories.enabled: 它启用JPA
存储库。默认值为true
。
spring.jpa.database: 它以要操作的数据库为目标。默认情况下,自动检测嵌入式数据库。
spring.jpa.database-platform: 它用于提供要操作的数据库的名称。默认情况下,它是自动检测的。
spring.jpa.generate-ddl: 它用于在启动时初始化schema
。默认情况下,该值为false
。
spring.jpa.hibernate.ddl-auto: 它是用于嵌入式数据库的DDL模式。默认值是 create-drop。
spring.jpa.hibernate.naming.implicit-strategy: 它是Hibernate 5
隐式命名策略的完全限定名。
spring.jpa.hibernate.naming.physical-strategy:它是Hibernate 5
物理命名策略的完全限定名。
spring.jpa.hibernate.use-new-id-generator-mappings: 它被用于Hibernate
IdentifierGenerator
的AUTO
、TABLE
和SEQUENCE
。
spring.jpa.open-in-view: 默认值为true
。它将JPA
的EntityManager
绑定到线程,用于请求的整个处理。
spring.jpa.properties.*: 它设置了额外的本地属性,以便在JPA
提供者上设置。
spring.jpa.show-sql: 它可以启用SQL语句的日志记录。默认值是false
。
8. Spring Boot REST + Spring Boot Data CrudRepository + JPA + Hibernate + MySQL CRUD 完整示例
项目结构
找到我们例子中使用的MySQL数据库表。
Database Table
CREATE DATABASE IF NOT EXISTS `concretepage`;
USE `concretepage`;
CREATE TABLE IF NOT EXISTS `articles` (
`article_id` bigint(5) NOT NULL AUTO_INCREMENT,
`title` varchar(200) NOT NULL,
`category` varchar(100) NOT NULL,
PRIMARY KEY (`article_id`)
) ENGINE=InnoDB;
INSERT INTO `articles` (`article_id`, `title`, `category`) VALUES
(1, 'Java Concurrency', 'Java'),
(2, 'Spring Boot Getting Started', 'Spring Boot'),
(3, 'Lambda Expressions Java 8 Example', 'Java 8');
现在找到完整的代码。
ArticleRepository.java
package com.concretepage.repository;
import java.util.List;
import org.springframework.data.repository.CrudRepository;
import com.concretepage.entity.Article;
public interface ArticleRepository extends CrudRepository<Article, Long> {
List<Article> findByTitle(String title);
List<Article> findDistinctByCategory(String category);
List<Article> findByTitleAndCategory(String title, String category);
}
Article.java
package com.concretepage.entity;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name="articles")
public class Article implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
@Column(name="article_id")
private long articleId;
@Column(name="title")
private String title;
@Column(name="category")
private String category;
public long getArticleId() {
return articleId;
}
public void setArticleId(long articleId) {
this.articleId = articleId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
}
IArticleService.java
package com.concretepage.service;
import java.util.List;
import com.concretepage.entity.Article;
public interface IArticleService {
List<Article> getAllArticles();
Article getArticleById(long articleId);
boolean addArticle(Article article);
void updateArticle(Article article);
void deleteArticle(int articleId);
}
ArticleService.java
package com.concretepage.service;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.concretepage.entity.Article;
import com.concretepage.repository.ArticleRepository;
@Service
public class ArticleService implements IArticleService {
@Autowired
private ArticleRepository articleRepository;
@Override
public Article getArticleById(long articleId) {
Article obj = articleRepository.findById(articleId).get();
return obj;
}
@Override
public List<Article> getAllArticles(){
List<Article> list = new ArrayList<>();
articleRepository.findAll().forEach(e -> list.add(e));
return list;
}
@Override
public synchronized boolean addArticle(Article article){
List<Article> list = articleRepository.findByTitleAndCategory(article.getTitle(), article.getCategory());
if (list.size() > 0) {
return false;
} else {
articleRepository.save(article);
return true;
}
}
@Override
public void updateArticle(Article article) {
articleRepository.save(article);
}
@Override
public void deleteArticle(int articleId) {
articleRepository.delete(getArticleById(articleId));
}
}
ArticleController.java
package com.concretepage.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.util.UriComponentsBuilder;
import com.concretepage.entity.Article;
import com.concretepage.service.IArticleService;
@Controller
@RequestMapping("user")
public class ArticleController {
@Autowired
private IArticleService articleService;
@GetMapping("article/{id}")
public ResponseEntity<Article> getArticleById(@PathVariable("id") Integer id) {
Article article = articleService.getArticleById(id);
return new ResponseEntity<Article>(article, HttpStatus.OK);
}
@GetMapping("articles")
public ResponseEntity<List<Article>> getAllArticles() {
List<Article> list = articleService.getAllArticles();
return new ResponseEntity<List<Article>>(list, HttpStatus.OK);
}
@PostMapping("article")
public ResponseEntity<Void> addArticle(@RequestBody Article article, UriComponentsBuilder builder) {
boolean flag = articleService.addArticle(article);
if (flag == false) {
return new ResponseEntity<Void>(HttpStatus.CONFLICT);
}
HttpHeaders headers = new HttpHeaders();
headers.setLocation(builder.path("/article/{id}").buildAndExpand(article.getArticleId()).toUri());
return new ResponseEntity<Void>(headers, HttpStatus.CREATED);
}
@PutMapping("article")
public ResponseEntity<Article> updateArticle(@RequestBody Article article) {
articleService.updateArticle(article);
return new ResponseEntity<Article>(article, HttpStatus.OK);
}
@DeleteMapping("article/{id}")
public ResponseEntity<Void> deleteArticle(@PathVariable("id") Integer id) {
articleService.deleteArticle(id);
return new ResponseEntity<Void>(HttpStatus.NO_CONTENT);
}
}
MyApplication.java
package com.concretepage;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
9. 使用 RestTemplate 的测试代码
RestClientUtil.java
package com.concretepage.client;
import java.net.URI;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import com.concretepage.entity.Article;
public class RestClientUtil {
public void getArticleByIdDemo() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
RestTemplate restTemplate = new RestTemplate();
String url = "http://localhost:8080/user/article/{id}";
HttpEntity<String> requestEntity = new HttpEntity<String>(headers);
ResponseEntity<Article> responseEntity = restTemplate.exchange(url, HttpMethod.GET, requestEntity, Article.class, 1);
Article article = responseEntity.getBody();
System.out.println("Id:"+article.getArticleId()+", Title:"+article.getTitle()
+", Category:"+article.getCategory());
}
public void getAllArticlesDemo() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
RestTemplate restTemplate = new RestTemplate();
String url = "http://localhost:8080/user/articles";
HttpEntity<String> requestEntity = new HttpEntity<String>(headers);
ResponseEntity<Article[]> responseEntity = restTemplate.exchange(url, HttpMethod.GET, requestEntity, Article[].class);
Article[] articles = responseEntity.getBody();
for(Article article : articles) {
System.out.println("Id:"+article.getArticleId()+", Title:"+article.getTitle()
+", Category: "+article.getCategory());
}
}
public void addArticleDemo() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
RestTemplate restTemplate = new RestTemplate();
String url = "http://localhost:8080/user/article";
Article objArticle = new Article();
objArticle.setTitle("Spring REST Security using Hibernate");
objArticle.setCategory("Spring");
HttpEntity<Article> requestEntity = new HttpEntity<Article>(objArticle, headers);
URI uri = restTemplate.postForLocation(url, requestEntity);
System.out.println(uri.getPath());
}
public void updateArticleDemo() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
RestTemplate restTemplate = new RestTemplate();
String url = "http://localhost:8080/user/article";
Article objArticle = new Article();
objArticle.setArticleId(1);
objArticle.setTitle("Update:Java Concurrency");
objArticle.setCategory("Java");
HttpEntity<Article> requestEntity = new HttpEntity<Article>(objArticle, headers);
restTemplate.put(url, requestEntity);
}
public void deleteArticleDemo() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
RestTemplate restTemplate = new RestTemplate();
String url = "http://localhost:8080/user/article/{id}";
HttpEntity<Article> requestEntity = new HttpEntity<Article>(headers);
restTemplate.exchange(url, HttpMethod.DELETE, requestEntity, Void.class, 4);
}
public static void main(String args[]) {
RestClientUtil util = new RestClientUtil();
//util.getArticleByIdDemo();
//util.addArticleDemo();
//util.updateArticleDemo();
//util.deleteArticleDemo();
util.getAllArticlesDemo();
}
}
10. 测试应用程序
为了测试该应用程序,首先在MySQL
中创建表,如例子中给出的。现在我们可以通过以下方式运行REST
网络服务。
1. 使用Eclipse
使用页面末尾的下载链接下载项目的源代码。
将该项目导入eclipse
。
使用命令提示符,进入项目的根文件夹并运行。
mvn clean eclipse:eclipse
然后在eclipse
中刷新该项目。点击Run as
-> Java Application
来运行主类MyApplication
。
Tomcat
服务器将被启动。
2. 使用Maven命令
下载项目的源代码。使用命令提示符进入项目的根文件夹并运行命令。
mvn spring-boot:run
Tomcat
服务器将被启动。
3. 使用可执行的JAR
使用命令提示符,转到项目的根文件夹并运行该命令。
mvn clean package
我们将在目标文件夹中得到可执行的spring-boot-demo-0.0.1-SNAPSHOT.jar
。以下列方式运行这个JAR
。
java -jar target/spring-boot-demo-0.0.1-SNAPSHOT.jar
Tomcat
服务器将被启动。
测试
现在我们已经准备好测试这个应用程序了。要运行客户端,在eclipse
中进入RestClientUtil
类,点击Run as
-> Java Application
。
我们也可以用Postman
测试应用程序。
参考文献
【1】Accessing Data with JPA
【2】Spring Boot REST + JPA + Hibernate + MySQL Example
【3】Spring Data CrudRepository Example
【4】Spring Boot CrudRepository Example
源码下载
提取码:mao4