简介:
-
JPA的英文全称是“Java Persistence API”,表示Java持久层API,是Java持久化规范。
-
它为Java开发人员提供了一种对象、关联映射工具来管理Java应用中的关系数据。
-
具有标准化、简单易用、集成方便等优势。
作用:
它的作用是在关系型数据库和对象之间形成一个映射,这样,我们在具体的操作数据库的时候,就不需要再去和复杂的SQL语句打交道,只要像平时操作对象一样操作它就可以了。
Mybatis使用带来的问题:
通过直接编写对应的SQL语句来实现数据访问,我们发现实际上我们在Java中大部分操作数据库的情况都是读取数据并封装为一个实体类,因此,为什么不直接将实体类直接对应到一个数据库表呢?
也就是说,一张表里面有什么属性,那么我们的对象就有什么属性,所有属性跟数据库里面的字段一一对应,而读取数据时,只需要读取一行的数据并封装为我们定义好的实体类既可以,而具体的SQL语句执行,完全可以交给框架根据我们定义的映射关系去生成,不再由我们去编写,因为这些SQL实际上都是千篇一律的。
基本使用步骤:
-
配置数据源
spring.datasource.url=jdbc:mysql://localhost:3306/demo?useUnicode=true&useSSL=false&serverTimezone=UTC spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.datasource.username=root spring.datasource.password=Admin@123
-
导入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>
-
创建实体类
package uyun.demo.pojo; import lombok.Data; import javax.persistence.*; @Data @Entity //表示这个类是一个实体类 @Table(name = "students") //对应的数据库中表名称 public class Account { @GeneratedValue(strategy = GenerationType.IDENTITY) //生成策略,这里配置为自增 @Column(name = "id") //对应表中id这一列 @Id //此属性为主键 Integer id; @Column(name = "stu_name") //对应表中username这一列 String StuName; @Column(name = "password") //对应表中password这一列 String password; }
这里我们还需要设置自动表定义ddl-auto
#开启SQL语句执行日志信息 spring.jpa.show-sql=true #配置为自动创建 spring.jpa.hibernate.ddl-auto=create
ddl-auto是设置自动表定义,可以实现自动在数据库中为我们创建一个表,表的结构会根据我们定义的实体类决定
-
create :启动时删数据库中的表,然后创建,退出时不删除数据表
-
create-drop:启动时删数据库中的表,然后创建,退出时删除数据表 如果表不存在报错
-
update :如果启动时表格式不一致则更新表,原有数据保留
-
validate :项目启动表结构进行校验 如果不一致则报错
-
-
创建好实体类之后,只需要启动一个测试类,即启动Spring容器,则会自动在数据库中创建表
-
访问创建的表(CRUD)
使用接口自带的方法
-
自定义接口继承JpaRepository类,JpaRepository有两个泛型:
-
具体操作的对象实体,也就是对应的表
-
ID的类型)
package uyun.demo.service; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; import uyun.demo.pojo.Student; /** * JpaRepository有两个泛型 * 1)具体操作的对象实体,也就是对应的表 * 2)ID的类型 */ @Repository public interface AccountRepository extends JpaRepository<Student,Integer> { }
-
-
在测试类中自动注入自定义的接口,并直接使用封装好的方法
@SpringBootTest public class JPATests { @Autowired AccountRepository repository; //根据id查找 @Test public void findById(){ Optional<Student> byId = repository.findById(1); System.out.println(byId); } //新增操作 @Test public void save(){ Student student = new Student(); student.setStuName("Auto-add"); student.setPassword("admin"); student = repository.save(student); System.out.println("自动生产的主键为:"+student.getId()); } //根据Id删除 @Test public void deleteById(){ repository.deleteById(7); } //分页操作 @Test public void pageStudent(){ Page<Student> all = repository.findAll(PageRequest.of(0, 2)); for (Student student : all) { System.out.println(student); } System.out.println(all); } }
-
-
使用方法名称拼接自定义SQL语句
这个是重点,根据表的定义,自定义方法来进行查询
-
在接口中按照表关键字进行函数名的拼接,框架会自动的生成对应的SQL
-
拼接不需要强制记忆,IDEA有提示,具体规则请看:JPA方法命名规则
注意:拼接的时候,By后面跟的一定是要数据库字段对应的,例如:ByName,那么数据库中一定要有一个字段是name
@Repository public interface AccountRepository extends JpaRepository<Student,Integer> { //根据Name模糊查询 List<Student> findAllByNameLike(String str); //根据Id和name查询 Student findByIdAndName(Integer id,String name); //根据id判断Student是否存在 boolean existsStudentsById(Integer id); } @Test public void like(){ for (Student student : repository.findAllByNameLike("%a%")) { System.out.println(student); } } @Test public void findByIdAndName(){ System.out.println(repository.findByIdAndName(1, "test")); } @Test public void existsStudentsById(){ System.out.println(repository.existsStudentsById(2)); }
-
-
不使用方法名称拼接(使用SQL自由度更高)
这里有两种SQL,一种是自定义的,就是对实体类进行操作然后框架自动生成对应的原生SQL,并且IDEA有提示,只需要SQL语句中的字段名字跟类名对应就行,不需要跟数据库中的表名对应。另外一种是原生SQL,就是直接对数据库中表对应,SQL语句中的字段名必须要跟数据库中的字段名对应
-
使用自定义SQL,对实体类操作
自定义SQL语句必须在事务环境下运行 必须有DML支持(Modifying)
//?2表示下面的形参的第二个位置,?1代表第一个形参的位置。这里不对表进行操作,直接对实体类进行操作,然后实体类映射到表中 @Transactional @Modifying @Query("update Student set password = ?2 where id = ?1") int updatePasswordById(int id,String newPassword); @Test public void updatePasswordById() { repository.updatePasswordById(1,"wangpeng"); }
-
使用原生SQL,对表进行操作
nativeQuery = true:开启原生SQL
@Transactional @Modifying @Query(value = "update students set password = ?2 where name = ?1",nativeQuery = true) int updatePasswordByName(String name,String newPassword); @Test public void updatePasswordByName() { repository.updatePasswordByName("one", "testTest"); }
-
-
关联查询(一对一)
我们会在表中添加一个外键字段,而此外键字段又指向了另一个表中的数据,当我们查询数据时,可能会需要将关联数据也一并获取
-
首先定义一个实体,用来表示被关联的表
这里设计的是一个学生详细信息表
@Data @Entity @Table(name = "student_detail") public class StudentDetail { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") Integer id; @Column(name = "address") String address; @Column(name = "email") String email; @Column(name = "phone") String phone; @Column(name = "real_name") String realName; }
-
然后在Student实体中,新增加一个属性(StudentDetail)表示和另外一个表关联,需要在这个属性上面添加两个注解:
@JoinColumn:设置新增的外键名字,JPA自动关联另外一个实体的主键
-
参数name:设置自己实体类对应数据库的表用来存外键的列名
@OneToOne:声明为一对一的关联
-
参数fetch:设置加载形式,这里设置为懒加载完成想查什么就查什么功能
-
参数cascade:置关联级别完成同时操作两张表。这里的关联级别也是有多个,一般设置为all就行
-
ALL:所有操作都进行关联操作
-
PERSIST:插入操作时才进行关联操作
-
REMOVE:删除操作时才进行关联操作
-
MERGE:修改操作时才进行关联操作
-
/*这段代码是增加在Student这个实体类的*/ // @JoinColumn(name = "detail_id") @OneToOne(fetch = FetchType.LAZY,cascade = CascadeType.ALL) StudentDetail detail;
-
-
测试:直接调用Student实体的方法,就能自动关联StudentDetail实体。
懒加载属性需要在事务环境下获取,因为repository方法调用完后Session会立即关闭。
//根据id查询,返回结果会自带detail中的内容 @Transactional @Test void pageAccount() { Optional<Student> student = repository.findById(1); System.out.println(student.get()); } //添加 @Test public void add(){ Student student = new Student(); student.setName("dyt"); student.setPassword("123456"); StudentDetail detail = new StudentDetail(); detail.setAddress("重庆市万州区"); detail.setEmail("dyt@qq.com"); detail.setPhone("173"); detail.setRealName("邓玉婷"); student.setDetail(detail); repository.save(student); } //删除 @Test//同时删除(建立在cascade = CascadeType.ALL的基础上) void delete(){ repository.deleteById(4); }
-
-
关联查询(一对多)
-
这里新建一个成绩实体(score)一个学生对应多个成绩,成绩表又一对一对应课程表
@Data @Entity @Table(name = "student_score") public class Score { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") Integer id; @OneToOne//成绩表一对一到课程表 @JoinColumn(name = "cid") Course course; @Column(name = "score") Double score; //学生id,被用来对应的列 @Column(name = "sid") Integer sid; }
-
然后在Student实体中,新增加一个List列表的属性(scores)表示关联属性表的多条数据;
@OneToMany(fetch = FetchType.EAGER,cascade = CascadeType.REMOVE) @JoinColumn(name = "sid")//这里的sid是指student_score里的sid字段 List<Score> scores;//成绩有多个,用列表存储
-
测试
-
@Test void selectScore() { repository.findById(1).ifPresent(student -> student.getScores().forEach(score -> System.out.println(score))); }
-