三種數據庫訪問——Spring3.2 + Hibernate4.2

前三篇隨筆中介紹了 用原生的JDBC訪問數據庫一種高效的數據庫鏈接池druid用Spring的JDBC框架訪問數據庫html

本文繼續介紹第三種數據庫訪問的解決方案:Spring3.2 + Hibernate4.2java

 

ORM框架

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相關的詳細內容能夠看我以前的一篇隨筆。

 

四、配置SessonFactory、配置事務處理器、配置bean的依賴關係

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類。

 

五、Dao接口及實現

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);
    }

}

 

六、Service接口及實現

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();
    }
}

測試結果,在這裏就不說了。

 

 

參考:http://kyfxbl.iteye.com/blog/1634355

相關文章
相關標籤/搜索