
1.Java Persistence API:Java对象持久化API
2.JDK5.0 平台的标准 ORM 规范,可以让 Java 程序用统一方式访问持久层

3.JPA 和 Hibernate 的关系
5.要使用到 JPA 的哪些技术
<properties>
<spring.version>5.2.6.RELEASEspring.version>
<hibernate.version>5.4.10.Finalhibernate.version>
<mysql.version>8.0.21mysql.version>
<ehcache.version>3.8.1ehcache.version>
<jpa.version>1.0.1.Finaljpa.version>
<slf4j.version>1.7.25slf4j.version>
<aspectj.version>1.9.5aspectj.version>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
<maven.compiler.source>1.8maven.compiler.source>
<maven.compiler.target>1.8maven.compiler.target>
properties>
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-beansartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-coreartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-jdbcartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aopartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-txartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-ormartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.hibernategroupId>
<artifactId>hibernate-coreartifactId>
<version>${hibernate.version}version>
dependency>
<dependency>
<groupId>org.hibernategroupId>
<artifactId>hibernate-entitymanagerartifactId>
<version>${hibernate.version}version>
dependency>
<dependency>
<groupId>org.hibernategroupId>
<artifactId>hibernate-hikaricpartifactId>
<version>${hibernate.version}version>
dependency>
<dependency>
<groupId>org.hibernategroupId>
<artifactId>hibernate-ehcacheartifactId>
<version>${hibernate.version}version>
dependency>
<dependency>
<groupId>org.hibernate.javax.persistencegroupId>
<artifactId>hibernate-jpa-2.0-apiartifactId>
<version>${jpa.version}version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>${mysql.version}version>
dependency>
<dependency>
<groupId>org.ehcachegroupId>
<artifactId>ehcacheartifactId>
<version>${ehcache.version}version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
<version>${slf4j.version}version>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>${aspectj.version}version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.12version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
dependencies>
创建 com.javakc.jpa 包
创建 entity 包并在其目录下创建实体类
创建 dao 包并在其目录下创建数据层实现类
在 resources 目录下创建 jdbc.properties 配置文件
jdbc.driverClass=com.mysql.cj.jdbc.Driver
jdbc.jdbcUrl=jdbc:mysql:///jpa?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
jdbc.user=root
jdbc.password=123456
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<context:component-scan base-package="com.javakc.jpa">context:component-scan>
<context:property-placeholder location="jdbc.properties">context:property-placeholder>
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
<property name="driverClassName" value="${jdbc.driverClass}">property>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}">property>
<property name="username" value="${jdbc.user}">property>
<property name="password" value="${jdbc.password}">property>
bean>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource">property>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">bean>
property>
<property name="packagesToScan" value="com.javakc.jpa.entity">property>
<property name="jpaProperties">
<props>
<prop key="hibernate.show_sql">trueprop>
<prop key="hibernate.format_sql">trueprop>
<prop key="hibernate.hbm2ddl.auto">updateprop>
props>
property>
bean>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory">property>
bean>
<tx:annotation-driven transaction-manager="transactionManager">tx:annotation-driven>
beans>
import com.javakc.jpa.dao.JpaDao;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring-jpa.xml"})
public class JpaTest {
@Autowired
private JpaDao jpaDao;
@Test
public void test() {
}
}
标注用于实体类声明语句之前,指出该Java 类为实体类,将映射到指定的数据库表
当实体类与其映射的数据库表名不同名时需要使用 @Table 标注说明,该标注与 @Entity
标注并列使用,置于实体类声明语句之前,可写于单独语句行,也可与声明语句同行
@Table 标注的常用选项是 name,用于指明数据库的表名
@Table标注还有一个两个选项 catalog 和 schema
用于设置表所属的数据库目录或模式,通常为数据库名。uniqueConstraints 选项用于设置约束条件,通常不须设置
标注用于声明一个实体类的属性映射为数据库的主键列。该属性通常置于属性声明语句之前
用于标注主键的生成策略,通过 strategy 属性指定。默认情况下,JPA 自动选择一个最适合底层数据库的主键生成策略
在 javax.persistence.GenerationType 中定义了以下几种可供选择的策略:
当实体的属性与其映射的数据库表的列不同名时需要使用@Column 标注说明,该属性通常置于实体的属性声明语句之前,还可与 @Id
标注一起使用
常用属性是 name,用于设置映射数据库表的列名。此外,该标注还包含其它多个属性,如:unique 、nullable、length 等
表示该属性并非一个到数据库表的字段的映射,ORM框架将忽略该属性
如果一个属性并非数据库表的字段映射,就务必将其标示为@Transient,否则,ORM框架默认其注解为@Basic
在核心的 Java API 中并没有定义 Date 类型的精度(temporal precision). 而在数据库中,表示 Date
类型的数据有 DATE, TIME, 和 TIMESTAMP 三种精度(即单纯的日期,时间,或者两者 兼备). 在进行属性映射时可使用
@Temporal 注解来调整精度
临时状态: 新创建的对象,尚未拥有持久性主键,不处于缓存中,数据库中也没有对应的记录
持久化状态:已经拥有持久性主键,位于缓存中,数据库中有对应记录
游离状态:拥有持久化主键,不处于缓存中,数据库中可能存在对应的记录
删除状态: 曾经位于缓存中,曾在数据库中有过记录,但现在已被删除
用于将新创建的 Entity 纳入到 EntityManager 的管理。该方法执行后,传入 persist() 方法的 Entity 对象转换成持久化状态
如果传入 persist() 方法的 Entity 对象已经处于持久化状态,则 persist() 方法什么都不做
如果对删除状态的 Entity 进行 persist() 操作,会转换为持久化状态
如果对游离状态的实体执行 persist() 操作,可能会在 persist() 方法抛出 EntityExistException(也有可能是在flush或事务提交后抛出)
public void persist() {
Student student = new Student();
student.setStudentName("AA");
student.setBirthday(new Date());
student.setCreateDate(new Date());
entityManager.persist(student);
System.out.println(student.getId());
}
public void find() {
Student student = entityManager.find(Student.class, 1);
Student student2 = entityManager.find(Student.class, 1);
System.out.println("-----------");
System.out.println(student);
System.out.println(student2);
}
public void getReference() {
Student student = entityManager.getReference(Student.class, 1);
System.out.println("-----------");
System.out.println(student);
}
public void remove() {
Student student = entityManager.find(Student.class, 1);
entityManager.remove(student);
}
public void merge1() {
Student student = new Student();
student.setStudentName("AA");
student.setBirthday(new Date());
student.setCreateDate(new Date());
Student student2 = entityManager.merge(student);
System.out.println("student#id=" + student.getId());
System.out.println("student2#id=" + student2.getId());
}
public void merge2() {
Student student = new Student();
student.setStudentName("AA");
student.setBirthday(new Date());
student.setCreateDate(new Date());
student.setId(100);
Student student2 = entityManager.merge(student);
System.out.println("student#id=" + student.getId());
System.out.println("student2#id=" + student2.getId());
}
public void merge3() {
Student student = new Student();
student.setStudentName("BB");
student.setBirthday(new Date());
student.setCreateDate(new Date());
student.setId(1);
entityManager.merge(student);
}
在双向的一对一关联中,需要在不维护关系端中的 @OneToOne 注解中指定 mappedBy,以指定是这一关联中的被维护端。同时需要在关系维护端建立外键列指向关系被维护端的主键列
设置关系
// ## 使用 @OneToOne 来映射 1-1 关联关系
// ## 在当前表中维护关系需要使用 @JoinColumn 来进行映射, 在 1-1 关联关系中需要添加 unique=true
@OneToOne
@JoinColumn(name = "student_id", unique = true)
private Student student;
// ## 当前表不维护关联关系,没有外键,使用 @OneToOne 来进行映射, 需要设置 mappedBy="student",如果两边都维护关联关系则会多出无用sql语句
@OneToOne(mappedBy = "student")
private Card card;
测试
保存
/**
* 双向 1-1 的关联关系,建议先去保存不维护外键的一方,这样不会多出 update 语句
*/
public void oneToOnePersist() {
Student student = new Student();
student.setStudentName("BB");
Card card = new Card();
card.setCardNum("110");
// ## 设置关联关系
student.setCard(card);
card.setStudent(student);
// ## 保存
entityManager.persist(student);
entityManager.persist(card);
}
/**
* 获取维护外键的一方,默认会使用左外连接获取其关联的对象
* 可通过 @OneToOne 的 fetch 属性来修改加载策略(@OneToOne(fetch = FetchType.LAZY))
*/
public void oneToOneFind() {
Card card = entityManager.find(Card.class, 1);
System.out.println(card.getCardNum());
System.out.println(card.getStudent().getClass().getName());
}
/**
* 获取不维护外键的一方,默认会使用左外连接获取其关联的对象
* 可通过 @OneToOne 的 fetch 属性来修改加载策略,但依然会查询另一方,来初始化关联对象
* 所以不建议修改加载策略
*/
public void oneToOneFind2() {
Student student = entityManager.find(Student.class, 10);
System.out.println(student.getStudentName());
System.out.println(student.getCard().getClass().getName());
}
单向一对多关系中,在 1 端设置 @OneToMany 注解,并使用 @JoinColumn 指定外键列的名称
// ## 使用 @OneToMany 映射单向 1-n 关联关系
// ## 使用 @JoinColumn 映射外键列的名称
@OneToMany
@JoinColumn(name = "classroom_id")
private List<Student> studentList = new ArrayList<>();
测试
保存
/**
* 单向 1-n 关联关系保存时一定会多出 update 语句
* 因为 n 端在插入数据时不会同时插入外键列需要用修改来补充外键值
*/
public void oneToManyPersist() {
ClassRoom classRoom = new ClassRoom();
classRoom.setClassRoomName("javakc80");
Student student1 = new Student();
student1.setStudentName("CC");
student1.setBirthday(new Date());
student1.setCreateDate(new Date());
Student student2 = new Student();
student2.setStudentName("DD");
student2.setBirthday(new Date());
student2.setCreateDate(new Date());
// ## 设置关联关系
classRoom.getStudentList().add(student1);
classRoom.getStudentList().add(student2);
// ## 保存
entityManager.persist(classRoom);
entityManager.persist(student1);
entityManager.persist(student2);
}
/**
* 默认为关联的 n 端使用懒加载策略
* 可通过 @OneToMany 的 fetch 属性来修改加载策略关闭懒加载
*/
public void oneToManyFind() {
ClassRoom classRoom = entityManager.find(ClassRoom.class, 1);
System.out.println(classRoom.getClassRoomName());
System.out.println(classRoom.getStudentList().size());
}
/**
* 默认删除 1 端的数据前,先把关联的 n 端的外键置空,再删除 1 端数据
* 可以通过 @OneToMany 的 cascade 属性来修改默认的删除策略
*/
public void oneToManyRemove() {
ClassRoom classRoom = entityManager.find(ClassRoom.class, 1);
entityManager.remove(classRoom);
}
public void oneToManyUpdate() {
ClassRoom classRoom = entityManager.find(ClassRoom.class, 1);
classRoom.getStudentList().get(0).setStudentName("FFF");
}
单向多对一关系中,在 n 端设置 @ManyToOne 注解,并使用 @JoinColumn 指定外键名称
// ## 使用 @ManyToOne 映射单向 n-1 关联关系
// ## 使用 @JoinColumn 映射外键
@ManyToOne
@JoinColumn(name = "classroom_id")
private ClassRoom classRoom;
测试
保存
/**
* 先保存 1 的一端,后保存 n 的一端,这样不会多出额外的 update 语句
*/
public void manyToOnePersist() {
ClassRoom classRoom = new ClassRoom();
classRoom.setClassRoomName("javakc80");
Student student1 = new Student();
student1.setStudentName("CC");
student1.setBirthday(new Date());
student1.setCreateDate(new Date());
Student student2 = new Student();
student2.setStudentName("DD");
student2.setBirthday(new Date());
student2.setCreateDate(new Date());
// ## 设置关联关系
student1.setClassRoom(classRoom);
student2.setClassRoom(classRoom);
// ## 保存
entityManager.persist(classRoom);
entityManager.persist(student1);
entityManager.persist(student2);
}
/**
* 使用左外连接的方式获取 n 端的对象和其关联的 1 端的对象数据
* 可通过 @ManyToOne 的 fetch 属性来修改加载策略
*/
public void manyToOneFind() {
Student student = entityManager.find(Student.class, 1);
System.out.println(student.getStudentName());
System.out.println(student.getClassRoom().getClassRoomName());
}
public void manyToOneRemove1() {
Student student = entityManager.find(Student.class, 1);
// ## 删除 n 端
entityManager.remove(student);
}
public void manyToOneRemove2() {
ClassRoom classRoom = entityManager.find(ClassRoom.class, 1);
// ## 删除 1 端
entityManager.remove(classRoom);
}
public void manyToOneUpdate() {
Student student = entityManager.find(Student.class, 1);
student.getClassRoom().setClassRoomName("javakc");
}
双向关系中,必须存在一个关系维护端,在 JPA 规范中,要求 many 的一方作为关系的维护端, one 的一端不维护关系。 可以在 one 方指定 @OneToMany 注解,并设置 mappedBy 属性,以指定它这一端不维护关联关系,many 为维护端。 在 many 方指定 @ManyToOne 注解,并使用 @JoinColumn 指定外键名称
// ## 使用 @ManyToOne 映射 n-1 关联关系
// ## 使用 @JoinColumn 映射外键
@ManyToOne
@JoinColumn(name = "classroom_id")
private ClassRoom classRoom;
// ## 使用 @OneToMany 映射 1-n 关联关系
// ## 使用 @JoinColumn 映射外键列的名称
// ## 在 1 端的 @OneToMany 中使用 mappedBy 属性放弃维护关系, 就无需再使用 @JoinColumn 注解
@OneToMany(mappedBy = "classRoom")
private List<Student> studentList = new ArrayList<>();
/**
* 先保存 1 的一端,后保存 n 的一端,这样不会多出额外的 update 语句
* 在双向关系下,保存 1 端会出现额外的 update 语句
* 建议使用 n 端来维护关联关系,1 端不维护关联关系, 这样就会减少 update 语句
* 注意: 在 1 端的 @OneToMany 中使用 mappedBy 属性, 就无需再使用 @JoinColumn 注解
*/
public void manyToOnePersist() {
ClassRoom classRoom = new ClassRoom();
classRoom.setClassRoomName("javakc80");
Student student1 = new Student();
student1.setStudentName("CC");
student1.setBirthday(new Date());
student1.setCreateDate(new Date());
Student student2 = new Student();
student2.setStudentName("DD");
student2.setBirthday(new Date());
student2.setCreateDate(new Date());
// ## 设置关联关系
student1.setClassRoom(classRoom);
student2.setClassRoom(classRoom);
classRoom.getStudentList().add(student1);
classRoom.getStudentList().add(student2);
// ## 保存
entityManager.persist(classRoom);
entityManager.persist(student1);
entityManager.persist(student2);
}
在双向多对多关系中,我们必须指定一个关系维护端,可以通过 @ManyToMany 注解中指定 mappedBy 属性来标识放弃关系维护
// ## 使用 @ManyToMany 映射 n-n 关联关系
// ## @JoinTable(name = "中间表名",
// ## joinColumns = @JoinColumn(name = "本类的外键"),
// ## inverseJoinColumns = @JoinColumn(name = "对方类的外键"))
@ManyToMany
@JoinTable(name = "jpa_course_student",
joinColumns = @JoinColumn(name = "course_id"),
inverseJoinColumns = @JoinColumn(name = "student_id"))
private List<Student> studentList = new ArrayList<>();
// ## 使用 @ManyToMany 映射 n-n 关联关系
// ## 使用 mappedBy 属性放弃维护关系
@ManyToMany(mappedBy = "studentList")
private List<Course> courseList = new ArrayList<>();
测试
保存
public void manyToManyPersist() {
Course course1 = new Course();
course1.setCourseName("钢琴");
Course course2 = new Course();
course2.setCourseName("美术");
Student student1 = new Student();
student1.setStudentName("小明");
Student student2 = new Student();
student2.setStudentName("小红");
// ## 设置关联关系
course1.getStudentList().add(student1);
course1.getStudentList().add(student2);
course2.getStudentList().add(student1);
course2.getStudentList().add(student2);
student1.getCourseList().add(course1);
student1.getCourseList().add(course2);
student2.getCourseList().add(course1);
student2.getCourseList().add(course2);
// ## 保存
entityManager.persist(course1);
entityManager.persist(course2);
entityManager.persist(student1);
entityManager.persist(student2);
}
public void manyToManyFind() {
Course course = entityManager.find(Course.class, 1);
System.out.println(course.getCourseName());
System.out.println(course.getStudentList().size());
}
JPQL语言,即 Java Persistence Query Language 的简称。JPQL 是一种和 SQL 非常类似的中间性和对象化查询语言,它最终会被编译成针对不同底层数据库的 SQL 查询,从而屏蔽不同数据库的差异
public void query() {
String jpql = "FROM Student";
Query query = entityManager.createQuery(jpql);
List<Student> list = query.getResultList();
System.out.println(list.size());
}
public void nativeQuery() {
String sql = "select * from jpa_student s";
Query query = entityManager.createNativeQuery(sql);
List list = query.getResultList();
System.out.println(list.size());
}
你不一定逆风翻盘,但一定要向阳而生。
山水相逢,我们江湖见。