SpringBoot:SpringData JPA:訪問關係型資料庫
一、SpringData JPA是什麼
上一節學習瞭如何入門SpringBoot,本篇章介紹springboot對資料庫是如何進行訪問的。在介紹之前,有必要了解一下SpringDataJPA,這是SpringCore中的一個專案,致力於簡化對資料庫的訪問,增強了ORM的操作。對於JPA(Java persisten API),全稱為Java持久化API,是JAVAEE中的一套規範API。它推出的目的是對ORM框架進行大統一,它提供一套介面,讓廠商們(如hibernate)對JPA提供實現。JPA與hibernate的關係就像JDBC與Mysql驅動、Oracle驅動一樣的關係,只是它更加高度抽象,可以稱之為ORM框架的介面,它的層遞關係是這樣的:
而SpringDataJPA是什麼樣子的呢?相信學過hibernate的同學一定也學過hibernate template,甚至自己實現過hibernate template。沒錯,SpringData JPA就很像這樣一款template,把該有的東西都給你封裝好,當然,其強大不止這一點點。相信學過mybatis的同學也知道,我們只需要一個mapper介面與一些mapper.xml,就可以讓其代理實現的持久層。SpringDataJPA在使用的時候,也只需要宣告一個介面,讓其Spring以代理的形式生成Dao。對於SpringDataJPA與hibernate等ORM框架、JPA的關係是這樣子的:
SpringDataJPA對類似hibernate這樣的框架又做了一層封裝,以便於我們程式設計的時候使用更方便。好了,說了那麼多理論,下面就開始實踐吧。
二、開始實踐
1.建立maven web專案,名稱為springboot-jpa,在pom.xml中匯入以下依賴:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.4.1.RELEASE</version> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.8</java.version> <springBoot.groupId>org.springframework.boot</springBoot.groupId> </properties> <dependencies> <!-- SpringBoot Start --> <dependency> <groupId>${springBoot.groupId}</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- jpa --> <dependency> <groupId>${springBoot.groupId}</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>${springBoot.groupId}</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> <!-- mysql --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> </dependencies>
其中SpringDataJPA核心支援依賴是spring-boot-starter-data-jpa。
2.編輯配置
待maven構建導包完成後,請在mysql中建立好資料庫springboot_test,在src/main/resources中新增application.yml配置,程式碼如下所示:
server:
port: 8080
tomcat:
uri-encoding: UTF-8
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/springboot_test?characterEncoding=utf8
username: root
password: root
jpa:
database: MYSQL
show-sql: true
#hibernate ddl auto(validate,create,update,create-drop)
hibernate:
ddl-auto: update
naming:
strategy: org.hibernate.cfg.ImprovedNamingStrategy
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL5Dialect
這些配置相信大家都能看得懂,最後一行是資料庫方言,如果是oracle,就有oracle的方言。
包結構組織如下圖:
在src/main/java中新建org.fage包,在其中建立一個Jpa的Java配置類:
package org.fage;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
*
* @author Caizhfy
* @email [email protected]
* @createTime 2017年10月30日
* @description JPA基礎配置類
*
*/
@Order(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@EnableTransactionManagement(proxyTargetClass=true)
@EnableJpaRepositories(basePackages={"org.fage.**.repository"})
@EntityScan(basePackages={"org.fage.**.domain"})
public class JpaConfiguration {
@Bean
PersistenceExceptionTranslationPostProcessor persistenceExceptionTranslationPostProcessor(){
return new PersistenceExceptionTranslationPostProcessor();
}
}
@Order聲明瞭元件載入的順序,其中接受一個整形值,值越低約先載入;
@Configuration聲明瞭這是一個配置類,該註解中包含有@Component註解,可以讓SpringBoot自動掃描載入;@EnableTransactionManagement聲明瞭開啟事務管理器代理;
@EnableJpaRepositories宣告repository(也就是原來的dao,SpringData中稱其為Repository)所在位置,值中的兩個星號是萬用字元,代表org.fage.任何路徑下的.repository包中都是repository;
@EntityScan是對實體元件位置的宣告與掃描,兩個星號依舊是萬用字元。在根包中建立入口類App.java:
package org.fage;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
*
* @author Caizhfy
* @email [email protected]
* @createTime 2017年10月30日
* @description Springboot-jpa學習建立步驟:
* 1.建立專案
* 2.新增依賴,填寫配置類
* 3.配置application.yml
* 4.配置實體jpa關係
* 5.繼承jpaRepository
* 6.編寫測試用例
*/
@SpringBootApplication
@ImportAutoConfiguration(value=JpaConfiguration.class)
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
3.建立實體建模:
建模關係是有部門、使用者、角色三個實體;部門與使用者是一對多的關係,使用者與角色是多對多的關係,在org.fage.domain包中建立三個實體,建模程式碼如下
部門實體:
@Entity
@Table(name = "department")
public class Department implements Serializable {
private static final long serialVersionUID = 159714803901985366L;
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy="department",fetch=FetchType.LAZY)
private List<User> users;
//getter and setter
...
}
使用者實體:
@Entity
@Table(name = "user")
public class User implements Serializable{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
@Column(name = "create_date")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Temporal(TemporalType.TIMESTAMP)
private Date createDate;
//一對多對映
@ManyToOne
@JoinColumn(name = "department_id")
@JsonBackReference // 防止物件的遞迴訪問
private Department department;
//多對多對映
@ManyToMany(fetch=FetchType.EAGER)
@JoinTable(name = "user_role",
joinColumns = {@JoinColumn(name="user_id")},
inverseJoinColumns = {@JoinColumn(name = "role_id")}
)
//getter and setter
}
角色實體:
@Entity
@Table(name="role")
public class Role implements Serializable{
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
private String name;
//getter and setter
}
實體對映關係建立完成,解釋一下其中的註解,這些註解其實大多數都是在學JPA與hibernate的時候學會的:@Entity宣告這是一個實體類;@Table宣告該實體在表中對應的表名是什麼;@Id宣告該屬性為實體對應表的主鍵;@GeneratedValue聲明瞭主鍵策略是什麼,這裡使用的是自動增長主鍵策略;@ManyToOne與@OneToMany聲明瞭該實體與對應屬性的實體是多對一或者一對多的關係,其中如果設立雙向關係記得設定mappedBy,fetch的值聲明瞭載入方式是懶載入還是立即載入;@ManyToMany是實體間多對多的關係,@JoinTable設定了兩個多對多的實體的中間表外來鍵。
4.建立持久層Dao
在SpringDataJPA中,我們不在稱其為mapper或者dao,而是稱為repository,我們來為三個實體分別建立自己的repository。在org.fage.repository中建立如下三個介面,繼承自JpaRepository,泛型左邊為實體型別,右邊為該實體的主鍵型別:
部門repository:
package org.fage.repository;
import org.fage.domain.Department;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface DepartmentRepository extends JpaRepository<Department, Long>{}
使用者repository:
@Repository
public interface UserRepository extends JpaRepository<User, Long>{
//And用法
User findById(long id);
User findByIdAndUsername(long id, String username);
//Or用法
User findByIdOrUsername(long id, String name);
//Between用法
User findByCreateDateBetween(Date start, Date end);
//LessThan用法
List<User> findByCreateDateLessThan(Date start);
//GreaterThan用法
List<User> findByCreateDateGreaterThan(Date start);
//IsNull/IsNutNull用法
List<User> findByUsernameIsNull();
//Like/NotLike用法
List<User> findByUsernameLike(String username);
//OrderBy用法
List<User> findByUsernameOrderByIdAsc(String username);
//Not用法
List<User> findByUsernameNot(String username);
//In/NotIn用法
List<User> findByUsernameIn(Collection<String> nameList);
}
角色repository:
@Repository
public interface RoleRepository extends JpaRepository<Role, Long>{}
至此,我們可以建立Junit程式,測試使用這三個持久層介面了,在src/test/java中可以建立測試程式進行測試了,hibernate實現會自動幫我們生成表,SpringDataJPA提供了常用的CRUD操作:
@RunWith(SpringRunner.class)
@SpringBootTest
public class RepositoryTest {
private final Logger log = LoggerFactory.getLogger(RepositoryTest.class);
@Autowired
UserRepository userRepository;
@Autowired
RoleRepository roleRepository;
@Autowired
DepartmentRepository departmentRepository;
@Before
public void initData() {
departmentRepository.deleteAll();
roleRepository.deleteAll();
userRepository.deleteAll();
Department d = new Department();
d.setName("開發部");
departmentRepository.save(d);
Assert.assertNotNull(d.getId());
Role r = new Role();
r.setName("部門經理");
roleRepository.save(r);
Assert.assertNotNull(r.getId());
List<Role> roles = new ArrayList<Role>();
roles.add(r);
User u = new User();
u.setUsername("蔡智法");
u.setCreateDate(new Date());
u.setDepartment(d);
u.setRoles(roles);
userRepository.save(u);
Assert.assertNotNull(u.getId());
}
@Test
public void testGeneralMethod(){
System.out.println(userRepository.findByUsernameLike("蔡智法"));
}
@Test
public void testFindPage() {
//hibernate一對多分頁原理:先分頁,然後在將id作為引子查詢(效率低)
Pageable pageable = new PageRequest(0, 5, new Sort(Sort.Direction.ASC, "id"));
Page<Department> page = departmentRepository.findAll(pageable);
System.out.println(page.getNumberOfElements());
}
}
至此,執行成功。其實如果你也動手試一試,發現增刪改查都不需要實現程式碼就能擁有這些功能,這些Repository介面中還有很多方法可以嘗試,希望大家自行踴躍嘗試,太便捷了。
沒搞錯吧?這些介面甚至一行業務程式碼都不用寫(UserRepository中的程式碼待會兒解釋),就能實現增刪改查、分頁操作??沒錯,確實就是這麼簡單。不難發現,我們獲得支援的最重要一點是繼承了JpaRepository(該介面提供了上層介面更多的查詢操作)介面,跟蹤原始碼發現JpaRepository介面繼承了上層的PageAndSortingRepository(該介面提供了分頁以及排序的支援),PageAndSortingRepository繼承自CrudRepository(該介面提供基礎的增刪改查操作),而CrudRepository又繼承自Repository。我們知道,他們都是介面,本身沒有實現方法,但是SpringDataJPA幫我們提供了一套實現,在執行的時候會以代理的形式給我們生成實現類,只要你繼承了Spring給你的這些Repository介面,那麼你就能獲得這些方法支援。
問題又來了,UserRepository介面中自定義宣告的方法我們也沒有實現,為什麼也能正常使用呢?
那是因為在SpringDataJPA中,自定義的方法一般有兩種,第一種就是這種“約定命名”法,這種方法一定要查詢命名規範,比如findByXXX,SpringData會根據字首、中間連線詞(Or、And、Like、NotNull等等類似Sql中的關鍵詞),詳情使用請看下錶,內部會自己轉換成JPQL使用:
內部幫我們拼接sql代理生成方法的實現,不得不感嘆,真的太方便了。
第二種方法就是使用@Query註解使用JPQL(類似SQL與EL的組合)語句查詢,這種查詢一般不使用代理,是直接內部轉化成SQL進行執行,這種方法比前一種靈活一些,後面章節會提到(還有一種方法,就是自定義增強Repository實現),使用原生SQL查詢,這樣方便優化SQL。自定義增強Repository與框架原理解析,在後續文章中會陸續更新。
以上原始碼在https://github.com/Phapha1996/springboot-jpa能檢出。
在本文中,最重要的詞就是代理了,如果不瞭解代理模式的同學,希望能惡補一波代理,這是一組非常值得學習而有用的模式。
原文參考:https://blog.csdn.net/phapha1996/article/details/78712597