框架系列——SpringData 、JPA 、SpringDataJPA

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 查询。
多种继承策略:支持表继承、单表继承和多表继承。

posted @ 2024-07-30 01:05  AbjLink  阅读(51)  评论(0编辑  收藏  举报