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