SpringData 、JPA 、SpringDataJPA
1. 基礎概念
1. JPA
全稱Java Persistence API,可以透過註解或者XML描述 "物件-關係表" 之間的對映關係,並將實體物件持久化到資料庫中。 //Persistence: 持久化
為我們提供了:
1)ORM對映後設資料:JPA支援XML和註解兩種後設資料的形式,後設資料描述物件和表之間的對映關係,框架據此將實體物件持久化到資料庫表中;
如:@Entity、@Table、@Column、@Transient等註解。
2)JPA 的API:用來操作實體物件,執行CRUD操作,框架在後臺替我們完成所有的事情,開發者從繁瑣的JDBC和SQL程式碼中解脫出來。
如:entityManager.merge(T t);
3)JPQL查詢語言:透過物件導向而非面向資料庫的查詢語言查詢資料,避免程式的SQL語句緊密耦合。
如:from Student s where s.name = ? //注意,後面的Student 是類名,不是表名
但是:
JPA僅僅是一種規範,也就是說JPA僅僅定義了一些介面,而介面是需要實現才能工作的。所以底層需要某種實現,而Hibernate就是實現了JPA介面的ORM框架。
2. SpringData
是一個用於簡化資料庫訪問,並支援雲服務的開源框架。其主要目標是使得對資料的訪問變得方便快捷。
可以極大的簡化JPA的寫法,可以在幾乎不用寫實現的情況下,實現對資料的訪問和操作。除了CRUD外,還包括如分頁、排序等一些常用的功能。
3.SpringDataJPA
是spring提供的一套簡化JPA開發的框架,按照約定好的【方法命名規則】寫dao層介面,就可以在不寫介面實現的情況下,
實現對資料庫的訪問和操作。同時提供了很多除了CRUD之外的功能,如分頁、排序、複雜查詢等等。
2. SpringBoot整合SpringDataJPA
1) 建立工程,引入 相關依賴
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.3</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<!-- springdata-jap 相關依賴,可以看到,它引用了 hibernate-core-5.6.10 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- mysql驅動 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 資料庫連線池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.18</version>
</dependency>
</dependencies>
2) 配置檔案
application.properties
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/shop?characterEncoding=utf8&useSSL=false&rewriteBatchedStatements=true&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.jpa.hibernate.ddl-auto=update //讓框架幫我們自動建表,及更新表結構
spring.jpa.show-sql=true //在執行sql的時候列印出sql語句
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
//springjpa會自做聰明的把 UserInfo 類名,對映到 user_info 這樣的表名上,上面的配置,就是讓它不要這樣做
3) com.entity.UserInfo 實體類
package com.entity;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class UserInfo {
@Id @GeneratedValue(strategy =GenerationType.IDENTITY )
private int id;
private String userName;
private String password;
private String note;
...
}
4) 建立dao層
package com.dao;
import org.springframework.data.jpa.repository.JpaRepository;
import com.entity.UserInfo;
//建立dao介面
//繼承自JpaRepository
//泛型,第一個代表操作的表對應的物件模型,第二個是表中的主鍵對應的型別
public interface UserDao extends JpaRepository<UserInfo,Integer> {
//這裡可以不寫任何內容
}
5) 主啟動
package com;
...
@SpringBootApplication
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
6) 測試
在 src/test/java 下:
package com.test;
import java.util.List;
import javax.annotation.Resource;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import com.App;
import com.dao.UserDao;
import com.entity.UserInfo;
@SpringBootTest(classes=App.class)
public class UserDaoTest {
@Resource
private UserDao dao;
@Test
public void testSave() {
UserInfo user=new UserInfo();
user.setUserName("jpa使用者2");
user.setPassword("1223");
user.setNote("這是另一個jpa使用者");
dao.save(user);
System.out.println("新增成功");
System.out.println("生成的自增主鍵是:"+user.getId()); //可以看到,它能返回生成的自增主鍵
}
@Test
public void testFindAll() {
List<UserInfo> userList = dao.findAll();
userList.forEach(System.out::println);
}
}
執行上面的程式,發現可以正常執行
哪怕沒有 userInfo表,它也會幫我們建立,在執行的時候,會輸出如下SQL語句:
//建表
Hibernate: create table UserInfo (id integer not null auto_increment,
note varchar(255),
password varchar(255),
userName varchar(255), primary key (id)) engine=InnoDB
//新增資料
Hibernate: insert into UserInfo (note, password, userName) values (?, ?, ?)
它為什麼能幫我們建表? 因為在配置檔案中我們配置過:
spring.jpa.hibernate.ddl-auto=update ,這個配置,我們也可以用 create, validate等
生產環境下,通常用 validate, 在這種情況下,如果物件模型不一致,會報錯
3. SpringData核心介面
1) Repository 介面
提供了各種 findby 和 query 的查詢方式
(1) 它提供了findBy +屬性的查詢方式
規則 findBy +屬性名(首字母大寫)+ 查詢條件( Like,Is,Equals ....) 比如:
findByUserName(String name) ==>select * from t where userName= ...
findByUserNameLike(String name) >select * from t where userName like ...
findByUserNameAndPassword(String name,String pwd)> select * from t where userName =.. and password=
findByUserNameOrAddress(String name,String addr) ==> select * from t where userName=.. or addr = ...
(2) 提供了 Query的查詢方式
HQL //預設
SQL //可以指定
package com.dao;
...
public interface UserDao_Repository extends Repository<UserInfo ,Integer> {
//各種findBy 方法
List<UserInfo>findByUserName(String userName);
List<UserInfo>findByUserNameLike(String userName);
List<UserInfo>findByUserNameOrId(String userName,int id);
List<UserInfo>findByUserNameAndPassword(String userName,String password);
//使用query的方式 hql
@Query("from UserInfo where userName=?1")
List<UserInfo>getUserListByNameXXX(String userName);
//使用query的方式 sql
@Query(value="select * from userInfo where userName=?1 and password=?2", nativeQuery=true)
UserInfo login(String userName,String password);
@Query(value="update UserInfo set userName=?1, password=?2,note =?3 where id =?4")
@Modifying
void updateUser(String userName,String password,String note,int id) ;
@Query(value="update UserInfo set userName=:#{#u.userName}, password=:#{#u.password},note =:#{#u.note} where id =:#{#u.id}")
@Modifying
void updateUserByBean(UserInfo u);
}
//例 測試用例
@SpringBootTest(classes=App.class)
public class UserDao_RepositoryTest {
@Resource
private UserDao_Repository repoDao;
@Test
void testFindByUserName() {
List<UserInfo> userList=repoDao.findByUserName("root");
for (UserInfo u : userList) {
System.out.println(u);
}
}
@Test
void testFindByUserNameLike() {
略
}
@Test
void testFindByUserNameOrId() {
略
}
@Test
void testFindByUserNameAndPassword() {
System.out.println("testFindByUserNameAndPassword 執行了");
List<UserInfo> userList=repoDao.findByUserNameAndPassword("admin", "123");
for(UserInfo u:userList) {
System.out.println(u);
}
}
@Test
void testGetUserListByNameXXX() {
List<UserInfo> userList =repoDao.getUserListByNameXXX("admin");
for(UserInfo u:userList) {
System.out.println(u);
}
}
@Test
void testLogin() {
UserInfo user=repoDao.login("admin" , "123");
System.out.println(user);
}
@Test
@Transactional
@Rollback(false)
void testUpdateUser() {
repoDao.updateUser("AAA", "BBB" , "更新後的資訊", 1);
System.out.println("更新成功");
}
@Test
@Transactional
@Rollback(false)
void testUpdateUserByBean() {
UserInfo user=repoDao.login("admin" , "123");
user.setUserName("smith");
user.setPassword("smith111");
user.setNote("測試更新");
repoDao.updateUserByBean(user);
System.out.println("更新成功");
}
}
2)CrudRepository 介面
繼承自 Repository ,主要提供了對資料庫的增,刪,改,查:
該介面提供的方法:<S extends T> S save(S entity); <S extends T> Iterable<S> saveAll(Iterable<S> entities); Optional<T> findById(ID id); boolean existsById(ID id); Iterable<T> findAll(); Iterable<T> findAllById(Iterable<ID> ids); long count(); void deleteById(ID id); void delete(T entity); void deleteAll(Iterable<? extends T> entities); void deleteAll();
該介面的預設實現是 SimpleJpaRepository
1) 新建dao層,
package com.dao;
public interface UserDao_CrudRepository extends CrudRepository<UserInfo, Integer> {
//本例中,這裡什麼都不用寫
}
2) 測試類
@SpringBootTest(classes = App.class)
public class UserDao_CrudRepositoryTest {
@Resource
private UserDao_CrudRepository crudDao;
@Test
//這裡不用事務處理的註解 @Transactional 了
void testSave() {
UserInfo user=new UserInfo();
user.setUserName("crud使用者");
user.setNote("這是crud使用者");
user.setPassword("pwdcrud");
crudDao.save(user);
System.out.println("生成的自增主鍵是:"+ user.getId());
}
@Test
void testFindAll() {
//此處要有強制型別轉換
List<UserInfo> userList= (List<UserInfo>) crudDao.findAll();
for (UserInfo u : userList) {
System.out.println(u);
}
}
@Test
void testDeleteById() {
crudDao.deleteById(5);
System.out.println("刪除成功");
}
@Test
void testCount() {
long count=crudDao.count();
System.out.println(count);
}
@Test
void testFindAllById() {
List<Integer> ids = Arrays.asList(1,3,4);
List<UserInfo> userList=(List<UserInfo>) crudDao.findAllById(ids);
userList.forEach(u->System.out.println(u));
}
@Test
void testFindById() {
//注意,它返回的是Optional型別
Optional<UserInfo> optional_user=crudDao.findById(1);
UserInfo user=optional_user.get();
System.out.println(user);
}
@Test
void testOptional() {
UserInfo user =new UserInfo();
user.setUserName("xxx");
user=null;
//System.out.println("得到的使用者名稱是:"+getUserName(user));
System.out.println("得到的使用者名稱是:"+getUserName2(user));
}
String getUserName(UserInfo user) {
if(user!=null)
return user.getUserName();
else
return "使用者為空,得到使用者名稱失敗!";
}
String getUserName2(UserInfo user) {
Optional<UserInfo> u= Optional.ofNullable(user);
if(u.isPresent()) {
return u.get().getUserName();
}
else {
return "使用者為空,得到使用者名稱失敗了!";
}
}
String getUserName3(UserInfo user) {
return Optional.ofNullable(user)
.map(u->u.getUserName())
.orElse("使用者名稱為空");
}
}
3)PagingAndSortingRepository 介面
繼承自 CrudRepository,提供了對資料庫的分頁和排序查詢 (缺點: 不能做多條件分頁查詢)
繼承自 CrudRepository 介面 它有兩個自已的方法
Iterable
Page
1) 宣告dao層介面
package com.dao;
import org.springframework.data.repository.PagingAndSortingRepository;
import com.entity.UserInfo;
public interface UserDao_PagingandSortRepository extends PagingAndSortingRepository<UserInfo, Integer> {
//本例中這裡什麼都不用寫
}
2) 測試
package com.test;
@SpringBootTest(classes = App.class)
public class UserDao_PagingandSortRepositoryTest {
@Resource
private UserDao_PagingandSortRepository pagingDao;
@Test
void testFindAllSort() {
Order o1=new Order(Direction.DESC,"id");
Order o2=new Order(Direction.ASC,"userName");
//select * from userInfo order by id desc, userNme asc
Sort sort=Sort.by(o1,o2);
List<UserInfo > userList= (List<UserInfo >)pagingDao.findAll(sort);
userList.forEach(System.out::println);
}
@Test
void testFindAllPageble() {
int pageIndex=1; //要注意pageIndex是從0開始的,如果傳1表求查第二頁
int pageSize=5;
Pageable pageable=PageRequest.of(pageIndex, pageSize);
Page<UserInfo> page=pagingDao.findAll(pageable);
System.out.println("查詢出來總條數:"+page.getNumberOfElements());
System.out.println("表中總資料量:"+page.getTotalElements());
System.out.println("總頁數:"+page.getTotalPages());
System.out.println("每頁大小"+page.getSize());
System.out.println("當前頁:"+page.getNumber());
List<UserInfo> userList=page.getContent();
userList.forEach(System.out::println);
}
}
4)JpaRepository 介面
繼承自 PagingAndSortingRepository,開發中常用的介面,對返回的型別做了適配
繼承自 PagingandSortRepository, 對父介面中方法的返回值進行適配
List<T> findAll(); List<T> findAll(Sort sort); List<T> findAllById(Iterable<ID> ids); <S extends T> List<S> saveAll(Iterable<S> entities); void flush(); <S extends T> S saveAndFlush(S entity); void deleteInBatch(Iterable<T> entities); void deleteAllInBatch(); T getOne(ID id); <S extends T> List<S> findAll(Example<S> example); <S extends T> List<S> findAll(Example<S> example, Sort sort);
1) dao層介面
package com.dao;
import org.springframework.data.jpa.repository.JpaRepository;
import com.entity.UserInfo;
public interface UserDao_JapRepository extends JpaRepository<UserInfo, Integer> {
//本例中這裡什麼都不用寫
}
2) 測試用例
package com.test;
....
@SpringBootTest(classes = App.class)
public class UserDao_JapRepositoryTest {
@Resource
private UserDao_JapRepository jpaDao;
@Test
void testFindAll() {
List<UserInfo> userList = jpaDao.findAll();
userList.forEach(System.out::println);
}
@Test
void testSave() {
UserInfo user=new UserInfo();
user.setId(9);
user.setUserName("xxx");
user.setPassword("123");
user.setNote("student");
//如果資料庫中有id對應的資料,則進行更新,否則執行新增
jpaDao.save(user);
}
@Test
void testSaveAll() {
List<UserInfo >userList=new ArrayList<>();
UserInfo u1=new UserInfo();
u1.setUserName("這是u1");
UserInfo u2=new UserInfo();
u2.setUserName("這是u2");
userList.add(u1);
userList.add(u2);
Set<UserInfo >userSet=new HashSet<>();
UserInfo u3=new UserInfo();
u3.setUserName("這是u3");
UserInfo u4=new UserInfo();
u4.setUserName("這是u4");
userSet.add(u3);
userSet.add(u4);
//saveAll方法的引數,是 Iterable型別
jpaDao.saveAll(userList);
jpaDao.saveAll(userSet);
}
}
5)JpaSpecificationExceutor 介面
不是從上面的繼承來的。提供了多條件查詢
它提供的API
Optional
List
Page
List
long count(@Nullable Specification
1) 介面的宣告
package com.dao;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import com.entity.UserInfo;
public interface UserDao_JpaSpecificationExceutor extends JpaRepository<UserInfo,Integer>, JpaSpecificationExecutor<UserInfo> {
//本例中這裡不用寫內容
}
2) 測試用例
package com.test;
..
@SpringBootTest(classes = App.class)
public class UserDao_JpaSpecificationExceutorTest {
@Resource
private UserDao_JpaSpecificationExceutor speDao;
@Test
public void test() {
Specification<UserInfo>spec=new Specification<UserInfo>() {
public Predicate toPredicate(Root<UserInfo> root, CriteriaQuery<?> query, CriteriaBuilder c) {
Predicate p1 = c.like(root.get("userName").as(String.class), "%admin%");
Predicate p2 =c.like(root.get("password").as(String.class), "%pig%");
//相當於: select * from userInfo where userName link '%admin%' and password like '%pig%';
return c.and(p1,p2);
}
};
Sort sort=Sort.by(Sort.Direction.DESC,"id");
List<UserInfo> userList=speDao.findAll(spec, sort); //最終執行的是 select * from userInfo where userName link '%admin%' and password like '%pig%' order by id desc
userList.forEach(System.out::println);
}
}
4. 關聯
一對多 , 多對一
學校表
@Entity
public class School {
@Id @GeneratedValue
private int id;
private String address;
private String schoolName;
//一對多
@OneToMany(mappedBy = "school", cascade = CascadeType.ALL,fetch = FetchType.EAGER)
private Set<Student> studentList;
..
}
學生表
@Entity
public class Student {
@Id @GeneratedValue
private String stuName;
private String stuGender;
@ManyToOne(cascade =CascadeType.ALL,optional = true )
@JoinColumn(name="school_id") //指向 school 表的外來鍵
private School school;
...
}
dao層
package com.dao;
import org.springframework.data.jpa.repository.JpaRepository;
import com.entity.School;
public interface SchoolDao extends JpaRepository<School,Integer> {
}
測試
package com.test;
...
@SpringBootTest(classes=App.class)
public class SchoolDao_Test {
@Resource
private SchoolDao schoolDao;
@Test
public void testSave() {
School school =new School();
school.setAddress("江北");
school.setSchoolName("師大");
Student stu1=new Student();
stu1.setStuName("向莉");
stu1.setStuGender("女");
stu1.setSchool(school);
Student stu2=new Student();
stu2.setStuName("王偉播");
stu2.setStuGender("男");
stu2.setSchool(school);
Set<Student> stuList=new HashSet<>();
stuList.add(stu1);
stuList.add(stu2);
school.setStudentList(stuList);
schoolDao.save(school);
System.out.println("操作成功");
}
@Test
public void testGet() {
Optional<School> opt_school = schoolDao.findById(1);
School school = opt_school.get();
System.out.println(school.getAddress());
System.out.println(school.getSchoolName());
//能關聯出學生資訊
Set<Student> stuList = school.getStudentList();
for (Student stu : stuList) {
System.out.println(stu.getId()+" "+ stu.getStuName()+" " +stu.getStuGender());
}
}
}
5. Spring 中的 JPA 和 Hibernate 有什麼區別?
1)JPA (Java Persistence APl)
旨在簡化 Java 應用程式的資料持久化。它是一個規範,定義了一組接JPA 是 Java 的官方持久化標準,口和規則,但不提供具體實現。
2)Hibernate:
是一個開源的 ORM 框架,提供了 JPA 規範要求的功能,同時還提供了一些高階功能,如快取、批次處理等。它就是 JPA 的一個實現!
JPA 是規範,Hibernate 是實現。可以將 JPA 看作介面,Hibernate 看作具體的實現類。
相關資訊:
JPA 的相關資訊:
1)主要介面:
EntityManager:負責實體的生命週期管理。
EntityTransaction:管理事務。
Query:執行查詢。
2)相關注解:
@Entity:標識一個類為實體類。
@Table:指定實體對應的資料庫表。
@ld:標識主鍵欄位。
@GeneratedValue:指定主鍵的生成策略,
@Column:指定欄位的對映
3)JPA 提供的功能
CRUD 操作:建立、讀取、更新和刪除實體。
查詢語言 (JPQL):類似 SQL的查詢語言,用於操作實體。
事務管理:宣告式和程式設計式事務管理。
關係對映:一對一、一對多、多對一、多對多關係的對映。
Hibernate 的詳細資訊:
1)核心元件:
SessionFactory:用於建立 Session 物件的工廠,通常在應Session:與資料庫的單個會話,用於執行CRUD 操作和查
Transaction:用於管理事務
2)註解(擴充套件部分)
@Cache:啟用二級快取。
@BatchSize:指定批次操作的大小。
@Fetch:控制關聯資料的抓取策略。
3)高階功能:
二級快取:快取經常訪問的資料,減少資料庫訪問次數。
批次處理:支援批次插入、更新和刪除操作。
原生 SQL 支援:可以執行原生 SQL 查詢。
多種繼承策略:支援表繼承、單表繼承和多表繼承。