1. Spring整合Hibernate,主要是解决什么问题?
a、让Spring提供的IOC容器来管理Hibernate的SessionFactory
b、让Hibernate使用Spring提供的声明式事物
2. 整合步骤:
新建一个Java工程,并新建用来保存依赖jar包的lib目录
①. 加入Hibernate支持:
1. 加入Hibernate的required的jar包:
antlr-2.7.7.jar
dom4j-1.6.1.jar
hibernate-commons-annotations-4.0.2.Final.jar
hibernate-core-4.2.5.Final.jar
hibernate-jpa-2.0-api-1.0.1.Final.jar
javassist-3.15.0-GA.jar
jboss-logging-3.1.0.GA.jar
jboss-transaction-api_1.1_spec-1.0.1.Final.jar
2.加入MySQL与C3p0连接池支持:
c3p0-0.9.2.1.jar
mchange-commons-java-0.2.3.4.jar
mysql-connector-java-5.1.7-bin.jar
注意:jar包加入之后,需要全部选中,右键-》BuildPath,将其导入
3.添加Hibernate的配置文件(hibernate.cfg.xml):
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <!-- 配置hibernate基本信息 --> <!-- 1.数据源配置在IOC容器中,此处不需要额外配置 --> <!-- 2.关联的.hbm.xml文件也在IOC容器配置SessionFactory时配置 --> <!-- 3.此处配置hibernate的基本信息:数据库方言、SQL显示及格式化,及生成数据表的策略,二级缓存等 --> <property name="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property> <property name="hibernate.show_sql">true</property> <property name="hibernate.format_sql">true</property> <property name="hibernate.hbm2ddl.auto">update</property> </session-factory> </hibernate-configuration>
4. 创建测试使用的持久化类以及生成Hibernate的映射文件
本例所用到的持久化类以其映射文件如下:
public class Book { private int id; private String bookName; private String isbn; private float price; private int stock; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getBookName() { return bookName; } public void setBookName(String bookName) { this.bookName = bookName; } public String getIsbn() { return isbn; } public void setIsbn(String isbn) { this.isbn = isbn; } public float getPrice() { return price; } public void setPrice(float price) { this.price = price; } public int getStock() { return stock; } public void setStock(int stock) { this.stock = stock; } }
public class Account { private int id; private String username; private float balance; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public float getBalance() { return balance; } public void setBalance(float balance) { this.balance = balance; } }
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <!-- Generated 2015-11-15 21:48:02 by Hibernate Tools 3.4.0.CR1 --> <hibernate-mapping> <class name="com.elgin.spring.hibernate.entity.Book" table="SH_BOOK"> <id name="id" type="int"> <column name="ID" /> <generator class="native" /> </id> <property name="bookName" type="java.lang.String"> <column name="BOOKNAME" /> </property> <property name="isbn" type="java.lang.String"> <column name="ISBN" /> </property> <property name="price" type="float"> <column name="PRICE" /> </property> <property name="stock" type="int"> <column name="STOCK" /> </property> </class> </hibernate-mapping>
<?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <!-- Generated 2015-11-15 21:48:02 by Hibernate Tools 3.4.0.CR1 --> <hibernate-mapping> <class name="com.elgin.spring.hibernate.entity.Account" table="SH_ACCOUNT"> <id name="id" type="int"> <column name="ID" /> <generator class="native" /> </id> <property name="username" type="java.lang.String"> <column name="USERNAME" /> </property> <property name="balance" type="float"> <column name="BALANCE" /> </property> </class> </hibernate-mapping>
②. 加入Spring支持:
1. 加入Spring的required的jar包:
com.springsource.net.sf.cglib-2.2.0.jarcom.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
commons-logging-1.1.3.jar
spring-aop-4.1.0.RELEASE.jar
spring-aspects-4.1.0.RELEASE.jar
spring-beans-4.1.0.RELEASE.jar
spring-context-4.1.0.RELEASE.jar
spring-core-4.1.0.RELEASE.jar
spring-expression-4.1.0.RELEASE.jar
spring-jdbc-4.1.0.RELEASE.jar
spring-orm-4.1.0.RELEASE.jar
spring-test-4.1.0.RELEASE.jar
spring-tx-4.1.0.RELEASE.jar
spring-web-4.1.0.RELEASE.jar
spring-webmvc-4.1.0.RELEASE.jar
同样需要BuildPath!
2.加入Spring的配置文件:applicationContext.xml
applicationContext.xml 配置内容:
<?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:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd"> <!-- 配置注解自动扫描的包 --> <context:component-scan base-package="com.elgin.spring.hibernate"></context:component-scan> <!-- 配置数据源 --> <!-- 导入资源文件 --> <context:property-placeholder location="classpath:db.properties"/> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="user" value="${jdbc.user}"></property> <property name="password" value="${jdbc.password}"></property> <property name="driverClass" value="${jdbc.driverClass}"></property> <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property> <property name="initialPoolSize" value="${jdbc.initPoolSize}"></property> <property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property> </bean> <!-- 配置Hibernate的SessionFactory,通过spring提供的 LocalSessionFactoryBean配置--> <bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"> <!-- 配置依赖的数据源属性 --> <property name="dataSource" ref="dataSource"></property> <!-- hibernate 配置文件的路径 --> <property name="configLocation" value="classpath:hibernate.cfg.xml"></property> <!-- 配置hibernate映射文件的路径,可以使用通配符 --> <property name="mappingLocations" value="classpath:com/elgin/spring/hibernate/entity/*.hbm.xml"></property> </bean> <!-- 配置 Spring 的声明式事物 --> <bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory"></property> </bean> <!-- 配置事物属性 ,需要事物管理器--> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="get*" read-only="true"/> <tx:method name="purchase" propagation="REQUIRES_NEW"/> <tx:method name="*"/> </tx:attributes> </tx:advice> <!-- 配置事物切点,并把事物属性和切点关联起来 --> <aop:config> <aop:pointcut expression="execution(* com.elgin.spring.hibernate.service.*.*(..))" id="txPointcut"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/> </aop:config> </beans>依赖的数据库配置文件db.properties:
jdbc.user=root jdbc.password=root123 jdbc.driverClass=com.mysql.jdbc.Driver jdbc.jdbcUrl=jdbc:mysql://localhost:3306/spring_hibernate jdbc.initPoolSize=5 jdbc.maxPoolSize=10至此,Spring的配置、Hibernate的配置、以及二者整合的相关配置都配置完毕。下面通过代码测试。
3.整合测试:
工程目录:
新建上述各个目录:
1. 在dao下面新建接口:
public interface BookShopDao { /** * 根据书号获取书的单价 * @param isbn * @return */ public float findPriceByIsbn(String isbn); /** * 更新书的库存,使书号对应的书本减少n本 * @param isbn * @param n */ public void updateBookStock(String isbn,int n); /** * 更新账户余额,使当前账户金额减少 price*n * @param username * @param price * @param n * @throws BalanceNotEnough */ public void updateAccount(String username,float price,int n); }对应的在impl下完成上述接口的实现类(注解@Repository,用来标注持久层,同时表示此组件交给spring的IOC容器管理)
在使用spring的相关注解时,需要在spring的配置文件中开启注解自动扫描:
<!-- 配置注解自动扫描的包 --> <context:component-scan base-package="com.elgin.spring.hibernate"/>
import org.hibernate.Query; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; import com.elgin.spring.hibernate.dao.BookShopDao; import com.elgin.spring.hibernate.exception.BalanceNotEnough; import com.elgin.spring.hibernate.exception.BookStockNotEnoughException; @Repository public class BookShopDaoImpl implements BookShopDao { @Autowired private SessionFactory sessionfactory; /** * 获取与当前线程绑定的session * @return */ private Session getSession(){ return sessionfactory.getCurrentSession(); } @Override public float findPriceByIsbn(String isbn) { String hql="select b.price from Book b where b.isbn=?"; float price=(float) getSession().createQuery(hql).setString(0, isbn).uniqueResult(); return price; } @Override public void updateBookStock(String isbn, int n) { //验证书的库存是否充足 String hq="Select b.stock from Book b where b.isbn=?"; int stock=(int) getSession().createQuery(hq).setString(0, isbn).uniqueResult(); if(stock < n){ throw new BookStockNotEnoughException("库存不足"); } String hql="update Book b set b.stock=b.stock-? where b.isbn=?"; Query query=getSession().createQuery(hql).setInteger(0, n).setString(1, isbn); query.executeUpdate(); } @Override public void updateAccount(String username, float price, int n) { //验证余额是否足够 String hql="select a.balance from Account a where a.username=?"; float balance=(float) getSession().createQuery(hql).setString(0, username).uniqueResult(); if(balance < n*price){ throw new BalanceNotEnough("余额不足"); } String hql1="update Account a set a.balance=a.balance-? where a.username=?"; getSession().createQuery(hql1).setFloat(0, n*price).setString(1, username).executeUpdate(); } }实现类中用到的2个异常类:
public class BalanceNotEnough extends RuntimeException { /** * */ private static final long serialVersionUID = 1L; public BalanceNotEnough(String msg) { super(msg); } }
public class BookStockNotEnoughException extends RuntimeException { /** * */ private static final long serialVersionUID = 1L; public BookStockNotEnoughException(String msg) { super(msg); } }在service包下新建下面的接口:
public interface BookShopService { /** 用户购买n本书 * @param username * @param isbn * @param n */ public void purchase(String username,String isbn,int n); }在service.impl下完成它的实现类(@Service 用来标注service层,表示此组件为service并交给Spring的IOC容器管理;@Autowired 自动注入 ,表示此对象由IOC容器负责初始化并注入):
@Service public class BookShopSeviceImpl implements BookShopService { @Autowired private BookShopDao bookShopDao; /** * Spring Hibernate的事物流程 * 1、方法开始之前 * ①. 获取session * ②. 把Session与当前线程绑定,这样就可以使用sessionFactory的getCurrentSession()方法来获取session了 * ③. 开启事务 * * 2、若方法正常结束,未出现异常,则 * ①.提交事物 * ②.把session与当前线程解除绑定 * ③.关闭session * * 3、若方法执行出现异常,则 * ①.回滚事务 * ②.把session与当前线程解除绑定 * ③.关闭session */ @Override public void purchase(String username, String isbn, int n) { float price=bookShopDao.findPriceByIsbn(isbn); bookShopDao.updateBookStock(isbn, n); bookShopDao.updateAccount(username, price, n); } }编写单元测试类:
若想使用如下配置进行单元测试,必须要加入spring单元测试的jar包:spring-test-4.1.0.RELEASE.jar
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml") public class SpringHibernateTest { @Autowired private BookShopService bookShopService; @Test public void testPurchaseBook(){ bookShopService.purchase("aaa", "java-1122", 2); } }首先,注释掉测试方法中额外的代码,启动单元测试,让Hibernate生成用到的数据表:SH_BOOK 与SH_ACCOUNT
让后在2个表中各加入测试所需要的数据如下图:
运行上述单元测试方法,执行成功,查看数据库,book的库存STOCK变为43,account中的账户余额变为130.
再次运行,程序抛出异常:
com.elgin.spring.hibernate.exception.BalanceNotEnough: 余额不足
查看数据库发现:
book的库存STOCK仍旧为43,account中的账户余额仍旧为130.
这就是spring的事物的作用,程序出现异常,回滚事务,关闭session。
本次整合测试代码: