• 【Java开发实战攻关】「JPA技术专题」带你一同认识和使用JPA框架


    一、JPA 介绍

    1、JDBC

    在这里插入图片描述

    2、JPA是什么

    1.Java Persistence API:Java对象持久化API
    2.JDK5.0 平台的标准 ORM 规范,可以让 Java 程序用统一方式访问持久层
    在这里插入图片描述
    3.JPA 和 Hibernate 的关系

    1. JPA 是 Hibernate 的一个抽象(JDBC Interface 与 JDBC驱动的关系)
    2. JPA 本质上就是一种 ORM 规范,不是 ORM 框架,因为 JPA 并未提供 ORM 实现,它只提供了 API 接口,具体的实现由 ORM 厂商提供实现
    3. Hibernate 是 一个 ORM 框架,同时也是一种 JPA 的实现
    4. Hibernate 从3.2版本开始兼容 JPA
    5. Hibernate 使用起来是XML配置文件的方式,而 JPA 是注解的方式,而注解是JDK5.0中自带的,所 以并不需要再引入第三方Jar包,实际上就是来学习如何使用注解的方式来使用 Hibernate

    5.要使用到 JPA 的哪些技术

    1. ORM 映射元数据:JPA 支持 XML 和 JDK5.0
      注解两种元数据的形式,元数据描述对象和表之间的映射关系,框架将实体对象持久化到数据库表中
    2. JPA 的 API:利用实体对象,操作 JPA 提供的接口进行 CRUD 操作,简化开发者编程代码
    3. 查询语言(JPQL):这是持久化操作中很重要的一个方面,通过面向对象而非面向数据库的查询语言去查询数据

    二、搭建 JPA 环境

    1. 使用Maven构建项目,项目名为:JPA
    2. 配置pom.xml
    <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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    1. 创建 com.javakc.jpa 包

    2. 创建 entity 包并在其目录下创建实体类

    3. 创建 dao 包并在其目录下创建数据层实现类

    4. 在 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
    
    • 1
    • 2
    • 3
    • 4
    1. 在 resources 目录下创建 spring-jpa.xml 配置文件
    
    <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>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    1. 创建测试类
    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() {
        }
     
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    三、JPA 注解

    1. @Entity

    标注用于实体类声明语句之前,指出该Java 类为实体类,将映射到指定的数据库表

    1. @Table

    当实体类与其映射的数据库表名不同名时需要使用 @Table 标注说明,该标注与 @Entity
    标注并列使用,置于实体类声明语句之前,可写于单独语句行,也可与声明语句同行

    @Table 标注的常用选项是 name,用于指明数据库的表名

    @Table标注还有一个两个选项 catalog 和 schema
    用于设置表所属的数据库目录或模式,通常为数据库名。uniqueConstraints 选项用于设置约束条件,通常不须设置

    1. @Id

    标注用于声明一个实体类的属性映射为数据库的主键列。该属性通常置于属性声明语句之前

    1. @GeneratedValue

    用于标注主键的生成策略,通过 strategy 属性指定。默认情况下,JPA 自动选择一个最适合底层数据库的主键生成策略

    在 javax.persistence.GenerationType 中定义了以下几种可供选择的策略:

    • IDENTITY:采用数据库 ID自增长的方式来自增主键字段,Oracle 不支持这种方式
    • AUTO: JPA自动选择合适的策略,是默认选项
    • SEQUENCE:通过序列产生主键,通过 @SequenceGenerator 注解指定序列名,MySql 不支持这种方式
    • TABLE:通过表产生主键,框架借由表模拟序列产生主键,使用该策略可以使应用更易于数据库移植
    1. @Column

    当实体的属性与其映射的数据库表的列不同名时需要使用@Column 标注说明,该属性通常置于实体的属性声明语句之前,还可与 @Id
    标注一起使用

    常用属性是 name,用于设置映射数据库表的列名。此外,该标注还包含其它多个属性,如:unique 、nullable、length 等

    1. @Transient

    表示该属性并非一个到数据库表的字段的映射,ORM框架将忽略该属性

    如果一个属性并非数据库表的字段映射,就务必将其标示为@Transient,否则,ORM框架默认其注解为@Basic

    1. @Temporal

    在核心的 Java API 中并没有定义 Date 类型的精度(temporal precision). 而在数据库中,表示 Date
    类型的数据有 DATE, TIME, 和 TIMESTAMP 三种精度(即单纯的日期,时间,或者两者 兼备). 在进行属性映射时可使用
    @Temporal 注解来调整精度

    四、JPA API

    1、缓存

    • 一级缓存:会话级别,对同一个id进行两次加载,不会发送两条sql给数据库,但会话关闭,一级缓存就会失效
    • 二级缓存:全局级别,一级缓存会话关闭,缓存也不会失效

    2、EntityManager

    1. 在 JPA 规范中, EntityManager 是完成持久化操作的核心对象。实体作为普通 Java 对象,只有在调用 EntityManager 将其持久化后才会变成持久化对象。EntityManager 对象在一组实体类与底层数据源之间进行 O/R 映射的管理。它可以用来管理和更新 Entity Bean, 根椐主键查找 Entity Bean, 还可以通过JPQL语句查询实体。
    2. 实体的状态:

    临时状态: 新创建的对象,尚未拥有持久性主键,不处于缓存中,数据库中也没有对应的记录

    持久化状态:已经拥有持久性主键,位于缓存中,数据库中有对应记录

    游离状态:拥有持久化主键,不处于缓存中,数据库中可能存在对应的记录

    删除状态: 曾经位于缓存中,曾在数据库中有过记录,但现在已被删除

    3、API

    1. persist:
    • 用于将新创建的 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());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    1. find:
    • 返回指定的 OID 对应的实体类对象,如果这个实体存在于当前的持久化环境,则返回一个被缓存的对象;否则会创建一个新的 Entity, 并加载数据库中相关信息;若 OID 不存在于数据库中,则返回一个 null。第一个参数为被查询的实体类类型,第二个参数为待查找实体的主键值
    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1. getReference:
    • 与find()方法类似,不同的是:如果缓存中不存在指定的 Entity, EntityManager 会创建一个 Entity 类的代理,但是不会立即加载数据库中的信息,只有第一次真正使用此 Entity 的属性才加载,所以如果此 OID 在数据库不存在,getReference() 不会返回 null 值, 而是抛出EntityNotFoundException
    public void getReference() {
        Student student = entityManager.getReference(Student.class, 1);
        System.out.println("-----------");
        System.out.println(student);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. remove:
    • 删除实例。如果实例是被管理的,即与数据库实体记录关联,则同时会删除关联的数据库记录
    public void remove() {
        Student student = entityManager.find(Student.class, 1);
        entityManager.remove(student);
    }
    
    • 1
    • 2
    • 3
    • 4
    1. merge:merge() 用于处理 Entity 的同步。即数据库的插入和更新操作
    • 创建一个新的对象,把临时对象的属性复制到新的对象中,然后对新的对象执行持久化操作。所以新的对象中有id,但以前的临时对象中没有id
    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());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 传入一个游离对象, 即传入的对象有 OID,如果在 EntityManager 缓存中没有该对象,如果在数据库中没有对应的记录,JPA 会创建一个新的对象, 然后把当前游离对象的属性复制到新创建的对象中,对新创建的对象执行 insert 操作
    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());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 传入一个游离对象, 即传入的对象有 OID,如果在 EntityManager 缓存中没有该对象,如果在数据库中有对应的记录,JPA 查询到对应的记录, 返回查询对象, 再然后会把游离对象的属性复制到查询到的对象中,对查询到的对象执行 update 操作
    public void merge3() {
        Student student = new Student();
        student.setStudentName("BB");
        student.setBirthday(new Date());
        student.setCreateDate(new Date());
        student.setId(1);
        entityManager.merge(student);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    五、关联关系映射

    1、一对一映射

    在双向的一对一关联中,需要在不维护关系端中的 @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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    1. 测试

    2. 保存

    /**
     * 双向 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);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    1. 获取1
    /**
     * 获取维护外键的一方,默认会使用左外连接获取其关联的对象
     * 可通过 @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());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    1. 获取2
    /**
     * 获取不维护外键的一方,默认会使用左外连接获取其关联的对象
     * 可通过 @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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2、单向一对多

    单向一对多关系中,在 1 端设置 @OneToMany 注解,并使用 @JoinColumn 指定外键列的名称

    1. 设置关系
    // ## 使用 @OneToMany 映射单向 1-n 关联关系
    // ## 使用 @JoinColumn 映射外键列的名称
    @OneToMany
    @JoinColumn(name = "classroom_id")
    private List<Student> studentList = new ArrayList<>();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. 测试

    2. 保存

    /**
     * 单向 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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    1. 获取
    /**
     * 默认为关联的 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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    1. 删除
    /**
     * 默认删除 1 端的数据前,先把关联的 n 端的外键置空,再删除 1 端数据
     * 可以通过 @OneToMany 的 cascade 属性来修改默认的删除策略
     */
    public void oneToManyRemove() {
        ClassRoom classRoom = entityManager.find(ClassRoom.class, 1);
        entityManager.remove(classRoom);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    1. 修改
    public void oneToManyUpdate() {
        ClassRoom classRoom = entityManager.find(ClassRoom.class, 1);
        classRoom.getStudentList().get(0).setStudentName("FFF");
    }
    
    • 1
    • 2
    • 3
    • 4

    3、单向多对一

    单向多对一关系中,在 n 端设置 @ManyToOne 注解,并使用 @JoinColumn 指定外键名称

    1. 设置关系
    // ## 使用 @ManyToOne 映射单向 n-1 关联关系
    // ## 使用 @JoinColumn 映射外键
    @ManyToOne
    @JoinColumn(name = "classroom_id")
    private ClassRoom classRoom;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. 测试

    2. 保存

    /**
     * 先保存 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);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    1. 获取
    /**
     * 使用左外连接的方式获取 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());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    1. 删除1
    public void manyToOneRemove1() {
        Student student = entityManager.find(Student.class, 1);
        // ## 删除 n 端
        entityManager.remove(student);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. 删除2
    public void manyToOneRemove2() {
        ClassRoom classRoom = entityManager.find(ClassRoom.class, 1);
        // ## 删除 1 端
        entityManager.remove(classRoom);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. 修改
    public void manyToOneUpdate() {
        Student student = entityManager.find(Student.class, 1);
        student.getClassRoom().setClassRoomName("javakc");
    }
    
    • 1
    • 2
    • 3
    • 4

    4、双向一对多及多对一

    双向关系中,必须存在一个关系维护端,在 JPA 规范中,要求 many 的一方作为关系的维护端, one 的一端不维护关系。 可以在 one 方指定 @OneToMany 注解,并设置 mappedBy 属性,以指定它这一端不维护关联关系,many 为维护端。 在 many 方指定 @ManyToOne 注解,并使用 @JoinColumn 指定外键名称

    1. 设置关系
    // ## 使用 @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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    1. 测试
    2. 保存
    /**
     * 先保存 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);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    5、双向多对多

    在双向多对多关系中,我们必须指定一个关系维护端,可以通过 @ManyToMany 注解中指定 mappedBy 属性来标识放弃关系维护

    1. 设置关系
    // ## 使用 @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<>();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    1. 测试

    2. 保存

    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);
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    1. 获取
    public void manyToManyFind() {
        Course course = entityManager.find(Course.class, 1);
        System.out.println(course.getCourseName());
        System.out.println(course.getStudentList().size());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    六、JPQL

    JPQL语言,即 Java Persistence Query Language 的简称。JPQL 是一种和 SQL 非常类似的中间性和对象化查询语言,它最终会被编译成针对不同底层数据库的 SQL 查询,从而屏蔽不同数据库的差异

    1、createQuery

    public void query() {
        String jpql = "FROM Student";
        Query query = entityManager.createQuery(jpql);
        List<Student> list = query.getResultList();
    
        System.out.println(list.size());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2、createNativeQuery

    public void nativeQuery() {
        String sql = "select * from jpa_student s";
        Query query = entityManager.createNativeQuery(sql);
        List list = query.getResultList();
        System.out.println(list.size());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    最后送所有正在努力的大家一句话:

    你不一定逆风翻盘,但一定要向阳而生。

    期待下次发布好的文章:

    山水相逢,我们江湖见。

  • 相关阅读:
    品牌发力 · 韧性增长 | 数说故事亮相第30届中国国际广告节
    Go编程:使用 Colly 库下载Reddit网站的图像
    【云原生 | Kubernetes 系列】--Gitops持续交付 CD Push Pipeline实现
    Java8 中新增的 Stream 流操作
    京东云开发者|京东云RDS数据迁移常见场景攻略
    希望计算机专业同学都知道这些老师
    JVM高频面试题
    苹果手机H5 video标签播放视频问题以及.mov格式处理方案
    RFID固定资产盘点系统给企业带来哪些便利?
    luffy-(9)
  • 原文地址:https://blog.csdn.net/heima005/article/details/133988848