Spring Data JPA(Java Persistence API),是Spring框架的主要構建塊之一。如果您想使用持久數據,它也是一個強大的工具。
目錄
一、Spring Data與JPA的介紹
Spring Data 是 Spring 的一個子項目。用於簡化數據庫訪問,支持NoSQL 和 關係數據存儲。其主要目標是使數據庫的訪問變得方便快捷。Spring Data 項目的目的是爲了簡化構建基於 Spring 框架應用的數據訪問技術,包括關係數據庫、非關係數據庫、Map-Reduce 框架、雲數據服務等等。Spring Data具有如下的特點:
-
SpringData 項目支持 NoSQL 存儲:
MongoDB (文檔數據庫)
Neo4j(圖形數據庫)
Redis(鍵/值存儲)
Hbase(列族數據庫) -
SpringData 項目所支持的關係數據存儲技術:
JDBC
JPA -
Spring Data Jpa 致力於減少數據訪問層 (DAO) 的開發量. 開發者唯一要做的,就是聲明持久層的接口,其他都交給 Spring Data JPA 來幫你完成!
-
框架怎麼可能代替開發者實現業務邏輯呢?比如:當有一個 UserDao.findUserById() 這樣一個方法聲明,大致應該能判斷出這是根據給定條件的 ID 查詢出滿足條件的 User 對象。Spring Data JPA 做的便是規範方法的名字,根據符合規範的名字來確定方法需要實現什麼樣的邏輯。
JPA是Java Persistence API的簡稱,中文名Java持久層API,是JDK 5.0註解或XML描述對象-關係表的映射關係,並將運行期的實體對象持久化到數據庫中。
- JPA 是 Hibernate 的一個抽象(就像JDBC和JDBC驅動的關係);
- JPA 是規範:JPA 本質上就是一種 ORM 規範,不是ORM 框架,這是因爲 JPA 並未提供 ORM 實現,它只是制訂了一些規範,提供了一些編程的 API 接口,但具體實現則由 ORM 廠商提供實現;
- Hibernate 是實現:Hibernate 除了作爲 ORM 框架之外,它也是一種 JPA 實現
- 從功能上來說, JPA 是 Hibernate 功能的一個子集
JPA具有如下的特點:
- 標準化: 提供相同的 API,這保證了基於JPA 開發的企業應用能夠經過少量的修改就能夠在不同的 JPA 框架下運行。
- 簡單易用,集成方便: JPA 的主要目標之一就是提供更加簡單的編程模型,在 JPA 框架下創建實體和創建 Java 類一樣簡單,只需要使用 javax.persistence.Entity 進行註解;JPA 的框架和接口也都非常簡單。
- 可媲美JDBC的查詢能力: JPA的查詢語言是面向對象的,JPA定義了獨特的JPQL,而且能夠支持批量更新和修改、JOIN、GROUP BY、HAVING 等通常只有 SQL 才能夠提供的高級查詢特性,甚至還能夠支持子查詢。
- 支持面向對象的高級特性: JPA 中能夠支持面向對象的高級特性,如類之間的繼承、多態和類之間的複雜關係,最大限度的使用面向對象的模型
二、基本操作CRUD
在實現CRUD的操作之前,當然是需要搭建Spring Data Jpa環境,即創建一個新的項目,選擇合適的模板即可:
項目創建完成之後打開pom文件,與之前所創建的項目不同的當然就是爲我們導入了相關的依賴包,
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
之後我們需要在application.properties文件中進行配置數據源配置:
server.port=80
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/scoot?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
#display sqp in console
spring.jpa.show-sql=true
創建實體類Dept,
@Entity//告訴SB這是一個實體類,在啓動SB的時候會加載這個類
@Table(name = "dept")//Dept類對應的表
@Getter//lombok使用
@Setter//lombok使用
public class Dept {
@Id//說明下面的deptno是主鍵
//GenerationType.IDENTITY代表使用數據庫底層自動增長的數字作爲主鍵
//oracle數據庫沒有自動增長屬性,而是使用Seuence序列生成
//@SequenceGenerator()生成Oracle主鍵值
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "deptno")//deptno屬性對應與deptno字段
private Integer deptno;
//@Column(name = "dname")
private String dname;
@Column(name = "loc")
private String location;
}
JpaRepository是Spring Boot爲我們提供的簡化類,默認提供了增刪改查方法,我們只需要定義接口就可以了,在SB啓動的時候會自動幫我們生成具體的實現類,來實現CRUD方法,編寫主要的業務邏輯類即可進行CRUD的操作,
@Controller
@RequestMapping("/dept")
public class DeptController {
@Resource
private DeptRepository deptRepository = null;
@GetMapping("/{id}")
@ResponseBody
//將路徑中符合要求的部分注入到對應的參數中
//這種方式被稱爲“路徑變量”
public Dept findByID(@PathVariable("id") Integer id) {
//Optional是實體類的包裝類,用於判斷對象是否存在
Optional<Dept> op = deptRepository.findById(id);
//op.isPresent();如果傳入的id有對應的數據返回true,否則返回false
Dept dept = null;
if (op.isPresent() == true) {
dept = op.get();//獲取到對應的實體類
}
return dept;
}
@GetMapping("/create")
@ResponseBody
public Dept create() {
Dept d = new Dept();
d.setDname("dfdff");
d.setLocation("New York");
deptRepository.save(d);
return d;
}
@GetMapping("/update")
@ResponseBody
public Dept update() {
Dept d = deptRepository.findById(30).get();
d.setDname("(" + d.getDname() + ")");
deptRepository.save(d);
return d;
}
@GetMapping("/delete")
@ResponseBody
public Dept delete() {
Dept d = deptRepository.findById(40).get();
deptRepository.save(d);
return d;
}
}
下面以update()方法進行測試,啓動進行測試,因爲在之前的配置文件中設置了將sql語句打印在控制檯,如下圖:
三、Jpa數據查詢
Spring Data Jpa有很多的方法的命名規則,我們在寫數據查詢的方法時可以按照此命名規則來寫,
Spring Data Jpa方法命名規則:https://blog.csdn.net/cyl101816/article/details/100524566
//selelct * from dept where dname = ?
public List<Dept> findByDname(String dname);
業務邏輯:
@GetMapping("/find")
@ResponseBody
public List<Dept> findDepts(String dname){
List<Dept> list = deptRepository.findByDname(dname);
return list;
}
查詢結果:
實際上呢,這樣的方法編寫在我們實際的開發過程中並不是很適用的,很難滿足我們實際的需求,我們需要使用更加靈活的方法去編寫,JPQL java persistence query language 持久化查詢語言,它是一種類sql語言,從SQL轉換爲JPQL只需要注意一下的幾點:
- 大多數的情況下將*替換爲別名
- 表名改爲類名
- 字段名改爲屬性名
如此我們便可以替換上面的方法:
//select * from dept d where d.dname = ? order by deptno desc
//:dn是命名參數,其本質就是一個佔位符,命名參數的格式爲:參數名
@Query("select d from Dept d where d.dname = :dn order by deptno desc")
public List<Dept> findDepts(@Param("dn")String dname);
四、對象關係映射
在上面的學習中已經完成了單一表數據的CRUD操作,如何在多張表中進行相應的操作,很顯然就需要用到對象關係映射了,在此還是以本地數據庫中的數據進行學習,創建實體類,邏輯控制類以及相應的接口,在一對多的情況下,通常是在多的一方進行映射的。
//dept與Emp的關係是1對多的關係
@ManyToOne//在多的一方使用ManyToOne多對一
@JoinColumn(name = "deptno")//JoinColumn指定關聯的一方的關聯字段,通常是主鍵
//只要獲取dept的時候,會自動查詢select * from dept where deptno = ...
private Dept dept;
在業務邏輯類中進行業務邏輯書寫,即可進行相關的CRUD操作。
public class EmpController {
@Autowired
private EmptRepository emptRepository;
@Autowired
private DeptRepository deptRepository;
@GetMapping("/{id}")
public Emp findById(@PathVariable("id")Integer id){
return emptRepository.findById(id).get();
}
@GetMapping("/create")
public Emp Create(){
Emp emp = new Emp();
emp.setComm(0f);
emp.setEname("laoqi");
emp.setHiredate(new Date());
emp.setJob("Teacher");
emp.setMgr(null);
emp.setSal(0f);
Dept dept = deptRepository.findById(20).get();
emp.setDept(dept);
emptRepository.save(emp);
return emp;
}
@GetMapping("/find")
public List<Emp> find(Integer deptno){
return emptRepository.findEmps(deptno);
}
}
測試結果:
控制檯:
對象關係映射中呢,通常是在多的一方進行映射的,如果在“一”的那一方進行映射,數據的獲取效率會非常差,而且極大多數的情況下會進入到死循環中。
五、連接池與Druid
連接池是創建和管理一個連接的緩衝池的技術,這些連接準備好被任何需要它們的線程使用。
SB對連接池的支持:
- 目前Spring Boot中默認支持的連接池有dbcp,dbcp2, tomcat, hikari三種連接池。
- 數據庫連接可以使用DataSource池進行自動配置。
- 由於Tomcat數據源連接池的性能和併發,在tomcat可用時,我們總是優先使用它。
- 如果HikariCP可用,我們將使用它。
- 如果Commons DBCP可用,我們將使用它,但在生產環境不推薦使用它。
- 最後,如果Commons DBCP2可用,我們將使用它。
儘管SB中有默認的三種連接池,但使用起來並不是最好用的。話不多說,並不是針對誰,在座的都是垃圾,個人還是支持阿里開發的連接池Druid。Druid是Java語言中最好的數據庫連接池。Druid能夠提供強大的監控和擴展功能。
https://github.com/alibaba/druid
因爲SB默認是對Drudi不支持的,需要添加它的依賴:
<!-- Druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.9</version>
</dependency>
然後在入口類SpringBootApplication中進行手動初始化:
@SpringBootApplication
public class SpringdatajpaApplication {
@Bean //手動初始化DruidDataSource對象
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource druid(){
DruidDataSource ds = new DruidDataSource();
return ds;
}
public static void main(String[] args) {
SpringApplication.run(SpringdatajpaApplication.class, args);
}
}
初始化結束後還需要進行相關的配置,打開application.properties文件:
#數據庫類型爲mysql
spring.datasource.dbType=mysql
#啓動時初始化5個連接
spring.datasource.initialSize=5
#最小空閒連接5個
spring.datasource.minIdle=5
#最大連接數量20
spring.datasource.maxActive=20
#獲取連接等待時間60秒,超出報錯
spring.datasource.maxWait=60000
#每60秒執行一次連接回收器
spring.datasource.timeBetweenEvictionRunsMillis=60000
#5分鐘內沒有任何操作的空閒連接會被回收
spring.datasource.minEvictableIdleTimeMillis=300000
#驗證連接有效性的SQL
spring.datasource.validationQuery=select 'x'
#空閒時校驗,建議開啓
spring.datasource.testWhileIdle=true
#使用中是否校驗有效性,推薦關閉
spring.datasource.testOnBorrow=false
#歸還連接時校驗有效性,推薦關閉
spring.datasource.testOnReturn=false
spring.datasource.poolPreparedStatements=false
#設置過濾器,stat用於接收狀態,wall用於防止SQL注入,logback則說明使用logback日誌輸出
spring.datasource.filters=stat,wall,logback
#統計所有數據源狀態
spring.datasource.useGlobalDataSourceStat=true
#sql合併統計,與設置慢SQL時間爲500毫秒
spring.datasource.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
數據連接池初始化後,我們需要創建一個 用於顯示後臺界面的Servlet,還需要添加監聽,這是其他的連接池所不具備的。
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@SpringBootApplication
public class SpringdatajpaApplication {
@Bean //手動初始化DruidDataSource 對象
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource druid(){
DruidDataSource ds = new DruidDataSource();
return ds;
}
//註冊後臺界面Servlet bean , 用於顯示後臺界面
@Bean
public ServletRegistrationBean statViewServlet(){
//創建StatViewServlet,綁定到/druid/路徑下
//開啓後,訪問localhost/druid就可以看到druid管理後臺
ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet() , "/druid/*");
Map<String ,String > param = new HashMap<String,String>();
param.put("loginUsername" , "admin");
param.put("loginPassword" , "123456");
param.put("allow" , "");//哪些IP允許訪問後臺“”代表所有地址
param.put("deny" , "33.31.51.88");//不允許這個IP訪問
bean.setInitParameters(param);
return bean;
}
//用於監聽獲取應用的數據 , Filter用於收集數據, Servlet用於展現數據
@Bean
public FilterRegistrationBean webStatFilter(){
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new WebStatFilter()); //設置過濾器
bean.addUrlPatterns("/*");
Map<String,String> param = new HashMap<String,String>();
//排除靜態資源
param.put("exclusions" , "*.png,*.woff,*.js,*.css,/druid/*");
bean.setInitParameters(param);
return bean;
}
public static void main(String[] args) {
SpringApplication.run(SpringdatajpaApplication.class, args);
}
}
創建完成後登陸後臺顯示頁面:
那麼如圖所示呢,數據源,SQL監控,防火牆等裏面都有相應的數據供你查看。
六、事物配置Transaction
在EmpController中開啓事物。在默認情況下,數據庫的事物作用範圍是在JpaRepository的CRUD方法上的。
@RestController //@Controller 使用@RestController的時候默認所有方法都返回JSON字符串,而不是跳轉頁面,我們也不用在方法上寫@ResponseBody
@RequestMapping("/emp")
//默認該類的所有方法都開啓事務
@Transactional(rollbackFor = Exception.class)
public class EmpController {
@Autowired
private EmptRepository empRepository;
@Autowired
private DeptRepository deptRepository;
@GetMapping("/{id}")
public Emp findById(@PathVariable("id") Integer id){
return empRepository.findById(id).get();
}
@GetMapping("/create")
public Emp create(){
Emp emp = new Emp();
emp.setComm(0f);
emp.setEname("laoqi");
emp.setHiredate(new Date());
emp.setJob("Teacher");
emp.setMgr(null);
emp.setSal(0f);
Dept d = deptRepository.findById(20).get();
emp.setDept(d);
empRepository.save(emp);
return emp;
}
@GetMapping("/find")
@Transactional(propagation = Propagation.NOT_SUPPORTED , readOnly = true)//不開啓事務的方法
public List<Emp> find(Integer deptno){
return empRepository.findEmps(deptno);
}
@GetMapping("/imp")
//在默認情況下,數據庫的事物作用範圍是在JpaRepository的CRUD方法上,
//save方法一旦執行成功馬上提交
//要保證數據的完整性,那就需要將事務提高至imp方法上
//在imp方法上開啓事務,是需要增加@Transactional
//針對於這種使用註解的事務形式,也有一個名詞叫做"聲明式事務" , ParseException
//一般情況下,事務註解要寫在最核心的Service上,而不是Controller
@Transactional(rollbackFor = Exception.class)//開啓事務,imp方法運行成功提交。運行失敗拋出RuntimeException及其子類的時候回滾
public void imp(){
for(int i = 0 ; i< 10 ; i++){
Emp emp = new Emp();
if(i == 3){
throw new RuntimeException("我出錯啦");
}
emp.setComm(0f);
emp.setEname("laoqi" + i);
emp.setHiredate(new Date());
emp.setJob("Teacher");
emp.setMgr(null);
emp.setSal(i*10f);
Dept d = deptRepository.findById(20).get();
emp.setDept(d);
//saveAndFlush立即執行
empRepository.saveAndFlush(emp);
}
}
}