MyBatis中如何經過繼承SqlSessionDaoSupport來編寫DAO(一)

本文示例完整源代碼與數據庫腳本下載地址:http://down.51cto.com/data/1970833 html

MyBatis中,當咱們編寫好訪問數據庫的映射器接口後,MapperScannerConfigurer就能自動成批地幫助咱們根據這些接口生成DAO對象(請參考本系列前面的博文:MyBatis MapperScannerConfigurer配置),而後咱們再使用Spring把這些DAO對象注入到業務邏輯層的對象(Service類的對象)。所以,在這種狀況下的DAO層,咱們幾乎不用編寫代碼,並且也沒有地方編寫,由於只有接口。這當然方便,不過若是咱們須要在DAO層寫一些代碼的話,這種方式就無能爲力了。此時,MyBatis-Spring提供給咱們的SqlSessionDaoSupport類就派上了用場。今天,就以訪問學生表爲例,經過繼承SqlSessionDaoSupport,來寫一個StudentDao java

首先來認識一個SqlSessionDaoSupport類。類org.mybatis.spring.support.SqlSessionDaoSupport繼承了類org.springframework.dao.support.DaoSupport,它是一個抽象類,自己就是做爲DAO的基類來使用的。它須要一個SqlSessionTemplate或一個SqlSessionFactory,若二者都設置了,則SqlSessionFactory會被忽略(實際上它接收了SqlSessionFactory後也會利用SqlSessionFactory建立一個SqlSessionTemplate)。這樣,咱們在子類中就能經過調用SqlSessionDaoSupport類的getSqlSession()方法來獲取這個SqlSessionTemplate對象。而SqlSessionTemplate類實現了SqlSession接口,所以,有了SqlSessionTemplate對象,訪問數據庫就不在話下了。因此,咱們須要SpringSqlSessionDaoSupport類的子類的對象(多個DAO對象)注入一個SqlSessionFactory或一個SqlSessionTemplate。好消息是,SqlSessionTemplate是線程安全的,所以,給多個DAO對象注入同一個SqlSessionTemplate是沒有問題的,本例也將注入SqlSessionFactory spring

但壞消息是,自mybatis-spring-1.2.0以來,SqlSessionDaoSupportsetSqlSessionTemplatesetSqlSessionFactory兩個方法上的@Autowired註解被刪除,這就意味着繼承於SqlSessionDaoSupportDAO類,它們的對象不能被自動注入SqlSessionFactorySqlSessionTemplate對象。若是在Spring的配置文件中一個一個地配置的話,顯然太麻煩。比較好的解決辦法是在咱們的DAO類中覆蓋這兩個方法之一,並加上@Autowired註解。那麼若是在每一個DAO類中都這麼作的話,顯然很低效。更優雅的作法是,寫一個繼承於SqlSessionDaoSupportBaseDao,在BaseDao中完成這個工做,而後其餘的DAO類再都從BaseDao繼承。BaseDao代碼以下: sql

package com.abc.dao.base;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import org.springframework.beans.factory.annotation.Autowired;
public class BaseDao extends SqlSessionDaoSupport {
 
 @Autowired
 public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate)
    {
     super.setSqlSessionTemplate(sqlSessionTemplate);
    }
 
}

 

接着的問題就是StudentDao該怎麼寫。獲取了SqlSessionTemplate以後,有兩種方式訪問數據庫,一種是經過SqlSessionTemplate的相關方法執行SQL映射文件中的SQL語句,一種是先經過SqlSessionTemplate獲取映射器對象(在這裏就是StudentMapper接口類型的對象),而後再調用這個映射器對象的數據庫訪問方法。鑑於第二種方法更方便(第一種方法須要寫冗長的SQL語句全名,全是字符串,無代碼提示,第二種方法能夠利用IDE的代碼提示功能)、及具備更好的類型安全性,本示例就採用後者。 數據庫

先看StudentMapper接口,代碼以下: 安全

package com.abc.mapper;
import com.abc.domain.Student;
public interface StudentMapper {
  
 //根據id查找學生
 public Student getById(int id);
 
 //添加一名學生
 public int add(Student student);
 
 //修改學生
 public int update(Student student);
 
 //刪除學生
 public int delete(int id);
 
}

 

至於StudentDao,應該先定義一個私有的StudentMapper類型的變量studentMapper,並在合適的時機初始化studentMapper,而後就簡單了,在各方法中調用studentMapper相應的方法便可。不過,難就難在這個合適的時機很差找。若是定義後直接初始化,以下所示: mybatis

private StudentMapper studentMapper
            = this.getSqlSession().getMapper(StudentMapper.class);

 

或者在構造方法中使用相似的代碼進行初始化,你都會獲得空指針異常。緣由是這些代碼在運行時,SqlSessionTemplate對象尚未被注入(若是你選擇注入SqlSessionFactory,結果是同樣的)。 app

彷佛進入了死衚衕。 dom

能不能寫一個初始化方法,在這個方法中對studentMapper進行初始化,而後在SqlSessionTemplate被注入後,這個方法被自動調用呢? ide

首先想到的是afterPropertiesSet()方法。接口org.springframework.beans.factory.InitializingBean只聲明瞭一個afterPropertiesSet()方法,顧名思義,此方法在Springbean注入完全部的依賴關係後會被Spring自動調用,若是StudentDao實現了此接口,就能夠在afterPropertiesSet()方法中對studentMapper進行初始化。

很完美是嗎?

不幸的是,org.springframework.dao.support.DaoSupport類(SqlSessionDaoSupport類的父類)已經實現了此接口,爲afterPropertiesSet()方法提供了實現,而且把此方法定義成了final類型的。也就是說,咱們在子類中不能覆蓋此方法。因此,afterPropertiesSet()方法不能爲咱們所用。

還有一種方法,就是先在StudentDao中寫好初始化方法,而後在Spring中使用xml配置bean的時候,指定init-method屬性的值爲此方法。則此方法也會在Spring注入完全部的依賴關係後,被Spring調用。不過這種方法須要使用xml配置每個DAO bean,不如使用註解方便,故這種方法也被排除。

還有沒有其餘辦法呢?

有的,咱們還可使用Springbean後處理器。一樣顧名思義,bean後處理器中的方法是在Spring注入完依賴關係以後被調用的,因此很適合咱們目前的要求。如今,咱們就用bean後處理器來解決咱們的問題。先上StudentDao的代碼以下(注意對studentMapper進行初始化的方法是init方法):

package com.abc.dao;
import org.springframework.stereotype.Repository;
import com.abc.dao.base.BaseDao;
import com.abc.domain.Student;
import com.abc.mapper.StudentMapper;
@Repository
public class StudentDao extends BaseDao{
    
 private StudentMapper studentMapper;
 
 public Student getById(int id)
 {
  return this.studentMapper.getById(id);
 }
 
 public void deleteById(int id)
 {
  int count = this.studentMapper.delete(id);
  System.out.println("刪除了" + count + "行數據。");
 }
 
 public void update(Student student)
 {
  int count = this.studentMapper.update(student);
  System.out.println("修改了" + count + "行數據。");
 }
 
 public void add(Student student) {
  // TODO Auto-generated method stub
  int count = this.studentMapper.add(student);
  System.out.println("添加了" + count + "行數據。");
 }
 //對studentMapper進行初始化的方法
 public void init()
 {
  System.out.println("初始化studentMapper...");
  this.studentMapper 
       = this.getSqlSession().getMapper(StudentMapper.class);
 }
 
}

 

StudentDao中的各數據庫訪問方法中,咱們就能夠根據本身的須要來編寫相應的代碼了。

而後是編寫bean後處理器,這要經過實現接口org.springframework.beans.factory.config.BeanPostProcessor實現,這個接口聲明瞭以下兩個方法:

Object  postProcessBeforeInitialization(Object bean, String beanName)

Object  postProcessAfterInitialization(Object bean, String beanName)

兩個方法的第一個參數都是待處理的bean,第二個參數都是待處理的bean的名字,Spring會調用這兩個方法對容器中的每一個bean進行處理,具體的運行順序是:

1、注入依賴關係;

2、調用postProcessBeforeInitialization方法;

3、調用afterPropertiesSet方法;

4、調用init-method方法(如前所述,在使用xml配置bean的時候,可以使用init-method屬性指定bean的初始化方法);

5、調用postProcessAfterInitialization方法。

因爲第一步就已經完成了依賴關係注入,所以咱們在postProcessBeforeInitializationpostProcessAfterInitialization這兩個方法中調用DAO(這裏只有StudentDao,但實際上應該有不少DAO類,每一個DAO類都應該有本身的init方法)的init方法均可以,這裏咱們在postProcessBeforeInitialization方法中調用。不過這裏還有一個問題須要解決,就是在postProcessBeforeInitialization方法中,參數bean的類型是Object類型,咱們最多隻能把它強制轉換爲BaseDao類型(由於具體的DAO類型有不少),但即便如此,也不能經過參數bean調用DAOinit方法,由於init方法不是在BaseDao中聲明的。而若是每種DAO類型都分別判斷一遍再作相應的強制類型轉換,則顯然很低效,並且每增長一種DAO類型,就得添加相應的類型判斷、強制類型轉換的代碼。對於這個問題,有兩個解決方法,一是用反射,二是用多態,而用多態顯然更優雅。具體作法是改造BaseDao,在BaseDao中聲明一個抽象的init方法,顯然此時BaseDao類也應該聲明爲抽象類,而後在各子類中實現此init方法。這樣,咱們只須要把參數bean強制轉換爲BaseDao類型,就能夠經過參數bean調用init方法了。而根據多態的原理,實際調用的是具體的子類(如StudentDao類)中實現的init方法。這樣,咱們的問題就完美解決了。經多態改造後的BaseDao代碼以下:

package com.abc.dao.base;



import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import org.springframework.beans.factory.annotation.Autowired;



public abstract class BaseDao extends SqlSessionDaoSupport {
 
 @Autowired
 public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate)
    {
     super.setSqlSessionTemplate(sqlSessionTemplate);
    }
 
 //抽象方法
 public abstract void init(); 
 
}

 

StudentDao的代碼不用改動,而bean後處理器的代碼以下:

package com.abc.post;


import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import com.abc.dao.base.BaseDao;

public class DaoPostProcessor implements BeanPostProcessor {
 
 @Override
 public Object postProcessAfterInitialization(Object bean, String beanName)
   throws BeansException {
  // TODO Auto-generated method stub
  //只處理BaseDao的子類的對象
  if(bean.getClass().getSuperclass()==BaseDao.class)
  {
   BaseDao dao = (BaseDao)bean;
   dao.init();
  }
  //返回原bean實例
  return bean;
 }
 
 @Override
 public Object postProcessBeforeInitialization(Object bean, String beanName)
   throws BeansException {
  // TODO Auto-generated method stub
  //直接返回原bean實例,不作任何處理
  return bean;
 }
}

最後一步就是要在Spring的配置文件中配置這個bean後處理器,跟配置普通的bean同樣。並且若是你不須要獲取這個bean後處理器的話,你甚至能夠不給它指定id屬性的值。配置代碼以下:

<bean class="com.abc.post.DaoPostProcessor"/>

 

若是Spring容器是BeanFactory,則還需手動註冊此後處理器;而若是Spring容器是ApplicationContext,則無需手動註冊,咱們這裏採用後者。執行類的代碼以下(StudentDao類的對象被注入到了StudentService對象,這裏是請求了StudentService對象並調用了相關的方法,具體請參考StudentServiceStudentDao的代碼,以及Spring中context:component-scan的相關配置。本示例完整源代碼與數據庫腳本下載地址:http://down.51cto.com/data/1970833):

package com.demo;

import org.springframework.context.ApplicationContext;
import com.abc.service.StudentService;
import com.abc.domain.Student;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestDaoSupport {
 
 private static ApplicationContext ctx;
 static {
  // 在類路徑下尋找spring主配置文件,啓動spring容器
  ctx = new ClassPathXmlApplicationContext(
    "classpath:/applicationContext.xml");
 }
 
 public static void main(String[] args) {
  // 從Spring容器中請求服務組件
  StudentService studentService = 
    (StudentService)ctx.getBean("studentService");
  
  Student student = studentService.getById(13);
  System.out.println(student.getName());
  
 }
}

 

運行結果以下:

wKiom1StWFqxOy3DAAmWi-j3zM8927.jpg

 

感謝你耐心看到這裏,若是我如今告訴你,其實用不着費這麼大勁寫後處理器,有更簡便的方法,你會不會很生氣?o(_)o

其實,在StudentDaoinit方法上,加上@PostConstruct註解(須要引入javax.annotation.PostConstruct)就能夠了。用@PostConstruct標註init方法後,init方法就會成爲初始化方法,而在Spring完成依賴注入後被Spring調用。也就是說,此註解與前面提到的init-method屬性的用途相似,讀者可自行嘗試一下。

不過,我仍是但願,前面介紹的利用bean後處理器解決問題的方法,能對你們有參考價值,感謝你的關注。

o(_)o

本文示例完整源代碼與數據庫腳本下載地址:http://down.51cto.com/data/1970833

     猛戳這裏全面系統地學習MyBatis 3

     MyBatis技術交流羣:188972810,或掃描二維碼:

wKioL1SaztmBchKiAADsv4YAWBY259.jpg


 


 

【MyBatis學習筆記】系列之預備篇一:ant的下載與安裝

【MyBatis學習筆記】系列之預備篇二:ant入門示例

【MyBatis學習筆記】系列之一:MyBatis入門示例

【MyBatis學習筆記】系列之二:MyBatis增刪改示例

【MyBatis學習筆記】系列之三:MyBatis的association示例

【MyBatis學習筆記】系列之四:MyBatis association的兩種形式

【MyBatis學習筆記】系列之五:MyBatis與Spring集成示例

【MyBatis學習筆記】系列之六:MyBatis與Spring集成示例續

【MyBatis學習筆記】系列之七:MyBatis一對多雙向關聯

【MyBatis學習筆記】系列之八:MyBatis MapperScannerConfigurer配置

【MyBatis學習筆記】系列之九:MyBatis collection的兩種形式

【MyBatis學習筆記】系列之十:MyBatis日誌之Log4j示例

【MyBatis學習筆記】系列之十一:MyBatis多參數傳遞之註解方式示例

【MyBatis學習筆記】系列之十二:MyBatis多參數傳遞之默認命名方式示例

【MyBatis學習筆記】系列之十三:MyBatis多參數傳遞之Map方式示例

【MyBatis學習筆記】系列之十四:MyBatis中的N+1問題

【MyBatis學習筆記】系列之十五:MyBatis多參數傳遞之混合方式

【MyBatis學習筆記】系列之十六:Spring聲明式事務管理示例

【MyBatis學習筆記】系列之十七:MyBatis多對多保存示例

【MyBatis學習筆記】系列之十八:MyBatis多對多關聯查詢示例

【MyBatis學習筆記】系列之十九:如何在MyBatis-3.2.7中使用Log4j2 rc2

MyBatis中如何經過繼承SqlSessionDaoSupport來編寫DAO(一)

MyBatis中如何經過繼承SqlSessionDaoSupport來編寫DAO(二)

相關文章
相關標籤/搜索