JPA

JPA 概述

  1. Java Persistence API(Java 持久层 API):用于对象持久化的 API
  2. 作用:使得应用程序以统一的方式访问持久层
  3. JPA 与 Hibernate的什么关系:
    1)JPA 是 Hibernate 的一个抽象
    2)Hibernate 是 JPA 的一个实现
  4. JPA 包括三个方面的技术:
    1)ORM JavaBean和数据库中的数据表建立映射关系,支持 XML(前面Hibernate已经玩了xml) 和 注解 两种形式
    2)JPA 的 API
    3)查询语言:JPQL,jpql其原型就是hibernate的hql
    JPA版本: -
    JPA 2.0 - 此版本于2009年下半年发布。以下是此版本的重要功能: -
    它支持验证。
    它扩展了对象关系映射的功能。
    它共享缓存支持的对象。
    JPA 2.1 - JPA 2.1于2013年发布,具有以下特性: -
    它允许提取对象。
    它为条件更新/删除提供支持。
    它生成模式。
    JPA 2.2 - JPA 2.2在2017年作为维护开发而发布。它的一些重要特性是: -
    1它支持Java 8的日期和时间。
    2它提供了@Repeatable注释,当想要将相同的注释应用到声明或类型用法时可以使用它。
    3它允许JPA注释在元注释中使用。
    4它提供了流式查询结果的功能,就能实现分批次查询,避免一次返回数据过大导致OOM,什么是OOM?程序申请内存过大,虚拟机无法满足我们,然后自杀了。

JPA应用环境搭建

这里选择实现JPA规范的持久层框架是
Hibernate,版本为5.2.17.Final,并且采用maven进行依赖包的管理,具体步骤如下:
1、创建Maven项目,这一步比较简单,可以直接在IDE创建。
2、添加hibernate-entitymanager依赖包:使用Hibernate来进行实体的管理,实现实体的CRUD操作,我们只需要引入这个hibernate-entitymanager依赖包即可,其它需要依赖的包将由maven自动引入,这样我们就不用关系具体需要依赖哪些jar包了。当然,junit也是必不可少的。所以需要引入的jar包就两个,简单方便:
①创建一个maven的java工程
pom.xml依赖:



org.hibernate
hibernate-entitymanager
5.2.17.Final


mysql
mysql-connector-java
5.1.46


        junit
        junit
        4.12
    

②创建一个jpa的核心配置文件src/main/resources/METAINF/persistence.xml






org.hibernate.jpa.HibernatePersistenceProvider



 













③创建实体类,并用注解链接实体类和数据表自己的映射关系,这里用注解,但是和前面学过的xml配置都是对应的,没学过,但是应该看的懂

package cn.ybzy.jpademo.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity                //代替hibernate的hbm.xml
@Table(name="users")    //表名users
public class User {
   private int id;
   private String username;
   private String passwrod;
   @Id
   @GeneratedValue(strategy=GenerationType.IDENTITY)   //表中id自增长
public int getId() {
    return id;
}
public void setId(int id) {
    this.id = id;
}
@Column(name="user_name")   //user_name表中对应的列名
public String getUsername() {
    return username;
}
public void setUsername(String username) {
    this.username = username;
}
public String getPasswrod() {
    return passwrod;
}
public void setPasswrod(String passwrod) {
    this.passwrod = passwrod;
}
public User() {
    super();
    
}
   
   
   
}

测试类:run as --junit 后,建了一个表名为users的表。
加了一条记录:

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

import org.junit.Test;

import cn.ybzy.jpademo.model.User;

public class JpaTest {
    @Test
   public void test() {
       EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("jpademo");
         //中的jpademo
       EntityManager entityManager = entityManagerFactory.createEntityManager();
       EntityTransaction transaction = entityManager.getTransaction();
       transaction.begin();
       
       User user = new  User();
       user.setUsername("admin");
       user.setPasswrod("xiong");
       entityManager.persist(user);
       transaction.commit();
       entityManager.close();
       entityManagerFactory.close();
   }
}

jpa的基本注解

基于hibernate-jpa-2.1-api-1.0.0.Final版本

  1. @Entity
    @Entity
    public class Student {
    @Id
    private String studentId;
    //省略getter()和setter()
    }
    说明这个类是实体类,并且使用默认的orm规则(类名即表名,类属性名即表字段名)。
    如果想改变这种默认的orm规则,就要使用@Table来改变class名与表名的映射规则,@Column来改变
    class中字段名与db中表的字段名的映射规则。仅使用 @javax.persistence.Entity 和 @javax.persistence.Id 这两个注解,就可以作为一个实体类与数据库中的表相对应。
  2. @Table
    @Entity
    @Table(name="STUDENT")
    public class Student {
    //省略此处冗余代码
    }

当实体类与其映射的数据库表名不同名时需要使用 @Table 标注说明,该标注与 @Entity标注并列使用。


image.png

当实体类名和数据库表名不一致时,name属性可以实现映射,即使表名一致,也推荐使用,提高程序的可读性 。

  1. @Id
@Id
<31>@GeneratedValue(strategy = GenerationType.IDENTITY) // mysql表中id自增长策略
<32>@GeneratedValue(generator="uuid2");//自定义自增策略:不过id要为String类型,不能为整数了
<33>@GeneratedValue(generator = "hibernate-uuid");//要配合下一条语句
   @GenericGenerator( name = "hibernate-uuid",strategy = "uuid")
<34>@GeneratedValue(strategy=GenerationType.AUTO);//让系统自已判 断,数据库底层支持什么自增策略
<35>@GeneratedValue(strategy=GenerationType.SEQUENCE,generator="sq_name")  //oracle中的应用自增长策略,不过要在类声明上加:
  @SequenceGenerator(name="sq_name",sequenceName="sq_name",allocationSize=1)
private String id;

上面五个方法,已可以适应绝大多数应用的自增长了。
一个实体中可以出现多个@Id注解,但需要@IdClass配合使用,以表示联合主键

@Entity 
@IdClass(Project.class)
  public class Project {
  @Id 
  private int departmentId;
  @Id 
  private long projectId;
}

一旦标注了主键,该实体属性的值可以指定,也可以根据一些特定的规则自动生成。
注:oracle主键自增长是如下图定义的格式。

image.png

注:@Id标识数据类型
image.png

  1. @Column
    @Column(name = "NAME",columnDefinition = "varchar(158) not null")
    private String name;
    标识实体类中属性与数据表中字段的对应关系。


    image.png
  2. @Temporal
    @Temporal(TemporalType.TIMESTAMP)
    private Date createTime;
    指明该属性获取时间精度。
    默认为TemporalType.TIMESTAMP类型。
    TemporalType枚举类型定义如下:
    public enum TemporalType {
    DATE, //java.sql.Date
    TIME, //java.sql.Time
    TIMESTAMP //java.sql.Timestamp
    }
    java.sql.Date、java.sql.Time和java.sql.Timestamp这三种类型不同,它们表示时间的精确度不同。
    三者的区别如表所示。


    image.png
  3. @Transient
    一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问
    (即:不与表字段映射)。
    常用于某属性仅为 临时变量时。

  4. @Basic
    @Basic(fetch = FetchType.LAZY) //懒加载,即如关联表字段,我们可能用不到,查询时就让其懒加载
    @Column(name = "CONTENT")
    private String content;
    表示一个简单的属性到数据库表的字段的映射,对于没有任何标注的属性,默认即为 @Basic。

梳理一下jpa的api

标准API历史:
《1》在JPA2.0中,标准查询API,查询的标准化开发。
《2》在JPA2.1,标准更新和删除(批量更新和删除)都包括在内。
标准查询结构
该标准与JPQL是密切相关的,并允许使用类似的操作符在他们的查询设计。它遵循javax.persistence.criteria包设计一个查询。查询结构指的语法条件查询。

public void test(){

EntityManagerFactory entityManagerFactory= e Persistence.createEntityManagerFactory("jpademo");
EntityManager entityManager = entityManagerFactory.createEntityManager();
EntityTransaction transaction = entityManager.getTransaction();
transaction.begin();
User user = new User();
user.setAge(11);
user.setEmail("[email protected]");
user.setPasword("pppp");
user.setUsername("admin");
entityManager. persist(user);
transaction.commit();
entityManager.close();
entityManagerFactory.close();
}

我使用到的几个基本的jpa的API类:
① Persistence:主要作用就是通过它的静态方法createEntityManagerFactory 获取 EntityManagerFactory ,相当于Hibernate的SessionFactory
②EntityManagerFactory :主要作用就是获取 EntityManager 的实例对象,相当于
Hibernate的Session
③EntityManager操作数据库的方法: 主要作用就是操作数据库,执行增删改查,对应的方法persist(增),find和getReferenc(查一条记录),remove(删),merge(改)

//添加记录到表中,只执一次,多次执行会重复数据
           /*User user = new  User();
           user.setUsername("admin");
           user.setPasswrod("xiong");
           entityManager.persist(user);//相当于hibernate的save()方法,持久化对象,不过不能持化游离对象
           transaction.commit();*/
           
     //查询记录,不用事务提交
           /*User user = entityManager.getReference(User.class, 1);   //getReference()相当于hibernate的load()懒加载
           //User user = entityManager.find(User.class, 1);  //加载一条记录,相当 hibernate的get()方法,立即加载
          System.out.println("----------------------------------------------------------");  //立即载,不管怎么都会打印------
          System.out.println(user);
           */
           
    //删除方法 remove()
           //session.delete(user);  //hibernate这样做是可以的,但jap不能这样直删除,首先了取出来放在缓存中,才可删除
          /* User user=entityManager.getReference(User.class, 2); 
           entityManager.remove(user);
           transaction.commit();*/
    //修改 merge 相当于hibernate的saveOrupdate(),它也可以插入记录
          //1.临时转变量,也就是转成user对象 ,没有id,当然也就没有在数据库中有对应的记录
          //2.游离状态,也就是说user对象有id(也分为数据库里有对应的id记录,发起update语句,没有相应id记录,发起insert句语,但id没用,是自增的)
          //3. 持久化状态的对象修改,当然会修改数据库里的相应的记录值。
       // 1   
           /*User user = new User();
           user.setUsername("kkkkkk");
           user.setPasswrod("123kkkk");
           entityManager.merge(user);   //此时,它无从找到id相当于插入了一条记录
           transaction.commit();*/
       //2.
           /*User user =new User();
           user.setUsername("熊少文");
           user.setPasswrod("123456");
           user.setId(5);  //user.setId(100);
           entityManager.merge(user);
           transaction.commit();*/
       //3.
           User user = entityManager.find(User.class, 5);
           user.setUsername("uuuuuuuu");
           entityManager.merge(user);
           transaction.commit();
           
           entityManager.close();
           entityManagerFactory.close();
       }
    

·
④ EntityManager的其他方法:flush()、refresh()和hibernate课程里讲的一样,这里即不赘述了
⑤ EntityManager的createQuery(),createNamedQuery(),createNativeQuery()这些方法也和hibernate里的方法非常类似,我们讲jpql查询的时候会具体使用的,到时再讲
⑥ EntityTransaction:jpa里的事务管理,begin(),commit(),rollback()等等常用方法和hibernate里也是一样的

jpa里用注解进行关系映射

1 单向多对一关联

<1>建两个对象模型(java类)
Student.java

@Entity
@Table(name="t_students")
public class Student {
    private int id;
    private String sname;
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    //不标明表字段名,默认就是java类(模型)成员变量名。
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getSname() {
        return sname;
    }
    public void setSname(String sname) {
        this.sname = sname;
    }
    @ManyToOne                      
    @JoinColumn(name="ct_id")   //单向多对一关联老师字段,学生表的表师字段名为ct_id
    public Teacher getTeacher() {
        return teacher;
    }
    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
    }
    private Teacher teacher;

}

Teacher.java

//多对一关联,该类是多端,不做其它设置
@Entity
@Table(name="t_teachers")  //映射的表名
public class Teacher {
    private int id;
    private String tname;
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getTname() {
        return tname;
    }
    public void setTname(String tname) {
        this.tname = tname;
    }

    
   
}

<2>
注意:注解@OneToMany(fetch=FetchType.LAZY)默认的是立即加载,需要懒加载的时候,要加fetch参数修改删除的1的这端的记录的时候,可以删除,会把外键设置为null,想要级联删除可以设
置@OneToMany(设置cascade属性)
<3>
测试类:
1如果只创建表,直接run as --junit test

 @Test
       public void test() {
           EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("jpademo");
             //中的jpademo,也是项目名
           EntityManager entityManager = entityManagerFactory.createEntityManager();
           EntityTransaction transaction = entityManager.getTransaction();
           transaction.begin();
         
           transaction.commit();*/

           transaction.commit();
           
           entityManager.close();
           entityManagerFactory.close();
       }
    

2 单向一对多的关联

一端Teahcer.java加@JoinColumn(name="ct_id") @OneToMany
多端学生Student.java不用修改

@Entity
@Table(name="t_students")
public class Student {
    private int id;
    private String sname;
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    //不标明表字段名,默认就是java类(模型)成员变量名。
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getSname() {
        return sname;
    }
    public void setSname(String sname) {
        this.sname = sname;
    }
  }
@Entity
@Table(name="t_teachers")  //映射的表名
public class Teacher {
    private int id;
    private String tname;
    private List studetns;
    @JoinColumn(name="ct_id")
    @OneToMany
    public List getStudetns() {
        return studetns;
    }
    
    public void setStudetns(List studetns) {
        this.studetns = studetns;
    }
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getTname() {
        return tname;
    }
    public void setTname(String tname) {
        this.tname = tname;
    }
    @Override
    public String toString() {
        return "Teacher [id=" + id + ", tname=" + tname + ", studetns=" + studetns + "]";
    }
}

注意:注解@OneToMany(fetch=FetchType.LAZY)默认的是懒加载,需要立即加载的时候,要加fetch参数修改fetch=FetchType.EAGER
***懒加载测试: ***

Teacher teacher = entityManager.find(Teacher.class, 1);  
System.out.println(teacher.getTname());
image.png
Teacher teacher = entityManager.find(Teacher.class, 1);  
System.out.println(teacher.getTname());

Theacher.java--toString()

@Override
    public String toString() {
        return "Teacher [id=" + id + ", tname=" + tname + ", studetns=" + studetns + "]";
    }

image.png

立即加载效果:
image.png

删除的1的这端的记录的时候,可以删除,会把外键设置为null,想要级联删除可以设

测试删除:

Teacher teacher = entityManager.find(Teacher.class, 2);
entityManager.remove(teacher);
 transaction.commit();
image.png

先查询老师与学生信息,再更新学生信息,再删除老师记录,而实际上学生其它信息没删除之。
老师第2条记录删除后,两表的效果如图:


image.png

image.png

级联删除

上述删除没有真正删除清学生记录,若要删除清,如下所述:

  1. 置Teacher.java@OneToMany(设置cascade属性)
    CascadeType是有很多个值,所以cascade是一个数组,所设置要用{}.
    数组定义想想就知道为什么用{}。
public class Teacher {
    private int id;
    private String tname;
    private List studetns;
    @JoinColumn(name="ct_id")
    @OneToMany(fetch=FetchType.EAGER,cascade= {CascadeType.REMOVE})  //标注为立即加载关联表信息,默认为LAZY
    public List getStudetns() {
        return studetns;
    }
  1. 测试代码为上面的删除代码一样。
image.png

image.png

3 双向的一对多的关联(和双向多对一同意义)

注意:
双向关联,一边要放弃关系维护,
①双向关联关系,两遍默认都会维护关系,可能冲突,也影响效率,正常情况关系交给“多”的一端维护,一的这端@OneToMany(mappedBy="teacher",这个属性放弃关系维护
②一旦使用 mappedBy属性,那么@JoinColumn这个注解就不能使用了,用了会出错
如下代码:
Teacher.java

@Entity
@Table(name="t_teachers")  //映射的表名
public class Teacher {
    private int id;
    private String tname;
    private List studetns;
    //@JoinColumn(name="ct_id")  //双向多对一关系中,mappedBy="teacher"表明一端放弃了关系的维护,就不用这一项了
    @OneToMany(fetch=FetchType.EAGER,cascade= {CascadeType.REMOVE},mappedBy="teacher")  //标注为立即加载关联表信息,默认为LAZY
    public List getStudetns() {
        return studetns;
    }

Student.java

@Entity
@Table(name="t_students")
public class Student {
    private int id;
    private String sname;
    private Teacher teacher;
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    //不标明表字段名,默认就是java类(模型)成员变量名。
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getSname() {
        return sname;
    }
    public void setSname(String sname) {
        this.sname = sname;
    }
    @ManyToOne                      
    @JoinColumn(name="ct_id")   //单向多对一关联老师字段,学生表的表师字段名为ct_id
    public Teacher getTeacher() {
        return teacher;
    }
    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
    }
    

}

测试:(直接在表中加记录用于测) run as --junit test

@Test
public void test(){
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("jpademo");
             //中的jpademo
           EntityManager entityManager = entityManagerFactory.createEntityManager();
           EntityTransaction transaction = entityManager.getTransaction();
           transaction.begin();
          //通过学生查询老师信息
           Student student =entityManager.find(Student.class, 1);
           System.out.println(student.getSname());
           System.out.println(student.getTeacher().getTname());
           //通过老师查询其教的所有学生
           Teacher teacher =entityManager.find(Teacher.class, 1);
           System.out.println(teacher.getTname());
           System.out.println(teacher.getStudetns());
             System.out.println(teacher.getStudents().size());
           entityManager.close();
           entityManagerFactory.close();
}

4 双向的一对一的关联

人与身份证的绑定
Person.java:

//一对一关系模型,关系维护由人来做,
@Entity
@Table(name="t_persions")
public class Person {
    private int id;
    private String pname;
    private IdCard idCard;
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getPname() {
        return pname;
    }
    public void setPname(String pname) {
        this.pname = pname;
    }
    @OneToOne(mappedBy="person")  //mappedBy放弃维护关联关系,person是IdCard中定义的属性
    public IdCard getIdCard() {
        return idCard;
    }
    public void setIdCard(IdCard idCard) {
        this.idCard = idCard;
    }
    

}

IdCard.java:

//一对一关系模型
@Entity
@Table(name="t_idcards")    //身份证号表名
public class IdCard {
    private int id;
    private String CardNo;
    private Person person;
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getCardNo() {
        return CardNo;
    }
    public void setCardNo(String cardNo) {
        CardNo = cardNo;
    }
    @OneToOne
    @JoinColumn(name="persion_id",unique=true)   //unique=true 唯一性约束,保证一对一关系
    public Person getPerson() {
        return person;
    }
    public void setPerson(Person person) {
        this.person = person;
    }
    
    
}

注意:负责维护关系的一端,要配置唯一约束,因为一对一关联,两边都是唯一的默认关联策略是:立即查询

5 双向多对多的关联

Student这边维护关系,Course就要放弃关系的维护
注意:在真项目开发中,我们处理双向多对多的关联,还可以变通来处理,就是将对对多关联,转换为两个一对多的关联
Student.java

@Entity
@Table(name="t_students")
public class Student {
    private int id;
    private String stuName;
    private List courses;
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getStuName() {
        return stuName;
    }
    public void setStuName(String stuName) {
        this.stuName = stuName;
    }
    @ManyToMany //多对多关系
    @JoinTable(name="t_student_course",
                joinColumns= {@JoinColumn(name="stu_id",referencedColumnName="id")},
                inverseJoinColumns= {@JoinColumn(name="course_id",referencedColumnName="id")})   //建一个中间表,
    public List getCourses() {
        return courses;
    }
    public void setCourses(List courses) {
        this.courses = courses;
    }
    
}

Course.java

@Entity
@Table(name="t_courses")
public class Course {
    private int id;
    private String courseName;
    private List students;
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getCourseName() {
        return courseName;
    }
    public void setCourseName(String courseName) {
        this.courseName = courseName;
    }
    @ManyToMany(mappedBy="courses")      //放弃维护关联关系,courses是在Student.java定义的属性(成员变量)
    public List getStudents() {
        return students;
    }
    public void setStudents(List students) {
        this.students = students;
    }
    
}
image.png

测试:

Student student=entityManager.find(Student.class, 1);
           System.out.println("学生"+student.getStuName()+"选择了"+student.getCourses().size()+"门课!");
           Course course = entityManager.find(Course.class, 1);
           System.out.println("课程"+course.getCourseName()+"有"+course.getStudents().size()+"个同学选修了!");

双向多对多拆会两个双向多对一

注意:在真项目开发中,我们处理双向多对多的关联,还可以变通来处理,就是将对对多关联,转换为两个一对多的关联
例如:如果按上面的做了中间表,那么中间表就不能增加字段了(成绩),这样在实际开发中就很麻烦,所以我们也给中间表做模型类,这 样有三个类,形成两个双向多对一关系。
Student.java 一对多StudentCourse
Course.java一对多StudentCourse
一端两个类不维护关系 @ManyToOne(mappedBy="student\course")//放弃维护关联关系。
***三个模型类: ***
Student.java:


@Entity
@Table(name="t_students1")
public class Student {
    private int id;
    private String stuName;
    private List studentCourses;
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getStuName() {
        return stuName;
    }
    public void setStuName(String stuName) {
        this.stuName = stuName;
    }
    @OneToMany(mappedBy="student")    //放弃关联关系的维护‘student’是多端StudentCourse.java定义的属性之一
    public List getStudentCourse() {
        return studentCourses;
    }
    public void setStudentCourse(List studentCourses) {
        this.studentCourses = studentCourses;
    }

}

Course.java

@Entity
@Table(name="t_courses")
public class Course {
    private int id;
    private String courseName;
    private List studentCourses;
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getCourseName() {
        return courseName;
    }
    public void setCourseName(String courseName) {
        this.courseName = courseName;
    }
    @OneToMany(mappedBy="course")   //放弃关联关系的维护 ‘student’是多端StudentCourse.java定义的属性之一
    public List getStudentCourse() {
        return studentCourses;
    }
    public void setStudentCourse(List studentCourses) {
        this.studentCourses = studentCourses;
    }

}

StudentCourse.java:

@Entity
@Table(name="t_student_course")  //中间表,但该中间表可以加其它字段
public class StudentCourse {
    private int id;
    private Student student;
    private Course course;
    private int score;   //成绩
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    @ManyToOne
    @JoinColumn(name="student_id")   //多对一Student的外键
    public Student getStudent() {
        return student;
    }
    public void setStudent(Student student) {
        this.student = student;
    }
    @ManyToOne
    @JoinColumn(name="course_id")   //对一端Course的外键
    public Course getCourse() {
        return course;
    }
    public void setCourse(Course course) {
        this.course = course;
    }
    public int getScore() {
        return score;
    }
    public void setScore(int score) {
        this.score = score;
    }
 
}

测试:

//双向多对多,拆分两个双向多对一关系映射测试:
           //注意:做该测试时,把model的下的 Student,Teacher,StudentCourse,Course的映射全注释了。
           //cn.ybzy.jpademo.duoduiduomodel下的Student,Course,StudentCourse三个类,不是上面的类呀
           Student student = entityManager.find(Student.class, 1);
           System.out.println(student.getStuName());
           List list = student.getStudentCourse();
           for(StudentCourse sc:list) {
               System.out.println("学生"+student.getStuName()+"的成绩"+sc.getScore()+"-----课程:"+sc.getCourse().getCourseName());
           }

jpa的jpql语言

一、什么是 JPQL

  1. 在 Java EE 中, JPQL ( Java 持久性查询语言)是专门为 Java 应用程序访问和导航实体实例设计的。JPQL 是 EJB2 使用的查询语言 EJB QL 的扩展,它继承了 EJB QL 并对其做了一些改变。
  2. JPQL 和 SQL 有很多相似之处。归根结底,它们都用于访问和操作 数据库数据。而且,二者都使用非过程
    语句 — 通过特殊解释程序识别的命令。此外, JPQL 在语法上与 SQL 也相似。JPQL 和 SQL 的主要区别在于,前者处理 JPA 实体,后者直接在数据库空间内对表、列、行等关系数据进行处理。
  3. JPQL与HQL 极其相似。

使用jpql

要从 Java 代码内发出 JPQL 查询,您需要利用EntityManager API 和 Query API 的相应方法,执行以下一
般步骤:

  1. 使用注入或通过 EntityManagerFactory 实例获取一个 EntityManager 实例。
  2. 通过调用相应 EntityManager 的方法(如 createQuery ),创建一个 Query 实例。
  3. 如果有查询参数,使用相应 Query 的 setParameter 方法进行设置。
  4. 如果需要,使用 setMaxResults 和 / 或 setFirstResult Query 的方法设置要检索的实例的最大数量和 / 或指
    定检索的起始实例位置。
  5. 如果需要,使用 setHint Query 的方法设置供应商特定的提示。
  6. 如果需要,使用 setFlushMode Query 的方法设置查询执行的刷新模式,覆盖实体管理器的刷新模式。
  7. 使用相应 Query 的方法 getSingleResult 或 getResultList 执行查询。如果进行更新或删除操作,您必须
    使用 executeUpdate 方法,它返回已更新或删除的实体实例的数量。
    动态查询
    可以使用 EntityManager.createQuery 方法创建动态查询,唯一的要求是把合法的 JPQL 语句传递给此方法。
    如下:
    Query query = em.createQuery(“select p from Person p where p.id=1033”);
    其中 where 语句是可选的。在这里 JPQL 看上去和 SQL 语句很像 , 但应当注意到的是 from 后边的 Person 是实体Bean 而不是数据表。
    在所写的 JPQL 语句中你可以像示例中那样的直接将查询条件写在语句中。但是还有更好的方法。在这里你可以使用设置查询参数的方式,其中又有位置参数和命名参数的分别。
    使用位置参数如下所示:
    Query query = em.createQuery(“select p from Person p where p.id=?1”);
    Query.setParameter(1, 1033);// 第一个参数是位置,第二个参数查询条件
    使用命名参数如下所示:
    Query query = em.createQuery(“select p from Person p where p.id=:id”);
    Query.setParameter(“id”, 1033);// 第一个参数是参数名称,第二个参数查询条件
    需要注意的是位置参数的是位置前加符号 ”?” ,命名参数是名称前是加符号 ”:” 。
    如果你需要传递 java.util.Date 或 java.util.Calendar 参数进一个参数查询,你需要使用一个特殊的setParameter() 方法。因为一个 Date 或 Calendar 对象能够描述一个真实的日期、时间或时间戳 . 所以我们需要告诉 Query 对象怎么使用这些参数,我们把 javax.persistence.TemporalType 作为参数传递进setParameter 方法,告诉查询接口在转换 java.util.Date 或 java.util.Calendar 参数到本地 SQL 时使用什么数据库类型。

例1:最基本的查询应用:

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.Query;
import java.util.List;
import org.junit.Test;
public class JpqlTest {
    @Test
       public void test() {
           EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("jpademo");
             //中的jpademo
           EntityManager entityManager = entityManagerFactory.createEntityManager();
           EntityTransaction transaction = entityManager.getTransaction();
           transaction.begin();
           //--------------------------------------------------------
  //最基本的查询应用,查询所有课程。
           String jpql = "select c from Course c where c.id courses=query.getResultList();
           //query.getSingleResult()  //获取单个
           for(Course c:courses) {
               System.out.println(c.getCourseName());
           }
           //--------------------------------------------------------
           entityManager.close();
           entityManagerFactory.close();
    }
}

例2:获取部分字段数据。

和 hibernate 没什么别,也可以封装成对应的对象,这样 javabean 里要有对应的构造器
User.java:


@Entity // 代替hibernate的hbm.xml
@Table(name = "users") // 表名users
public class User {
    private int id;
    private String username;
    private String passwrod;
    private String address;
   
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) // 表中id自增长
    //针对oracle数据库@GeneratedValue(strategy=GenerationType.SEQUENCE,gen
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Column(name = "user_name") // user_name表中对应的列名
    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPasswrod() {
        return passwrod;
    }

    public void setPasswrod(String passwrod) {
        this.passwrod = passwrod;
    }

    public User() {
        super();

    }

}

查询部分字段示例:

//查询部分字段
          String jpql = "select u.username,u.address from User u where u.id=?1";  //这样查询的是Object对象数组
           String jpql2="select u from User u where u.id=?1";  //这样查询的对象绝对是User对象
           String jpql3="select new User(u.username,u.address) from User u where u.id=?1";  //该查询返回User对象,不过前提是Bean中要有该两参构造方法
           Query query=entityManager.createQuery(jpql);
           Query query2= entityManager.createQuery(jpql2);
           Query query3= entityManager.createQuery(jpql3);
           query.setParameter(1,1);
           query2.setParameter(1, 1);
           query3.setParameter(1, 1);
           Object[] objs=(Object[]) query.getSingleResult();
           User user= (User) query2.getSingleResult();
           User user2=(User) query3.getSingleResult();
           for(int i=0;i

例3 命名查询 @NamedQuery

image.png

测试:


image.png

例4 执行原生的 sql 语句

image.png

例5 ORDER BY 排序查询,按照 id 降序排列

       String jpql = "from User";   //升序
           String jpql1= "from User u order by  u.id desc";  //降序
           Query query = entityManager.createQuery(jpql1);
           List resultList = query.getResultList();
           for(User u:resultList) {
               System.out.println(u.getId()+"_----"+u.getUsername());
           }

下图是,结合having条件查询。


image.png

6 GROUP BY ....HAVING 分组查询,查询每个老师有多少个学生 很少用 Mysql 5.7以前版本没错,后的版本出错,要另作设置。

image.png

7 关联查询:(不是对象导航查询用到左外连接命令)

注:’left outer join fetch 定义的关联对象‘的用法。
单向多对一关联关系。Student多端,Teacher一端(不配关系)
Studetn.java

@Entity
@Table(name="t_students")
public class Student {
    private int id;
    private String sname;
    private Teacher teacher;
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    //不标明表字段名,默认就是java类(模型)成员变量名。
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getSname() {
        return sname;
    }
    public void setSname(String sname) {
        this.sname = sname;
    }
    @ManyToOne                      
    @JoinColumn(name="ct_id")   //单向多对一关联老师字段,学生表的表师字段名为ct_id
    public Teacher getTeacher() {
        return teacher;
    }
    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
    }
    @Override
    public String toString() {
        return "Student [id=" + id + ", sname=" + sname + "]";
    }
    

}

Teacher.java

@Entity
@Table(name="t_teachers")  //映射的表名
public class Teacher {
    private int id;
    private String tname;
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getTname() {
        return tname;
    }
    public void setTname(String tname) {
        this.tname = tname;
    }
    @Override
    public String toString() {
        return "Teacher [id=" + id + ", tname=" + tname +  "]";
    }
}

测试:

             //t_students   t_teachers两个表
       String jpql = "select s from Student s left outer join fetch s.teacher where s.id=?1";
         //  String jpql = "select s from Student s  where s.id=?1";  //会比上一条语句多一个查旬语句。
           Query query = entityManager.createQuery(jpql);
           query.setParameter(1, 1);
           Student student = (Student) query.getSingleResult();
           System.out.println("学生"+student.getSname()+"的班主任是"+student.getTeacher().getTname());

结果如下两图,可以对比效率:


image.png

image.png

8 子查询示例

用到7的表。

//子查询:     查出班主任名为熊少文的所有学生信息。
           String jpql ="select s from Student s where s.teacher.id in (select t.id from Teacher t where t.tname=?1)";
           Query query = entityManager.createQuery(jpql);
           query.setParameter(1, "熊少文");
           List students =query.getResultList();
           System.out.println(students);

9 查询中使用函数

使用字符串函数
JPQL 定义了内置函数方便使用。这些函数的使用方法和 SQL 中相应的函数方法类似。包括:

  1. CONCAT 字符串拼接
  2. SUBSTRING 字符串截取
  3. TRIM 去掉空格
  4. LOWER 转换成小写
  5. UPPER 装换成大写
  6. LENGTH 字符串长度
  7. LOCATE 字符串定位
    使用算术函数
    JPQL 仅仅支持了最低限度的算术函数集合,可以在 JPQL 的 where 和 having 子句中使用算术函数。 JPQL 定
    义的算术函数包括:
    ABS 绝对值
    SQRT 平方根
    MOD 取余数
    SIZE 取集合的数量
    使用时间函数
    JPQL 像大多数语言一样提供了获得当前日期、时间或是时间标记的函数。这些函数转换为数据库专有的SQL 函数,从数据库检索当前日期、时间或时间标记。包含的时间函数如下:
    CURRENT_DATE 返回当前日期
    CURRENT_TIME 返回当前时间
    CURRENT_TIMESTAMP 返回当前时间标记
    下示例:把查询到的学生名转为大写。
String jpql="select UPPER(s.sname) from Student s where s.id=1";
           Query query = entityManager.createQuery(jpql);
           String  sname=(String)query.getSingleResult();
           System.out.println("学生姓名输出大写:"+sname);
      

10 修改和删除 executeUpdate()

//修改
         /*  String jpql="update User u set u.username=?1 where u.id=?2";
           Query query = entityManager.createQuery(jpql);
           query.setParameter(1,"毛泽东");           //名字修改为'毛泽'
           query.setParameter(2, 1);                   //修改第一条记录的
           query.executeUpdate();
           transaction.commit();   //不是查询,修改,删除都要用到提交*/

//删除
           String jpql="delete User u where u.id=?1";
           Query query = entityManager.createQuery(jpql);
           query.setParameter(1, 5);
           query.executeUpdate();
           transaction.commit();

缓存

这里用我们用的是jpa的hibernate实现,当然肯定是有一级和二级缓存的

1 一级缓存。

证明有缓存:

               Teacher t1= entityManager.find(Teacher.class, 1);
           Teacher t2=entityManager.find(Teacher.class, 1);
           System.out.println(t1);
           System.out.println(t2);

只发送一条sql语句。

image.png

测试清除缓存:

Teacher t1= entityManager.find(Teacher.class, 1);
           System.out.println(t1);
           transaction.commit();
           entityManager.close();
           entityManager = entityManagerFactory.createEntityManager();
           transaction =entityManager.getTransaction();
           transaction.begin();
           Teacher t2=entityManager.find(Teacher.class, 1);
            System.out.println(t2);
image.png

二级缓存。

ehcache作为二级缓存,配置步骤:
①配置pom.xml,导入jar包


    org.hibernate
   hibernate-ehcache
   5.2.17.Final

②在jpa的核心配置文件中,配置开启二级缓存,和hibernate课程中讲的配置一样
开启二级缓存


配置二级缓存技术提供者


开启查询缓存


③把ehcache的配置文件 ehcache.xml 加进来





 

④配置文件中properties标签前配置缓存策略和在实体类上加开启二级缓存的注解@Cacheable(true):

ENABLE_SELECTIVE

相当于前面讲的配置:

shared-cached-mode这个标签有5个值:
a)ALL:缓存所有实体类
b)NONE:所有实体类都不缓存
c) ENABLE-SELECTED:有@cacheable(true)的实体类被缓存
d)DISABLE-SELECTED:除了有 @cacheable(false)这个注解外的所有实体类
e)UNSPECIFIED:默认值,jpa的实现的默认值
⑤,如果查询关联对象,则又要开启查询缓存。

image.png

举例:
本例模型类是Teacher.java在 cn.ybzy.japdemo.model包中。

public void test(){
String jpql="select t from Teacher t where t.id=?1";
        Query query=entityManager.createQuery(jpql);
        query.setHint(QueryHints.HINT_CACHEABLE, true);  //query中开启查询缓存
        query.setParameter(1, 1);
        Teacher t1 = (Teacher) query.getSingleResult();
        Teacher t2 = (Teacher) query.getSingleResult();
        System.out.println(t1);
        System.out.println(t2);
               transcation.commit();
              entityManager.close();
        entityManagerFactory.close();
}

jpa和spring的整合

image.png

--------jpa_spring整合 和 hibernate----spring的整合非常相似,基本思路都一样,整合两个方面,①将jpa的事务交给spring的声明式事务来管理。②将jpa的EntityMangerFactory注入到spring的ioc容器里来管理:
步骤

  1. 导入sping,jpa相关的jar包:


org.springframework
spring-context
5.0.6.RELEASE


org.springframework
spring-aspects
5.0.6.RELEASE


org.springframework
spring-orm
5.0.6.RELEASE


log4j
log4j
1.2.12


commons-logging
commons-logging
1.2


org.hibernate
hibernate-entitymanager
5.2.17.Final


mysql
mysql-connector-java
5.1.46  


 org.hibernate
hibernate-ehcache
5.2.17.Final


c3p0
c3p0
0.9.1.1


junit
junit
4.12


  1. 加入spring的配置文件,jdbc和log4j的properties配置文件
    jdbc.properties:
jdbc.user=root
jdbc.password=root
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.jdbcUrl=jdbc:mysql://localhost:3306/springmvc
jdbc.acquireIncrement=10
jdbc.initialPoolSize=30
jdbc.minPoolSize=5
jdbc.maxPoolSize=40
jdbc.maxStatements=1000
jdbc.maxStatementsPerConnection=100

log4j.properties:

log4j.rootLogger = debug,stdout, D
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.Threshold = INFO
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p %m%n
log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File = ./log4j.log
log4j.appender.D.Append = true
log4j.appender.D.Threshold = DEBUG
log4j.appender.D.layout = org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern=%d %p %m%n

Spring的配置 spring.xml:




    
    

    
    
    
    
        
        
        
        
        
        
        
        
        
        
    


    
    
    
        
        
        
            
        
        
        
        
        
        
        
            
                true
                true
                update
            
        
    
    
    
    
        
    
    
    
    
        
            
            
            
            
            
        
    
    
    
    
        
        
    



  1. 其它业务代码:
    Customer.java 模型类
@Entity   //指定为映射关系中的实体
@Table(name="t_customers")   //实体映射成表t_customers
public class Customer {
    private int id;
    private String username;
    private double balance;
    @Id            //一定有一个主键
    @GeneratedValue(strategy=GenerationType.IDENTITY)  //自增策略
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public double getBalance() {
        return balance;
    }
    public void setBalance(double balance) {
        this.balance = balance;
    }
    public Customer() {
        super();
    }
    @Override
    public String toString() {
        return "Customer [id=" + id + ", username=" + username + ", balance=" + balance + "]";
    }
    

}

DAO:

import cn.ybzy.jpaspringdemo.model.Customer;

//不写接口了,这里只是测试用
@Repository("customerDao")
public class CustomerDao {
    //@Autowired   不能用这个注解自动装备jpa(类似hibernate会话工厂类)实体管理器
    //private EntityManagerFactory entityManagerFactory;
    @PersistenceContext  //相当天hibernate里getCurrentsession方法,获取与当前线程绑定的会话
    private EntityManager entityManagerFactory1;
    
    public void add(Customer customer) {
        entityManagerFactory1.persist(customer);  //添加记录
    }
}

service:


//这里测试,不建接口类
@Service("customerService")
public class CustomerService {
    @Autowired
    private CustomerDao customerDao;
    //由于加了事务,两个记录,要么全加,要么全不加
    public void add(Customer c1,Customer c2) {
        customerDao.add(c1);
        //double i = 1/0;    //可以加这条语句,看看事务有没有作用
        customerDao.add(c2);
    }

}

测试:我把CustomerController.java当作有main方法测试类。

public class CustomerController {
    public static void main(String args[]) {
        @SuppressWarnings("resource")
        ApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
        CustomerService customerService = (CustomerService) context.getBean("customerService");
        Customer c1= new Customer();
        c1.setUsername("熊少文");
        Customer c2 = new Customer();
        c2.setUsername("徐会凤");
        customerService.add(c1, c2);
    }

}

RUN AS ----JAVAAPPLICATION:
因有了事务切入,所有,两条记录,要么全加进表中,要么全不加进表中。
可以在CustomerDao类中加
double i=10/0;
让其产生异常,不会加记录进表。

你可能感兴趣的:(JPA)