休眠与 sqlserver 死锁问题



下面是两个表及其实体类。

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 服务器锁定表查询)
  • 调用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();

保存规则:

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

保存基准:

@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;
}

弹簧-希布 confg 文件:

<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属性)上同样工作正常。

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

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

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

根据我的理解,您已经定义了规则和BenchMark之间的One对Many关系。因此,在构建规则/对象时,您也构建了基准测试。我说的对吗?

分析:我假设基准对象也被填充并保存在设置基准中。所以现在你正在保存规则,作为休眠,它也会尝试保存基准测试。在同一事务中,您尝试再次保存 Benchmark,并且由于此事务管理器进入死锁状态。

溶液:在规则对象中设置基准之前,请尝试填充基准 id 并仅保存规则,这也将保存您的 behcmark 对象。

@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 文档 太。

最新更新