一、JPA 、Hibernate、Spring Data JPA與Spring Data簡介
Spring Data JPA官網介紹:https://spring.io/projects/spring-data-jpa
1、什麼是JPA?
JPA(Java Persistence API)是Sun官方提出的Java持久化API規範。可以通過JDK 5.0註解或者XML描述【對象-關係表】之間的映射關係,並將實體對象持久化到數據庫中。主要是爲了簡化現有的持久化開發工作和整合ORM技術,使得應用程序以統一的方式訪問持久層。
2、什麼是Hibernate?
Hibernate 是一個開源的全自動的ORM框架,它對JDBC進行了非常輕量級的對象封裝,它將POJO與數據庫表建立映射關係,Hibernate 是一個實現了 ORM 思想的框架,封裝了 JDBC。
3、什麼是Spring Data JPA?
Spring Data JPA是Spring提供的一套簡化JPA開發的框架,它是在JPA規範下提供了Repository層的實現,按照約定好的【方法命名規則】寫dao層接口,就可以在不寫接口實現的情況下,實現對數據庫的訪問和操作。同時提供了很多除了CRUD之外的功能,如分頁、排序、複雜查詢等等。
Spring Data JPA的功能實現默認使用的Hibernate,也可以使用其他持久層框架。
4、什麼是Spring Data?
Spring Data項目是爲了簡化構建基於Spring框架應用的數據訪問技術,是Spring官方提供的一套數據層的綜合解決方案。(或者可以封裝其他的持久層解決方案的一個解決方案)。它支持關係型數據庫、非關係型數據庫、Map-Reduce框架、雲數據服務等。
Spring Data 包含很多個子模塊:
- Commons - 提供共享的基礎框架,適合各個子項目使用,支持跨數據庫持久化
- Hadoop - 基於 Spring 的 Hadoop 作業配置和一個 POJO 編程模型的 MapReduce 作業
- Key-Value - 集成了 Redis 和 Riak ,提供多個常用場景下的簡單封裝
- Document - 集成文檔數據庫:CouchDB 和 MongoDB 並提供基本的配置映射和資料庫支持
- Graph - 集成 Neo4j 提供強大的基於 POJO 的編程模型
- Graph Roo AddOn - Roo support for Neo4j
- JDBC Extensions - 支持 Oracle RAD、高級隊列和高級數據類型
- JPA - 簡化創建 JPA 數據訪問層和跨存儲的持久層功能
- Mapping - 基於 Grails 的提供對象映射框架,支持不同的數據庫
- Examples - 示例程序、文檔和圖數據庫
- Guidance - 高級文檔
Spring Data JPA是Spring Data的一個模塊。
5、它們之間的關係
JPA是一套ORM規範接口,接口是需要實現才能工作。而 Hibernate就是實現了JPA接口的ORM框架。
Spring Data JPA 可以理解爲 JPA 規範的再次封裝抽象,底層默認還是使用了 Hibernate 對JPA 技術實現。
Spring Data JPA是Spring Data的一個模塊。
按照時間線來說就明白了
- 開發 Hibernate 的團隊開發了 Hibernate
- 制訂 J2ee 規範的團隊邀請 Hibernate 的核心在 Hibernate 基礎上制訂了 JPA (Java Persistent API)標準。從功能上看,JPA 是 Hibernate 的子集。
- Spring 的團隊使用 Spring 對 JPA 做了封裝,就是 Spring Data JPA 了。
總之,JPA 是一個 API 標準,除了 Hibernate 外,還有其它廠商的實現,例如 Eclipse 的 TopLink。Spring Data Jpa 是個對 JPA 的封裝,幫助程序員以 Spring 的方式來使用 JPA。
6、一些名詞含義
ORM(Object Relational Mapping):通過使用描述對象和數據庫之間映射的元數據,將程序中的對象自動持久化到關係數據庫中。本質就是將關係和對象通過映射文件進行聯繫。我們在程序中只需要操作對象即可操作對應的數據庫表的數據。
POJO(Plain Ordinary Java Object)簡單的Java對象,實際就是普通JavaBeans。
HQL(Hibernate Query Language)是面向對象的查詢。
JPQL(Java Persistence Query Language)查詢語言,通過面向對象而非面向數據庫的查詢語言查詢數據,避免程序的SQL語句緊密耦合。
JPA 規範提供了 JPQL ,Hibernate 提供了 HQL來查詢數據庫。JPQL的原型就是Hibernate 的HQL。
上面內容更多參考這兩篇文章,整的明明白白
Hibernate和Spring Data JPA有什麼區別?
二、Spring與Spring Data JPA的整合
在實際的工程中,推薦採用 Spring Data JPA + ORM(如:Hibernate)進行開發,這樣在切換不同的ORM提供了方面,同時也使得Repository變得簡單。程序低耦合。
1、創建一個maven java項目,在 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>cn.jq.springdatajpademo</groupId>
<artifactId>springdatajpademo</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
<!-- slf4j -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<!-- hibernate -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.2.17.Final</version>
</dependency>
<!-- hibernate-c3p0包含c3p0 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-c3p0</artifactId>
<version>5.2.17.Final</version>
</dependency>
<!-- spring-data-jpa -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>2.3.1.RELEASE</version>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.10.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.datatype/jackson-datatype-jsr310 -->
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.10.3</version>
</dependency>
<!-- spring-test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.7.RELEASE</version>
<scope>test</scope>
</dependency>
<!-- junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
</plugin>
</plugins>
</build>
</project>
2、jdbc.properties :
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/springdatajpa_demo?useUnicode=true&characterEncoding=utf8&useSSL=true
jdbc.user=root
jdbc.password=123456
c3p0.initialPoolSize=5
c3p0.acquireIncrement=5
c3p0.maxPoolSize=20
c3p0.minPoolSize=5
c3p0.maxStatements=200
c3p0.maxStatementsPerConnection=5
hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
hibernate.show_sql=true
hibernate.format_sql=true
hibernate.hbm2ddl.auto=update
3、log4j.properties :
log4j.rootLogger = debug,stdout, D
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.Threshold = INFO
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p %m%n
log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File = ./log4j.log
log4j.appender.D.Append = true
log4j.appender.D.Threshold = DEBUG
log4j.appender.D.layout = org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern=%d %p %m%n
4、spring.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
<!-- 1. 導入源屬性文件 -->
<context:property-placeholder location="classpath:jdbc.properties" />
<!-- c3p0 連接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!-- 類似EL表達式取資源文件的值 -->
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="initialPoolSize" value="${c3p0.initialPoolSize}"></property>
<property name="acquireIncrement" value="${c3p0.acquireIncrement}"></property>
<property name="maxPoolSize" value="${c3p0.maxPoolSize}"></property>
<property name="minPoolSize" value="${c3p0.minPoolSize}"></property>
<property name="maxStatements" value="${c3p0.maxStatements}"></property>
<property name="maxStatementsPerConnection" value="${c3p0.maxStatementsPerConnection}"></property>
</bean>
<!-- 2. 整合jpa, 配置Hibernate的Sessionfactory實例 -->
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<!-- 數據源 -->
<property name="dataSource" ref="dataSource"></property>
<!-- jpa的實現產品 -->
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"></bean>
</property>
<!-- 配置實體類所在的包 -->
<property name="packagesToScan" value="cn.jq.springdatajpademo.model"></property>
<!-- jpa的實現的基本屬性 -->
<property name="jpaProperties">
<props>
<prop key="hibernate.dialect">${hibernate.dialect}</prop>
<prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
<prop key="hibernate.format_sql">${hibernate.format_sql}</prop>
<prop key="hibernate.hbm2ddl.auto">${hibernate.hbm2ddl.auto}</prop>
</props>
</property>
</bean>
<!-- 配置spring的jpa聲明事務管理器 -->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"></property>
</bean>
<!-- 開啓事務註釋 @Transactional -->
<!--<tx:annotation-driven transaction-manager="transactionManager" /> -->
<!-- 配置事務通知 -->
<tx:advice id="tsAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="load*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
<tx:method name="select*" read-only="true"/>
<tx:method name="*" read-only="false"/>
</tx:attributes>
</tx:advice>
<!-- 配置事務切入點 -->
<aop:config>
<aop:pointcut expression="execution(* cn.jq.springdatajpademo.service.*.*(..))" id="txPointcut"/>
<aop:advisor advice-ref="tsAdvice" pointcut-ref="txPointcut"/>
</aop:config>
<!-- 配置spring data jpa : base-package會掃描對應的包,把裏面的接口的實現對象放到spring的ioc容器裏-->
<jpa:repositories base-package="cn.jq.springdatajpademo.dao" entity-manager-factory-ref="entityManagerFactory"></jpa:repositories>
</beans>
5、pojo層
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.springframework.format.annotation.DateTimeFormat;
import javax.persistence.*;
import java.time.LocalDateTime;
/**
* 用戶的實體類
* 配置映射關係
*
* 1.實體類和表的映射關係
* @Entity:聲明實體類
* @Table : 配置實體類和表的映射關係
* name : 配置數據庫表的名稱
* 2.實體類中屬性和表中字段的映射關係
*
*
*/
@Entity
@Table(name = "t_user")
public class User {
/**
* @Id:聲明主鍵的配置
* @GeneratedValue:配置主鍵的生成策略
* strategy
* GenerationType.IDENTITY :自增,mysql
* * 底層數據庫必須支持自動增長(底層數據庫支持的自動增長方式,對id自增)
* GenerationType.SEQUENCE : 序列,oracle
* * 底層數據庫必須支持序列
* GenerationType.TABLE : jpa提供的一種機制,通過一張數據庫表的形式幫助我們完成主鍵自增
* GenerationType.AUTO : 由程序自動的幫助我們選擇主鍵生成策略
* @Column:配置屬性和字段的映射關係
* name:數據庫表中字段的名稱
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
private Integer age;
@JsonFormat(shape = JsonFormat.Shape.STRING, timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonSerialize(using = LocalDateTimeSerializer.class)
@Column(name = "create_time")
private LocalDateTime createtime; /** 創建時間*/
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public LocalDateTime getCreatetime() {
return createtime;
}
public void setCreatetime(LocalDateTime createtime) {
this.createtime = createtime;
}
// 空構造
public User() {
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", age=" + age +
", createtime=" + createtime +
'}';
}
}
6、dao層
import cn.jq.springdatajpademo.model.User;
import org.springframework.data.repository.Repository;
/**
* 符合SpringDataJpa的dao層接口規範
* Repository<操作的實體類類型,實體類中主鍵屬性的類型>
* * Repository標記接口,並沒有提供任何操作方法
*/
public interface UserDao extends Repository<User, Long> {
// 定義一個方法(方法名有一定的規則), 不需要做任何的sql實現
User getById(Long id);
}
7、測試類
import cn.jq.springdatajpademo.dao.UserDao;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @Description:
* @Auther: leijq
* @Date: 2020-07-06 22:54
* @Version: V1.0
*/
@RunWith(SpringJUnit4ClassRunner.class) //聲明spring提供的單元測試環境
@ContextConfiguration(locations = "classpath:spring.xml")//指定spring容器的配置信息
public class UserDaoTest {
@Autowired
private UserDao userDao;
@Test
public void testGetById() {
// 先註釋,運行UserDaoTest類,生成表,手動在表中添加一個記錄,然後再測試testGetById
// User user = userDao.getById(1L);
// System.out.println(user);
}
}
三、測試類的實現分析
1、dao層接口繼承的Repository接口
查看 Repository接口的源碼:該接口中沒有定義任何方法,即稱之爲標記接口
這個標記接口的作用:是把繼承它的子接口,比如:UserDao標記爲 Repository Bean,Spring就會爲這個接口創建代理實現類,並且放入到IOC反轉容器裏,就像以前自己寫的 UserDao的實現類 UserDaoImpl一樣。
2、extends Repository<User, Integer>繼承的寫法還可以使用一個 @RepositoryDefinition註解來替代
import org.springframework.data.repository.RepositoryDefinition;
@RepositoryDefinition(domainClass = User.class, idClass = Long.class)
public interface UserDao {
// 定義一個方法(方法名有一定的規則), 不需要做任何的sql實現
User getById(Long id);
}
3、在dao層的接口中定義方法
遵守一些規定:
1)查詢的方法:find或get或read開頭
2)查詢條件:用關鍵字鏈接,涉及屬性首寫字母大寫
3)支持級聯查詢,關聯對象和它的屬性之間用_連接
Keyword | Sample | JPQL snippet |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4、Repository接口的子接口和實現類(重點內容)
到此,Spring Data JPA與Spring的簡單使用搞定,對於Repository接口的子接口和實現類是後面掌握的重點
—— Stay Hungry. Stay Foolish. 求知若飢,虛心若愚。