zoukankan      html  css  js  c++  java
  • 【java框架】JPA(2) -- JPA基础

    1.   JPA核心API对象

    1.1.Persistence持久化对象

    Persisitence主要用于创建EntityMangerFactory,它可以根据传入的持久化单元名称来创建对应的EntityMangerFactory。

    // 对应配置文件里面的persistence-unit name="cn.yif.jpa02"
    // 通过持久化类创建一个实体类管理工厂
    EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("cn.yif.jpa02");

    1.2.EntityMangerFactory实体管理工厂

    EntityMangerFactory是一个线程安全的对象,在一个项目中只需要一个EntityManger对象,对应一个数据库资源,EntityMangerFactory对象是一个重量级对象,创建与销毁是比较耗费资源的,其中包含了数据库的配置信息,所有实体及关系,预定义JPQL语句、二级缓存。

    1.3.EntityManger实体管理对象

    EntityManger提供和持久化相关的操作:包括增、删、改、查等,是线程不安全的对象,因此在项目中应该每次只能让一个线程所使用,避免多个线程共享使用。EntityManger是轻量级的,创建与销毁不需要消耗太多的资源,应该用完就关闭。Web中对应一次请求,创建一个EntityManger对象。

    1.4.Transcation事务

    分为javax.persistence.EntityTransaction与javax.transaction.Transaction JTA事务两种。

    javax.persistence.EntityTransaction:只能控制相同一个数据库不同表的事务管理,大多数情况使用这种。

    javax.transaction.Transaction:处理不同数据库不同表的事务管理。Tomcat默认不支持JTA事务(可以通过插件来解决),或者需要用JavaEE服务器:如jboss、weblogic服务器。

    JTA事务使用场景:比如跨行转账、事务列表。

    2.   主键生成策略

    主键实现有两种类型,分为:自然主键与代理主键。

    自然主键:表示把含有业务意义的字段作为主键,一般比如使用身份证号、手机号等作为自然主键,而且此字段必须唯一。

    注意:在JPA中不定义@GeneratedValue则表示自然主键,在保存之前自己设置主键值。

    代理主键:主键一般没有任何意义,用来区别每行数据是不同的。

    在JPA中提供了四种主键生成的策略:IDENTITY、SEQUENCE、TABLE、AUTO。

    2.1.IDENTITY

    IDENTITY表示主键自增,JPA配置IDENTITY策略时会自动将主键设置为自增+1,具体配置如下:

    //@Id是必须的注解,表示对应数据库的主键
       @Id
       //GeneratedValue表示主键生成策略
       //①IDENTITY:表示的是自增,
       // 特点:1.主键必须是数值类型;2.使用的数据库必须支持自增功能;3.使用数据库原生的生成策略,性能是很高的
       // 4.支持IDENTITY的数据库类型:MySQL, SQL Server, DB2, Derby, Sybase, PostgreSQL
       @GeneratedValue(strategy = GenerationType.IDENTITY)
       private Integer id;

    2.2.SEQUENCE

    SEQUENCE表示自增方式为序列方式,一般在Oracle中使用,一个Domain创建一张表,对应一个序列,Oracle不支持ID自增长列而是使用序列机制生成主键ID。

    //②SEQUENCE:序列
        //特点:1.主键必须是数值类型;2.使用的数据库必须支持序列;3.使用数据库原生的生成策略,性能上是很高的
        //4.会默认去创建一个序列,这个序列的名称叫做:HIBERNATE_SEQUENCE,所有Domain都默认使用这个序列,ID在这个序列基础上自增
        //使用名叫SEQ特定的序列
        @GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "SEQ")
        //name:序列生成器的名称,会在@GeneratedValue中进行引用
        //sequenceName会对应真实数据库中序列的名称OIDDOMAIN_SEQUENCE,在数据库中创建名称为OIDDOMAIN_SEQUENCE的序列
        //如果不指定序列生成器的名称sequenceName = "OIDDOMAIN_SEQUENCE",
        //则使用厂商提供的默认序列生成器,比如Hibernate默认提供的序列名称为HIBERNATE_SEQUENCE
        @SequenceGenerator(name = "SEQ", sequenceName = "OIDDOMAIN_SEQUENCE")
        private Integer id;

    支持SEQUENCE的数据库:Oracle、Post greSQL、DB2。

    2.3.TABLE

    TABLE支持所有的数据库类型,在实际项目中如果前期确定不了数据库,可以使用TABLE。

    //③TABLE(高低位)
        //特点:1.主键必须是数值类型;2.支持所有的数据库类型(使用默认的序列);3.性能超级低,每次使用都要进行查询与修改(兼容性牺牲)
        //如果不使用表生成器,会使用默认的表,比如Oracle中使用HIBERNATE_SEQUENCE
        @TableGenerator(name = "SEQ", table = "OidDomain_TABLE", pkColumnName = "SEQUENCE_NAME", valueColumnName = "SEQUENCE_COUNT", initialValue = 1, allocationSize = 1)
        @GeneratedValue(strategy = GenerationType.TABLE, generator = "SEQ")
        private Integer id;

    2.4.AUTO

    //④AUTO 自动根据具体的数据库选择合适的策略,可以是TABLE/SEQUENCE/IDENTITY中的一种
       //假如数据库是Oracle,则选择显示配置Sequence,不使用默认序列配置
       //假如数据库是MySQL,则选择Identity
       //如果不特别指定,这是默认的主键生成策略
       @GeneratedValue(strategy = GenerationType.AUTO)
        private Integer id;

    3.   JPA持久对象状态

    JPA中有四种持久化对象的状态:临时状态(transient):瞬时状态、持久化状态(persistent):托管状态、游离状态(detached):脱管状态、删除状态(removed)。

    临时状态:表示该对象刚刚使用new语句创建,没有和EntityManager发生关系。没有被持久化,不处于EntityManager中。

    持久化状态:表示该对象和EntityManager发生了关系,被加入到EntityManager的一级缓存中,一级缓存中有数据了。

    游离状态:这个对象和EntityManager解除了关系,已经被持久化,但不处于EntityManager中。

    删除状态:调用了EntityManager.remove()方法,对象有关联的ID,并且在EntityManager的管理下,可能计划被删除,事务被commit提交后真正删除。

    3.1.脏数据更新

    脏数据:一个持久化对象在事务管理内,发生在事务访问数据并对数据进行了修改,但还未提交到数据库中,未提交的这个数据就叫做脏数据。

    而脏数据在JPA中执行merge时,与是否调用merge方法无关,此时当事务进行提交commit时,会自动更新到数据库中。

    @Test
        public void JPAUpdate() {
            EntityManager entityManager = JPAUtil.getEntityManager();
            EntityTransaction transaction = entityManager.getTransaction();
            transaction.begin();
            //从数据库中拿到的对象是一个持久化的对象
            Employee employee = entityManager.find(Employee.class, 1);
            employee.setName("修改者哈哈");
            employee.setAge(223);
            //执行修改
            //在对象是持久化对象的状态下,用不用merge方法,和修改没有关系,即调用与否,都会向数据库做修改
            //entityManager.merge(employee);
            //提交的时候会从数据库查询有没有这个employee,如果变更了,就执行merge进行数据更新
            //即commit的时候才执行sql,这个过程叫做脏数据更新(一旦持久化数据对象进行修改,就是脏数据)
            transaction.commit();
    
            JPAUtil.close(entityManager);
    }

    3.2.脏数据更新执行流程

    ①    拿到entityManager对象,开启事务;

    ②    通过entityManager对象从数据库中查询到对应的持久化对象,这个对象会放入到一级缓存中,JPA会为当前的这个对象准备一个快照(相当于把这个对象进行一个备份);

    ③    在提交事务之前,JPA会将当前这个快照对象与数据库中查询到的对象做一个对比,如果相同,就不需要进行修改了,也不会发送对应的merge SQL进行修改;如果对比发现不同,那么JPA就会认为现在这个数据是脏数据,脏数据在事务进行提交的时候,发送merge SQL语句进行数据库数据的同步。

    3.3.Persist与Merge对比

    @Test
        public void JPAPersistOrMerge() {
            Employee employee = new Employee("创建者001", 20);
            EntityManager entityManager = JPAUtil.getEntityManager();
            EntityTransaction transaction = entityManager.getTransaction();
            transaction.begin();
            /**
             *  persist:放到方法中的对象会变成持久化对象,改变的是原对象(这个对象就在一级缓存当中)
             */
            entityManager.persist(employee);
            employee.setName("更新创建者001");
            transaction.commit();
            JPAUtil.close(entityManager);
        }

    对应会执行两条SQL语句:先发送insert语句,之后发送update语句进行脏数据更新。

    @Test
        public void JPAPersistOrMerge() {
            Employee employee = new Employee("创建者001", 20);
            EntityManager entityManager = JPAUtil.getEntityManager();
            EntityTransaction transaction = entityManager.getTransaction();
            transaction.begin();
            /**
             *  merge:放到方法中的对象不会变成持久化对象,但是它会返回一个持久化对象,修改持久化对象同样生效
             */
            //entityManager.persist(employee);
            Employee employee1 = entityManager.merge(employee);
            employee1.setName("更新创建者001");
            transaction.commit();
            JPAUtil.close(entityManager);
        }

    执行上述操作同样会发送两条SQL语句:

    3.4.Remove的使用

    JPA中remove是不是直接删除数据,而是修改一个对象的状态为删除状态,当事务提交commit之后,delelte SQL语句才会发送,对应去删除数据中的脏数据。

    @Test
    public void JPARemove() {
          EntityManager entityManager = JPAUtil.getEntityManager();
          EntityTransaction transaction = entityManager.getTransaction();
          transaction.begin();
          /**
           * remove并不是直接删除数据,而是修改一个持久化对象的状态为删除状态(计划删除)
           * 对应只能有一个entityManager,其它entityManager对象的持久化状态在这里是不能使用的
           */
          Employee employee = entityManager.find(Employee.class, 7);
          entityManager.remove(employee);
          transaction.commit();
          JPAUtil.close(entityManager);
    }

    4.   单向多对一配置

    4.1.@ManyToOne

    @ManyToOne
    在JPA中使用@ManyToOne可以建立多对一中多方与一方的关系,一般我们在多方会使用面向对象的概念来创建一个一方的属性,在一方属性上加上@ManyToOne标签。默认外键名称为一方属性名称_id。

    4.2.@JoinColumn(name = "外键名")

    使用@JoinColumn可以自定义外键的名称,在实际中我们可以配置这个标签进行自定义name外键进行修改。
    /**
     *  单向多对一:使用JPA配置
     */
    @Entity
    @Table(name = "t_product")
    public class Product {
        @Id
        @GeneratedValue
        private Integer id;
    
        @Column(name = "t_name")
        private String name; //产品名称
        @ManyToOne
        /**
         *  使用 @ManyToOne来建立多对一多方中一方的关系
         *  默认的外键名称--dir_id,即多方字段名_id
         *  通过ProductDir类--可以找到这张表@Table(name = "t_productdir")
         *  使用@JoinColumn(name = "p_dirid"),可以自定义外键的名称
         */
        @JoinColumn(name = "p_dirid")
        private ProductDir dir; //分类id
        public Product() {
        }

    对应数据库中的显示:

    4.3.保存先后的性能影响

    在对应JPA多对一中的数据库表的关系时,应该先保存一方,再保存多方,性能更高。具体实例见如下代码:

    public class SaveSQLTest {
        @Test
        public void SaveSQLTest() {
           //创建一个产品分类
            ProductDir productDir = new ProductDir();
            productDir.setDirName("鼠标");
            //创建两个产品
            Product product = new Product();
            product.setName("罗技鼠标");
            product.setDir(productDir);
            Product product1 = new Product();
            product1.setName("雷蛇鼠标");
            product1.setDir(productDir);
            //事务处理
            EntityManager entityManager = JPAUtil.getEntityManager();
            EntityTransaction transaction = entityManager.getTransaction();
            transaction.begin();
            /**
             * 1.先保存一方(产品类型),再保存多方(产品),发送3条sql -- 性能更高
             *    insert into t_productdir (dName) values (?)  先插入dir,dir有值
             *    insert into t_product (p_dirid, t_name) values (?, ?) 后面插入可以直接使用这个值
             * 2.先保存多方(产品),再保存一方(产品类型),发送5条sql
             *    insert into t_product (p_dirid, t_name) values (?, ?) 注:p_dirid是没有值的
             *    insert into t_productdir (dName) values (?) 这里插入使p_dirid双方之间建立了关系
             *    在事务进行提交的时候,JPA处理发现是数据脏了,为了维护数据的一致性,后面发送了两条update
             *    update t_product set p_dirid=?, t_name=? where id=?
             * 3.总结:在多对一关系处理中,应该先保存一方,再保存多方
             */
            entityManager.persist(product);
            entityManager.persist(product1);
            entityManager.persist(productDir);
            transaction.commit();
            JPAUtil.close(entityManager);
        }
    }

    4.4.懒加载配置

    懒加载配置基于JPA的抓取策略,即JPA发出对应的SQL获取关联对象。

    主要有@ManyToOne(fetch = FetchType.LAZY)与@ManyToOne(fetch = FetchType.EAGER)两种策略,默认JPA使用迫切加载FetchType.EAGER类型,这将要求持续性提供程序运行时必须迫切左外连接获取数据。FetchType.LAZY表示程序在首次访问数据时并不急切获取数据,只有当具体使用的时候才会去获取数据。

    具体配置如下:

    /**
     *  单向多对一:使用JPA配置
     */
    @Entity
    @Table(name = "t_product")
    public class Product {
        @Id
        @GeneratedValue
        private Integer id;
    
        @Column(name = "t_name")
        private String name; //产品名称
    
       @ManyToOne(fetch = FetchType.LAZY)
        /**
         *  使用 @ManyToOne来建立多对一多方中一方的关系
         *  默认的外键名称--dir_id,即多方字段名_id
         *  通过ProductDir类--可以找到这张表@Table(name = "t_productdir")
         *  使用@JoinColumn(name = "p_dirid"),可以自定义外键的名称
         */
        @JoinColumn(name = "p_dirid")
        private ProductDir dir; //分类id
    
        public Product() {
        }

    配置懒加载LAZY可以提高性能,具体EGER与LAZY懒加载的SQL执行查询是不一样的,具体SQL如下:

    对应执行获取ProductDir才会发送对应的SQL语句,内部主要是用到了javassist-3.18.1-GA.jar的一个jar包,对应创建一个基类的子类对象,具体代码如下:

    public class LazyLoadTest {
        @Test
        public void LazyLoadTest() {
            EntityManager entityManager = JPAUtil.getEntityManager();
            /**
             *  注:一条SQL查询出了所有的值Product,ProductDir
             *      有这种情况:现在只想拿Product的值,并不想拿ProductDir,当我需要的时候,再发送SQL去拿
             *      这里只需要配置懒加载(延时加载)即可
             */
            Product product = entityManager.find(Product.class, 1);
            ProductDir dir = product.getDir();
            System.out.println(dir);
            /**
             * 这里用到了javassist-3.18.1-GA.jar:主要作用是修改字节码文件,
             * 它创建了一个新的ProductDir,这个Dir是原来那个类的子类
             * class cn.yif.jpa02.manytoone.ProductDir_$$_jvst497_2
             */
            System.out.println(dir.getClass());
            entityManager.close();
        }
    }
    
    
  • 相关阅读:
    package相关知识
    【算法设计与分析】5个数7次比较排序的算法
    Android下的应用编程——用HTTP协议实现文件上传功能
    5个数通过7次比较排序的方法
    数据库范式(1NF、2NF、3NF、BCNF)
    HttpDownloader2011年09月20日 写好用于下载的类 并且封装好
    POJ 1692 DP
    POJ 1682 多重DP
    TYVJ 1744 逆序对数(加强版)
    POJ 2151 概率DP
  • 原文地址:https://www.cnblogs.com/yif0118/p/12815316.html
Copyright © 2011-2022 走看看