1. 程式人生 > >SpringBoot使用JPA操作資料庫

SpringBoot使用JPA操作資料庫

使用資料庫是開發基本應用的基礎,藉助於開發框架,我們已經不用編寫原始的訪問資料庫的程式碼,也不用呼叫JDBC(Java Data Base Connectivity)或者連線池等諸如此類的被稱作底層的程式碼,我們將從更高的層次上訪問資料庫,這在Springboot中更是如此,本章我們將詳細介紹在Springboot中使用 Spring Data JPA 來實現對資料庫的操作。

JPA & Spring Data JPA

JPA是Java Persistence API的簡稱,中文名Java持久層API,是Sun官方提出的Java持久化規範,其設計目標主要是為了簡化現有的持久化開發工作和整合ORM技術。JPA使用XML檔案或註解(JDK 5.0或更高版本)來描述物件-關聯表的對映關係,能夠將執行期的實體物件持久化到資料庫,它為Java開發人員提供了一種ORM工具來管理Java應用中的關係資料。 簡單地說,JPA就是為POJO(Plain Ordinary Java Object)提供持久化的標準規範,即將Java的普通物件通過物件關係對映(Object-Relational Mapping,ORM)持久化到資料庫中。由於JPA是在充分吸收了現有Hibernate,TopLink,JDO等ORM框架的基礎上發展而來的,因而具有易於使用、伸縮性強等優點。
Spring Data JPA 是 Spring 基於 Spring Data 框架、在JPA 規範的基礎上開發的一個框架,使用 Spring Data JPA 可以極大地簡化JPA 的寫法,可以在幾乎不用寫實現的情況下實現對資料庫的訪問和操作,除了CRUD外,還包括分頁和排序等一些常用的功能。

配置Maven依賴

以MySQL資料庫為例,為了使用JPA和MySQL,首先在工程中引入它們的Maven依賴。
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.1.RELEASE</version>
	</parent>
	<dependencies>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-test</artifactId>
		</dependency>
	</dependencies>

配置資料來源和JPA

在Springboot核心配置檔案 application.properties 中配置MySQL資料來源和JPA。
########################################################
### Spring datasource -- Datasource configuration.
########################################################
spring.datasource.url = jdbc:mysql://localhost:3306/mybatis?characterEncoding=UTF-8
spring.datasource.username = root
spring.datasource.password = 123456
spring.datasource.driverClassName = com.mysql.jdbc.Driver
spring.datasource.type = org.apache.tomcat.jdbc.pool.DataSource

########################################################
### Java Persistence Api --  Spring jpa configuration.
########################################################
# Specify the DBMS
spring.jpa.database = MYSQL
# Show or not log for each sql query
spring.jpa.show-sql = true
# Hibernate ddl auto (create, create-drop, update, validate, none)
spring.jpa.hibernate.ddl-auto = update
# Naming strategy
#[org.hibernate.cfg.ImprovedNamingStrategy  #org.hibernate.cfg.DefaultNamingStrategy]
spring.jpa.hibernate.naming-strategy = org.hibernate.cfg.ImprovedNamingStrategy
# stripped before adding them to the entity manager)
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
其中,spring.jpa.hibernate.ddl-auto 引數用來配置是否開啟自動更新資料庫表結構,可取create、create-drop、update、validate、none五個值。
  • create 每次載入hibernate時,先刪除已存在的資料庫表結構再重新生成;
  • create-drop 每次載入hibernate時,先刪除已存在的資料庫表結構再重新生成,並且當 sessionFactory關閉時自動刪除生成的資料庫表結構;
  • update 只在第一次載入hibernate時自動生成資料庫表結構,以後再次載入hibernate時根據model類自動更新表結構;
  • validate 每次載入hibernate時,驗證資料庫表結構,只會和資料庫中的表進行比較,不會建立新表,但是會插入新值。
  • none 關閉自動更新

建立POJO實體

首先建立一些普通物件,用來與資料庫的表建立對映關係,在此我們只定義了員工和部門兩個實體來進行示例。
@Entity
@Table(name = "tbl_employee") // 指定關聯的資料庫的表名
public class Employee implements Serializable {

	private static final long serialVersionUID = 1L;

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id; // 主鍵ID

	private String name; // 姓名

	@ManyToOne
	@JoinColumn(name = "department_id")
	private Department department; // 部門

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Department getDepartment() {
		return department;
	}

	public void setDepartment(Department department) {
		this.department = department;
	}

}
@Entity
@Table(name = "tbl_department")
public class Department  implements Serializable{

	private static final long serialVersionUID = 1L;

	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id; // 主鍵ID

	private String name; // 部門名稱

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

}
其中,註解@Entity用來標記該類是一個JPA實體類,並使用了註解@Table指定關聯的資料庫的表名;註解@Id用來定義記錄的唯一標識,並結合註解@GeneratedValue將其設定為自動生成。

資料持久化

使用 JPA 進行資料持久化有兩種實現方式。
  • 方式一:使用Spring Data JPA 提供的介面預設實現,
  • 方式二:自定義符合Spring Data JPA規則的查詢方法,由框架將其自動解析為SQL。

使用Spring Data JPA介面(方式一)

Spring Data JPA提供了一些實現了基本的資料庫操作的介面類,如下圖所示。

CrudRepository

CrudRepository提供了一些簡單的增刪查改功能,介面定義如下。
package org.springframework.data.repository;

import java.io.Serializable;

@NoRepositoryBean
public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> {

	<S extends T> S save(S entity); // 儲存並返回(修改後的)實體

	<S extends T> Iterable<S> save(Iterable<S> entities); // 儲存並返回(修改後的)實體集合

	T findOne(ID id); // 根據ID獲取實體

	boolean exists(ID id); // 判斷指定ID的實體是否存在

	Iterable<T> findAll(); // 查詢所有實體

	Iterable<T> findAll(Iterable<ID> ids); // 根據ID集合查詢實體

	long count(); // 獲取實體的數量

	void delete(ID id); // 刪除指定ID的實體

	void delete(T entity); // 刪除實體

	void delete(Iterable<? extends T> entities); // 刪除實體集合

	void deleteAll(); // 刪除所有實體
}

PagingAndSortingRepository

PagingAndSortingRepository繼承於CrudRepository,除了具有CrudRepository介面的能力外,還新增了分頁和排序的功能,介面定義如下。
package org.springframework.data.repository;

import java.io.Serializable;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;

@NoRepositoryBean
public interface PagingAndSortingRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {

	Iterable<T> findAll(Sort sort); // 查詢所有實體並排序

	Page<T> findAll(Pageable pageable); // 分頁查詢實體
}

JpaRepository

JpaRepository繼承於PagingAndSortingRepository,所以它傳遞性地擁有了以上介面的所有方法,同時,它還繼承了另外一個QueryByExampleExecutor介面,擁有了該介面匹配指定樣例的能力,JpaRepository介面定義如下。
package org.springframework.data.jpa.repository;

import java.io.Serializable;
import java.util.List;

import javax.persistence.EntityManager;

import org.springframework.data.domain.Example;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.QueryByExampleExecutor;

@NoRepositoryBean
public interface JpaRepository<T, ID extends Serializable>
		extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {

	List<T> findAll(); // 查詢所有實體

	List<T> findAll(Sort sort); // 查詢所有實體並排序

	List<T> findAll(Iterable<ID> ids); // 根據ID集合查詢實體

	<S extends T> List<S> save(Iterable<S> entities); // 儲存並返回(修改後的)實體集合

	void flush(); // 提交事務

	<S extends T> S saveAndFlush(S entity); // 儲存實體並立即提交事務

	void deleteInBatch(Iterable<T> entities); // 批量刪除實體集合

	void deleteAllInBatch();// 批量刪除所有實體

	T getOne(ID id); // 根據ID查詢實體

	@Override
	<S extends T> List<S> findAll(Example<S> example); // 查詢與指定Example匹配的所有實體

	@Override
	<S extends T> List<S> findAll(Example<S> example, Sort sort);// 查詢與指定Example匹配的所有實體並排序

}

QueryByExampleExecutor

QueryByExampleExecutor介面允許開發者根據給定的樣例執行查詢操作,介面定義如下。
package org.springframework.data.repository.query;

import org.springframework.data.domain.Example;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;

public interface QueryByExampleExecutor<T> {

	<S extends T> S findOne(Example<S> example); // 查詢與指定Example匹配的唯一實體

	<S extends T> Iterable<S> findAll(Example<S> example); // 查詢與指定Example匹配的所有實體

	<S extends T> Iterable<S> findAll(Example<S> example, Sort sort); // 查詢與指定Example匹配的所有實體並排序

	<S extends T> Page<S> findAll(Example<S> example, Pageable pageable);// 分頁查詢與指定Example匹配的所有實體

	<S extends T> long count(Example<S> example); // 查詢與指定Example匹配的實體數量

	<S extends T> boolean exists(Example<S> example); // 判斷與指定Example匹配的實體是否存在
}
以部門實體資源庫介面DepartmentRepository為例,只需繼承CrudRepository介面便會自動擁有基礎的增刪查改功能,無須編寫一條SQL。
@Repository
public interface DepartmentRepository extends CrudRepository<Department, Long> {

}

自定義查詢方法(方式二)

除了可以直接使用Spring Data JPA介面提供的基礎功能外,Spring Data JPA還允許開發者自定義查詢方法,對於符合以下命名規則的方法,Spring Data JPA能夠根據其方法名為其自動生成SQL,除了使用示例中的 find 關鍵字,還支援的關鍵字有:query、get、read、count、delete等。
另外,Spring Data JPA 還提供了對分頁查詢、自定義SQL、查詢指定N條記錄、聯表查詢等功能的支援,以員工實體資源庫介面EmployeeRepository為例,功能程式碼示意如下。
@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {

	/**
	 * 根據部門ID獲取員工數量
	 */
	int countByDepartmentId(Long departmentId);

	/**
	 * 根據部門ID分頁查詢
	 */
	Page<Employee> queryByDepartmentId(Long departmentId, Pageable pageable);

	/**
	 * 根據員工ID升序查詢前10條
	 */
	List<Employee> readTop10ByOrderById();

	/**
	 * 根據員工姓名取第一條記錄
	 */
	Employee getFirstByName(String name, Sort sort);

	/**
	 * 聯表查詢
	 */
	@Query("select e.id as employeeId,e.name as employeeName,d.id as departmentId,d.name as departmentName from Employee e , Department d where e.id= ?1 and d.id= ?2")
	EmployeeDetail getEmployeeJoinDepartment(Long eid, Long did);

	/**
	 * 修改指定ID員工的姓名
	 */
	@Modifying
	@Transactional(timeout = 10)
	@Query("update Employee e set e.name = ?1 where e.id = ?2")
	int modifyEmployeeNameById(String name, Long id);

	/**
	 * 刪除指定ID的員工
	 */
	@Transactional(timeout = 10)
	@Modifying
	@Query("delete from Employee where id = ?1")
	void deleteById(Long id);

}

JUnit測試

為例驗證上面介面的正確性,我們使用JUnit來進行測試,先增加一個JPA的配置類,程式碼如下。
@Order(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@EnableTransactionManagement(proxyTargetClass = true)
@EnableJpaRepositories(basePackages = {"com.pengjunlee.repository"})
@EntityScan(basePackages = {"com.pengjunlee.entity"})
@EnableAutoConfiguration
public class JpaConfiguration {

	@Bean
	PersistenceExceptionTranslationPostProcessor persistenceExceptionTranslationPostProcessor() {
		return new PersistenceExceptionTranslationPostProcessor();
	}
}
其中,@EnableTransactionManagement註解用來啟用JPA事務管理,@EnableJpaRepositories註解用來啟用JPA資源庫發現,@EntityScan註解用來啟用實體發現。
配置類定義好之後,編寫一個JUnit Test Case測試程式。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = JpaConfiguration.class)
@TestPropertySource(locations = { "classpath:application.properties" })
public class JpaTest {

	@Autowired
	DepartmentRepository departmentRepository;

	@Autowired
	EmployeeRepository employeeRepository;

	@Test
	public void addDepartmentTest() {

	}

	@Test
	public void initData() {
		// 清空員工表中的資料
		employeeRepository.deleteAll();
		// 清空部門表中的資料
		departmentRepository.deleteAll();
		// 部門表中新增一個開發部
		Department dept1 = new Department();
		dept1.setName("開發部");
		departmentRepository.save(dept1);
		// 部門表中新增一個測試部
		Department dept2 = new Department();
		dept2.setName("測試部");
		departmentRepository.save(dept2);

		String[] names = new String[] { "lucy", "tom", "hanmeime", "jacky", "francky", "lilly", "xiaoming", "smith",
				"walt", "sherry" };
		// 員工表中增加10條記錄
		for (int i = 0; i < 10; i++) {
			Employee emp = new Employee();
			emp.setName(names[i]);
			if (i < 5) {
				emp.setDepartment(dept1);
			} else {
				emp.setDepartment(dept2);
			}
			employeeRepository.save(emp);
		}
	}

	@Test
	public void testCountByDepartmentId() {
		int count = employeeRepository.countByDepartmentId(19L);
		System.out.println(count);
	}

	@Test
	public void testQueryByDepartmentId() {
		Pageable pageable = new PageRequest(0, 10, new Sort(Sort.Direction.ASC, "name"));
		Page<Employee> emps = employeeRepository.queryByDepartmentId(19L, pageable);

		for (Employee emp : emps.getContent()) {
			System.out.println("員工姓名:" + emp.getName() + ",所屬部門:" + emp.getDepartment().getName());
		}
	}

	@Test
	public void testReadTop10ByOrderById() {
		List<Employee> emps = employeeRepository.readTop10ByOrderById();
		for (Employee emp : emps) {
			System.out.println("員工姓名:" + emp.getName() + ",所屬部門:" + emp.getDepartment().getName());
		}
	}

	@Test
	public void testGetFirstByName() {
		Employee emp = employeeRepository.getFirstByName("xiaoming", new Sort(Direction.ASC, "id"));
		System.out.println("員工姓名:" + emp.getName() + ",所屬部門:" + emp.getDepartment().getName());
	}

	@Test
	public void testGetEmployeeJoinDepartment() {
		EmployeeDetail empDetail = employeeRepository.getEmployeeJoinDepartment(5L, 19L);
		System.out.println("員工姓名:" + empDetail.getEmployeeName() + ",部門名稱:" + empDetail.getDepartmentName());
	}

	@Test
	public void testModifyEmployeeNameById() {
		employeeRepository.modifyEmployeeNameById("chris", 5L);
	}

	@Test
	public void testDeleteById() {
		employeeRepository.deleteById(11L);
	}
}

本文專案原始碼已上傳至CSDN,資源地址:https://download.csdn.net/download/pengjunlee/10366305