- 环境
- Spring v3.0.2
- Hibernate V3.3.1
- MySql v5.0.22
- JDK 1.6.x
drop database if exists `springlearn`;
create database `springlearn`;
use `springlearn`;
create table `springlearn`.`spring_seq_no` (
seq_no int(20) not null primary key,
table_code varchar(20) not null
)
ENGINE=INNODB
DEFAULT CHARSET=utf8;
create table `springlearn`.`spring_product` (
product_id int(20) not null primary key,
product_name varchar(20) not null
)
ENGINE=INNODB
DEFAULT CHARSET=utf8;
- 参照http://wangxiangblog.blogspot.com/2010/04/hibernate-tools-with-eclipse.html 自动生成域对象
package com.spring.jdbc.dao.domain;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
/**
* SpringSeqNo generated by hbm2java
*/
@Entity
@Table(name = "spring_seq_no", catalog = "springlearn")
public class SpringSeqNo implements java.io.Serializable {
private static final long serialVersionUID = -198923549321643996L;
private Long seqNo;
private String tableCode;
public SpringSeqNo() {
}
public SpringSeqNo(Long seqNo, String tableCode) {
this.seqNo = seqNo;
this.tableCode = tableCode;
}
@Id
@Column(name = "seq_no", unique = true, nullable = false)
public Long getSeqNo() {
return this.seqNo;
}
public void setSeqNo(Long seqNo) {
this.seqNo = seqNo;
}
@Column(name = "table_code", nullable = false, length = 20)
public String getTableCode() {
return this.tableCode;
}
public void setTableCode(String tableCode) {
this.tableCode = tableCode;
}
public String toString(){
return "SpringSeqNo{seqNo="+seqNo+",tableCode="+tableCode+"}";
}
}
--------------------------------------------------------------------------------------------
// Generated Oct 10, 2010 9:45:03 AM by Hibernate Tools 3.2.4.GA
package com.spring.jdbc.dao.domain;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
/**
* SpringProduct generated by hbm2java
*/
@Entity
@Table(name = "spring_product", catalog = "springlearn")
public class SpringProduct implements java.io.Serializable {
private static final long serialVersionUID = -4565317465342411183L;
private Long productId;
private String productName;
public SpringProduct() {
}
public SpringProduct(Long productId, String productName) {
this.productId = productId;
this.productName = productName;
}
@Id
@Column(name = "product_id", unique = true, nullable = false)
public Long getProductId() {
return this.productId;
}
public void setProductId(Long productId) {
this.productId = productId;
}
@Column(name = "product_name", nullable = false, length = 20)
public String getProductName() {
return this.productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public String toString() {
return "SpringProduct{productId=" + productId + ",productName="
+ productName + "}";
}
}
- 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:component-scan base-package="com.spring.jdbc" />
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/springlearn" />
<property name="username" value="root" />
<property name="password" value="" />
<!-- 连接池启动时的初始值 -->
<property name="initialSize" value="5" />
<!-- 连接池的最大值 -->
<property name="maxActive" value="500" />
<!-- 最大空闲值.当经过一个高峰时间后,连接池可以慢慢将已经用不到的连接慢慢释放一部分,一直减少到maxIdle为止 -->
<property name="maxIdle" value="10" />
<!-- 最小空闲值.当空闲的连接数少于阀值时,连接池就会预申请去一些连接,以免洪峰来时来不及申请 -->
<property name="minIdle" value="5" />
</bean>
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="annotatedClasses">
<list>
<value>
com.spring.jdbc.dao.domain.SpringProduct
</value>
<value>
com.spring.jdbc.dao.domain.SpringSeqNo
</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
<prop key="hibernate.show_sql">false</prop>
<prop key="hibernate.current_session_context">thread</prop>
</props>
</property>
</bean>
<bean id="txManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory" />
</bean>
<tx:annotation-driven transaction-manager="txManager" />
</beans>
无需传统的hibernate.cfg.xml
- 源代码
import com.spring.jdbc.dao.domain.SpringProduct;
public interface IProductDao {
public void saveProduct(SpringProduct product) ;
public Long getSeqNo(String tableCode, int increment);
}
--------------------------------------------------------------------------------------
package com.spring.jdbc.dao.impl;
import javax.annotation.Resource;
import org.hibernate.Query;
import org.hibernate.SessionFactory;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import com.spring.jdbc.dao.IProductDao;
import com.spring.jdbc.dao.domain.SpringProduct;
import com.spring.jdbc.dao.domain.SpringSeqNo;
@Repository
@Transactional
public class ProductDao implements IProductDao {
@Resource
private SessionFactory sessionFactory;
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
private static final int FIRST_SEQ_NUM = 1;// init No
private static final String SQL_GET_SEQ_NUM = "select t from SpringSeqNo as t where t.tableCode=:table_code";
private static final String SQL_UPDATE_SEQ_NUM = "update SpringSeqNo set seqNo=seqNo+:increment where tableCode=:table_code";
@Override
@Transactional
public void saveProduct(SpringProduct product) {
Long currSeqNo = getSeqNo("PRODUCT", 1);//取下一个ID,该值由表spring_seq_no存储
product.setProductId(currSeqNo);
sessionFactory.getCurrentSession().save(product);
System.out.println("save " + product);
}
@Override
@Transactional
public synchronized Long getSeqNo(String tableCode, int increment) {
SpringSeqNo result = null;
int row = 0;
// Important, update the table first to lock the record.
Query queryUpdate = sessionFactory.getCurrentSession().createQuery(
SQL_UPDATE_SEQ_NUM); //先update数据库加上锁,而不是先查!!
queryUpdate.setString("table_code", tableCode);
queryUpdate.setInteger("increment", increment);
row = queryUpdate.executeUpdate();
if (row == 0) {
// row=0 means it is the first time to get seq no.
// insert a new record which seq_no=1
SpringSeqNo seqNo = new SpringSeqNo();
seqNo.setTableCode(tableCode);
seqNo.setSeqNo(Long.valueOf(increment + FIRST_SEQ_NUM));
sessionFactory.getCurrentSession().save(seqNo);
result = new SpringSeqNo();
result.setTableCode(tableCode);
result.setSeqNo(Long.valueOf(FIRST_SEQ_NUM));
} else {
/** get current sequence no. */
Query query = sessionFactory.getCurrentSession().createQuery(
SQL_GET_SEQ_NUM);
query.setString("table_code", tableCode);
SpringSeqNo temp = (SpringSeqNo) query.setMaxResults(1)
.uniqueResult();
result = new SpringSeqNo();
result.setTableCode(tableCode);
result.setSeqNo(temp.getSeqNo() - increment);
}
return result.getSeqNo();
}
}
----------------------------------------------------------------------------------------------------
写一个多线程测试看是否可正确取到sequence number
package com.spring.jdbc.test;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.spring.jdbc.dao.IProductDao;
import com.spring.jdbc.dao.domain.SpringProduct;
public class ProductProcessor extends Thread {
private int number = 100;
AbstractApplicationContext ctx = new ClassPathXmlApplicationContext(
"spring.xml");
public ProductProcessor() {
}
public void run() {
while (number > 0) {
IProductDao prodDao = (IProductDao) ctx.getBean("productDao");
String threadName = Thread.currentThread().getName();
SpringProduct product = new SpringProduct();
product.setProductName(threadName);
prodDao.saveProduct(product);
number--;
}
}
}
-----------------------------------------
package com.spring.jdbc.test;
public class Tester {
public static void main(String[] args) {
// TODO Auto-generated method stub
Thread t1 = new ProductProcessor();
Thread t2 = new ProductProcessor();
Thread t3 = new ProductProcessor();
Thread t4 = new ProductProcessor();
Thread t5 = new ProductProcessor();
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
注意:
先在spring_seq_no给一个初始值,
insert into spring_seq_no(seq_no,table_code) value (1,'PRODUCT');
运行Tester.java,可以看到每个线程可以拿到正确的ID.
如果不给初始值, 会碰到这个异常。
Caused by: java.sql.BatchUpdateException: Deadlock found when trying to get lock; try restarting transaction
at com.mysql.jdbc.PreparedStatement.executeBatchSerially(PreparedStatement.java:1684)
at com.mysql.jdbc.PreparedStatement.executeBatch(PreparedStatement.java:1108)
at org.apache.commons.dbcp.DelegatingStatement.executeBatch(DelegatingStatement.java:297)
at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:70)
at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:268)
在ProductDao.java中的saveProduct()和getSeqNo()中任意位置抛出RuntimeException,事务会回滚;两个方法中的 sessionFactory.getCurrentSession() 对应的session是同一个session,事务也是同一个事务。
以上代码也在Oralce 10g环境下测试,可得同样的结果。
即便不用spring来管理事务,而是通过hibernate单独处理,也可得同样的结果。
No comments:
Post a Comment