之前讲过纯粹的Hibernate开发,但是需要手动创建SessionFactory实例。在实际开发中,应该利用Spring容器声明式管理的特点,以配置文件来管理SessionFactory实例。Spring的IoC容器不仅能以声明式的方式配置SessionFactory实例,也可充分利用IoC容器的作用,为SessionFactory注入数据源引用。以下讲解的是Spring整合Hibernate的示例,从而理清整个过程具体步骤。
我接下来举的例子就是有一家餐厅,假设每个来吃饭的用户都会带一张充值卡来消费,然后根据菜单点菜,点完之后要进行刷卡,如果正常则保存余额,如果菜品不足或者余额不足则会进行提醒。那么从该例子可以看出持久化实体类有菜品和卡账户相关信息。
1.持久化类的创建并完成映射
1.1.持久化类的创建
何谓持久化类,简单来说就是要映射成数据库的对象,该例子涉及到数据操作的是菜品数量变化,以及消费卡账户余额的变化。所以接下来进行持久化类的创建,分别是Food类和Customer类。
package spring.hibernate.plugin.entity;
public class Food {
private int id;
private String foodName;
private String identi;
private float price;
private int stock;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getFoodName() {
return foodName;
}
public void setfoodName(String foodName) {
this.foodName = foodName;
}
public String getIdenti() {
return identi;
}
public void setIdenti(String identi) {
this.identi = identi;
}
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;
}
}
package spring.hibernate.plugin.entity;
public class Customer {
private int id;
private String customername;
private float balance;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getCustomername() {
return customername;
}
public void setCustomername(String customer) {
this.customername = customer;
}
public float getBalance() {
return balance;
}
public void setBalance(float balance) {
this.balance = balance;
}
}
1.2.映射配置--hbm.xml文件
接下来对持久化类进行映射,这里采用*.hbm.xml文件分别对每个类进行映射配置,配置文件如下:
Food.hbm.xml:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="spring.hibernate.plugin.entity.Food" table="FOOD_MENU">
<id name="id" type="int">
<column name="ID" />
<generator class="native" />
</id>
<property name="foodName" type="java.lang.String">
<column name="FOODNAME" />
</property>
<property name="identi" type="java.lang.String">
<column name="IDENTI" />
</property>
<property name="price" type="float">
<column name="PRICE" />
</property>
<property name="stock" type="int">
<column name="STOCK" />
</property>
</class>
</hibernate-mapping>
Customer.hbm.xml:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="spring.hibernate.plugin.entity.Customer" table="CUSTOMER">
<id name="id" type="int">
<column name="ID" />
<generator class="native" />
</id>
<property name="customername" type="java.lang.String">
<column name="CUSTOMERNAME" />
</property>
<property name="balance" type="float">
<column name="BALANCE" />
</property>
</class>
</hibernate-mapping>
其实,配置文件比较简单,根据模板修改即可,主要是对持久化类属性的配置。
1.3.hibernate配置文件-hibernate.cfg.xml
这里的hibernate配置文件有别于纯粹的hibernate开发,这里只需要一些hibernate基本配置即可,并不需要跟纯粹的hibernate开发那样将hbm.xml文件配置其中,注意,整合开发的hbm.xml是和hibernate.cfg.xml文件一起配置到Spring容器之中的,整合开发将Spring容器的工厂作用发挥的淋漓尽致。以下是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>
<property name="hibernate.temp.use_jdbc_metadata_defaults">false</property>
<property name="hibernate.current_session_context_class">org.springframework.orm.hibernate4.SpringSessionContext</property>
</session-factory>
</hibernate-configuration>
注意:上面配置文件中"hibernate.temp.use_jdbc_metadata_defaults"和"hibernate.current_session_context_class"这两个属性的配置最好添加,在有的环境没有这两个属性则会报错,所以建议一开始就配置好这两个属性。
2.Spring容器的配置文件applicationContext.xml文件
<?xml version="1.0" encoding="GBK"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<!-- 配置注解自动扫描的包 -->
<context:component-scan base-package="spring.hibernate.plugin"></context:component-scan>
<!-- 定义数据源Bean,使用C3P0数据源实现,并注入数据源的必要信息 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close"
p:driverClass="org.gjt.mm.mysql.Driver"
p:jdbcUrl="jdbc:mysql://localhost:3306/spring_hibernate"
p:user="root"
p:password="root"
p:maxPoolSize="40"
p:minPoolSize="2"
p:initialPoolSize="2"
p:maxIdleTime="30"/>
<!-- 定义Hibernate的SessionFactory,SessionFactory需要依赖数据源,注入dataSource -->
<!-- 配置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:spring/hibernate/plugin/entity/*.hbm.xml"></property>
</bean>
</beans>
在纯粹的Hibernate访问中,应用程序需要手动创建SessionFactory,而这里使用的是声明式管理SessionFactory实例,可以把它放进Spring大容器进行管理。目前的applicationContext.xml文件先把持久类配置文件现装入其中,然后将管理Hibernate的SessionFactory实例化,而SessionFactory需要传入数据源,所以在这之前先将数据源进行Bean实例化,并对数据源进行参数设置。这样SessionFactory实例就放入了Spring大容器中进行管理了。
3.DAO组件的基类与实现类以及相应的异常类
3.1.DAO组件的基类与实现类
其实,DAO组件就是一种对数据库进行CRUD方法提取的基类。这里包含了各种操作方法,为了后面传给Service层。
package spring.hibernate.plugin.dao;
public interface RestaurantDao {
/**
* 根据菜单上的菜品号获取食物价格
*/
public float findPriceByIdenti(String identi);
/**
* 更新书的库存,使菜品号对应的书本减少n份
*
*/
public void updateFoodStock(String identi,int n);
/**
* 更新账户余额,使顾客所带钱减少 price*n
*/
public void updateAccount(String customername,float price,int n);
}
package spring.hibernate.plugin.dao.impl;
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 spring.hibernate.plugin.dao.RestaurantDao;
import spring.hibernate.plugin.exception.*;
/**
* 用hql进行数据库操作
* @author carson0408
*
*/
//dao组件用@Repository注释
@Repository
public class RestaurantDaoImpl implements RestaurantDao {
//自动装配,省去了setter方法
@Autowired
private SessionFactory sessionfactory;
/**
* 连接当前线程,使用SessionFactory的getCurrentSession()方法。
* @return
*/
private Session getSession(){
return sessionfactory.openSession();
}
@Override
public float findPriceByIdenti(String identi) {
// TODO Auto-generated method stub
String hql="select f.price from Food f where f.identi=:a";
float price=(float) getSession().createQuery(hql).setString("a", identi).uniqueResult();
return price;
}
@Override
public void updateFoodStock(String identi, int n) {
// TODO Auto-generated method stub
String hq="Select f.stock from Food f where f.identi=:identi";
int stock=(int) getSession().createQuery(hq).setString("identi", identi).uniqueResult();
if(stock < n){
throw new OutofStockException("so sorry,this food is sold out");
}
String hql="update Food f set f.stock=f.stock-:a where f.identi=:b";
getSession().createQuery(hql).setInteger("a", n).setString("b", identi).executeUpdate(); //更新事物剩余量
}
@Override
public void updateAccount(String customername, float price, int n) {
// TODO Auto-generated method stub
//验证客户所持有的饭店支付卡余额是否足够
float cost=n*price;
String hql="select c.balance from Customer c where c.customername=:a";
float balance=(float) getSession().createQuery(hql).setString("a", customername).uniqueResult();
if(balance < cost){
throw new OutofBalanceException("sorry,your card's money is not enough to pay for this food");
}
String hql1="update Customer c set c.balance=c.balance-:a where c.customername=:b";
getSession().createQuery(hql1).setFloat("a", cost).setString("b", customername).executeUpdate();
}
}
由上DAO实现类可知,对数据库进行操作需要用到Session,所以需要开启Session,在applicationContext.xml中已经将SessionFactory实例化,说明已经在Spring中管理,所以利用Spring的自动装配,用@Autowired进行修饰SessionFactory,相当于setter方法,这里利用了自动装配可以省略setter方法。然后利用SessionFactory的openSession获取Session,然后进行操作。这里的操作都使用hql查询。这里的DAO类用@Repository注释。
3.2.异常类
上面DAO实现类中出现了异常,以下是DAO组件中用到的异常类:
package spring.hibernate.plugin.exception;
public class OutofStockException extends RuntimeException {
public OutofStockException() {
// TODO Auto-generated constructor stub
}
public OutofStockException(String arg0) {
super(arg0);
// TODO Auto-generated constructor stub
}
}
package spring.hibernate.plugin.exception;
public class OutofBalanceException extends RuntimeException {
public OutofBalanceException() {
// TODO Auto-generated constructor stub
}
public OutofBalanceException(String message) {
super(message);
// TODO Auto-generated constructor stub
}
}
以上两个异常分别是菜品不足和余额不足,对应的是菜品对象以及客户对象。
4.Service层
Service层主要是针对具体功能进行实际操作,比如进行实际的点菜消费等等。这里主要是指消费功能。
package spring.hibernate.plugin.service;
public interface RestaurantService {
public void cost(String username,String identi,int n);
}
实现Service:
package spring.hibernate.plugin.service.impl;
import spring.hibernate.plugin.service.RestaurantService;
import spring.hibernate.plugin.dao.*;
import org.springframework.stereotype.*;
import org.springframework.beans.factory.annotation.Autowired;
@Service
public class RestaurantServiceImpl implements RestaurantService {
@Autowired
private RestaurantDao restaurantDao;
@Override
public void cost(String username, String identi, int n) {
// TODO Auto-generated method stub
float price=restaurantDao.findPriceByIdenti(identi);
restaurantDao.updateFoodStock(identi, n);
restaurantDao.updateAccount(username, price, n);
}
}
5.事务配置以及AOP配置
对其进行事务配置,比如出现异常之后要进行回滚。并且对每一个操作都进行增强处理,以下是完整的applicationContext.xml文件
<?xml version="1.0" encoding="GBK"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<!-- 配置注解自动扫描的包 -->
<context:component-scan base-package="spring.hibernate.plugin"></context:component-scan>
<!-- 定义数据源Bean,使用C3P0数据源实现,并注入数据源的必要信息 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close"
p:driverClass="org.gjt.mm.mysql.Driver"
p:jdbcUrl="jdbc:mysql://localhost:3306/spring_hibernate"
p:user="root"
p:password="root"
p:maxPoolSize="40"
p:minPoolSize="2"
p:initialPoolSize="2"
p:maxIdleTime="30"/>
<!-- 定义Hibernate的SessionFactory,SessionFactory需要依赖数据源,注入dataSource -->
<!-- 配置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:spring/hibernate/plugin/entity/*.hbm.xml"></property>
</bean>
<!-- 配置Hibernate的局部事务管理器,使用HibernateTransactionManager类 -->
<!-- 该类是PlatformTransactionManager接口针对采用Hibernate的特定实现类 -->
<!-- 配置HibernateTransactionManager需依赖注入SessionFactory -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate4.HibernateTransactionManager"
p:sessionFactory-ref="sessionFactory"/>
<!-- 配置事务增强处理Bean,指定事务管理器 -->
<tx:advice id="txAdvice"
transaction-manager="transactionManager">
<!-- 用于配置详细的事务定义 -->
<tx:attributes>
<!-- 所有以'get'开头的方法是read-only的 -->
<tx:method name="get*" read-only="true"/>
<!-- 其他方法使用默认的事务设置,指定超时时长为5秒 -->
<tx:method name="*" isolation="DEFAULT"
propagation="REQUIRED" timeout="5"/>
</tx:attributes>
</tx:advice>
<!-- AOP配置的元素 -->
<aop:config>
<!-- 配置一个切入点 -->
<aop:pointcut id="myPointcut" expression="execution(* com.elgin.spring.hibernate.service.*.*(..))"/>
<!-- 指定在myPointcut切入点应用txAdvice事务增强处理 -->
<aop:advisor advice-ref="txAdvice"
pointcut-ref="myPointcut"/>
</aop:config>
<!-- 启动@AspectJ支持 -->
<aop:aspectj-autoproxy/>
</beans>
6.测试
以下便是测试代码:
package spring.hibernate.plugin.test;
import spring.hibernate.plugin.service.*;
import org.springframework.beans.factory.annotation.*;
import org.springframework.stereotype.Repository;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
import org.junit.Test;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class PluginTest {
@Autowired
private RestaurantService restaurantService;
@Test
public void testCost(){
restaurantService.cost("carson", "1102", 2);
}
}
右键选择Run as->Run configurations然后选择Junit,这里将项目进行Junit测试。测试代码上需要有@RunWith(SpringJUnit4ClassRunner.class)和@ContextConfiguration("classpath:applicationContext.xml") 两个注释,运行之后,刚开始若没有建表,相应的数据库中会生成两个相应的表,同时报错。这是因为没法对空表进行相应操作。所以对food_menu和customer两个表添加数据如下:
数据库spring_hibernate:
food_menu:
customer:
然后再次进行测试,得到如下结果:
由上面测试例子可以知道,客户"carson"买了两个菜品号为1102的食物,所以库存变为0,客户余额为60元,底层hibernate对数据库的操作代码如下:
Hibernate:
select
food0_.PRICE as col_0_0_
from
FOOD_MENU food0_
where
food0_.IDENTI=?
Hibernate:
select
food0_.STOCK as col_0_0_
from
FOOD_MENU food0_
where
food0_.IDENTI=?
Hibernate:
update
FOOD_MENU
set
STOCK=STOCK-?
where
IDENTI=?
Hibernate:
select
customer0_.BALANCE as col_0_0_
from
CUSTOMER customer0_
where
customer0_.CUSTOMERNAME=?
Hibernate:
update
CUSTOMER
set
BALANCE=BALANCE-?
where
CUSTOMERNAME=?
接下来为了检测异常功能,将测试代码更改如下:
package spring.hibernate.plugin.test;
import spring.hibernate.plugin.service.*;
import org.springframework.beans.factory.annotation.*;
import org.springframework.stereotype.Repository;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
import org.junit.Test;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class PluginTest {
@Autowired
private RestaurantService restaurantService;
@Test
public void testCost(){
restaurantService.cost("carson", "1101", 2); //1
restaurantService.cost("adam", "1102", 2);//2
}
}
分别测试1和2,分别得到报错:
spring.hibernate.plugin.exception.OutofBalanceException: sorry,your card's money is not enough to pay for this food和spring.hibernate.plugin.exception.OutofStockException: so sorry,this food is sold out并且当余额不足时不能购买,则余额不会改变,而东西卖完之后报错,也不会改变,说明了事务回滚到之前状态。
有需要完整代码的可以点击下面链接下载: