• 杂题3道


    Hibernate:有了 save,为什么还需要 persist?

    背景返回目录

    万物皆自然,每个 API 的设计,无论是否正确,都有其意图。因此,在学习某些框架的时候,我们需要经常思考:这个 API 的设计意图是啥?

    本文来探讨一下 Session 中 persist 的设计意图。

    官方注释返回目录

    save

    复制代码
     1     /**
     2      * Persist the given transient instance, first assigning a generated identifier. (Or
     3      * using the current value of the identifier property if the <tt>assigned</tt>
     4      * generator is used.) This operation cascades to associated instances if the
     5      * association is mapped with {@code cascade="save-update"}
     6      *
     7      * @param object a transient instance of a persistent class
     8      *
     9      * @return the generated identifier
    10      */
    11     public Serializable save(Object object);
    复制代码

    persist

    复制代码
    1     /**
    2      * Make a transient instance persistent. This operation cascades to associated
    3      * instances if the association is mapped with {@code cascade="persist"}
    4      * <p/>
    5      * The semantics of this method are defined by JSR-220.
    6      *
    7      * @param object a transient instance to be made persistent
    8      */
    9     public void persist(Object object);
    复制代码

    解释

    save 因为需要返回一个主键值,因此会立即执行 insert 语句,而 persist 在事务外部调用时则不会立即执行 insert 语句,在事务内调用还是会立即执行 insert 语句的。

    看 persist 的注释会觉得其不会立即执行 insert 语句,为何 在事务内调用会立即执行 insert 语句,后面再分析。

    测试返回目录

    SessionHelper

    复制代码
     1 package demo;
     2 
     3 import org.hibernate.*;
     4 import org.hibernate.cfg.*;
     5 
     6 public final class SessionHelper {
     7     private static SessionFactory factory;
     8 
     9     public static void execute(SessionAction action) {
    10         execute(action, false);
    11     }
    12 
    13     public static void execute(SessionAction action, boolean beforeTransaction) {
    14         Session session = openSession();
    15         try {
    16             if (beforeTransaction) {
    17                 System.out.println("action");
    18                 action.action(session);
    19             }
    20 
    21             System.out.println("begin transaction");
    22             session.beginTransaction();
    23             if (!beforeTransaction) {
    24                 System.out.println("action");
    25                 action.action(session);
    26             }
    27 
    28             System.out.println("flush and commit");
    29             session.flush();
    30             session.getTransaction().commit();
    31         } catch (Exception ex) {
    32             session.getTransaction().rollback();
    33         } finally {
    34             session.close();
    35         }
    36     }
    37 
    38     @SuppressWarnings("deprecation")
    39     public static Session openSession() {
    40         if (factory == null) {
    41             factory = new Configuration().configure().buildSessionFactory();
    42         }
    43 
    44         return factory.openSession();
    45     }
    46 }
    复制代码

    save

    复制代码
     1 package demo;
     2 
     3 import model.*;
     4 
     5 import org.hibernate.*;
     6 /*
     7  * save 会导致 insert 语句的立即执行。
     8  */
     9 public class SaveDemo implements Demo {
    10 
    11     @Override
    12     public void run() {
    13         SessionHelper.execute(new SessionAction() {
    14 
    15             @Override
    16             public void action(Session session) {
    17                 User user = new User();
    18                 user.setUsername("woshishui");
    19                 user.setPassword("123456");
    20 
    21                 session.save(user);
    22             }
    23 
    24         });
    25     }
    26 
    27 }
    复制代码

    输出结果

    复制代码
     1 begin transaction
     2 action
     3 Hibernate: 
     4     /* insert model.User
     5         */ insert 
     6         into
     7             USERS
     8             (USERNAME, PASSWORD) 
     9         values
    10             (?, ?)
    11 flush and commit
    复制代码

    persis in transactiont

    复制代码
     1 package demo;
     2 
     3 import model.*;
     4 
     5 import org.hibernate.*;
     6 
     7 /*
     8  * persist 在事务中执行,会导致 insert 语句的立即执行。
     9  */
    10 public class PersisInTransactiontDemo implements Demo {
    11 
    12     @Override
    13     public void run() {
    14         SessionHelper.execute(new SessionAction() {
    15 
    16             @Override
    17             public void action(Session session) {
    18                 User user = new User();
    19                 user.setUsername("woshishui");
    20                 user.setPassword("123456");
    21 
    22                 session.persist(user);
    23             }
    24 
    25         });
    26     }
    27 
    28 }
    复制代码

    输出结果

    复制代码
     1 begin transaction
     2 action
     3 Hibernate: 
     4     /* insert model.User
     5         */ insert 
     6         into
     7             USERS
     8             (USERNAME, PASSWORD) 
     9         values
    10             (?, ?)
    11 flush and commit
    复制代码

    persis before transactiont

    复制代码
     1 package demo;
     2 
     3 import model.*;
     4 
     5 import org.hibernate.*;
     6 
     7 /*
     8  * persist 不在事务中执行,不会导致 insert 语句的立即执行,而是在 flush 时执行 insert 语句。
     9  */
    10 public class PersisBeforeTransactiontDemo implements Demo {
    11 
    12     @Override
    13     public void run() {
    14         SessionHelper.execute(new SessionAction() {
    15 
    16             @Override
    17             public void action(Session session) {
    18                 User user = new User();
    19                 user.setUsername("woshishui");
    20                 user.setPassword("123456");
    21 
    22                 session.persist(user);
    23             }
    24 
    25         }, true);
    26     }
    27 
    28 }
    复制代码

    输出结果

    复制代码
     1 action
     2 begin transaction
     3 flush and commit
     4 Hibernate: 
     5     /* insert model.User
     6         */ insert 
     7         into
     8             USERS
     9             (USERNAME, PASSWORD) 
    10         values
    11             (?, ?)
    复制代码

    《算法设计手册》杂题3道

      最近找工作的事告一段落,发一些之前整理但未发表的文章。鉴于各个公司一般要求将所做笔试题和面试题保密,并要求在试卷上签署了相关协议,因此本人不会在后续博文中提及并分析,见谅。

    1.归纳法找递归函数的输出(原书P16~17,1.3.4节)

    归纳并证明下面函数的输出:

    复制代码
    int increment(int y) {
    
      if (y==0)
    
        return 1;
      if(y%2 == 1)
    
        return 2*increment(y/2);
      else
    
        return y+1;
    }
    复制代码

    解答:

      事实上increment(y)=y+1。

      证明:

    (1)(归纳法)y=0时输出1。

    (2)

    y为偶数时,输出y+1;

    y为奇数时,不妨令y=2m+1,那么2*increment((2m+1)/2) = 2*increment(m)。由于m<y且increment(m)=m+1 (归纳假设),那么输出为2*(m+1) = y+1。

    得证。

    2.动态数组的时间复杂度(原书P67,3.1.1节)

    用下面的方式实现动态数组:首先分配大小为1的连续空间,当需要填入第2个元素时,分配大小为2个连续空间,把原来的1个元素复制进新空间,再把新元素填入,销毁原空间;依次类推,动态数组的容量变化过程为1->2->4->8->...。当数组中一共有n个元素时,一共被复制了几次?

    解答:

    为简化分析,n此时是2的幂且刚好把数组填满。可以看出,其中1/2的元素没有被复制过,另外1/2在上次扩展时复制了一次。即,上次扩展时复制了n*(1/2)个元素。一共扩展了logn次,因此复制的总次数为:


    n*(frac{1}{2}+frac{1}{4}+...+frac{1}{2^{logn}})

    其中2^logn = n。由极限知识可知,当n→∞时,和为2n。因此n个元素的复制次数不超过2n次,保持了O(n)的时间复杂度。

    附注:

      Nginx封装的数据结构ngx_array_t就是一种动态数组,采用了类似的内存分配策略。

    3.最小堆第k小的元素与给定x的大小关系:kth>=x是否成立?(原书P116~117,4.3.2节)

    对一个数组实现的n个元素的最小堆和给定的实数x,判断堆中第k小元素是否大于等于x?(0<k<n)要求算法最坏时间复杂度为O(k)。(提示:没必要找出第k小元素的具体值)

    解答:

      最直接的两种解法:从堆中选取最小元素k次,时间复杂度O(klogn);检查堆中前k层所有元素,时间复杂度O(n,2^k)。这两种解法都不符合时间复杂度的要求。

      实际上是从根开始遍历所有值小于x的结点:

    复制代码
    //第一次调用时,i=1,count=k
    int heap_compare(heap *h,int i,int count,int x)
    {
        if((count<=0)||(i>h->size))
            return count;
        if(h->h[i]<x) {
            count = heap_compare(h,LEFT(i),count-1,x);
            count = heap_compare(h,RIGHT(i),count,x);
        }
        return count;
    }
    复制代码

      如果根大于等于x,那么其余所有元素都不可能小于x,第k小的元素必然大于等于x。这时返回值为k>0。(若k=1,那么第1小的元素就是根,结果不变)

      如果根小于x,需要对根的两个后继进行遍历。如果你没有看明白这段程序的含义,下面以几个例子来帮助理解这个函数在递归调用时发生了什么。假定k=3,x=3,即判断第3小的元素是否大于等于3。简单起见,结点以下标代替,根为1(与原书一致)。

      实例1:第k小的元素大于等于x。

      实例2:第k小的元素大于等于x。原图交换顺序。

      实例3:第k小的元素小于x。

      从上面三个例子可以看出:

      每找到一个小于x的元素都会消耗一个count,如果消耗完了k个,表示至少前k个都小于x。那么第k个必然小于x,后面不必再进行查找,返回值为0。

      反之,如果遍历所有小于k的元素时未消耗完k个count,表示小于x的元素不足k个,那么第k个必然大于等于x,返回值为正值。

      原代码中令人迷惑的地方在于count的使用,理清上面的两个关系就好理解了。虽然看上去这个函数对于一个结点的两个后继的处理地位不同,但实际上没有影响。

      至于时间复杂度,由于我们只遍历了所有小于x的结点以及它们的后继,而且遍历时不超过k个(用完k个就return 0),因此大约是3k个结点,时间复杂度只有O(k),符合要求。

      这道题曾在Amazon的面试中出现。

      stackoverflow上有一个变形:判断n元最大堆中第k大的元素是否大于给定的数x,处理方法类似。

    分析返回目录

    为何 persist 在事务内和事务外表现的行为不同呢?为何这样设计?目前还没有查到官方的第一手资料(刚学习 Java 和 Hibernate),我的猜测是:事务外的 persist 是为了应对长事务,事务内的 persist 是为了和 save 保持一个语义

    备注返回目录

    学习 Hibernate 的过程也相当于从新学习了一遍 EntityFramework,换个视角总有好处。

  • 相关阅读:
    谈谈JavaScript中继承方式
    《前端架构设计》学习笔记一--架构的种子
    正则中1的用法---反向引用
    JavaScript千分符---正则实现
    JavaScript开发中常用的代码规范配置文件
    前端开发流程---我们应该做些什么
    JavaScript中数据类型判断
    冒泡排序以及改进
    Number和toString中的坑
    Fluent Ribbon 第五步 ToolBar
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3356354.html
Copyright © 2020-2023  润新知