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(二)

相关文章
相关标签/搜索