简体   繁体   English

Hibernate与sqlserver死锁问题

[英]Hibernate with sqlserver deadlock issue

Following are the two tables with its entity classes. 以下是具有实体类的两个表。

tbl_rules tbl_rules

| | rule_id | rule_id | rule_name | 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 tbl_benchmark

| | benchmark_id | benchmark_id | rule_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
}

Facing deadlock issue with sql server db in the following case 在以下情况下面临sql server db 死锁问题

  • HibernateSessionManager.beginTransaction() ; HibernateSessionManager.beginTransaction() ;
  • call saveRule(rule) // in the backend rule and benchmark both tables are locked (using sql server locked tables query) 调用saveRule(rule) //在后端规则和基准测试中两个表都被锁定(使用sql server锁定表查询)
  • call saveBenchmark(benchmark) // deadlock at this method 在此方法中调用saveBenchmark(benchmark) // deadlock
  • HibernateSessionManager.commit() ; HibernateSessionManager.commit() ;

Code where deadlock is happened: 发生死锁的代码:

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: 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: 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 confg file: 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 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;
    }
}

But same working fine with oracle db (with oracle hib properties). 但同样适用于oracle db (使用oracle hib属性)。

There has to be something wrong with your code or you should never lock yourself out. 你的代码必须有问题,否则你永远不应该把自己锁在外面。 Two different connections can block each other, but one connection should never block on its own locks. 两个不同的连接可以相互阻塞,但是一个连接永远不会阻塞它自己的锁。 I haven't looked at the code in detail, I'll focus on why you get the problem with SQL Server and not with Oracle. 我没有详细查看代码,我将重点讨论为什么你会遇到SQL Server而不是Oracle的问题。

Oracle always uses versioning for rows, so rows are never locked just because they are read. Oracle总是对行使用版本控制,因此行永远不会因为它们被读取而被锁定。 SQL Server on the other hand normally takes read locks and a read lock will prevent writes from other sessions. 另一方面,SQL Server通常采用读锁定,读锁定将阻止来自其他会话的写入。 You can probably change the SQL Server isolation level to READ_COMMITED_SNAPSHOT in order to hide the issue, but it will still be there. 您可以将SQL Server隔离级别更改为READ_COMMITED_SNAPSHOT以隐藏问题,但它仍然存在。

I don't understand why you are clearing the session in several locations, that should almost never be done. 我不明白你为什么要在几个地方清理会议,这几乎不可能完成。 I also don't understand all that code dealing with the transaction in HibernateSessionManager, that is probably the root cause of the issue. 我也不理解在HibernateSessionManager中处理事务的所有代码,这可能是问题的根本原因。 Somehow you're running with more than one transaction. 不知何故,你运行多个交易。 Keep things simple instead and the issue is likely to go away! 保持简单,问题可能会消失!

As per my understanding, you already defined OneToMany relation between Rule & BenchMark. 根据我的理解,您已经定义了Rule和BenchMark之间的OneToMany关系。 So while building Rule Entitiy / Object you build Benchmark too. 因此,在构建Rule Entitiy / Object时,您也构建了Benchmark。 Am I right? 我对吗?

Analysis: I am assuming Benchmark object is also populated and saved in Set Benchmarks. 分析:我假设Benchmark对象也被填充并保存在Set Benchmarks中。 So right now you are saving Rule, as hibernate it will try to save Benchmark too. 所以现在你正在保存规则,因为休眠它也会尝试保存Benchmark。 In same transaction you are trying to save Benchmark again and due to this transaction manager goes in deadlock situation. 在同一个事务中,您正在尝试再次保存Benchmark,并且由于此事务管理器处于死锁状态。

Solution: Before setting benchmark in rule object try to populate benchmarkid and save only Rule which will save your behcmark object too. 解决方案:在规则对象中设置基准测试之前,尝试填充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: Please refer Hibernate docs too. PS:请参考Hibernate文档。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM