Hibernate在高并发的情况下的一个问题 博客分类: hibernate hibernate多线程
程序员文章站
2024-03-24 20:33:52
...
在用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&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:本来想上传工程的,发现依赖的包比较多,我想大家估计也不会下载,所以就没上传。
求各位大神如果知道原因的话,麻烦告诉小弟,小弟将感激涕零啊。