MyBatis中如何通过继承SqlSessionDaoSupport来编写DAO(一)

简介:

MyBatis中,当我们编写好访问数据库的映射器接口后,MapperScannerConfigurer就能自动成批地帮助我们根据这些接口生成DAO对象(请参考本系列前面的博文:MyBatis MapperScannerConfigurer配置),然后我们再使用Spring把这些DAO对象注入到业务逻辑层的对象(Service类的对象)。因此,在这种情况下的DAO层,我们几乎不用编写代码,而且也没有地方编写,因为只有接口。这固然方便,不过如果我们需要在DAO层写一些代码的话,这种方式就无能为力了。此时,MyBatis-Spring提供给我们的SqlSessionDaoSupport类就派上了用场。今天,就以访问学生表为例,通过继承SqlSessionDaoSupport,来写一个StudentDao

首先来认识一个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

但坏消息是,自mybatis-spring-1.2.0以来,SqlSessionDaoSupportsetSqlSessionTemplatesetSqlSessionFactory两个方法上的@Autowired注解被删除,这就意味着继承于SqlSessionDaoSupportDAO类,它们的对象不能被自动注入SqlSessionFactorySqlSessionTemplate对象。如果在Spring的配置文件中一个一个地配置的话,显然太麻烦。比较好的解决办法是在我们的DAO类中覆盖这两个方法之一,并加上@Autowired注解。那么如果在每个DAO类中都这么做的话,显然很低效。更优雅的做法是,写一个继承于SqlSessionDaoSupportBaseDao,在BaseDao中完成这个工作,然后其他的DAO类再都从BaseDao继承。BaseDao代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
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接口,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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相应的方法即可。不过,难就难在这个合适的时机不好找。如果定义后直接初始化,如下所示:

1
2
private  StudentMapper studentMapper
             this .getSqlSession().getMapper(StudentMapper. class );

 

或者在构造方法中使用类似的代码进行初始化,你都会得到空指针异常。原因是这些代码在运行时,SqlSessionTemplate对象还没有被注入(如果你选择注入SqlSessionFactory,结果是一样的)。

似乎进入了死胡同。

能不能写一个初始化方法,在这个方法中对studentMapper进行初始化,然后在SqlSessionTemplate被注入后,这个方法被自动调用呢?

首先想到的是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方法):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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后处理器的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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属性的值。配置代码如下:

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

 

如果Spring容器是BeanFactory,则还需手动注册此后处理器;而如果Spring容器是ApplicationContext,则无需手动注册,我们这里采用后者。执行类的代码如下(StudentDao类的对象被注入到了StudentService对象,这里是请求了StudentService对象并调用了相关的方法,具体请参考StudentServiceStudentDao的代码,以及Spring中context:component-scan的相关配置。本示例完整源代码与数据库脚本下载地址:http://down.51cto.com/data/1970833):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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










本文转自 NashMaster2011 51CTO博客,原文链接:http://blog.51cto.com/legend2011/1600478,如需转载请自行联系原作者
目录
相关文章
|
22天前
|
XML Oracle Java
mybatis反向生成实体类、dao层以及映射文件
mybatis反向生成实体类、dao层以及映射文件
13 1
|
2月前
|
Java 数据库连接 Maven
使用mybatis插件generator生成实体类,dao层和mapper映射
使用mybatis插件generator生成实体类,dao层和mapper映射
47 0
|
27天前
ssm(Spring+Spring mvc+mybatis)Dao层实现类——DeptDaoImpl
ssm(Spring+Spring mvc+mybatis)Dao层实现类——DeptDaoImpl
12 0
|
27天前
ssm(Spring+Spring mvc+mybatis)Dao接口——IDeptDao
ssm(Spring+Spring mvc+mybatis)Dao接口——IDeptDao
8 0
|
27天前
|
Java 数据库连接 mybatis
mybatis简单案例源码详细【注释全面】——Dao层映射文件(UserMapper.xml)【重要】
mybatis简单案例源码详细【注释全面】——Dao层映射文件(UserMapper.xml)【重要】
9 0
|
27天前
|
Java 数据库连接 mybatis
mybatis简单案例源码详细【注释全面】——Dao层接口(UserMapper.java)
mybatis简单案例源码详细【注释全面】——Dao层接口(UserMapper.java)
7 0
|
3月前
|
Java 数据库连接 mybatis
MyBatis的Dao层实现方式
MyBatis的Dao层实现方式
11 0
|
26天前
|
SQL Java 数据库连接
挺详细的spring+springmvc+mybatis配置整合|含源代码
挺详细的spring+springmvc+mybatis配置整合|含源代码
33 1
|
1月前
|
druid Java 数据库连接
Spring Boot3整合MyBatis Plus
Spring Boot3整合MyBatis Plus
39 1
|
3月前
|
Java 数据库连接 Maven
SSM框架整合:掌握Spring+Spring MVC+MyBatis的完美结合!
SSM框架整合:掌握Spring+Spring MVC+MyBatis的完美结合!