Spring Boot 2.x快速上手(八)Spring Data 與JPA

Spring Data JPA(Java Persistence API),是Spring框架的主要構建塊之一。如果您想使用持久數據,它也是一個強大的工具。


目錄

一、Spring Data與JPA的介紹

二、基本操作CRUD

三、Jpa數據查詢

四、對象關係映射

五、連接池與Druid

六、事物配置Transaction


一、Spring Data與JPA的介紹

Spring Data 是 Spring 的一個子項目。用於簡化數據庫訪問,支持NoSQL 和 關係數據存儲。其主要目標是使數據庫的訪問變得方便快捷。Spring Data 項目的目的是爲了簡化構建基於 Spring 框架應用的數據訪問技術,包括關係數據庫、非關係數據庫、Map-Reduce 框架、雲數據服務等等。Spring Data具有如下的特點:

  1. SpringData 項目支持 NoSQL 存儲:
    MongoDB (文檔數據庫)
    Neo4j(圖形數據庫)
    Redis(鍵/值存儲)
    Hbase(列族數據庫)

  2. SpringData 項目所支持的關係數據存儲技術:
    JDBC
    JPA

  3. Spring Data Jpa 致力於減少數據訪問層 (DAO) 的開發量. 開發者唯一要做的,就是聲明持久層的接口,其他都交給 Spring Data JPA 來幫你完成!

  4. 框架怎麼可能代替開發者實現業務邏輯呢?比如:當有一個 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對連接池的支持:

  1. 目前Spring Boot中默認支持的連接池有dbcp,dbcp2, tomcat, hikari三種連接池。
  2. 數據庫連接可以使用DataSource池進行自動配置。
  3. 由於Tomcat數據源連接池的性能和併發,在tomcat可用時,我們總是優先使用它。
  4. 如果HikariCP可用,我們將使用它。
  5. 如果Commons DBCP可用,我們將使用它,但在生產環境不推薦使用它。
  6. 最後,如果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);
        }
    }

}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章