Spring data jpa 的使用與詳解(一):框架整合及基本使用
1 什麼是JPA
JPA全稱Java Persistence API,可以通過註解或者XML描述【物件-關係表】之間的對映關係,並將實體物件持久化到資料庫中。JPA的出現主要是為了簡化持久層開發以及整合ORM技術,結束Hibernate、TopLink、JDO等ORM框架各自為營的局面。
JAP為我們提供了ORM對映元資料,JPA的API,JPQL查詢語言等,但JPA僅僅是一種規範,也就是說JPA僅僅定義了一些介面,而介面是需要實現才能工作的。所以底層需要某種實現,而Hibernate就是實現了JPA介面的ORM框架。
2 什麼是Hibernate框架
Hibernate是Java中的物件關係對映解決方案。物件關係對映或ORM是將應用程式域模型物件對映到關係資料庫表的程式設計技術。Hibernate是一個基於Java的ORM工具,它提供了一個框架,用於將應用程式域物件對映到關係資料庫表。
Hibernate提供了Java Persistence API的參考實現,使其成為具有鬆散耦合優勢的ORM工具的絕佳選擇。
3 什麼是Spring Data JPA
Spring Data是Spring Framework的一部分。Spring Data儲存庫抽象的目標是顯著減少為各種永續性儲存實現資料訪問層所需的程式碼量。
Spring Data JPA不是JPA提供者。它是一個庫/框架,它在我們的JPA提供程式(如Hibernate)的頂部添加了一個額外的抽象層。
4 Hibernate和Spring Data JPA的關係
Hibernate是一個JPA實現,而Spring Data JPA是一個JPA資料訪問抽象。Spring Data提供了GenericDao自定義實現的解決方案,它還可以通過方法名稱約定代表您生成JPA查詢。
Spring Data JPA不是一個實現或JPA提供者,它只是一個抽象,用於顯著減少為各種永續性儲存實現資料訪問層所需的程式碼量。Spring Data JPA始終需要JPA提供程式,如Hibernate。
5 Spring data jpa概述
JPA Spring Data:致力於減少資料訪問層(DAO)的開發量,開發者唯一要做的,就只是宣告持久層的介面,其他都交給Spring Data JPA來完成。
框架怎麼可能代替開發者實現業務邏輯呢?比如:當有一個 UserDao.findUserById() 這樣一個方法宣告,大致應該能判斷出這是根據給定條件的 ID 查詢出滿足條件的 User 物件。Spring Data JPA 做的便是規範方法的名字,根據符合規範的名字來確定方法需要實現什麼樣的邏輯。
5 Spring data JPA使用
5.1 jar包引入
<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>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
複製程式碼
5.2 Spring data jpa配置
@Configuration
// 藉助spring data實現自動化的jpa repository,只需編寫介面無需編寫實現類
// 相當於xml配置的<jpa:repositories base-package="com.example.repository" />
// repositoryImplementationPostfix預設就是Impl
// entityManagerFactoryRef預設就是entityManagerFactory
// transactionManagerRef預設就是transactionManager
@EnableJpaRepositories(basePackages = {"com.wtj.repository"},repositoryImplementationPostfix = "Impl",entityManagerFactoryRef = "entityManagerFactory",transactionManagerRef = "transactionManager")
// 啟用事務管理器
@EnableTransactionManagement
//審計功能 用來自動填充@CreateDate等
@EnableJpaAuditing(dateTimeProviderRef = "dateTimeProvider")
public class SpringDataJpaConfig {
@Bean
public DateTimeProvider dateTimeProvider(DateTimeService dateTimeService) {
return dateTimeService::getNow;
}
@Bean
public JpaVendorAdapter jpaVendorAdapter() {
HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
// 設定資料庫型別(可使用org.springframework.orm.jpa.vendor包下的Database列舉類)
jpaVendorAdapter.setDatabase(Database.MYSQL);
// 設定列印sql語句
jpaVendorAdapter.setShowSql(true);
// 設定不生成ddl語句
jpaVendorAdapter.setGenerateDdl(false);
// 設定hibernate方言
jpaVendorAdapter.setDatabasePlatform("org.hibernate.dialect.MySQL5Dialect");
return jpaVendorAdapter;
}
// 配置實體管理器工廠
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
DataSource dataSource,JpaVendorAdapter jpaVendorAdapter) {
LocalContainerEntityManagerFactoryBean emfb = new LocalContainerEntityManagerFactoryBean();
// 注入資料來源
emfb.setDataSource(dataSource);
// 注入jpa廠商介面卡
emfb.setJpaVendorAdapter(jpaVendorAdapter);
// 設定掃描基本包
emfb.setPackagesToScan("com.wtj.entity");
return emfb;
}
// 配置jpa事務管理器
@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
// 配置實體管理器工廠
transactionManager.setEntityManagerFactory(emf);
return transactionManager;
}
}
複製程式碼
啟用web支援還需要在Spring MVC配置類上新增@EnableSpringDataWebSupport
註解
@Configuration
@ComponentScan(basePackages = {"com.wtj.controller"})
@EnableWebMvc // 啟用spring mvc
@EnableSpringDataWebSupport // 啟用springmvc對spring data的支援
public class WebMvcConfig extends WebMvcConfigurerAdapter {
}
複製程式碼
配置檔案
server:
port: 20000
servlet:
context-path: /
spring:
datasource:
url: jdbc:mysql://localhost:3306/mytest1?useUnicode=true&serverTimezone=UTC&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&useSSL=false
username: root
password: root
jpa:
database: MySQL
database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
show-sql: true
hibernate:
ddl-auto: update
複製程式碼
ddl-auto 解釋:
- create : 每次執行程式時,都會重新建立表,故而資料會丟失。
- create-drop:每次執行程式時會先建立表結構,然後待程式結束時清空表。
- upadte:每次執行程式,沒有表時會建立表,如果物件發生改變會更新表結構,原有資料不會清空,只會更新(推薦使用)。
- validate:執行程式會校驗資料與資料庫的欄位型別是否相同,欄位不同會報錯。
- none: 禁用DDL處理。
5.3 簡單的Spring dat jpa例子
例子簡單就不寫service層了,直接在controller中呼叫。
建立實體類
@Data
@Entity
@Table(name = "account")
@ToString
public class Account {
@Id
@GenericGenerator(name = "idGenerator",strategy = "uuid")
@GeneratedValue(generator = "idGenerator")
private String id;
@Column(name = "username",unique = true,nullable = false,length = 64)
private String username;
@Column(name = "password",length = 64)
private String password;
@Column(name = "email",length = 64)
private String email;
}
複製程式碼
主鍵採用UUID策略
@GenericGenerator
是Hibernate提供的主鍵生成策略註解,注意下面的@GeneratedValue
(JPA註解)使用generator = "idGenerator"引用了上面的name = "idGenerator"主鍵生成策略。
JPA自帶的幾種主鍵生成策略
TABLE: 使用一個特定的資料庫表格來儲存主鍵。
SEQUENCE: 根據底層資料庫的序列來生成主鍵,條件是資料庫支援序列。這個值要與generator一起使用,generator 指定生成主鍵使用的生成器(可能是orcale中自己編寫的序列)。
IDENTITY: 主鍵由資料庫自動生成(主要是支援自動增長的資料庫,如mysql)。
AUTO: 主鍵由程式控制,也是GenerationType的預設值。
dao層
@Repository
public interface AccountRepository extends JpaRepository<Account,String> {
}
複製程式碼
controller層
@RestController
@RequestMapping(value = "/role")
public class AccountController {
@Autowired
private AccountRepository repository;
@PostMapping()
public Account save(@RequestBody Account account) {
return repository.save(account);
}
@DeleteMapping("/{id}")
public void delete(@PathVariable("id") String accountId) {
repository.deleteById(accountId);
}
@PutMapping("/{id}")
public Account update(@PathVariable("id") String accountId,@RequestBody Account account) {
account.setId(accountId);
return repository.saveAndFlush(account);
}
@GetMapping("/{id}")
public Account getAccountInfo(@PathVariable("id") String accountId) {
Optional<Account> optional = repository.findById(accountId);
return optional.orElseGet(Account::new);
}
}
複製程式碼
最後在資料庫中造幾條假資料進行crud就可以了。
5.4 Repository介面
Repository 介面概述:
- Repository 介面是 Spring Data 的一個核心介面,它不提供任何方法,是一個空介面,即是一個標記介面。開發者需要在自己定義的介面中宣告需要的方法 。
public interface Repository<T,ID extends Serializable> { }
複製程式碼
- Spring Data可以讓我們只定義介面,只要遵循Spring Data的規範,就無需寫實現類。
- 若我們定義的介面繼承了Repository,則該介面會被IOC容器識別為一個Repository Bean,納入到IOC容器中,進而可以在該介面中定義滿足一定規範的方法。實際上在IOC容器中放的是該介面的實現類,只不過spring幫我們實現了,實際上它是一個代理。
- 與繼承 Repository 等價的一種方式,就是在持久層介面上使用 @RepositoryDefinition 註解,併為其指定 domainClass 和 idClass 屬性,指定物件和主鍵型別。
Repository 的子介面:
基礎的 Repository提供了最基本的資料訪問功能,其幾個子介面則擴充套件了一些功能。它們的繼承關係如下:
- Repository:僅僅是一個標識,表明任何繼承它的均為倉庫介面類.
- CrudRepository:繼承Repository,實現了一組CRUD相關的方法.
- PagingAndSortingRepository: 繼承 CrudRepository,實現了一組分頁排序相關的方法.
- JpaRepository:繼承PagingAndSortingRepository,實現一組JPA規範相關的方法.
- 自定義的 XxxxRepository 需要繼承 JpaRepository,這樣的XxxxRepository介面就具備了通用的資料訪問控制層的能力.
- JpaSpecificationExecutor:不屬於Repository體系,實現一組JPACriteria 查詢相關的方法.
5.5 自定義條件查詢語句
按照 Spring Data的規範,查詢方法以find | read | get
開頭,涉及條件查詢時,條件的屬性用條件關鍵字連線,要注意的是:條件屬性以首字母大寫。
比如說現在要按照account的名稱進行查詢:
在AccountRepository介面中新增方法
@Repository
public interface AccountRepository extends JpaRepository<Account,String> {
Account findAccountByUsername(String username);
}
複製程式碼
controller中新增方法:
@GetMapping("/name/{username}")
public Account getAccountByName(@PathVariable("username") String userName){
Account account = repository.findAccountByUsername(userName);
return account;
}
複製程式碼
這樣就可以根據名稱進行查詢了,當然你也可以使用這種方法進行復雜查詢,spring data jpa中支援的關鍵字如下:
5.6 自定義sql語句
有些時候spring data jpa提供的查詢條件滿足不了業務需求的時候,可以使用自定義的sql來進行查詢。
想要使用自定義sql需要使用@Query
註解,@Query註解使用起來很簡單,預設的屬性是value,就是當前寫的SQL語句,有時會用到nativeQuery屬性,這個屬性是用來標記當前的SQL是本地SQL,還是符合JPA語法規範的SQL。這裡需要解釋一下本地SQL和JPA語法規範的SQL區別。
- 本地SQL,是根據實際使用的資料庫型別寫的SQL,這種SQL中使用到的一些語法格式不能被JPA解析以及可能不相容其他資料庫,這種SQL稱為本地SQL,此時需要將nativeQuery屬性設定為true,否則會報錯。
- JPA語法規範的SQL,往往這種SQL本身是不適用於任何資料庫的,需要JPA將這種SQL轉換成真正當前資料庫所需要的SQL語法格式。
JPA很好的一個特性就是用JPA語法規範寫的SQL,會根據當前系統使用的資料庫型別改變生成的SQL語法,相容資料庫型別的切換,如之前使用的是MySQL,現在換成Oracle,由於不同型別的資料庫,SQL語法會有區別,如果使用的是mybatis,就需要手動去改SQL相容Oracle,而JPA就不用啦,無縫對接。
很大的時候使用JPA感覺都是為了相容後期可能會有資料庫切換的問題,所以在使用JPA的時候,不要去使用本地SQL,這就違背了使用JPA的初衷,讓nativeQuery屬性保持預設值就可以啦!
AccountRepository中新增方法
@Query("select a from Account a where a.username = :username")
Account findAccountByName(@Param("username")String name);
@Query("select a from Account a where a.email = ?1")
Account findAccountByEmail(String email);
@Query(value = "select * from account where username = ?1",nativeQuery = true)
Account getAccount(String username);
複製程式碼
- 在SQL上使用佔位符的兩種方式,第一種是使用":"後加變數的名稱,第二種是使用"?"後加方法引數的位置。如果使用":"的話,需要使用@Param註解來指定變數名;如果使用"?"就需要注意引數的位置。
- 使用JPA語句中SQL語句中直接用實體類代表表名,因為在實體類中使用了@Table註解,將該實體類和表進行了關聯。 controller中新增方法(方法名還有url起的都比較隨意。。。。。)
@GetMapping("/sql/{username}")
public Account findAccountByName(@PathVariable("username") String userName){
Account account = repository.findAccountByName(userName);
return account;
}
@GetMapping("/email/{email}")
public Account findAccountByEmail(@PathVariable("email") String email){
Account account = repository.findAccountByEmail(email);
return account;
}
@GetMapping("/username/{username}")
public Account getAccount(@PathVariable("username") String username){
Account account = repository.getAccount(username);
return account;
}
複製程式碼
最後通過請求就可以獲得資料並且在控制檯可以看到打印出的響應的sql。
當自定義sql涉及到刪除,修改,插入的操作的時候需要加上@Modifying
註解。註明當前方法是修改操作。