SpringData 基於SpringBoot快速入門
本章通過學習SpringData 和SpringBoot 相關知識將面向服務架構(SOA)的單點登錄系統(SSO)需要的代碼實現。這樣可以從實戰中學習兩個框架的知識,又可以為單點登錄系統打下基礎。通過本章你將掌握 SpringBoot項目的搭建,Starter pom的使用,配置全局文件,核心注解SpringBootApplication 介紹以及單元測試 SpringBootTest注解的使用。SpringData 的入門使用,Repository接口的使用,查詢方法關鍵字的規則定義,@Query,@Modifying 注解的使用,最后是開發中的建議和遇到的問題。文章底部提供源碼。
SpringBoot 知識
SpringBoot 是一個用於簡化Spring應用搭建開發的框架。開發過程中,我們經常通過配置xml文件來整合第三方技術。而這些重復整合的工作交給了SpringBoot完成。SpringBoot使用"習慣優於配置"的理念幫我們快速搭建並運行項目。對主流的開發框架能無配置集成。筆者用的開發工具是sts(Spring Tool Suite),其操作和eclipse幾乎一致。若沒有這個工具,創建Maven項目是一樣的。

Starter pom
先看看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.itdragon</groupId>
<artifactId>springbootStudy</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>springbootStudy</name>
<description>Demo project for Spring Boot</description>
<!--
添加 spring boot的父級依賴,它是SpringBoot項目的標志
spring-boot-starter-parent 它是一個特殊的starter,提供了很多相關的Maven依賴,不用再為version而頭疼了,大大簡化了開發
-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency><!-- 添加web依賴 ,包含spring和springmvc等-->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency><!-- 添加對jpa的支持,包含spring-data和Hibernate -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency><!-- mysql連接的jar包 -->
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency><!-- 因為SpringBoot內嵌的tomcat不支持jsp頁面,同時SpringBoot也不推薦用jsp -->
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
<dependency><!-- jsp標簽庫 -->
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin><!-- SpringBoot 編譯插件 -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
細心的同學會發現該文件中出現大量的 spring-boot-starter-* 的語句。SpringBoot之所以能簡化開發的秘密就在這里------ Starter pom
spring-boot-starter-parent :父級依賴,SpringBoot項目的標志。里面封裝了很多jar的版本
spring-boot-starter-web :對web項目的支持,其中包含了SpringMVC和tomcat
spring-boot-starter-data-jpa :對JPA的支持,其中包含了常用的SpringData和Hibernate,沒有Mybatis哦
spring-boot-starter-tomcat :使用tomcat作為Servlet容器
spring-boot-starter-test :對常用測試框架的支持,如JUnit
還有很多......
配置全局文件
再看看SpringBoot項目全局配置文件 application.properties
# 配置tomcat端口號
server.port=8081
# 配置SpringMVC視圖解析器
spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp
# 配置連接池,默認使用的是tomcat的連接池,但實際很少用tomcat的連接池
spring.datasource.url=jdbc:mysql://localhost:3306/jpa?useUnicode=true&characterEncoding=UTF8
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
# 配置方言 否則提示:Access to DialectResolutionInfo cannot be null when 'hibernate.dialect' not set
spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect
# 自動更新數據庫表結構,也可以是 validate | update | create | create-drop
spring.jpa.properties.hibernate.hbm2ddl.auto=update
# 顯示sql語句
spring.jpa.show-sql=true
全局配置文件可以是application.properties 也可以是 application.yml,建議放在resources目錄下。更多配置: https://github.com/ITDragonBlog/daydayup/blob/master/SpringBoot/SpringData/springbootStudy/src/main/resources/springboot.properties
核心注解
最后是SpringBoot HelloWorld項目的入口類,只需要下面一個java文件,執行main方法,即可實現頁面的跳轉和數據返回的功能。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@SpringBootApplication
public class SpringbootStudyApplication {
@RequestMapping("/")
public String index() {
return "index";
}
@RequestMapping("hello")
@ResponseBody
public String helloWorld() {
return "Hello SpringBoot !";
}
public static void main(String[] args) {
SpringApplication.run(SpringbootStudyApplication.class, args);
}
}
@SpringBootApplication:是 SpringBoot 的核心注解,一般用在入口類上。它是一個組合注解,其中主要內容有一下三個
@SpringBootConfiguration:是一個類級注釋,指示對象是一個bean定義的源,可以理解為xml中的beans,一般和 @Bean 注解一起使用。
@EnableAutoConfiguration:啟用 Spring 應用程序上下文的自動配置,試圖猜測和配置您可能需要的bean。自動配置類通常采用基於你的 classpath 和已經定義的 beans 對象進行應用。
@ComponentScan:該注解會自動掃描指定包下的全部標有 @Component、@Service、@Repository、@Controller注解 的類,並注冊成bean
SpringData入口類
package com.itdragon;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class StartApplication {
public static void main(String[] args) {
SpringApplication.run(StartApplication.class, args);
}
}
SpringDataJPA 知識
SpringData 是一個用於簡化數據庫訪問,並支持雲服務的開源框架。支持非關系型數據庫(NoSQL) 和 關系型數據庫。其主要目的是使數據庫的訪問變得方便快捷。
SpringData JPA 是由Spring提供的簡化JPA開發的框架,致力於減少數據訪問層的開發量。
POJO層
創建實體類User 表,對應數據庫表名是 itdragon_user,id作為自增長的主鍵,plainPassword是不保存到數據庫的明文密碼。
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Transient;
/**
* 用戶實體類
* @author itdragon
*
*/
@Table(name="itdragon_user")
@Entity
public class User {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Long id; // 自增長主鍵
private String account; // 登錄的賬號
private String userName; // 注冊的昵稱
@Transient
private String plainPassword; // 登錄時的密碼,不持久化到數據庫
private String password; // 加密后的密碼
private String salt; // 用於加密的鹽
private String iphone; // 手機號
private String email; // 郵箱
private String platform; // 用戶來自的平台
private String createdDate; // 用戶注冊時間
private String updatedDate; // 用戶最后一次登錄時間
// 省略get/set/toString 方法
}
Repository接口層
創建UserRepository,這是SpringData 的核心知識點,我們先看代碼
import java.util.List;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.Param;
import com.itdragon.pojo.User;
/**
* 核心知識:SpringData Repository 接口
*
* CrudRepository 接口提供了最基本的對實體類的添刪改查操作
* - T save(T entity); //保存單個實體
* - T findOne(ID id); // 根據id查找實體
* - void delete(ID/T/Iterable); // 根據Id刪除實體,刪除實體,批量刪除
* PagingAndSortingRepository 提供了分頁與排序功能
* - <T, ID extends Serializable> // 第一個參數傳實體類,第二個參數傳注解數據類型
* - Iterable<T> findAll(Sort sort); // 排序
* - Page<T> findAll(Pageable pageable); // 分頁查詢(含排序功能)
* JpaSpecificationExecutor 提供了Specification(封裝 JPA Criteria查詢條件)的查詢功能
* - List<T> findAll(Specification<T> spec);
* - Page<T> findAll(Specification<T> spec, Pageable pageable);
* - List<T> findAll(Specification<T> spec, Sort sort);
*
* 開發建議
* 1. 這里值列出的是常用方法
* 2. CrudRepository 中的findAll() 方法要慎用。當數據庫中數據量大,多線程腳本調用findAll方法,系統可能會宕機。
* 3. CrudRepository 中的deletAll()方法要慎用。這是物理刪除,現在企業一般采用邏輯刪除。
* 4. PagingAndSortingRepository 和 JpaSpecificationExecutor 能滿足大部分業務需求。
*/
public interface UserRepository extends PagingAndSortingRepository<User, Long>,
JpaSpecificationExecutor<User>{
/**
* 重點知識:SpringData 查詢方法定義規范
*
* 1. 查詢方法名一般以 find | read | get 開頭,建議用find
* findByAccount : 通過account查詢User
* account是User的屬性,拼接時首字母需大寫
* 2. 支持的關鍵詞有很多比如 Or,Between,isNull,Like,In等
* findByEmailEndingWithAndCreatedDateLessThan : 查詢在指定時間前注冊,並以xx郵箱結尾的用戶
* And : 並且
* EndingWith : 以某某結尾
* LessThan : 小於
*
* 注意
* 若有User(用戶表) Platform(用戶平台表) 存在一對一的關系,且User表中有platformId字段
* SpringData 為了區分:
* findByPlatFormId 表示通過platformId字段查詢
* findByPlatForm_Id 表示通過platform實體類中id字段查詢
*
* 開發建議
* 表的設計,盡量做單表查詢,以確保高並發場景減輕數據庫的壓力。
*/
// 1 通過賬號查用戶信息
User findByAccount(String account);
// 2 獲取指定時間內以xx郵箱結尾的用戶信息
List<User> findByEmailEndingWithAndCreatedDateLessThan(String email, String createdDate);
/**
* 重點知識:使用 @Query 注解
*
* 上面的方法雖然簡單(不用寫sql語句),但它有最為致命的問題-----不支持復雜查詢,其次是命名太長
* 1. 使用@Query 注解實現復雜查詢,設置 nativeQuery=true 使查詢支持原生sql
* 2. 配合@Modifying 注解實現創建,修改,刪除操作
* 3. SpringData 默認查詢事件為只讀事務,若要修改數據則需手動添加事務注解
*
* 注意
* 若@Query 中有多個參數,SpringData 提供兩種方法:
* 第一種 ?1 ... ?2 要求參數順序一致
* 第二種 :xxx ... :yyy xxx 和 yyy 必須是實體類對應的屬性值,不要求參數順序但參數前要加上@Param("xxx")
* 模糊查詢可使用 %xxx%
*
* 開發建議
* 1. 參數填寫的順序要保持一致,不要給自己添加麻煩
* 2. 建議使用@Query,可讀性較高
*/
// 3 獲取某平台活躍用戶數量
@Query(value="SELECT count(u.id) FROM User u WHERE u.platform = :platform AND u.updatedDate <= :updatedDate")
long getActiveUserCount(@Param("platform")String platform, @Param("updatedDate")String updatedDate);
// 4 通過郵箱或者手機號模糊查詢用戶信息
@Query(value="SELECT u FROM User u WHERE u.email LIKE %?1% OR u.iphone LIKE %?2%")
List<User> findByEmailAndIhpneLike(String email, String iphone);
// 5 修改用戶郵箱
@Modifying
@Query("UPDATE User u SET u.email = :email WHERE u.id = :id")
void updateUserEmail(@Param("id") Long id, @Param("email") String email);
// 6 傳入的值是對象
@Modifying
@Query("update Variable v set v.lastSavedValue =:#{#variable.lastSavedValue}, v.lastSavedValueTime =:#{#variable.lastSavedValueTime} where v.id=:#{#variable.id}")
Variable updateVariableValue(Variable: variable);
}
代碼中共有五個方法,每個方法都包含了很多的知識點。
方法一和方法二主要介紹的是SpringData關鍵字的用法。
1 關鍵字的解析
這里用findByPlatFormId() 方法來介紹SpringData 解析查詢方法的流程。
第一步:去除關鍵字findBy
第二步:剩下的PlatFormId 首字母小寫並在User對象中找是否有該屬性,若有則查詢並結束。若沒有則第三步
第三步:platFormId,從右到左截取到第一個大寫字母,再判斷剩下的platForm是否為User對象,如此循環直到結束為止。
2 級聯屬性區分
若查詢的屬性是實體類,為了避免誤會和沖突,用"_"表示屬性中的屬性
3 查詢分頁排序
若findByPlatFormId() 方法想要排序或者分頁,是可以在后面加Pageable,Sort參數。
findByPlatFormId(String platFormId, Pageable pageable)
findByPlatFormId(String platFormId, Sort sort)
4 其他的關鍵字

方法三到方法五主要介紹的是 @Query 注解的使用。
1 傳參方式
索引參數:?n ,n從1開始,表示第一個參數。方法傳入的參數的照順序和個數要和 n 保持一致。
命名參數::key ,傳參必須有 @Param("key") 注解修飾
2 原生的sql
@Query 注解支持本地查詢,即用原生的sql語句。如:@Query(value="xxxx", nativeQuery=true)
3 Modifying
若直接執行修改操作,SpringDataJPA 會提示錯誤信息 Executing an update/delete query 。是因為Spring Data 默認所有的查詢均聲明為只讀事務。
所以我們要在Service層添加 @Transactional 注解。
SpringDataJPA 核心知識Repository接口
1 Repository: 空接口,標識作用,表明任何繼承它的均為Repository接口類
2 CrudRepository: 繼承 Repository,實現了一組 CRUD 相關的方法
3 PagingAndSortingRepository: 繼承 CrudRepository,實現了一組分頁排序相關的方法
4 JpaRepository: 繼承 PagingAndSortingRepository,實現一組 JPA 規范相關的方法
5 JpaSpecificationExecutor: 不屬於Repository體系,實現一組 JPA Criteria 查詢相關的方法
PagingAndSortingRepository 和 JpaSpecificationExecutor 基本滿足企業中大部分的需求。也可以自定義Repository,只需繼承 JpaRepository 即可具備了通用的數據訪問控制層的能力。
進入各自接口類中,使用快捷鍵 Ctrl + o 即可查看當前類的所有方法,所以這里就不貼出來了。
Service層
創建UserService 並加上注解 @Transactional
import javax.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.itdragon.common.ItdragonResult;
import com.itdragon.pojo.User;
import com.itdragon.repository.UserRepository;
@Service
@Transactional
public class UserService {
@Autowired
private UserRepository userRepository;
public ItdragonResult registerUser(User user) {
// 檢查用戶名是否注冊,一般在前端驗證的時候處理,因為注冊不存在高並發的情況,這里再加一層查詢是不影響性能的
if (null != userRepository.findByAccount(user.getAccount())) {
return ItdragonResult.build(400, "");
}
userRepository.save(user);
// 注冊成功后選擇發送郵件激活。現在一般都是短信驗證碼
return ItdragonResult.build(200, "");
}
public ItdragonResult editUserEmail(String email) {
// 通過Session 獲取用戶信息, 這里假裝從Session中獲取了用戶的id,后面講解SOA面向服務架構中的單點登錄系統時,修改此處代碼 FIXME
long id = 3L;
// 添加一些驗證,比如短信驗證
userRepository.updateUserEmail(id, email);
return ItdragonResult.ok();
}
}
單元測試
SpringBoot 的單元測試,需要用到 @RunWith 和 @SpringBootTest注解,代碼注釋中有詳細介紹。
import java.util.List;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.test.context.junit4.SpringRunner;
import com.itdragon.StartApplication;
import com.itdragon.common.ItdragonUtils;
import com.itdragon.pojo.User;
import com.itdragon.repository.UserRepository;
import com.itdragon.service.UserService;
/**
* @RunWith 它是一個運行器
* @RunWith(SpringRunner.class) 表示讓測試運行於Spring測試環境,不用啟動spring容器即可使用Spring環境
* @SpringBootTest(classes=StartApplication.class) 表示將StartApplication.class納入到測試環境中,若不加這個則提示bean找不到。
*
* @author itdragon
*
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes=StartApplication.class)
public class SpringbootStudyApplicationTests {
@Autowired
private UserService userService;
@Autowired
private UserRepository userRepository;
@Test
public void contextLoads() {
}
@Test // 測試注冊,新增數據
public void registerUser() {
User user = new User();
user.setAccount("gitLiu");
user.setUserName("ITDragonGit");
user.setEmail("itdragon@git.com");
user.setIphone("12349857999");
user.setPlainPassword("adminroot");
user.setPlatform("github");
user.setCreatedDate(ItdragonUtils.getCurrentDateTime());
user.setUpdatedDate(ItdragonUtils.getCurrentDateTime());
ItdragonUtils.entryptPassword(user);
userService.registerUser(user);
}
@Test // 測試SpringData 關鍵字
public void findByEmailEndingWithAndCreatedDateLessThan() {
List<User> users = userRepository.findByEmailEndingWithAndCreatedDateLessThan("qq.com", ItdragonUtils.getCurrentDateTime());
System.out.println(users.toString());
}
@Test // 測試SpringData @Query 注解和傳多個參數
public void getActiveUserCount() {
long activeUserCount = userRepository.getActiveUserCount("weixin", ItdragonUtils.getCurrentDateTime());
System.out.println(activeUserCount);
}
@Test // 測試SpringData @Query 注解,傳多個參數 和 like 查詢
public void findByEmailAndIhpneLike() {
List<User> users = userRepository.findByEmailAndIhpneLike("163.com", "6666");
System.out.println(users.toString());
}
@Test // 測試SpringData @Query 注解 和 @Modifying 注解
public void updateUserEmail() {
/**
* org.springframework.dao.InvalidDataAccessApiUsageException:Executing an update/delete query; nested exception is javax.persistence.TransactionRequiredException: Executing an update/delete query
* userRepository.updateUserEmail(3L, "update@email.com");
*/
userService.editUserEmail("update@email.com");
}
@Test // 測試SpringData PagingAndSortingRepository 接口
public void testPagingAndSortingRepository() {
int page = 1; // 從0開始,第二頁
int size = 3; // 每頁三天數據
PageRequest pageable = new PageRequest(page, size, new Sort(new Order(Direction.ASC, "id")));
Page<User> users = userRepository.findAll(pageable);
System.out.println(users.getContent().toString()); // 當前數據庫中有5條數據,正常情況可以打印兩條數據,id分別為4,5 (先排序,后分頁)
}
@Test // 測試SpringData JpaSpecificationExecutor 接口
public void testJpaSpecificationExecutor(){
int pageNo = 1;
int pageSize = 3;
PageRequest pageable = new PageRequest(pageNo, pageSize);
Specification<User> specification = new Specification<User>() {
@Override
public Predicate toPredicate(Root<User> root,
CriteriaQuery<?> query, CriteriaBuilder cb) {
Predicate predicate = cb.gt(root.get("id"), 1); // 查詢id 大於 1的數據
return predicate;
}
};
Page<User> users = userRepository.findAll(specification, pageable);
System.out.println(users.getContent().toString()); // 當前數據庫中有5條數據,正常情況可以打印一條數據,id為5
}
}
可能存在的問題
項目啟動時提示 Unknown character set: 'utf8mb4'
導致的原因可能是mysql服務器版本安裝不正確,解決方法有兩種。
第一種:換mysql-connector-java jar包版本為 5.1.6 (不推薦); 當前jar版本為 5.1.44。
第二種:重裝mysql版本,當前最新版本是5.7。教程都准備好了。
https://www.cnblogs.com/sshoub/p/4321640.html (mysql安裝)
http://blog.csdn.net/y694721975/article/details/52981377 (mysql卸載)
SpringBoot 連接池配置疑惑
我們只是在全局配置文件中設置了相關值,就完成了連接池的配置,想必大家都有所疑惑。其實當我們在pom.xml文件中加入spring-boot-starter-data-jpa 依賴時,SpringBoot就會自動使用tomcat-jdbc連接池。
當然我們也可以使用其他的連接池。
https://www.cnblogs.com/gslblog/p/7169481.html (springBoot數據庫連接池常用配置)
https://www.cnblogs.com/xiaosiyuan/p/6255292.html (SpringBoot使用c3p0)
STS工具 ctrl + shift + o 重新導包快捷鍵失效
解決方法:preference -> general -> keys ,找到 Organize Imports ,然后 在 When 里面選擇 Editing Java Source
總結
1 SpringDataJPA 是簡化JPA開發的框架,SpringBoot是簡化項目開發的框架。
2 spring-boot-starter-parent 是SpringBoot項目的標志
3 SpringBootApplication 注解是SpringBoot項目的入口
4 SpringData 通過查詢關鍵字和 @Query注解實現對數據庫的訪問
5 SpringData 通過PagingAndSortingRepository 實現分頁,排序和常用的crud操作
源碼地址:https://github.com/ITDragonBlog/daydayup/tree/master/SpringBoot/SpringData
到這里 SpringData 基於SpringBoot快速入門就結束了,如果有什么問題請指教,如果覺得不錯可以點一下推薦。
