在用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:本来想上传工程的,发现依赖的包比较多,我想大家估计也不会下载,所以就没上传。
求各位大神如果知道原因的话,麻烦告诉小弟,小弟将感激涕零啊。