Hibernate在高并发的情况下的一个问题

在用hibernate开发的过程中,无意间碰到如下的一个问题。

 

我的测试代码如下:

1.vo类:

package com.huajtech.vo;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

@Table(name = "tbl_comment")
@Entity
public class Comment {

    @Id
    @GeneratedValue
    private Long id;

    @Column(name = "name")
    private String name;

    @ManyToOne(fetch = FetchType.EAGER, targetEntity = Topic.class)
    @JoinColumn(name = "topic")
    private Topic topic;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Topic getTopic() {
        return topic;
    }

    public void setTopic(Topic topic) {
        this.topic = topic;
    }


}

 

package com.huajtech.vo;

import java.util.HashSet;
import java.util.Set;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
import javax.persistence.Table;

@Table(name = "tbl_topic")
@Entity
public class Topic {

    @Id
    @GeneratedValue
    private Long id;

    @Column(name = "name")
    private String name;

    @OneToMany(mappedBy = "topic", fetch = FetchType.LAZY, targetEntity = Comment.class)
    private final Set<Comment> comments = new HashSet<Comment>();

    @Column(name = "comment_count")
    private Long commentCount = 0L;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Long getCommentCount() {
        return commentCount;
    }

    public void setCommentCount(Long commentCount) {
        this.commentCount = commentCount;
    }

    public Set<Comment> getComments() {
        return comments;
    }
}

 2.Dao类

package com.huajtech.dao;

import org.hibernate.Session;

public interface GenericDao {

    <T> T getObject(Class<T> cls, Long id);

    Long saveObject(Object entity);

    void updateObject(Object entity);

    void saveOrUpdateObject(Object entity);

    <T> void deleteObject(Class<T> cls, Long id);

    int execSqlUpdate(String sqlStr, Object[] params);

    Session getCurrentSession();

}

 

package com.huajtech.dao;

import org.hibernate.HibernateException;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate4.HibernateCallback;
import org.springframework.orm.hibernate4.HibernateTemplate;
import org.springframework.stereotype.Repository;

@Repository(value = "genericDao")
public class GenericDaoImpl implements GenericDao {

    @Autowired
    private HibernateTemplate hibernateTemplate;

    @Override
    public Long saveObject(Object entity) {
        Long entityId = (Long) hibernateTemplate.save(entity);
        hibernateTemplate.flush();
        return entityId;
    }

    @Override
    public void updateObject(Object entity) {
        hibernateTemplate.update(entity);
        hibernateTemplate.flush();
    }

    @Override
    public <T> void deleteObject(Class<T> cls, Long id) {
        T obj = hibernateTemplate.get(cls, id);
        if (null != obj) {
            hibernateTemplate.delete(obj);
        }
        hibernateTemplate.flush();
    }

    @Override
    public void saveOrUpdateObject(Object entity) {
        hibernateTemplate.saveOrUpdate(entity);
        hibernateTemplate.flush();
    }

    @Override
    public <T> T getObject(Class<T> cls, Long id) {
        return hibernateTemplate.get(cls, id);
    }

    @Override
    public int execSqlUpdate(final String sqlStr, final Object[] params) {
        return hibernateTemplate.execute(new HibernateCallback<Integer>() {
            @Override
            public Integer doInHibernate(Session session) throws HibernateException {
                SQLQuery query = session.createSQLQuery(sqlStr);
                if (params != null && params.length > 0) {
                    for (int i = 0; i < params.length; i++) {
                        query.setParameter(i, params[i]);
                    }
                }
                Integer resultCount = query.executeUpdate();
                hibernateTemplate.flush();
                return resultCount;
            }
        });
    }

    @Override
    public Session getCurrentSession() {
        return hibernateTemplate.getSessionFactory().getCurrentSession();
    }

}

 3. service类

package com.huajtech.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.huajtech.dao.GenericDao;
import com.huajtech.vo.Comment;
import com.huajtech.vo.Topic;

@Service
public class TopicServiceImpl {

    @Autowired
    private GenericDao genericDao;

    @Transactional(propagation = Propagation.REQUIRED)
    public synchronized void saveComment(Long topicId, String commentName) {
        System.out.println(Thread.currentThread() + "=======================" + genericDao.getCurrentSession().hashCode());
        Topic topic = genericDao.getObject(Topic.class, topicId);
        Comment comment = new Comment();
        comment.setName(commentName);
        comment.setTopic(topic);
        genericDao.saveObject(comment);
        System.out.println(topic.getCommentCount() + ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
        topic.setCommentCount(topic.getCommentCount() + 1L);
        // 如果改成如下的方式就可以正确的计数...
        // genericDao.execSqlUpdate("update tbl_topic set comment_count=comment_count+1 where id=?",
        // new Object[] {topicId});

        genericDao.saveObject(topic);

        System.out.println(topic.getCommentCount() + "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");
        System.out.println(Thread.currentThread() + "----------------------" + genericDao.getCurrentSession().hashCode());
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void saveTopic() {
        Topic topic = new Topic();
        topic.setName("话题1");
        genericDao.saveObject(topic);
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void deleteRecord() {
        genericDao.execSqlUpdate("delete from tbl_comment", null);
        // genericDao.execSqlUpdate("delete from tbl_topic", null);
        Topic topic = genericDao.getObject(Topic.class, 1L);
        topic.setCommentCount(0L);
    }

}

 

4.测试类:

package com.huajtech.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;

import com.huajtech.service.TopicServiceImpl;

public class TransactionTest {

    public static void main(String[] args) throws InterruptedException {
        ApplicationContext ac = new FileSystemXmlApplicationContext("D:\\dev\\workspaces\\ssh\\src\\spring-hibernate.xml");
        TopicServiceImpl service = (TopicServiceImpl) ac.getBean("topicServiceImpl");

        // service.deleteRecord();
        service.saveTopic();

        Runnable run = new SaveThread(service);

        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        Thread t3 = new Thread(run);
        Thread t4 = new Thread(run);
        Thread t5 = new Thread(run);

        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();

        t1.join();
        t2.join();
        t3.join();
        t4.join();
        t5.join();

    }


    private static class SaveThread implements Runnable {
        private final TopicServiceImpl service;

        public SaveThread(TopicServiceImpl service) {
            super();
            this.service = service;
        }

        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // service.saveComment(1L, "线程" + Thread.currentThread().getId() + "的comment" + i);
                service.saveComment(1L, "" + Thread.currentThread().getId() + "comment" + i);
            }
        }
    }


}

 

5.spring的配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:p="http://www.springframework.org/schema/p" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans  
      http://www.springframework.org/schema/beans/spring-beans-4.1.xsd  
      http://www.springframework.org/schema/context  
      http://www.springframework.org/schema/context/spring-context-4.1.xsd
      http://www.springframework.org/schema/tx
      http://www.springframework.org/schema/tx/spring-tx-4.1.xsd">

    <!-- 启动自动扫描该包下所有的Bean  注意这块,也非常重要 -->
    <context:component-scan base-package="com.huajtech" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
        <context:include-filter type="annotation" expression="org.springframework.beans.factory.annotation.Autowired"/>
        <context:include-filter type="annotation" expression="org.springframework.beans.factory.annotation.Qualifier"/>
    </context:component-scan>

    <!-- 配置数据源 -->
    <bean id="dataSource"
        class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver" />
        <property name="url"
            value="jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=UTF-8" />
        <property name="username" value="root" />
        <property name="password" value="" />
    </bean>

    <!-- 配置hibernate SessionFactory -->
    <bean id="sessionFactory"
        class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
                <prop key="hibernate.hbm2ddl.auto">create</prop>
                <prop key="hibernate.show_sql">true</prop>
                <prop key="hibernate.format_sql">true</prop>
            </props>
        </property>
        <property name="packagesToScan" value="com.huajtech.vo." />
    </bean>

    <bean id="hibernateTemplate" class="org.springframework.orm.hibernate4.HibernateTemplate">
        <property name="sessionFactory">
            <ref bean="sessionFactory" />
        </property>
    </bean>

    <!-- 事务管理器 -->
    <bean id="transactionManager" name="transactionManager"
        class="org.springframework.orm.hibernate4.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory"></property>
    </bean>
    
    <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>

</beans>  

 

测试结果如下:

Thread[Thread-3,5,main]=======================731806090
Hibernate: 
    select
        topic0_.id as id1_0_,
        topic0_.comment_count as comment2_1_0_,
        topic0_.name as name1_0_ 
    from
        tbl_topic topic0_ 
    where
        topic0_.id=?
Hibernate: 
    insert 
    into
        tbl_comment
        (name, topic) 
    values
        (?, ?)
0>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Hibernate: 
    update
        tbl_topic 
    set
        comment_count=?,
        name=? 
    where
        id=?
1<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
Thread[Thread-3,5,main]----------------------731806090
Thread[Thread-5,5,main]=======================1134383678
Hibernate: 
    select
        topic0_.id as id1_0_,
        topic0_.comment_count as comment2_1_0_,
        topic0_.name as name1_0_ 
    from
        tbl_topic topic0_ 
    where
        topic0_.id=?
Hibernate: 
    insert 
    into
        tbl_comment
        (name, topic) 
    values
        (?, ?)
0>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Hibernate: 
    update
        tbl_topic 
    set
        comment_count=?,
        name=? 
    where
        id=?
1<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
Thread[Thread-5,5,main]----------------------1134383678
Thread[Thread-6,5,main]=======================1317455901
Hibernate: 
    select
        topic0_.id as id1_0_,
        topic0_.comment_count as comment2_1_0_,
        topic0_.name as name1_0_ 
    from
        tbl_topic topic0_ 
    where
        topic0_.id=?
Hibernate: 
    insert 
    into
        tbl_comment
        (name, topic) 
    values
        (?, ?)
1>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Hibernate: 
    update
        tbl_topic 
    set
        comment_count=?,
        name=? 
    where
        id=?
2<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
Thread[Thread-6,5,main]----------------------1317455901

 

要求:自己要实现的一个功能,如果给给comment插入一条记录的话,那么就更新这条comment对应的topic的记录保存的comment的总数。(为测试代码,我只更新topic记录为1的记录)

 

结果:总数总是统计的不正确。

 

自己的分析过程:

1.第一次写没有加同步,所以mysql数据库报死锁,这个是肯定的,然后加了同步之后,不发生死锁了。

2.加了同步之后 ,不会出现死锁,但仍然记录不正确,日志中前两个线程打印出来的日志就可以看到,第一个线程已经把计数更新为1了(数据库真的更新了么?我就不知道了),然后第一个线程查询的话,还是之前的0。

3.我怀疑就是mysql事务的问题,此时我把service中的方法注释的放开(让hibernate直接执行原始的mysql)这下就好了。

4.如果是mysql事务的问题的话,那用原生态的sql更新记录的话,仍然会出现不一致的问题。

5.会不会同一个session,这样hibernate取的就是内存中的数据了,但是通过sql的语句就知道每次都是去数据差,并且打印的session都不一样。

6.myql的缓存池?也不对啊,用hibernate的save方法,也看到了它执行的sql,跟原生态的区别不大。

7.分析到这里就真不会了。

 

PS:本来想上传工程的,发现依赖的包比较多,我想大家估计也不会下载,所以就没上传。

 

求各位大神如果知道原因的话,麻烦告诉小弟,小弟将感激涕零啊。哭

发表评论

邮箱地址不会被公开。 必填项已用*标注

昵称 *