JPA学习总结

一.JPA:是一个ORM规范

JPA:JPA是基于JDBC的封装,是Sun公司推出的接口规范,交给其他厂商去实 现,实现得最完整的是Hibernate(创始人:Gavin King)
ORM:对象关系映射
Hibernate是一个ORM框架(实现了ORM规范)

  • JPA(Hibernate)和JDBC区别:
    • JDBC更偏底层,开发效率低,运行效率高,但是要求你是一个SQL高手
    • JPA(Hibernate)开发效率高,性能低(不好控制),兼容所有数据,好的缓存机制
    • JPA是完全基于面向对象方式来操作数据库【核心就是domain实体类+注解】

二.2.JPA入门案例

重点

  • 1.导入jar包【maven】(hibernate-core,entityManager,mysql,junit)
 <!-- 导入Hibernate核心包 -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>4.3.8.Final</version>
        </dependency>

        <!-- 导入Hibernate对JPA的实现包 -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>4.3.8.Final</version>
        </dependency>

        <!-- 导入mysql的驱动包 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.13</version>
        </dependency>

        <!-- 导入junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
  • 2.配置文件
    • \META-INF\persistence.xml
      配置一个持久化单元(连接数据库的信息,方言,建表策略,显示SQL)
      persistence.xml:必须放在src/main/resource/META-INF之下
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">


    <!--
    persistence-unit  持久单元
        name属性可以随便写,遵守命名规范
            【当前xml文件中可以写很多个persistence-unit,但是每个持久单元的name属性值不能重复】
        transaction-type 事务类型
            RESOURCE_LOCAL 本地数据库默认的事务隔离级别
            JTA 分布式事务
    -->
    <persistence-unit name="cn.itsource.jpa01" transaction-type="RESOURCE_LOCAL">
        <properties>
            <!-- JDBC访问数据库必须那4个基本配置 -->
            <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver" />
            <property name="hibernate.connection.url" value="jdbc:mysql:///java0307" />
            <property name="hibernate.connection.username" value="root" />
            <property name="hibernate.connection.password" value="root" />

            <!-- 使用JPA必须配置方言【Hibernate是依靠方言包来实现跨数据库产品】 -->
            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect" />

            <!-- 下面的是可选配置 -->
            <!-- 是否自动生成表 -->
            <property name="hibernate.hbm2ddl.auto" value="update" />
            <!-- 是否显示sql -->
            <property name="hibernate.show_sql" value="true" />
            <!-- 格式化sql -->
            <property name="hibernate.format_sql" value="true" />

        </properties>
    </persistence-unit>

</persistence>

  • 3.准备Domain
    @Entity
    @Table(name=“xx”)
    //一个类必需要加主键
    @Id
    @GeneratedValue
    private Long id;

domain

package cn.itsource.domain;

import javax.persistence.*;
import java.math.BigDecimal;
import java.util.Date;

@Entity//表示当前实体类交给JPA来管理,默认是以当前类名称来作为表名称
@Table(name="tb_user")// 表示指定表名称,如果不指定就默认使用类名称作为表名称
public class User {

    /**
     * @GeneratedValue 表示主键生成策略
     *  GenerationType.AUTO  表示自动【默认】  Hibernate会自动检测你配置的数据库产品类型,自动选择适合该数据库产品的主键生成策略
     *  GenerationType.IDENTITY 表示自动递增  mysql sqlServer DB2  SQLITE Sysbase
     *  GenerationType.SEQUENCE 表示序列  DB2 Oracle
     *  GenerationType.TABLE    所有数据库支持这种,性能低
     */
    @Id//表示主键
    @GeneratedValue
    private Long id; //要求:主键,使用数据库的自动生成策略

    @Column(name="name",length=20,nullable=false,unique=true)
    private String name;  //要求:varchar(20),数据库名称为username,不能为空,唯一

    private String password;

    /**
     * insertable 插入数据的时候是否允许插入这个列的值,默认是true
     * updatable  修改数据的时候是否允许修改这个列的值,默认是true
     */
    @Column(name="age",insertable=false)
    private Integer age = 25; //要求:默认值是25,在插入数据时不允许覆盖(添加数据时不操作该字段)

    private Boolean sex;// 数据库没有布尔类型,bit对象

    @Column(name="salary",columnDefinition="decimal(9,2) check(salary>0.0)")
    private BigDecimal salary;// 9,2 工资不能为负数 check(salary>0.0)

    @Column(name="createTime",updatable=false)
    @Temporal(TemporalType.TIMESTAMP)
    private Date createTime;//包含年月日时分秒,不允许修改

    @Temporal(TemporalType.DATE)
    private Date birthday;//包含年月日

    @Temporal(TemporalType.TIME)
    private Date time;//包含时分秒

    @Lob//长字符串
    private String text;//这是一个大文本

    @Transient//临时状态
    private String temp;//这一个字段是临时字段,不需要同步到数据库


    public Long getId() {
        return id;
    }

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

   ......
}

4.代码完成功能
创建工厂
EntityManagerFactory f =Persistence.createEntityManagerFactory();
创建entitymanager对象
EntityManager eg = f.createEntityManager();
增删改需要事务
删除的对象是查出来的(最好判断是否为空)
查询所有(jpql) from Employee

提取工具类
package cn.itsource.utils;

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

public class JpaUtils {

    private JpaUtils(){}


    private static EntityManagerFactory factory;

    static{
        factory = Persistence.createEntityManagerFactory("cn.itsource.jpa01");
    }

    /**
     * 获取EntityManager对象
     * @return
     */
    public static EntityManager getEntityManager(){
        return factory.createEntityManager();
    }

    public static void close(EntityManager entityManager){
        entityManager.close();
    }
    public static void close(){
        factory.close();
    }


}

  • 增删查改
package cn.itsource.jpa02test;

import cn.itsource.domain.Teacher;
import cn.itsource.utils.JpaUtils;
import org.junit.Test;

import javax.persistence.EntityManager;
import java.math.BigDecimal;

public class Jpa02Test {

    @Test
    public void testSave() throws Exception {
        //得到EntityManager对象
        EntityManager entityManager = JpaUtils.getEntityManager();

        try {
            //开启事务
            entityManager.getTransaction().begin();

            //准备数据[临时状态]
            Teacher teacher = new Teacher();
            teacher.setName("小明");
            teacher.setSalary(new BigDecimal(5000.89));

            //保存数据[持久状态]
            entityManager.persist(teacher);


            //提交事务
            entityManager.getTransaction().commit();

            //事务提交后,teacher对象变成游离状态


        } catch (Exception e) {
            e.printStackTrace();
            //事务回滚
            entityManager.getTransaction().rollback();
        } finally {
            //关闭资源
            JpaUtils.close(entityManager);
        }

    }

    @Test
    public void testUpdate() throws Exception {
        //得到EntityManager对象
        EntityManager entityManager = JpaUtils.getEntityManager();

        try {
            //开启事务
            entityManager.getTransaction().begin();

//            //准备数据[临时状态]
//            Teacher teacher = new Teacher();
//            teacher.setName("小明");
//            teacher.setSalary(new BigDecimal(7000.89));
//            teacher.setTid(1L);

            //准备数据:查询【持久状态】
            Teacher teacher = entityManager.find(Teacher.class, 1L);
            teacher.setSalary(new BigDecimal(8000));

            System.out.println("=====================================");

            //保存数据[持久状态]
            //merge 先通过id去查询[持久状态],然后再修改
            entityManager.merge(teacher);//merge修改数据【主键必须有值,主键没有值就是新增数据】


            //提交事务
            entityManager.getTransaction().commit();

            //事务提交后,teacher对象变成游离状态


        } catch (Exception e) {
            e.printStackTrace();
            //事务回滚
            entityManager.getTransaction().rollback();
        } finally {
            //关闭资源
            JpaUtils.close(entityManager);
        }

    }

    /**
     * 持久--》删除--》游离
     * @throws Exception
     */
    @Test
    public void testDelete() throws Exception {
        //得到EntityManager对象
        EntityManager entityManager = JpaUtils.getEntityManager();

        try {
            //开启事务
            entityManager.getTransaction().begin();

            //准备数据:查询【持久状态】
            Teacher teacher = entityManager.find(Teacher.class, 1L);

            System.out.println("=====================================");

            //删除【删除状态】
            entityManager.remove(teacher);


            //提交事务
            entityManager.getTransaction().commit();

            //事务提交后,teacher对象变成游离状态
            System.out.println(teacher);

        } catch (Exception e) {
            e.printStackTrace();
            //事务回滚
            entityManager.getTransaction().rollback();
        } finally {
            //关闭资源
            JpaUtils.close(entityManager);
        }

    }
    

}

补充

  /**
     * 测试单表查询单行数据
     * @throws Exception
     */
    @Test
    public void testFindOne() throws Exception {
        /**
         * 生成实体管理工厂【可以理解成一个数据库连接池对象】
         *  方法传入的参数是persistence.xml文件中的persistence-unit标签的name属性值
         */
        EntityManagerFactory factory = Persistence.createEntityManagerFactory("cn.itsource.jpa01");

        //获取EntityManager对象[可以吧它理解成一个Connection链接对象]
        EntityManager entityManager = factory.createEntityManager();

        //通过id去查询一个对象[持久状态的对象]
        Employee employee = entityManager.find(Employee.class, 2L);
        System.out.println(employee);

        //关闭资源
        entityManager.close();
        factory.close();
    }

    /**
     * 测试单表查询多行数据
     * @throws Exception
     */
    @Test
    public void testFindAll() throws Exception {
        /**
         * 生成实体管理工厂【可以理解成一个数据库连接池对象】
         *  方法传入的参数是persistence.xml文件中的persistence-unit标签的name属性值
         */
        EntityManagerFactory factory = Persistence.createEntityManagerFactory("cn.itsource.jpa01");

        //获取EntityManager对象[可以吧它理解成一个Connection链接对象]
        EntityManager entityManager = factory.createEntityManager();

        //创建执行JPQL语句的工具[select e 表示查询所有字段  相当于select * ]
//        String jpql = "select e from cn.itsource.domain.Employee e";
//        String jpql = "select e.id,e.name from Employee e";
        String jpql = "from Employee";
        Query query = entityManager.createQuery(jpql);
        //查询所有数据
        List<Employee> list = query.getResultList();

        list.forEach(e -> System.out.println(e));

        //关闭资源
        entityManager.close();
        factory.close();
    }


    /**
     * 测试单表查询单行数据[一级缓存命中]
     * @throws Exception
     */
    @Test
    public void testFindOneFirstLevelCache() throws Exception {
        /**
         * 生成实体管理工厂【可以理解成一个数据库连接池对象】
         *  方法传入的参数是persistence.xml文件中的persistence-unit标签的name属性值
         */
        EntityManagerFactory factory = Persistence.createEntityManagerFactory("cn.itsource.jpa01");

        //获取EntityManager对象[可以吧它理解成一个Connection链接对象]
        EntityManager entityManager = factory.createEntityManager();

        //通过id去查询一个对象[持久状态的对象]
        /**
         * 一级缓存[默认开启的]
         *  一级缓存是在entityManager对象身上保存的,entityManager只要未关闭,缓存数据就一直存在,直到程序运行完毕为止
         *  当JPA收到一条查询语句,都会先到缓存【内存】中查找是否有目标数据:
         *      1)有:直接取出并且返回给用户【不再执行SQL语句】 ------ 一级缓存命中
         *      2)没有:发送SQL语句到数据库服务器进行查询,将查询到的数据保存到内存中【键值对】,再将此数据返回用户
         *          当用户或者其他再次查询相同sql的时候,直接从内存中取出就返回。
         */
        for (int i = 0; i < 5; i++) {
            Employee employee1 = entityManager.find(Employee.class, 2L);
            System.out.println(employee1);
        }



        //关闭资源
        entityManager.close();
        factory.close();
    }
  • entityManager的方法改变持久对象的状态
    在这里插入图片描述

三.建表策略

create-drop -> 删 - 建 -删(保存关闭EntityManagerFactory)【一般不用】
create -> 删 -建 【第一次需要建表的时候用】
update -> 修改(如果没有表会创建)【只增不减】update【推荐,有表有数据】
validate -> 验证 【只验证domain中有的数据】

四.四大对象

Persistence:工具类,创建EntityManagerFactory(解析xml)
EntityManagerFactory:【重量级对象】
重(数据库连接池,JPQL,二级缓存,domain关系(对象之间的关系)等)
线程安全 , 1:1:1
创建EntityManager
EntityManager:【轻量级对象】
轻(数据库连接对象,一级缓存)
线程不安全的
一个请求(线程)一个EntityManager
一级缓存:重点
一级缓存默认开启
一级缓存命中:同一个EntityManagerFactory,同一个EntityManager,同一个OID
OID:对象的全限定名#id的值
提高性能、降低数据库服务器的压力
EntityManagerTransaction【事务】
EntityManager拿到的事务是同一个
分布式:JTA

五、主键生成策略

AUTO:自动检测你的数据库产品类型,自动选择对应的主键生成策略
IDENTITY:主键自动递增【mysql、db2、sqlserver】
SEQUENCES:序列【oracle、db2】
TABLE:额外创建一张hibernate_sequences表来保存其他所有表的主键最大值

六.关系

  • 1.domain实体类对象之间的关系
    • 泛化【继承】
    • 实现
    • 组合【整体与部分不可分割】
    • 聚合【整体与部分可以分割】
    • 关联【A类持有一个B类对象作为属性】
    • 依赖【将一个对象作为方法调用时的实参传入】
  • 2.JPA的关联关系重点
  • 多重性
    • 单向多对多、双向多对多
    • 单向一对一、双向一对一
  • 导航性
    • 单向
    • 双向

七、单向多对一

  • 配置@ManyToOne
  • @JoinColumn可以去指定外键列的名称,如果不指定默认使用 字段名称_id
    Transient instance:不能删除临时对象
  • 测试代码
package cn.itsource.jpa02test;

import cn.itsource.domain.Department;
import cn.itsource.domain.Employee;
import cn.itsource.utils.JpaUtils;
import org.junit.Test;

import javax.persistence.EntityManager;
import javax.persistence.Query;
import java.util.List;

public class ManyToOneTest {

    @Test
    public void testSave() throws Exception {
        EntityManager entityManager = JpaUtils.getEntityManager();

        try {
            /**
             * 保存数据
             *  先保存一方,再保存多方【效率更高】(部门:一方,员工:多方)
             *      3条insert语句sql
             *  先保存多方,再保存一方
             *      5条sql  3条insert 2条update
             *      先执行2条 insert into tb_employee(name,deptid) values(?, null)
             *      再执行1条 insert into tb_department(name) values(?)    [才能得到部门的id]
             *      再执行2条 update tb_employee set deptid=? where id=?
             */
            Department d = new Department();
            d.setName("公关部");

            Employee employee01 = new Employee();
            employee01.setName("刘伟2");
            employee01.setDepartment(d);

            Employee employee02 = new Employee();
            employee02.setName("佳佳2");
            employee02.setDepartment(d);

            //开启事务
            entityManager.getTransaction().begin();

            entityManager.persist(employee01);
            entityManager.persist(employee02);
            entityManager.persist(d);

            //提交事务
            entityManager.getTransaction().commit();
        } catch (Exception e) {
            e.printStackTrace();
            //事务回滚
            entityManager.getTransaction().rollback();
        } finally {
            JpaUtils.close(entityManager);
        }
    }


    /**
     * 修改数据[执行的SQL语句有4条]  先查询员工 --》 再查询原部门 --》 再查询新部门 --》 修改
     * @throws Exception
     */
    @Test
    public void testUpdate() throws Exception {
        EntityManager entityManager = JpaUtils.getEntityManager();

        try {
            Department d = new Department();
            d.setName("技术部");
            d.setId(1L);

            Employee employee01 = new Employee();
            employee01.setName("刘伟2");
            employee01.setId(3L);
            employee01.setDepartment(d);

            //开启事务
            entityManager.getTransaction().begin();

            entityManager.merge(employee01);

            //提交事务
            entityManager.getTransaction().commit();
        } catch (Exception e) {
            e.printStackTrace();
            //事务回滚
            entityManager.getTransaction().rollback();
        } finally {
            JpaUtils.close(entityManager);
        }


    }

    /**
     * 删除一方数据【保证多方没有数据关联一方】
     * @throws Exception
     */
    @Test
    public void testDelete() throws Exception {
        EntityManager entityManager = JpaUtils.getEntityManager();

        try {
            Department d = entityManager.find(Department.class, 1L);

            //开启事务
            entityManager.getTransaction().begin();

            entityManager.remove(d);

            //提交事务
            entityManager.getTransaction().commit();
        } catch (Exception e) {
            e.printStackTrace();
            //事务回滚
            entityManager.getTransaction().rollback();
        } finally {
            JpaUtils.close(entityManager);
        }


    }


    /**
     * 查询多方数据,会默认将其关联的一方数据一并查询出来
     *  使用left outer join 左外连接去查询,相同的部门也会查询很多次,有点浪费资源
     *  使用懒加载就可以解决这个问题
     * @throws Exception
     */
    @Test
    public void testFindOne() throws Exception {
        EntityManager entityManager = JpaUtils.getEntityManager();

        Employee employee = entityManager.find(Employee.class, 1L);
        System.out.println(employee);

        System.out.println("-----------------------------");

        System.out.println(employee.getDepartment().getName());

        //关闭资源
        JpaUtils.close(entityManager);

    }


}

  • 异常

    • Transient instance:不能删除临时对象
    • N to N:不能修改持久对象的主键
      在这里插入图片描述在这里插入图片描述
  • 脏数据更新:持久对象不需要调用merge方法直接提交事务就会修改数据

    • 执行流程分析
      第一步:拿到entityManager,开启事务
      第二步:
      通过entityManager拿到一个对象,那么现在这个对象就是持久化的对象
      这个对象会放到一级缓存里面
      JPA会为当前这个对象准备一个快照(把这个对象进行了备份)
      第三步:提交事务
      它会把快照 与 你现在这个对象的数据进行对比
      如果相同,就不需要修改,也不会发送SQL(性能就高了)
      当不相同的时候,JPA就会认为现在这个数据是脏数据
      脏数据它就会在事务提交的时候,把它进行数据库的同步(发送update SQL语句)
/**
     * 测试脏数据更新  merge调用与否都会执行脏数据更新(前提是当前对象时是持久状态)
     * @throws Exception
     */
    @Test
    public void testUpdateZang() throws Exception {
        //得到EntityManager对象
        EntityManager entityManager = JpaUtils.getEntityManager();

        try {
            //开启事务
            entityManager.getTransaction().begin();

            //准备数据:查询【持久状态】
            Teacher teacher = entityManager.find(Teacher.class, 1L);
            teacher.setSalary(new BigDecimal(8000));
            teacher.setName("张三丰");



            //保存数据[持久状态]
            //merge 先通过id去查询[持久状态],然后再修改
            //entityManager.merge(teacher);


            //提交事务
            entityManager.getTransaction().commit();

            //事务提交后,teacher对象变成游离状态


        } catch (Exception e) {
            e.printStackTrace();
            //事务回滚
            entityManager.getTransaction().rollback();
        } finally {
            //关闭资源
            JpaUtils.close(entityManager);
        }

    }
  • 懒加载:配置@ManyToOne(fetch=FetchType.LAZY) 提高性能
    实体类字段:

    @ManyToOne(fetch = FetchType.LAZY)//懒加载
    @JoinColumn(name = "depar_id")
    private Department department;
/**
     * 查询多方数据,会默认将其关联的一方数据一并查询出来
     *  使用left outer join 左外连接去查询,相同的部门也会查询很多次,有点浪费资源
     *  使用懒加载就可以解决这个问题
     * @throws Exception
     */
    @Test
    public void testFindOne() throws Exception {
        EntityManager entityManager = JpaUtils.getEntityManager();

        Employee employee = entityManager.find(Employee.class, 1L);
        System.out.println(employee);

        System.out.println("-----------------------------");

        System.out.println(employee.getDepartment().getName());

        //关闭资源
        JpaUtils.close(entityManager);

    }

在这里插入图片描述

  • 懒加载原理[方法重写+多态] 重点

    • 利用java动态编译技术,编译成class文件
    • 利用类加载器将其加载到内存中,得到一个Class对象
    • 再利用反射技术创建这个对象
    • 而这个子类里面重写父类的所有属性的get方法,内部发送SQL去查询
    • 打印输出懒加载对象的完全限定名的时候,打印结果是…Department_$$_jvst_r68_1
    • 最终返回给调用者的其实是子类对象
  • 异常:no session

八、二级缓存

  • 导入jar包

        <!-- 导入二级缓存jar包【ehcache】 -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-ehcache</artifactId>
            <version>4.3.8.Final</version>
        </dependency>
  • 开启二级缓存
    实体类加注解@Cacheable
import javax.persistence.*;
@Cacheable(true)//二级缓存
@Entity
@Table(name="t_employee")
public class Employee {}
  • 配置文件persistence.xml
 <!-- 启用二级缓存 -->
            <property name="hibernate.cache.use_second_level_cache" value="true" />
            <!-- 配置二级缓存的中间件【类】 -->
            <!-- 二级缓存的实现类,文档里面的包名有错的 -->
            <!-- 错误   org.hibernate.cache.internal.EhCacheRegionFactory -->
            <!-- 正确的 org.hibernate.cache.ehcache.EhCacheRegionFactory -->
            <property name="hibernate.cache.region.factory_class" value="org.hibernate.cache.ehcache.EhCacheRegionFactory" />

            <!-- 启用查询缓存 -->
            <property name="hibernate.cache.use_query_cache" value="true" />
  • 在 < properties > 上面添加配置二级缓存扫描的策略

        <!-- ALL:所有的实体类都被缓存 -->
        <!-- NONE:所有的实体类都不被缓存. -->
        <!-- ENABLE_SELECTIVE:标识 @Cacheable(true) 注解的实体类将被缓存 -->
        <!-- DISABLE_SELECTIVE:缓存除标识 @Cacheable(false) 以外的所有实体类 -->
        <!-- UNSPECIFIED:默认值,JPA 产品默认值将被使用 -->
        <shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>
  • 测试二级缓存命中
    命中条件:同一个EntityManagerFactory,不同的EntityManager,同一个OID


    /**
     * 测试使用二级缓存
     * @throws Exception
     */
    @Test
    public void testUseSecondLevelCache() throws Exception {
        EntityManager entityManager = JpaUtils.getEntityManager();

        /**
         * 第一次查询:
         *  先到一级缓存在查找有没有目标数据
         *      没有
         *  再到二级缓存中查找
         *      没有  发送sql语句去查询  一级缓存中保存  二级缓存中也保存
         */
        Employee employee1 = entityManager.find(Employee.class, 1L);
        System.out.println(employee1);

        /**
         * 第二次查询:
         *  先到一级缓存在查找有没有目标数据
         *      有【同一个factory 同一个EntityManager,同一个OID】 直接获取出来进行返回  没有sql
         *          查找一下二级缓存中有没有,如果没有,那就copy一份过去
         */
        Employee employee2 = entityManager.find(Employee.class, 1L);
        System.out.println(employee2);
        //关闭资源
        JpaUtils.close(entityManager);



        EntityManager entityManager2 = JpaUtils.getEntityManager();

        /**
         * 第三次查询:
         *  先到一级缓存在查找有没有目标数据
         *      没有【已经更换了一个EntityManager对象】
         *  再到二级缓存中找
         *      有 不发送sql   copy一份到一级缓存中
         */
        Employee employee3 = entityManager2.find(Employee.class, 1L);
        System.out.println(employee3);

        /**
         * 第4次查询:
         *  先到一级缓存在查找有没有目标数据
         *      有【同一个factory 同一个EntityManager,同一个OID】 直接获取出来进行返回  没有sql
         *          查找一下二级缓存中有没有,如果没有,那就copy一份过去
         */
        Employee employee4 = entityManager2.find(Employee.class, 1L);
        System.out.println(employee4);
        //关闭资源
        JpaUtils.close(entityManager2);
    }
  • 适用场景
    • 查询多于增删改
    • 财务、金融不适合【二级缓存有可能不准确】
    • 数据量太大不适合使用二级缓存
    • 数据库不允许被多方系统增删改
  • 了解ehcache.xml
  • 默认这个缓存配置文件,使用默认配置文件
    在这里插入图片描述
<defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="true"
            maxElementsOnDisk="10000000"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU"
            />

九、单向一对多

  • 1.配置@OneToMany【默认就是懒加载,建议所有关系都配置懒加载】
  • 2.性能很差
  • 3.集合类型的字段必须使用List或者Set接口来声明
    • JPA底层会创建一个PersistentBag或者PersistentSet赋值给集合字段
    • List和Set集合类型字段最好直接new出来,方便保存数据的时候使用
  • 4.级联
  • cascade表示级联【级联操作一般不用,特别是级联删除非常危险】
    • 级联保存: CascadeType.PERSIST
    • 级联更新:CascadeType.MERGE
    • 级联删除: CascadeType.REMOVE
    • 最强级联: CascadeType.ALL
  • 如果两边都配置了级联称为双向级联,双向级联删除更危险【删除其中一行数据,可能导致整个数据库的所有表的数据全部清空】
    • 最好不要级联,要用也不要使用级联删除,千万不要使用双向级联删除
    • 今后项目的单据这种数据必须使用双向级联,而且使用最强级联

    /**
     * 测试级联删除
     * @throws Exception
     */
    @Test
    public void testCascadeRemove() throws Exception {
        EntityManager entityManager = JpaUtils.getEntityManager();

        try {
            entityManager.getTransaction().begin();

            //准备数据
            //Department department = entityManager.find(Department.class, 1L);
            Employee employee = entityManager.find(Employee.class, 3L);


            //删除
            entityManager.remove(employee);



            entityManager.getTransaction().commit();
        } catch (Exception e) {
            e.printStackTrace();
            //事务回滚
            entityManager.getTransaction().rollback();
        } finally {
            //关闭
            JpaUtils.close(entityManager);
        }

    }
  • 5.孤儿清除orphanRemoval
  • orphanRemoval表示是否清除孤儿数据【与一方对象断开联系的多方数据】 默认是false 写一对多的一方实体类中
  • 一方对象的集合字段中添加或者删除数据,提交事务的时候自动拿内存中的数据与数据库中的数据进行对比,自动执行insert或者delete语句

    /**
     * 测试孤儿清除
     * @throws Exception
     */
    @Test
    public void testOrphanRemovel() throws Exception {
        EntityManager entityManager = JpaUtils.getEntityManager();

        try {
            entityManager.getTransaction().begin();

            //准备数据【持久状态的】
            Department department = entityManager.find(Department.class, 4L);

            //将Department对象的employees【集合】中的数据删除一些,被删除的多方数据将会变成孤儿,只是与一方断开了联系。
            //这里的删除只是从内存中的集合里面删除
            department.getEmployees().remove(0);

            Employee employee = new Employee();
            employee.setName("阿伟");
            employee.setDepartment(department);
            department.getEmployees().add(employee);

            //内存中的一方对象的集合变量中已经少了一些对象了,但是数据库表中没有少,事务提交的时候为了让二者中的数据保存一致,就自动发送delete语句去删除孤儿
            entityManager.getTransaction().commit();
        } catch (Exception e) {
            e.printStackTrace();
            //事务回滚
            entityManager.getTransaction().rollback();
        } finally {
            //关闭
            JpaUtils.close(entityManager);
        }

    }
  • 6.组合关系的数据【单据】
    • 最强级联
    • 孤儿清除
package cn.itsource.domain;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * 部门【一个部门有多个员工】
 */
@Entity
@Table(name="tb_department")
public class Department {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String name;

    /**
     * 单向一对多 基本上不用  性能都差
     *  @OneToMany 表示一对多 默认就是懒加载
     *  记不住:
     *      今后只要你配置字段关系,你可以全部给他写成懒加载
     *  单向一对多的关系字段,目前只能使用List和Set接口来定义,而且只能使用接口,不能用实现类:
     *      为什么?
     *      因为懒加载导致集合类型的变量对象的类型是 org.hibernate.collection.internal.PersistentBag
     *      底层其实是创建了一个PersistentBag对象赋值给当前字段,如果你使用实现类的话,就无法赋值【语法错误】
     *      List  org.hibernate.collection.internal.PersistentBag
     *      Set   org.hibernate.collection.internal.PersistentSet
     *
     *  集合类型字段建议直接new出来
     *      方便保存数据的时候使用  department.getEmployees().add(employee01);
     *  而非集合的字段类型千万不能new出来
     *      如果new出来就会报错:对象关联了一个未保存的临时对象  transient instance
     *  不管是单向一对多还是单向多对一,数据库中的结构都一样!!!
     *
     *  mappedBy表示放弃维护关系,填关联的多方实体类中关联一方的字段名称
     *
     *  cascade表示级联【级联操作一般不用,特别是级联删除非常危险】
     *      CascadeType.PERSIST 表示级联保存
     *      CascadeType.MERGE   表示级联修改
     *      CascadeType.REMOVE  级联删除
     *      CascadeType.ALL     最强级联
     *    如果两边都配置了级联称为双向级联,双向级联删除更危险【删除其中一行数据,可能导致整个数据库的所有表的数据全部清空】
     *    最好不要级联,要用也不要使用级联删除,千万不要使用双向级联删除
     *    今后项目的单据这种数据必须使用双向级联,而且使用最强级联
     *  orphanRemoval表示是否清除孤儿数据【与一方对象断开联系的多方数据】 默认是false  写一对多的一方实体类中
     */
    @OneToMany(fetch = FetchType.LAZY,mappedBy="department",cascade=CascadeType.ALL,orphanRemoval=true)
    //@JoinColumn(name = "dept_id") //单向一对多 如果不写@JoinColumn就会额外创建一张中间表
    private List<Employee> employees = new ArrayList<>();

    public List<Employee> getEmployees() {
        return employees;
    }

    public void setEmployees(List<Employee> employees) {
        this.employees = employees;
    }

    public Long getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
//        return "Department{" +
//                "id=" + id +
//                ", name='" + name + '\'' +
//                '}';
        return super.toString();
    }
}

十、双向一对多/多对一

  • 1.双向都可以提取数据
  • 2.为了提高性能,让一方放弃维护关系,保存数据的时候就相当于单向多对一了
    • mappedBy:填多方关联字段的字段名称
 @ManyToMany(fetch = FetchType.LAZY,mappedBy = "products")
  • 3.搞不清楚的时候优先配置单向多对一
  • 4.两边指定的外键列名称必须一致,否则会创建两个外键,多此一举
    配置关系的时候,要根据实际的业务功能来分析到底使用单向还是双向,如果搞不清楚业务功能的情况下,先配置一个单向多对一就可以了。

十一、多对多

1.@ManyToMany

  • 也可以其中一方放弃维护关系
  • name:中间表名称
    2.@JoinTable
  • joinColumns:指定当前实体类与中间表的关系外键列名称
  • inverseJoinColumns:指定另一个实体类与中间表的关系外键列名称
package cn.itsource.domain;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name = "tb_user")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String name;


    /**
     * @ManyToMany 表示多对多
     *  默认会创建一张中间表 叫tb_user_tb_product  将两边的表名称之间用下划线连在一起 作为中间表名称
     *      中间表里面有两个外键,默认名称为当前实体类对应的表名称_id[tb_user_id],另一个是集合类型字段名称_id[products_id]
     * 可以指定中间表名称和外键列名称
     *  @JoinTable 指定中间表名称
     *      joinColumns             指定当前实体类与中间表产生关联关系的外键名称
     *      inverseJoinColumns      指定另一个实体与中间表产生关联关系的外键名称
     * 双向多对多:
     *  在另一个实体类中仍然配置一个集合字段
     *  同样加@ManyToMany注解
     *  @JoinTable 里面的中间表名称要一致,joinColumns和inverseJoinColumns刚好位置交换
     *
     *  mappedBy表示放弃维护关系
     */
    @ManyToMany(fetch = FetchType.LAZY,cascade = CascadeType.PERSIST)
    @JoinTable(name="tb_user_product",
            joinColumns={@JoinColumn(name="userId")},
            inverseJoinColumns={@JoinColumn(name="productId")})
    private List<Product> products = new ArrayList<>();

    public List<Product> getProducts() {
        return products;
    }

    public void setProducts(List<Product> products) {
        this.products = products;
    }

    public Long getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", products=" + products +
                '}';
    }
}

十二、一对一

  • 1.唯一外键【推荐】
    • 与单向多对一没有太大区别:@OneToOne
    • @JoinColumn里面加上unique=true
    • optional=true表示关联字段【外键列】不允许为null

@Entity
@Table(name="tb_qqzone")
public class QQZone {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    //QQ空间名称
    private String zoneName;

    /**
     * @JoinColumn 指定一个外键列名称,同时添加外键列的唯一约束
     */
    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name="qqid",unique=true)
    private QQ qq;

  • 2.共享主键
    • 需要自定义主键生成策略、
    • 使用自定义的主键生成策略
    • 关系字段上面@PrimaryKeyJoinColumn:让它既是主键又是外键
@Entity
public class QQZone {
  @Id
  @GeneratedValue(generator = "pkGenerator")
  @GenericGenerator(name = "pkGenerator", strategy = "foreign", parameters = @Parameter(name = "property", value = "qq"))
  private Long id;
  private String name;
  // 一对一,一个qq空间输入一个qq号码
  @OneToOne(optional = false)
  // 如果不加这个注解,添加QQZone信息时,就会自动在QQZone表中增加了一个外键qq_id
  @PrimaryKeyJoinColumn
  private QQ qq;
}

关系小结

  • 单向多对一是用得最多的关系
  • 根据实际业务功能需求而定,到底要不要配置成双向
  • 分析:一个一个的去分析关系

如何优化JPA

  • 1.使用双向一对多多对一关联,不使用单向一对多

  • 2.灵活使用单向多对一关联

  • 3.不用一对一,用多对一取代(不要使用共享主键一对一,使用唯一外键一对一)

  • 4.配置对象二级缓存,不使用集合二级缓存,如果使用了集合二级缓存,集合里面的对象也必须二级缓存;查询缓存(jpql查询),没有查询条件才使用查询缓存

  • 5.组合关系集合使用List(顺序,重复),多对多集合使用Set

  • 6.表字段要少,表关联不要怕多,有二级缓存撑腰,设计表尽量达到第三范式

  • JPA难在什么地方 -> 细节(练,思考)太多了,性能太难控制了(经验)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
完整版:https://download.csdn.net/download/qq_27595745/89522468 【课程大纲】 1-1 什么是java 1-2 认识java语言 1-3 java平台的体系结构 1-4 java SE环境安装和配置 2-1 java程序简介 2-2 计算机中的程序 2-3 java程序 2-4 java类库组织结构和文档 2-5 java虚拟机简介 2-6 java的垃圾回收器 2-7 java上机练习 3-1 java语言基础入门 3-2 数据的分类 3-3 标识符、关键字和常量 3-4 运算符 3-5 表达式 3-6 顺序结构和选择结构 3-7 循环语句 3-8 跳转语句 3-9 MyEclipse工具介绍 3-10 java基础知识章节练习 4-1 一维数组 4-2 数组应用 4-3 多维数组 4-4 排序算法 4-5 增强for循环 4-6 数组和排序算法章节练习 5-0 抽象和封装 5-1 面向过程的设计思想 5-2 面向对象的设计思想 5-3 抽象 5-4 封装 5-5 属性 5-6 方法的定义 5-7 this关键字 5-8 javaBean 5-9 包 package 5-10 抽象和封装章节练习 6-0 继承和多态 6-1 继承 6-2 object类 6-3 多态 6-4 访问修饰符 6-5 static修饰符 6-6 final修饰符 6-7 abstract修饰符 6-8 接口 6-9 继承和多态 章节练习 7-1 面向对象的分析与设计简介 7-2 对象模型建立 7-3 类之间的关系 7-4 软件的可维护与复用设计原则 7-5 面向对象的设计与分析 章节练习 8-1 内部类与包装器 8-2 对象包装器 8-3 装箱和拆箱 8-4 练习题 9-1 常用类介绍 9-2 StringBuffer和String Builder类 9-3 Rintime类的使用 9-4 日期类简介 9-5 java程序国际化的实现 9-6 Random类和Math类 9-7 枚举 9-8 练习题 10-1 java异常处理 10-2 认识异常 10-3 使用try和catch捕获异常 10-4 使用throw和throws引发异常 10-5 finally关键字 10-6 getMessage和printStackTrace方法 10-7 异常分类 10-8 自定义异常类 10-9 练习题 11-1 Java集合框架和泛型机制 11-2 Collection接口 11-3 Set接口实现类 11-4 List接口实现类 11-5 Map接口 11-6 Collections类 11-7 泛型概述 11-8 练习题 12-1 多线程 12-2 线程的生命周期 12-3 线程的调度和优先级 12-4 线程的同步 12-5 集合类的同步问题 12-6 用Timer类调度任务 12-7 练习题 13-1 Java IO 13-2 Java IO原理 13-3 流类的结构 13-4 文件流 13-5 缓冲流 13-6 转换流 13-7 数据流 13-8 打印流 13-9 对象流 13-10 随机存取文件流 13-11 zip文件流 13-12 练习题 14-1 图形用户界面设计 14-2 事件处理机制 14-3 AWT常用组件 14-4 swing简介 14-5 可视化开发swing组件 14-6 声音的播放和处理 14-7 2D图形的绘制 14-8 练习题 15-1 反射 15-2 使用Java反射机制 15-3 反射与动态代理 15-4 练习题 16-1 Java标注 16-2 JDK内置的基本标注类型 16-3 自定义标注类型 16-4 对标注进行标注 16-5 利用反射获取标注信息 16-6 练习题 17-1 顶目实战1-单机版五子棋游戏 17-2 总体设计 17-3 代码实现 17-4 程序的运行与发布 17-5 手动生成可执行JAR文件 17-6 练习题 18-1 Java数据库编程 18-2 JDBC类和接口 18-3 JDBC操作SQL 18-4 JDBC基本示例 18-5 JDBC应用示例 18-6 练习题 19-1 。。。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值