框架系列——SpringData 、JPA 、SpringDataJPA

AbjLink發表於2024-07-30

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 findAll(Sort sort);
Page findAll(Pageable pageable);

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 findOne(@Nullable Specification spec);
List findAll(@Nullable Specification spec);
Page findAll(@Nullable Specification spec, Pageable pageable);
List findAll(@Nullable Specification spec, Sort sort);
long count(@Nullable Specification spec);

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 查詢。
多種繼承策略:支援表繼承、單表繼承和多表繼承。

相關文章