前三篇隨筆中介紹了 用原生的JDBC訪問數據庫、一種高效的數據庫鏈接池druid、用Spring的JDBC框架訪問數據庫。html
本文繼續介紹第三種數據庫訪問的解決方案:Spring3.2 + Hibernate4.2java
Hibernate是一個開源的ORM框架,能自動爲對象生成相應SQL並透明的持久化對象到數據庫,咱們首先來了解一下什麼是「ORM」。mysql
ORM全稱對象關係映射(Object/Relation Mapping),指將Java對象狀態自動映射到關係數據庫中的數據上,從而提供透明化的持久化支持,即把一種形式轉化爲另外一種形式。
對象與關係數據庫之間是不匹配,咱們把這種不匹配稱爲阻抗失配,主要表如今:spring
1. 關係數據庫首先不支持面向對象技術,如繼承、多態;
2. 關係數據庫是由表來存放數據,而面向對象使用對象來存放狀態;
3. 如何將對象透明的持久化到關係數據庫表中;
4. 若是一個對象存在橫跨多個表的數據,應該有一種策略來對象建模和映射。sql
其中這些阻抗失配只是其中的一小部分,好比還有如何將SQL集合函數結果集映射到對象,如何在對象中處理主鍵等。ORM框架就是用來解決這種阻抗失配,提供關係數據庫的對象化支持。數據庫
目前已經有許多ORM框架產生,如Hibernate、JDO、JPA、iBATIS等等,這些ORM框架各有特點,Spring對這些ORM框架提供了很好的支持。session
接下來,咱們仍是經過一個實際的項目實踐Spring+Hibernate框架訪問數據庫。假設該項目的功能有:保存用戶信息、查詢用戶信息。app
這是一個Maven工程,工程結構、依賴的配置以下:框架
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.26</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>0.2.25</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>3.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>4.2.4.Final</version>
</dependency>
依賴的項目有:Junit;mysql驅動;druid:一款高效的數據庫鏈接池,下面會看到如何應用它;spring-context;spring-orm;hibernate-coreide
CREATE TABLE `user` ( `id` int(10) NOT NULL auto_increment, `name` varchar(30) default NULL, `age` int(3) default NULL, PRIMARY KEY (`id`) )
import java.io.Serializable; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; @Entity @Table(name="user") public class User implements Serializable{ private static final long serialVersionUID = 1724315528421427938L; @Id @GeneratedValue(strategy=GenerationType.IDENTITY) @Column(name = "id", nullable = false) private Long id; @Column(name="name",nullable=true,length=25) private String name; @Column(name="age",nullable=true) private Integer age; //setter getter 略 @Override public String toString() { return "User [id=" + id + ", name=" + name + ", age=" + age + "]"; } }
這個Pojo類和上面的數據庫表對應,咱們採用JPA註解來配置這種對應關係。(JPA是一種規範,經過JDK 5.0註解或XML描述對象-關係表的映射關係)。
applicationContext-dataSource.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" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="url" value="jdbc:mysql://localhost:3306/test" /> <property name="username" value="root" /> <property name="password" value="123456" /> <property name="initialSize" value="1" /> <property name="maxActive" value="20" /> </bean> </beans>
在這,我應用了阿里巴巴開發的一個高效的數據庫鏈接池:druid。
阿里巴巴官網上介紹,druid是目前最好的數據庫鏈接池,在功能、性能、擴展性方面,都超過其餘數據庫鏈接池,包括DBCP、C3P0、BoneCP、Proxool、JBoss DataSource。Druid已經在阿里巴巴部署了超過600個應用,通過一年多生產環境大規模部署的嚴苛考驗。
與druid相關的詳細內容能夠看我以前的一篇隨筆。
applicationContext-hibernate.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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-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"> <import resource="applicationContext-dataSource.xml" /> <bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="packagesToScan" value="edu.shao.springHibernate.po" /> <property name="hibernateProperties"> <props> <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop> <prop key="hibernate.show_sql">true</prop> <prop key="hibernate.format_sql">false</prop> <prop key="hibernate.use_sql_comments">false</prop> <prop key="hibernate.cache.use_second_level_cache">false</prop> </props> </property> </bean> <bean id="userService" class="edu.shao.springHibernate.service.impl.UserService"> <property name="userDao"> <bean class="edu.shao.springHibernate.dao.impl.UserDaoImpl"> <property name="sessionFactory" ref="sessionFactory"></property> </bean> </property> </bean> <!-- 事務管理器 --> <bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory"/> </bean> <tx:annotation-driven transaction-manager="transactionManager"/> </beans>
在這裏用的是基於註解的事務配置方式。用註解的好處,是Service中的方法命名不須要特別規定(基於xml的事務配置方式須要Service的方法命名按照某一自定義規範命名),只須要在Service類上或方法上加上@Transactional註解便可;缺點是沒有作到集中聲明,若是在某個Service層的接口忘記聲明事務,那麼事務就沒法生效。
<tx:annotation-driven transaction-manager="txManager" /> 這句話的做用是註冊事務註解處理器。
若是方法發生運行期異常(RuntimeException),事務會進行回滾;若是發生通常的異常(Exception),事務不進行回滾。
<property name="packagesToScan" value="edu.shao.springHibernate.po" /> 讓Hibernate自動掃描指定的包下面註解的實體類,也就是User類。
AbstractHibernateDao是一個抽象的Dao的父類,實現了通常的保存、刪除、查詢等方法。 具體的Dao類能夠繼承該類,並實現對某一實體特殊的訪問方法。
AbstractHibernateDao類中注入了SessionFactory ,經過方法「sessionFactory.getCurrentSession();」來得到一個session,用此session保存、更新、刪除、查找實體。
這裏由於session.get()方法,都須要傳入一個Class類型的參數,因此定義了entityClass字段,在具體Dao的構造方法中傳入,下面會看到。
import java.io.Serializable; import java.util.List; import org.hibernate.Query; import org.hibernate.Session; import org.hibernate.SessionFactory; public abstract class AbstractHibernateDao <E extends Serializable>{ private Class<E> entityClass; private SessionFactory sessionFactory; protected AbstractHibernateDao(Class<E> entityClass) { this.entityClass = entityClass; } public Session getCurrentSession() { return sessionFactory.getCurrentSession(); } public E findById(Serializable id) { return (E) getCurrentSession().get(entityClass, id); } public void save(E e) { getCurrentSession().save(e); } public void delete(E e) { getCurrentSession().delete(e); } public List<E> query(String hql, Object[] args) { Query query=getCurrentSession().createQuery(hql); if(args!=null){ for (int i = 0; i < args.length; i++) { query.setParameter(i, args[i]); } } return query.list(); } //setter getter }
下面是具體的Dao的接口和實現。
UserDaoImpl在構造參數中傳入了User.class,用於初始化AbstractHibernateDao裏的entityClass字段 。
public interface IUserDao { public void save(User user); public List<User> query(String sql,Object[] args); } public class UserDaoImpl extends AbstractHibernateDao<User> implements IUserDao { public UserDaoImpl() { super(User.class); } }
public interface IUserService { void saveUser(); void saveUserThrowException() throws Exception; void findUsers(); }
import java.util.List; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import edu.shao.springHibernate.dao.IUserDao; import edu.shao.springHibernate.po.User; import edu.shao.springHibernate.service.IUserService; @Transactional public class UserService implements IUserService{ private IUserDao userDao; public void saveUser() { User u1=new User(); u1.setName("邵"); u1.setAge(24); userDao.save(u1); //測試時,能夠添加此異常,或去掉異常,測試Spring對事物的管理 // if(1+1>1){ // throw new RuntimeException("Runtime error...");//拋出運行時異常:RuntimeException // } User u2=new User(); u2.setName("陳"); u2.setAge(20); userDao.save(u2); } @Transactional(rollbackFor={Exception.class}) public void saveUserThrowException() throws Exception { User u1=new User(); u1.setName("邵"); u1.setAge(24); userDao.save(u1); if(1+1>1){ throw new Exception("Runtime error...");//拋出通常的異常:Exception } User u2=new User(); u2.setName("陳"); u2.setAge(20); userDao.save(u2); } @Transactional(propagation=Propagation.REQUIRED,readOnly=true) public void findUsers() { List<User> users=userDao.query("from User where name=?", new Object[]{"邵"}); for (User user : users) { System.out.println(user); } } public IUserDao getUserDao() { return userDao; } public void setUserDao(IUserDao userDao) { this.userDao = userDao; } }
若是沒有事務,執行代碼「sessionFactory.getCurrentSession(); 」,會拋出No Session found for current thread。
getCurrentSession() 只有在一個事務以內纔有意義,因此hibernate4要求最小事務級別必須是「Required」,若是是如下的級別,或者沒有開啓事務的話,沒法獲得當前的Session。
上面代碼中,propagation參數,至少要到REQUIRED,不然會拋出異常:No Session found for current thread
對於運行時,這個可能不是很大的問題,由於在Service層通常都會開啓事務,只要保證級別高於Required就能夠了。
更多關於Spring事務的介紹,狀況上一篇隨筆:三種數據庫訪問——Spring JDBC
package edu.shao.springHibernate; import org.junit.BeforeClass; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import edu.shao.springHibernate.service.IUserService; public class SpringHibernateTest { private static ApplicationContext ctx = null; @BeforeClass public static void onlyOnce() { ctx = new ClassPathXmlApplicationContext("db/applicationContext-hibernate.xml"); } @Test public void testSave(){ IUserService service=ctx.getBean("userService",IUserService.class); service.saveUser(); } // @Test public void testSaveThrowException() throws Exception{ IUserService service=ctx.getBean("userService",IUserService.class); service.saveUserThrowException(); } // @Test public void testJDBCDaoQuery(){ IUserService service=ctx.getBean("userService",IUserService.class); service.findUsers(); } }
測試結果,在這裏就不說了。