JPA -- Java持久层API


目 录



1 JPA概述
1.1 JPA是什么
1.1.1 ORM是什么
1.1.2 标准是什么
1.2 JPA的实现者
1.3 JPA的作用是什么

2 入门示例
2.1 配置流程图
2.2 配置步骤说明
2.3 配置步骤
2.3.1 第一步:创建Maven项目
2.3.2 第二步:创建一个总配置文件
2.3.3 第三步:封装JPAUtils工具类
2.3.4 第四步:创建映射实体类
2.3.5 第五步:在总配置文件中加载映射实体类
2.3.6 第六步:操作实体类保存数据

3 移植Hibernate  JPA代码到OpenJPA(了解)
3.1 说明
3.2 示例代码
3.2.1 第一步:导入OpenJPA依赖
3.2.2 第二步:修改总配置文件
3.3 使用JPA的好处

4 JPA实现CRUD
5 JPA常用  API说明
5.1 映射注解说明
5.2 JPA常用API说明

6 JPA多表关联查询
6.1 一对多实现
6.1.1 说明
6.1.2 配置步骤
6.2 多对一实现
6.2.1 说明
6.2.2 配置步骤
6.3 双向一对多|多对一(了解)
6.3.1 说明
6.3.2 配置步骤
6.4 一对一实现
6.4.1 说明
6.4.2 配置步骤
6.5 配置多对多
6.5.1 说明
6.5.2 配置步骤

7 JPA逆向工程
7.1 说明
7.2 步骤
7.2.1 第一步:创建JPA项目
7.2.2 第二步:生成JPA代码
7.2.3 第三步:导入所需jar依赖
7.2.4 第四步:更新项目

8 JPQL语言
8.1 说明
8.2 示例代码
8.3 JPQL补充:N+1问题



1 JPA概述

1.1 JPA是什么

JPA (Java Persistence API) Java持久化API。是一套Sun公司 Java官方制定的ORM 方案,是规范,是标准 ,sun公司自己并没有实现

关注点: ORM ,标准 概念 (关键字)

1.1.1 ORM是什么

ORM(Object Relational Mapping) 对象关系映射。

问:ORM有什么用?

在操作数据库之前,先把数据表与实体类关联起来。 然后通过实体类的对象操作(增删改查)数据库表,这个就是ORM的行为!

所以:ORM是一个实现使用对象操作数据库的设计思想!!!

通过这句话,我们知道JPA的作用就是通过对象操作数据库的,不用编写sql语句。

1.2 JPA的实现者

既然我们说JPA是一套标准,意味着,它只是一套实现ORM理论的接口。没有实现的代码。

那么我们必须要有具体的实现者才可以完成ORM操作功能的实现!

市场上的主流的JPA框架 (实现者)有:

Hibernate (JBoos)、EclipseTop(Eclipse社区)、OpenJPA (Apache基金会)。

其中Hibernate是众多实现者之中,性能最好的。所以,我们本次教学也是选用Hibernate框架作为JPA的主讲框架。

提醒: 学习一个JPA框架,其他的框架都是一样使用

1.3 JPA的作用是什么(问题)

JPA是ORM的一套标准,既然JPA为ORM而生,那么JPA的作用就是实现使用对象操作数据库,不用写SQL!!!.

问题:数据库是用sql操作的,那用对象操作,由谁来产生SQL?

答:JPA实现框架

2 入门示例

任何框架的学习,都建议从配置流程图开始。所以我们来一起理解JPA的配置流程图。

2.1 配置流程图




java 实体字段是关键字 java实体类常用注解_java 实体类 临时注解


1. 我们需要一个总配置文件persistence.xml存储框架需要的信息 (注意,文件名不要写错,而且必须放在classpath/META-INF文件夹里面)

2. 我们需要一个Persistence持久类对象来读取总配置文件,创建实体管理工厂对象

3. 我们需要实体管理工厂获得数据库的操作对象实体管理对象EntityManager。

4. 我们通过EntityManager操作数据库之前,必须要先配置表与实体类的映射关系,从而实现使用对象操作数据库!!!

2.2

第一步:导入包 (不管什么框架,首先要做的事情)

第二步:创建一个总配置文件

第三步:创建一个JPAUtils获得操作对象EntityManager

第四步:创建一个实体类,并且配置好映射注解

第五步:在总配置文件加载实体类

第六步:测试代码(需求:插入数据到用户表)

2.3

需求:编写一个JPA的项目,插入一条数据到学生信息表。

2.3.1

说明:我们这里是基于hibernate实现的,所以要导入Hibernate的JPA规范包

--使用maven构建的配置--


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 <modelVersion>4.0.0</modelVersion>
 <groupId>cn.zj</groupId>
 <artifactId>jpa-demo01-start</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 
 <dependencies>
  <!-- hibernate框架 实现 JPA 依赖 -->
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-entitymanager</artifactId>
        <version>4.3.6.Final</version>
    </dependency>
    <!--jdbc驱动 -->
    <dependency>
       <groupId>mysql</groupId>
       <artifactId>mysql-connector-java</artifactId>
       <version>5.1.40</version>
    </dependency>
 </dependencies>
</project>


2.3.2 第二步:创建一个总配置文件

注意:文件必须放在classpath:/META-INF/persistence.xml

说明:Eclipse已经支持了JPA框架,所有不需要配置xsd文件,直接使用


java 实体字段是关键字 java实体类常用注解_java读取mysql配置文件_02


配置信息如下:


<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd ">
 <persistence-unit name="mysql-jpa">
 <!-- 四要素 org.hibernate.cfg.Environment-->
 <properties>
 <!-- 如果使用Hibernate实现的JPA,使用的就是Hibernate的环境参数 -->
 <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver" />
 <property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/jpa" />
 <property name="hibernate.connection.username" value="root" />
 <property name="hibernate.connection.password" value="root" />
<!--可选配置-->
<!--控制台打印sql语句-->
 <property name="hibernate.show_sql" value="true" />
 <!-- 格式化输出SQL -->
 <property name="hibernate.format_sql" value="true" />
 </properties>
 </persistence-unit>
</persistence>


2.3.3 第三步:封装JPAUtils工具类

创建一个工具类JPAUtils,获得操作对象(EntityManager)


package cn.zj.jpa.util;
 
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
 
public class JPAUtils {
 
 //同一个应用中,应该保证只有一个实例工厂。
 public static EntityManagerFactory emf = createEntityManagerFactory();
 
 //1.获得实体管理工厂
 private static EntityManagerFactory createEntityManagerFactory(){
       EntityManagerFactory emf = Persistence.createEntityManagerFactory("mysql-jpa");
 return emf;
    }
 
 //2.获得实体管理类对象
 public static EntityManager getEntityManger(){
       EntityManager entityManager = emf.createEntityManager();
 return entityManager;
    }
}


2.3.4 第四步:创建映射实体类

创建一个映射的实体类,将JPA的映射注解写在实体类里面。


package cn.zj.jpa.entity;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
 
//1.指定实体类与表名的关系
//@Entity注解,指定该实体类是一个基于JPA规范的实体类
@Entity
//@Table注解,指定当前实体类关联的表
@Table(name="tb_student")
public class Student {
 //@Id注解:声明属性为一个OID属性
 @Id
 //@GeneratedValue注解,指定主键生成策略
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 //@Column注解,设置属性与数据库字段的关系,如果属性名和表的字段名相同,可以不设置
 @Column(name="stu_id")
 private Long stuId;//BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '学生编号',
 @Column(name="stu_name")
 private String stuName;//VARCHAR(50) NULL DEFAULT NULL COMMENT '学生名字',
 @Column(name="stu_age")
 private Integer stuAge;//INT(11) NULL DEFAULT NULL COMMENT '学生年龄',
 @Column(name="stu_password")
 private String stuPassword;//VARCHAR(50) NULL DEFAULT NULL COMMENT '登录密码',
 
public Student() {
 super();
    }
//补全get、set方法
}


2.3.5 第五步:在总配置文件中加载映射实体类


<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd ">
 <persistence-unit name="mysql-jpa">
 
       <!-- 加载实体类
 基于hibernate框架的JPA已经实现了自动载入映射实体类 ,所以不配置也是可以的。建议还是加上配置。如果不写容易忽略加载的实体类有哪些
         -->
        <class>cn.zj.jpa.entity.Student</class>
 <!-- 四要素 org.hibernate.cfg.Environment-->
 <properties>
 <!-- 如果使用Hibernate实现的JPA,使用的就是Hibernate的环境参数 -->
 <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver" />
 <property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/jpa" />
 <property name="hibernate.connection.username" value="root" />
 <property name="hibernate.connection.password" value="zj" />
<!--可选配置-->
<!--控制台打印sql语句-->
 <property name="hibernate.show_sql" value="true" />
 <!-- 格式化输出SQL -->
 <property name="hibernate.format_sql" value="true" />
 </properties>
 </persistence-unit>
</persistence>


2.3.6 第六步:操作实体类保存数据

创建一个StudentDAOTest类,测试保存一个学生。


package cn.zj.jpa;
 
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import org.junit.Test;
import cn.zj.jpa.entity.Student;
import cn.zj.jpa.util.JPAUtils;
 
public class StudentDAOTest {
 
 @Test
 public void persist(){
 //1.获得实体管理类
       EntityManager manager = JPAUtils.getEntityManger();
 //2、获取事物管理器
       EntityTransaction transaction = manager.getTransaction();
 transaction.begin();
 //3、创建实体对象
       Student s=new Student();
 s.setStuName("张三");
 s.setStuAge(18);
 s.setStuPassword("zj");
 //4、保存到数据库
 manager.persist(s);
 //5、提交事物
 transaction.commit();
 //6、关闭资源
 manager.close();
    }
}


测试结果


java 实体字段是关键字 java实体类常用注解_java 实体类 临时注解_03


通过操作实体对象保存数据成功!!!

2.4 使用JPA的好处

使用JPA,可以直接使用对象操作数据库,由框架根据映射的关系生成SQL。不用开发人员编写。这样做,开发人员就不用编写SQL语句了

问题:这样有什么好处呢?

答:不同的数据库的SQL语法是有差异,如果不需要编写SQL语句。就屏蔽各种数据库SQL的差异。那么,编写的代码就可以一套代码兼容多种数据库!!!!

3 JPA实现CRUD

修改StudentDAOTest类,测试crud操作


//通过OID删除
 @Test
 public void remove(){
 //1.获得实体管理类对象
       EntityManager entityManager = JPAUtils.getEntityManger();
 //2.打开事务
       EntityTransaction transaction = entityManager.getTransaction();
 //3.启动事务
 transaction.begin();
 //4.创建数据,删除数据必须使用持久化对象
       Student s=entityManager.find(Student.class, 2L);
 
 //5.插入
 entityManager.remove(s);;
 //6。提交
 transaction.commit();
 //7.关闭
 entityManager.close();
    }
 
 
 //更新
 @Test
 public void merge(){
 //1.获得实体管理类对象
       EntityManager entityManager = JPAUtils.getEntityManger();
 //2.打开事务
       EntityTransaction transaction = entityManager.getTransaction();
 //3.启动事务
 transaction.begin();
 //4.创建数据
       Student s=new Student();
 s.setStuName("李四");
 //更新必须要有一个OID
 s.setStuId(3L);
 //5.更新
 entityManager.merge(s);
 //6。提交
 transaction.commit();
 //7.关闭
 entityManager.close();
 
    }
 
 //通过OID获得数据
 @Test
 public void find(){
 //1.获得实体管理类对象
       EntityManager entityManager = JPAUtils.getEntityManger();
 //通过OID查询数据
       Student student = entityManager.find(Student.class, 1L);
       System.out.println(student.getStuName());
 entityManager.close();
    }
 
 //通过OID获得数据
 @Test
 public void getReference(){
 //1.获得实体管理类对象
       EntityManager entityManager = JPAUtils.getEntityManger();
 /**
        * getReference()和find()方法的区别:
        * getReference基于懒加载机制,即需要使用对象的时候,才执行查询。
        */
       Student student = entityManager.getReference(Student.class, 1L);
       System.out.println(student.getStuName());
 entityManager.close();
    }


4 JPA常用 API说明

4.1 映射注解说明


java 实体字段是关键字 java实体类常用注解_java读取mysql配置文件_04


4.2 JPA常用API说明


java 实体字段是关键字 java实体类常用注解_java 实体类 临时注解_05


5

多个关联查询作用(导航查询):就是实现使用一个实体类对象查询多个表的数据。

配置多表联系查询必须有两个步骤;

(1)、在实体类里面建立表与表之间的关系。

(2)、在实体类配置关联关系,JPA使用注解配置

多表关联的E-R图如下:


java 实体字段是关键字 java实体类常用注解_java 实体类 临时注解_06


根据ER图,创建数据库表!!!

5.1 一对多实现 (单向)

需求:通过ID查询一条学生表的记录,同时查询该学生的对应的成绩的信息!

5.1.1 说明

如图所示:一个学生可以有多条成绩的记录,一条成绩的记录只属于一个学生,所以学生表与成绩表的关系是一对多的关系。


java 实体字段是关键字 java实体类常用注解_java读取mysql配置文件_07


所以,通过JPA配置一对多的关系,可以通过学生表对应的实体类对象同时获得两个表的数据。

5.1.2 配置步骤

5.1.2.1 第一步:创建项目

说明:复制入门示例的项目即可。

5.1.2.2 第二步:创建单表实体类

(1)创建Student类


@Entity
@Table(name="tb_student")
public class Student {
 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 @Column(name="stu_id")
 private Long stuId;
 @Column(name="stu_name")
 private String stuName;
 @Column(name="stu_age")
 private Integer stuAge;
 @Column(name="stu_password")
 private String stuPassword;
 
public Student() {
 super();
    }
//补全get、set方法
}


(2)创建Score类


@Entity
@Table(name="tb_score")
public class Score{
 
 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 @Column(name="sco_id")
 private Long scoId; 
 @Column(name="sco_subject")
 private String scoSubject;
 @Column(name="sco_score")
 private Float scoScore;
 @Column(name="stu_id")
 private Long stuId;
 
 public Score() {
 super();
    }
// 补全get、set方法
}


5.1.2.3 第三步:配置一对多关联关系

说明:通过@OneToMany注解配置。

修改Student类,配置一对多关系。


/**
     * 单向一对对,应该有学生来维护关系
     *
     * 一个学生对应多个成绩,一对多关系
     * 多个成绩我们使用list封装起来
     *
     * JPA 使用 @OneToMany 映射一对多
     * fetch : 抓取策略
     *      FetchType.LAZY 懒加载,默认 (只有关联对象在用到的时候才会去发送新的sql,默认关联对象不会查询)
     *          会多生成sql语句 :N+1
     *      FetchType.EAGER 迫切查询 (多表连接查询,只会发送一条sql语句)
     * @JoinColumn 设置两张表之间外键列
     */
 @OneToMany(fetch=FetchType.EAGER)
 @JoinColumn(name="stu_id")
 private List<Score> scores;
 public void setScores(List<Score> scores) {
 this.scores = scores;
    }


5.1.2.4 第四步:测试一对多查询


@Test
public void testOne2Many(){
 //1.获得实体管理类
    EntityManager manager = JPAUtils.getEntityManger();
 
    Student student = manager.find(Student.class, 1L);
    System.out.println("学生id:"+student.getStuId()+",学生姓名:"+student.getStuName());
 
    List<Score> scores = student.getScores();
 for (Score score : scores) {
       System.out.println("科目:"+score.getScoSubject()+",分数:"+score.getScoScore());
    }
 
 //6、关闭资源
 manager.close();
}


查询结果:


java 实体字段是关键字 java实体类常用注解_java读取mysql配置文件_08


一对多关联查询成功!!!

5.2 多对一实现 (单向)

5.2.1 说明

需求:通过ID查询一条成绩表的记录,同时查询该成绩的对应的学生的信息!

如图所示:成绩表里面,每一条记录只能对应一个学生,但是学生编号不是唯一的。所以成绩表里面的多条数据可以对应一个学生,所以我们称多对一的关系。


java 实体字段是关键字 java实体类常用注解_java 实体类 临时注解_09


5.2.2 配置步骤

5.2.2.1 第一步:创建项目

复制一对多示例项目即可。

5.2.2.2 第二步:创建单表实体类

修改Student类,去掉一对多配置即可。

5.2.2.3 第三步:配置多对一关联关系

修改Score类,配置多对一关系


@Entity
@Table(name="tb_score")
public class Score{
 
 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 @Column(name="sco_id")
 private Long scoId; 
 
 @Column(name="sco_subject")
 private String scoSubject;
 
 @Column(name="sco_score")
 private Float scoScore;
 
 /**
     * jpa的多对一,关联关系中指定的外键 和 关联表的属性有冲突 
     *    解决的方案:去掉关联表中外键对应的属性
 
    /*@Column(name="stu_id")
    private Long stuId;
 
    public Long getStuId() {
       return stuId;
    }
 
    public void setStuId(Long stuId) {
       this.stuId = stuId;
    }
    */
 
 /**
     * 1、分数和学生信息是多对一的关系
     * 2、只需要一个学生的实体来引用学生的信息
     */
 @ManyToOne
    @JoinColumn(name="stu_id")
    private Student student;
 
    public Student getStudent() {
       return student;
    }
    public void setStudent(Student student) {
       this.student = student;
    }
    //补全get、set方法
}


5.2.2.4 第四步:测试


@Test
 public void testMany2One(){
 //1.获得实体管理类
       EntityManager manager = JPAUtils.getEntityManger();
 
       Score score = manager.find(Score.class, 1L);
       System.out.println("科目:"+score.getScoSubject()+",分数:"+score.getScoScore());
 
       Student student = score.getStudent();
       System.out.println("学生id:"+student.getStuId()+",学生姓名:"+student.getStuName());
 //6、关闭资源
 manager.close();
    }


查询结果:


java 实体字段是关键字 java实体类常用注解_java 实体类 临时注解_10


多对一配置成功!!!

5.3 双向一对多|多对一(了解)

5.3.1 说明

(1)查询学生信息,同时查询成绩信息。

(2)查询成绩信息,同时也可以查询学生信息。

5.3.2 配置步骤

在同一个项目中,在Student类和Score类中,同时配置关联关系。

(1)在Student类中配置一对多


@OneToMany //声明是一对多的关系
 @JoinColumn(name="stu_id")   //指定关联表中 外键的字段
 private List<Score> scores;
 
 public List<Score> getScores() {
 return scores;
    }
 
 public void setScores(List<Score> scores) {
 this.scores = scores;
    }


(2)在Score类中配置多对一


/**
     * jpa的多对一,关联关系中指定的外键 和 关联表的属性有冲突 
     *    解决的方案:去掉关联表中外键对应的属性
 
 @Column(name="stu_id")
    private Long stuId;
 
    public Long getStuId() {
       return stuId;
    }
 
    public void setStuId(Long stuId) {
       this.stuId = stuId;
    }
    */
 
 /**
     * 1、分数和学生信息是多对一的关系
     * 2、只需要一个学生的实体来引用学生的信息
     */
 @ManyToOne
 @JoinColumn(name="stu_id")
 private Student student;
 
 public Student getStudent() {
 return student;
    }
 
 public void setStudent(Student student) {
 this.student = student;
    }


5.4 一对一实现

5.4.1 说明

需求:通过ID查询学生的信息,通过也获得学生对应的学生身份信息。


java 实体字段是关键字 java实体类常用注解_java读取mysql配置文件_11


5.4.2 配置步骤

5.4.2.1 第一步:创建项目

复制一对多的示例项目即可。

5.4.2.2 第二步:创建单表实体类

(1)创建Student类

(2)创建Identity类


package cn.zj.jpa.entity;
 
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
 
@Entity
@Table(name="tb_identity")
public class Identity {
 
 @Id
/*由于一对一,外键表的主键字段值来自于主键表,所以只能手工输入
手工输入ID值,可以不指定主键生成策略 */
//  @GeneratedValue  
 @Column(name="stu_id")
 private Long stuId;
 
 @Column(name="stu_identity")
 private String stuIdentity;
 
 @Column(name="stu_no")
 private String stuNo;
 public Identity() {
 super();
    }
 //  补全get、set方法
}


5.4.2.3 第三步:配置一对一关联关系

说明:一对一关联关系,也是支持双向配置的。

可以在Student类、Identity类中同时配置关联关系。

(1)修改Student类,配置一对一关系。


//学生表与学生身份表是一对一的关系,意味着,一个学生只能对应一条学生身份信息
//所以使用引用
//1.声明关系,一对一
@OneToOne
//2.必须要指定关联的外键
@JoinColumn(name="stu_id")
private Identity identity;
 
public Identity getIdentity() {
return identity;
}
public void setiIdentity(Identity identity) {
this.identity = identity;
}


(2)修改Score类


//1.声明关系,一对一
@OneToOne
//2.指定关联的外键
@JoinColumn(name="stu_id")
private Student student;
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}


5.4.2.4 第四步:测试

说明:可以分别测试方向一对一关系。


@Test
 public void testOne2One1(){
 //1.获得实体管理类
       EntityManager manager = JPAUtils.getEntityManger();
 
       Student student = manager.find(Student.class, 1L);
       System.out.println("学生id:"+student.getStuId()+",学生姓名:"+student.getStuName());
       Identity identity = student.getIdentity();
       System.out.println("学生学号:"+identity.getStuNo()+",身份证号:"+identity.getStuIdentity());
 
 //6、关闭资源
 manager.close();
    }
 
@Test
 public void testOne2One2(){
 //1.获得实体管理类
       EntityManager manager = JPAUtils.getEntityManger();
       Identity identity = manager.find(Identity.class, 1L);
       System.out.println("学生学号:"+identity.getStuNo()+",身份证号:"+identity.getStuIdentity());
       Student student = identity.getStudent();
        System.out.println("学生id:"+student.getStuId()+",学生姓名:"+student.getStuName());
 //6、关闭资源
 manager.close();
    }


(1)测试学生关联身份信息

测试结果:


java 实体字段是关键字 java实体类常用注解_java 实体类 临时注解_12


(2)测试身份信息关联学生。


java 实体字段是关键字 java实体类常用注解_java读取mysql配置文件_13


一对一配置成功!!!

5.5 配置多对多

5.5.1 说明

需求:

(1)通过ID查询学生的信息,通过该学生信息也获得对应的教师信息。

(2)通过ID查询教师的信息,通过教师信息也获得该对应的学生信息。

如图所示:一个学生可以有多个教师,一个教师也可以有多个学生,所以学生和教师的关系是多对多的关系。


java 实体字段是关键字 java实体类常用注解_java读取mysql配置文件_14


如上图所示:

如果要从学生表的信息获得教师表的信息。必须需要三个条件

1. 必须需要有一个中间表

2. 必须需要中间表对应本表的外键

3. 必须需要中间表对应关联表的外键

5.5.2 配置步骤

5.5.2.1 第一步:创建项目

复制一个示例项目即可。

5.5.2.2 第二步:创建单表实体类

学生实体类Student


//1.指定实体类与表名的关系
//@Entity注解,指定该实体类是一个基于JPA规范的实体类
@Entity
//@Table注解,指定当前实体类关联的表
@Table(name="tb_student")
public class Student {
 //@Id注解:声明属性为一个OID属性
 @Id
 //@GeneratedValue注解,指定主键生成策略
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 //@Column注解,设置属性与数据库字段的关系,如果属性名和表的字段名相同,可以不设置
 @Column(name="stu_id")
 private Long stuId;//BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '学生编号',
 @Column(name="stu_name")
 private String stuName;//VARCHAR(50) NULL DEFAULT NULL COMMENT '学生名字',
 @Column(name="stu_age")
 private Integer stuAge;//INT(11) NULL DEFAULT NULL COMMENT '学生年龄',
 @Column(name="stu_password")
 private String stuPassword;//VARCHAR(50) NULL DEFAULT NULL COMMENT '登录密码',
 
public Student() {
 super();
    }
//补全get、set方法
}


教师实体类Teacher


@Entity
@Table(name="tb_teacher")
public class Teacher {
 
 @Id
 @GeneratedValue(strategy=GenerationType.IDENTITY)
 @Column(name="tea_id")
 private Long teaId;
 
 @Column(name="tea_name")
 private String teaName;
 
 @Column(name="tea_password")
 private String teaPassword;
 
 public Teacher() {
 super();
    }
    //补全get、set方法
}


5.5.2.3 第三步:配置多对多关系

(1)学生关联教师,修改Student类


// 学生表和教师表是多对多的关系
// 所以,一个学生可以有多个教师,所以需要使用集合来存储教师信息
//1.声明关系,多对多
@ManyToMany
//2.设置关联的条件
//JoinTable用于对应中间表的设置     joinColumns设置中间表与本表关联的外键    inverseJoinColumns设置中间表与关联表对应的外键
@JoinTable(name="tb_stu_tea" ,joinColumns=@JoinColumn(name="stu_id"),inverseJoinColumns=@JoinColumn(name="tea_id"))
private List<Teacher> teachers;
 
public List<Teacher> getTeachers() {
 return teachers;
}
 
public void setTeachers(List<Teacher> teachers) {
 this.teachers = teachers;
}


(2)教师关联学生,修改Teacher类


//因为教师与学生是多对多的关系,所以一个教师也可以有多个学生,需要使用集合来存储学生的数据
//1.声明教师和学生的关系,多对多
@ManyToMany
//2.设置关联的条件
@JoinTable(name="tb_stu_tea" ,joinColumns=@JoinColumn(name="tea_id"),inverseJoinColumns=@JoinColumn(name="stu_id"))
private List<Student> students;
 
public List<Student> getStudents() {
 return students;
}
 
public void setStudents(List<Student> students) {
 this.students = students;
}


5.5.2.4 测试

5.5.2.4.1 Step1:测试学生关联教师

测试代码


@Test
 public void testMany2Many1(){
 
 //1.获得实体管理类
       EntityManager manager = JPAUtils.getEntityManger();
 
       Student student = manager.find(Student.class, 1L);
       System.out.println("学生id:"+student.getStuId()+",学生姓名:"+student.getStuName());
 
       List<Teacher> teachers = student.getTeachers();
 for (Teacher teacher : teachers) {
           System.out.println("教师id:"+teacher.getTeaId()+",教师姓名:"+teacher.getTeaName());
       }
 //2、关闭资源
 manager.close();
    }


测试结果:


java 实体字段是关键字 java实体类常用注解_java读取mysql配置文件_15


5.5.2.4.2 Step2:测试教师关联学生

测试代码


@Test
 public void testMany2Many2(){
 
 //1.获得实体管理类
       EntityManager manager = JPAUtils.getEntityManger();
 
       Teacher teacher = manager.find(Teacher.class, 1L);
       System.out.println("教师id:"+teacher.getTeaId()+",教师姓名:"+teacher.getTeaName());
 
       List<Student> students = teacher.getStudents();
 for (Student student : students) {
           System.out.println("学生id:"+student.getStuId()+",学生姓名:"+student.getStuName());
       }
 //2、关闭资源
 manager.close();
    }


测试结果:


java 实体字段是关键字 java实体类常用注解_java 实体类 临时注解_16


多对多配置成功!!!

6 JPA逆向工程

6.1 说明

所谓的逆向工程就是通过数据库的结构生成代码。

目的:提高开发的效率


java 实体字段是关键字 java实体类常用注解_java读取mysql配置文件_17


6.2 步骤

6.2.1 第一步:创建JPA项目

(1)创建项目


java 实体字段是关键字 java实体类常用注解_java 实体类 临时注解_18


(2)指定项目名、JPA版本


java 实体字段是关键字 java实体类常用注解_java 实体类 临时注解_19


(3)完成创建


java 实体字段是关键字 java实体类常用注解_java 实体类 临时注解_20


6.2.2 第二步:生成JPA代码

右击项目的src文件夹,选择new --> Other.. -->JPA的JPA Entities from Tables

6.2.2.1 Step1:创建新的数据库连接

(1)选择新建数据库连接


java 实体字段是关键字 java实体类常用注解_java 实体类 临时注解_21


(2)指定数据库类型


java 实体字段是关键字 java实体类常用注解_java读取mysql配置文件_22


(3)新建数据库驱动


java 实体字段是关键字 java实体类常用注解_java读取mysql配置文件_23


(4)配置驱动信息

驱动版本号


java 实体字段是关键字 java实体类常用注解_java 实体类 临时注解_24


加载驱动jar包


java 实体字段是关键字 java实体类常用注解_java读取mysql配置文件_25


配置jdbc四要素


java 实体字段是关键字 java实体类常用注解_java读取mysql配置文件_26


6.2.2.2 Step2:配置表与表直接的关联关系

(1)配置表关联关系

选择表,全选即可。


java 实体字段是关键字 java实体类常用注解_java读取mysql配置文件_27


配置Student、Score一对多


java 实体字段是关键字 java实体类常用注解_java读取mysql配置文件_28


java 实体字段是关键字 java实体类常用注解_java读取mysql配置文件_29


java 实体字段是关键字 java实体类常用注解_java 实体类 临时注解_30


依次配置其它的关联关系即可。


java 实体字段是关键字 java实体类常用注解_java读取mysql配置文件_31


6.2.2.3 Step3:指定生成实体类的名称及结构

(1)修改关联对象的属性名


java 实体字段是关键字 java实体类常用注解_java 实体类 临时注解_32


指定实体类的生成属性


java 实体字段是关键字 java实体类常用注解_java 实体类 临时注解_33


指定实体类的类名


java 实体字段是关键字 java实体类常用注解_java 实体类 临时注解_34


依次修改其它类的属性即可。

6.2.2.4 Step4:生成代码


java 实体字段是关键字 java实体类常用注解_java读取mysql配置文件_35


6.2.3 第三步:导入所需jar依赖

说明:Maven项目是可以导入jar包到本地的。

方法:打开DOS窗口,进入项目的pom文件所在目录,执行命令:

mvn dependency:copy-dependencies

前提:已经配置了Maven环境变量。


java 实体字段是关键字 java实体类常用注解_java 实体类 临时注解_36


6.2.4 第四步:更新项目


java 实体字段是关键字 java实体类常用注解_java 实体类 临时注解_37


7

7.1

JPQL : Java Persistence Query Language : java持久化查询语言。

它的作用是通过类似SQL的语法去操作实体类的对象。

语法和SQL一样的,SQL操作的数据表,JPQL操作的对象

作用:实现个性化的查询需求

7.2


package cn.zj.jpa;
 
import java.util.List;
 
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
 
import org.junit.Test;
 
import cn.zj.jpa.entity.Student;
import cn.zj.jpa.util.JPAUtils;
 
public class StudentDAOTest {
 
 //1.查询所有学生的信息
 @Test
 public void findAll(){
 //1获得操作对象
       EntityManager manager = JPAUtils.getEntityManager();
 //2.获得JPQL查询对象
 //标准的JPQL是必须要使用select
 //select语法: select 别名  from 类名 别名
       TypedQuery<Student> query = manager.createQuery("select s from Student s", Student.class);
 //返回多条查询的数据,getResultList
 //TypedQuery解决了HIbernate返回有警告的问题
       List<Student> students = query.getResultList();
 for (Student student : students) {
       System.out.println("学生名:"+student.getStuName());
       }
 manager.close();
    }
 
 //2.条件查询
 //需求:查询名字有张字的学生
 //注意:JPQL的语法,使用?设置参数,必须要在?后面设置下标值,下标值不能为负数
 @Test
 public void findByCondition(){
 //1获得操作对象
       EntityManager manager = JPAUtils.getEntityManager();
 //2.获得JPQL查询对象
 //标准的JPQL是必须要使用select
       TypedQuery<Student> query = manager.createQuery("select s from Student s where s.stuName like ?1", Student.class);
 //3.设置条件
 query.setParameter(1, "%张%");
       List<Student> students = query.getResultList();
 for (Student student : students) {
       System.out.println("学生名:"+student.getStuName());
       }
 manager.close();
    }
 
 @Test
 public void findByCondition1(){
 //1获得操作对象
       EntityManager manager = JPAUtils.getEntityManager();
 //2.获得JPQL查询对象
 //标准的JPQL是必须要使用select
       TypedQuery<Student> query = manager.createQuery("select s from Student s where s.stuName like :stuName", Student.class);
 //3.设置条件
 query.setParameter("stuName", "%张%");
       List<Student> students = query.getResultList();
 for (Student student : students) {
       System.out.println("学生名:"+student.getStuName());
       }
 manager.close();
    }
 
 //需求:返回学生表的记录数
 @Test
 public void count(){
 //1获得操作对象
       EntityManager manager = JPAUtils.getEntityManager();
 //2.获得JPQL查询对象
 //标准的JPQL是必须要使用select
 //JPQL中的count操作返回值是Long值,所以用Long类型接收
       TypedQuery<Long> query = manager.createQuery("select count(s) from Student s", Long.class);
 //如果返回的是一个值的查询,使用getSingleResult
       Long count = query.getSingleResult();
       System.out.println(count);
 manager.close();
    }
 
 //需求:第二页,每页三条
 @Test
 public void findByPage(){
 //1获得操作对象
       EntityManager manager = JPAUtils.getEntityManager();
 //2.获得JPQL查询对象
 //标准的JPQL是必须要使用select
       TypedQuery<Student> query = manager.createQuery("select s from Student s", Student.class);
 //设置分页条件
 //1.设置开始位置,下标从0开始,第四条数据的下标为3
 query.setFirstResult(3);
 //2.设置每页的记录
 query.setMaxResults(3);
       List<Student> students = query.getResultList();
 for (Student student : students) {
       System.out.println("学生名:"+student.getStuName());
       }
 manager.close();
    }
 
 /*
     * 命名查询语句的调用
     *
     * 所谓的命名查询,就是在实体类对象使用一个名字声明一条JPQL语句
     * 这样可以通过name值获得Query的语句
     *
     * 命名查询,在类名上做如下声明:
 
        @NamedQuery(name="Student.findAll", query="SELECT s FROM Student s")
        public class Student {
 
     */
 @Test
 public void findAllByNamedQuery(){
 //1获得操作对象
       EntityManager manager = JPAUtils.getEntityManager();
 //2.获得一个查询命名查询语句的对象
 //可以通过该对象调用实体类声明的命名查询语句
       TypedQuery<Student> query = manager.createNamedQuery("Student.findAll", Student.class);
       List<Student> students = query.getResultList();
 for (Student student : students) {
           System.out.println(student.getStuName());
       }
 manager.close();
    }
 
 //需求:通过JQOL删除有张字的学生
 @Test
 public void removeByCondition(){
 //1获得操作对象
       EntityManager manager = JPAUtils.getEntityManager();
       EntityTransaction transaction = manager.getTransaction();
 transaction.begin();
 try {
 //2.获得JPQL查询对象
 //注意,调用操作的JPQL是不需要指定返回的类型
           Query query = manager.createQuery("delete from Student s where s.stuName like ?1");
 //参数对应?设置的下标值
 query.setParameter(1, "%张%");
 int count = query.executeUpdate();
           System.out.println(count);
 transaction.commit();
 manager.close();
       } catch (Exception e) {
 transaction.rollback();
 e.printStackTrace();
       }
    }
}


7.3 JPQL补充:N+1问题

在一对多或者多对多查询过程中,首先查询1这一方的数据,然后根据1这一份的数据,查询多的一方的数据。

当学生有N个的时候,总共查询次数N+1次。

这个就称之为N+1问题

当我们数据库的量不大的时候,N+1问题基本没有什么影响。如果当数据量很大的时候,查询数据库的次数,就很大了,这个就会影响数据库的性能。

如何解决这个问题?

可以通过JPQL来解决。


@Test
 public void one2manybyOne(){
 //1、获取实体操作对象
       EntityManager manager = JPAUtils.getEntityManager();
 
 //JPQL是通过fetch这个关键词,在查询学生的信息的时候,一起将分数也查询出来
 //最终执行的sql就只有一条
       TypedQuery<Student> query = manager.createQuery("select distinct s from Student s inner join fetch s.scores", Student.class);
 
       List<Student> students = query.getResultList();
 for (Student student : students) {
           System.out.println("学生姓名:"+student.getStuName()+",学生id:"+student.getStuId());
 
           List<Score> scores = student.getScores();
 for (Score score : scores) {
              System.out.println("科目:"+score.getScoSubject()+",分数:"+score.getScoScore());
           }
           System.out.println("---------------------------------");
       }
    }


执行结果:


java 实体字段是关键字 java实体类常用注解_java读取mysql配置文件_38


执行过程中,确实一条sql语句!!!

解决了频繁查询数据库,带来的数据库性能损耗。