Monday, October 11, 2010

Spring v3.0.2 Learning Note 12 - Integrate with Hiberate

与hibernate的集成
  • 环境
  1. Spring v3.0.2
  2. Hibernate V3.3.1
  3. MySql v5.0.22
  4. 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 自动生成域对象
// 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;

/**
 * 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 + "}";
    }
}

  • 配置文件
Spring配置文件内容为:
<?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

  • 源代码
package com.spring.jdbc.dao;

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