Hibernate与sqlserver死锁问题

时间:2017-06-13 10:58:52

标签: java sql-server spring oracle hibernate

以下是具有实体类的两个表。

tbl_rules

| rule_id | rule_name |

    @Entity
    @Table(name = "db_user_name.tbl_rules")
    public class Rule implements Serializable {
        private static final long serialVersionUID = 1L;

        @Id
        @Basic(optional = false)
        @GenericGenerator(name = "incre", strategy = "increment")
        @GeneratedValue(generator = "incre")
        @Column(name = "rule_id", unique = true, nullable = false)
        private long ruleId;

        @Column(name = "rule_name", length = 250)
        private String ruleName;

        @OneToMany(fetch = FetchType.LAZY, mappedBy = "rules")
        private Set<Benchmark> benchmarks = new HashSet<Benchmark>(0);
        ... getters and setters
    }

tbl_benchmark

| benchmark_id | rule_id |

@Entity
@Table(name = "tbl_benchmark", catalog = "db_user_name")
@DynamicUpdate(value = true)
public class Benchmark implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @Basic(optional = false)
    @Column(name = "benchmark_id", unique = true, nullable = false)
    private Long benchmarkId;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "rule_id", nullable = false)
    private Rule rules;
    .. getter and setters
}

在以下情况中遇到sql server db死锁问题

  • HibernateSessionManager.beginTransaction();
  • 在后端规则中调用saveRule(rule) //并对两个表进行基准测试 被锁定(使用sql server锁定表查询)
  • 使用此方法调用saveBenchmark(benchmark) //死锁
  • HibernateSessionManager.commit();

发生死锁的代码:

HibernateSessionManager.beginTransaction();
            UserIdManager.setCurrentGroupId(2);
            if (savingObjects.get(AmlConstants.USERCREDENTAILS_STRING) != null){
                    userCredentials = (UserCredentials) savingObjects.get(AmlConstants.USERCREDENTAILS_STRING);
                    Util.setAuditLogField(AmlConstants.USERIDSTRING);
                    this.getAmlDAOFactory().getUserCredentialsDAO().updateUserDetails(userCredentials);
                if (savingObjects.get(AmlConstants.USERBRANCHMAPPING_STRING) != null){
                    userBranchMapping = (UserBranchMapping) savingObjects.get(AmlConstants.USERBRANCHMAPPING_STRING);
                      Util.setAuditLogField(AmlConstants.BRANCH_STRING);
                      this.getAmlDAOFactory().getUserBranchMappingDAO().saveUserBranchMapping(userBranchMapping);
                }
            }
            HibernateSessionManager.commit();

saveRule:

@Override
    public Rule saveRule(Rule rule) throws Exception {
        try {
            getSession().saveOrUpdate(rule);
            getSession().flush();
        } catch (RuntimeException e) {
            e.printStackTrace();
            throw e;
        }
        return rule;
    }

saveBenchmark:

@Override
    public Benchmark saveBenchMark(Benchmark benchmark) throws Exception {
        try {
            if (benchmark.getBenchmarkId() == null)
                benchmark.setBenchmarkId(getBenchmarkCount() + 1);
            getSession().clear();
            getSession().saveOrUpdate(benchmark);
            getSession().flush();
        } catch (RuntimeException e) {
            // logger.error("Runtime error while saving benchmark", e);
            e.printStackTrace();
        } catch (Exception e) {
            logger.error("Exception while saving benchmark " + e.getMessage(), e);
        }
        return benchmark;
    }

Spring-Hib配置文件:

    <bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" 
destroy-method="close">
            <property name="driverClassName" value="${jdbc.driverClassName}" />
    ..
     <property name="hibernateProperties">
                <props>
                   <prop key="hibernate.dialect">com.aml.hibernate.SQLServerCustomeDialect</prop>
                   <prop key="hibernate.character_encoding">UTF-8</prop>
                   <prop key="hibernate.connection.useUnicode">true</prop>
                    <prop key="hibernate.show_sql">true</prop>
                    <prop key="hibernate.generate_statistics">false</prop>
                </props>
            </property>
    ..

HibernateSessionManager.java

public class HibernateSessionManager {

    public  static Logger logger = Logger.getLogger(HibernateSessionManager.class);
    public static final ThreadLocal<Session> currentSession = new ThreadLocal<Session>();
    public static final ThreadLocal<java.util.List<Session>> sessionList = new ThreadLocal<java.util.List<Session>>();

    /** Store transaction object on thread local
     * this helps to make a request processing transactional */
    public static final ThreadLocal<Transaction> transaction = new ThreadLocal<Transaction>();
    public static final ThreadLocal<Map<String, Transaction>> transactionMap = new ThreadLocal<Map<String, Transaction>>();

    /** keep the beginner method path which helps to commit on the same method only 
     * we are not supposed to make nested commits under a single request */
    public static final ThreadLocal<String> callerXPath = new ThreadLocal<String>();



    /**
     * Returns existing hibernate session binded with current request thread, 
     * if no session already bind with current thread then it will open a new session, bind to current thread 
     * and returns the session object
     * 
     * @param sessionFactory
     * @return
     * @throws HibernateException
     */
    public static Session currentSession(SessionFactory sessionFactory) throws HibernateException {

        Session s = (Session) currentSession.get();
        // Open a new Session, if this Thread has none yet
        if (s == null || !s.isOpen())
        {
            s = sessionFactory.openSession();
            currentSession.set(s);
            if(sessionList.get()==null)
                sessionList.set(new LinkedList<Session>());
            sessionList.get().add(s);
            logger.debug("Opened new session:"+currentSession.get().hashCode());
        }else{
            logger.debug("returning existing session:"+currentSession.get().hashCode());
        }
        return s;
    }


    /**
     * Closes all the sessions binded with current request thread
     * @throws HibernateException
     */
    public static void closeSession() throws HibernateException {
        currentSession.set(null);
        transaction.set(null);
        callerXPath.set(null);

        try{
            if(sessionList.get()!=null)
                for (int i = 0; i < sessionList.get().size(); i++) {
                    Session s = sessionList.get().get(i);
                    try{
                        if (s != null && s.isOpen())
                            s.close();
                        logger.debug("Closed session - session local:"+ (s!=null?s.hashCode(): ""));
                    }catch (Exception e) { logger.debug("Error while closing session: ", e); }
                }
            transactionMap.get().clear();
        }catch (Exception e) { logger.debug("Error while closing session: ", e); }
        sessionList.set(null);
        transactionMap.set(null);
    }




    // ------------------- Transaction management ------------------
    /**
     * Starts a new hibernate transaction on the session binded to current request thread
     * if there is already a transaction started on this thread, ignores creation of another transaction
     * all the db calls on a single request thread has to come under a single transaction
     * @return
     */
    public static boolean beginTransaction(){
        try{
            logger.debug("beginTransaction............... ");

            Transaction t = transaction.get();
            if(t == null && callerXPath.get()==null){
                Session s = currentSession.get();
                t = s.beginTransaction();
                t.registerSynchronization(new Synchronization() {

                    @Override
                    public void beforeCompletion() {
                        logger.debug("Transaction-beforeCompletion............... ");

                    }

                    @Override
                    public void afterCompletion(int status) {
                        logger.debug("Transaction-afterCompletion............... "+status);

                    }
                });
                transaction.set(t);
                callerXPath.set(getCallerMethodInvolvedinTransaction());

                if(transactionMap.get()==null)
                    transactionMap.set(new HashMap<String, Transaction>());

                transactionMap.get().put(callerXPath.get(), t);
                logger.debug("Started new hibernate transaction:"+t);
            }
        }catch (Exception e) {
            logger.error("Error while starting new transaction: ", e);
            return false;
        }
        return true;
    }


    /**
     * if we already have a hibernate transaction created on the current request thread and some thing is committed on it
     * it will rollback the changes done after the transaction initialization
     */
    public static void rollback(){
        try{
            Transaction t = transactionMap.get().get(callerXPath.get());
            if(t != null){
                t.rollback();
                logger.debug("Roll back success on transaction:"+t);
            }
        }catch (Exception e) {
            logger.error("Exception while trying to rollback", e);
        }
    }


    /**
     * Commits all the changes done after the transaction started on the current request thread
     * this accepts the commit command from the only method which started the transaction
     * This will unlink the current session and then currentSession() method can give another session as existing one is unlinked on the thread local
     */
    public static void commit(){
        try{
            logger.debug("commit............... ");

            Transaction t = transaction.get();
            if(t != null /*&& !t.wasCommitted()*/
                    && callerXPath.get()!=null && callerXPath.get().equals(getCallerMethodInvolvedinTransaction())){
                t.commit();

                currentSession.get().clear();
                currentSession.set(null);
                transaction.set(null);
                callerXPath.set(null);

                logger.debug("Commit success on transaction:"+t);
            }
        }catch (Exception e) {
            logger.error("Exception while trying to commit", e);
        }
    }









    /**
     * get the caller method xpath: <package>.<classname>.<methodname>
     * @return
     */
    public static String getCallerMethodInvolvedinTransaction() {
        try{
            StackTraceElement[] stElements = Thread.currentThread().getStackTrace();
            return stElements[3].toString().split("\\(")[0];
            /*for (int i = 3; i < stElements.length; i++) {
                String rawFQN = stElements[i].toString().split("\\(")[0];
                String className = rawFQN.substring(0, rawFQN.lastIndexOf('.'));
                String methodName = rawFQN.substring(rawFQN.lastIndexOf('.')+1);
                Object carObj = Class.forName(className).newInstance();

                ClassPool pool = ClassPool.getDefault();
                CtClass cc = pool.get(className);
                CtMethod methodX = cc.getDeclaredMethod(methodName);
                int xlineNumber = methodX.getMethodInfo().getLineNumber(0);

                Method method = carObj.getClass().getMethod(methodName);
                if (method.isAnnotationPresent(JCTransaction.class))
                {
                    return rawFQN;
                }
            }*/
        }catch (Exception e) {
            logger.error("" , e);
        }
        return null;
    }
}

但同样适用于oracle db(使用oracle hib属性)。

2 个答案:

答案 0 :(得分:3)

你的代码必须有问题,否则你永远不应该把自己锁在外面。两个不同的连接可以相互阻塞,但是一个连接永远不会阻塞它自己的锁。我没有详细查看代码,我将重点讨论为什么你会遇到SQL Server而不是Oracle的问题。

Oracle总是对行使用版本控制,因此行永远不会因为它们被读取而被锁定。另一方面,SQL Server通常采用读锁定,读锁定将阻止来自其他会话的写入。您可以将SQL Server隔离级别更改为READ_COMMITED_SNAPSHOT以隐藏问题,但它仍然存在。

我不明白为什么要在几个地方清除会话,这几乎不应该完成。我也不理解在HibernateSessionManager中处理事务的所有代码,这可能是问题的根本原因。不知何故,你运行多个交易。保持简单,问题可能会消失!

答案 1 :(得分:2)

根据我的理解,您已经定义了规则与优化之间的OneToMany关系。基准。因此,在构建Rule Entitiy / Object时,您也构建了Benchmark。我对吗?

<强>分析: 我假设Benchmark对象也被填充并保存在Set Benchmarks中。所以现在你正在保存规则,因为休眠它也会尝试保存Benchmark。在同一个事务中,您正试图再次保存Benchmark,并且由于此事务管理器处于死锁状态。

<强>解决方案: 在规则对象中设置基准测试之前,尝试填充benchmarkid并仅保存将保存behcmark对象的Rule。

@Override
    public Rule saveRule(Rule rule) throws Exception {
        try {
            //Get BenchMark from rule...
            // Your code to get Benchmark from rule.
            // Populate benchmarkId
            if (benchmark.getBenchmarkId() == null) {
                benchmark.setBenchmarkId(getBenchmarkCount() + 1);
            }
            getSession().saveOrUpdate(rule);
            getSession().flush();
        } catch (RuntimeException e) {
            e.printStackTrace();
            throw e;
        }
        return rule;
    }

PS:请参考Hibernate文档。