Spring5.x集成Mybatis构建REST服务

       在上一篇博客写了Spring5.x与CXF结合构建了REST服务,但是数据库访问仍然使用原生jdbc以及DriverManager等,在真实的工程应用中访问自身的数据库极少采用这种方式,因为这种方式将sql直接写到了Java代码中,对于数据库有变化以及数据库访问需求有变化时扩展非常麻烦,最容易体会的动态sql。直接用Mybatis管理也能够实现,利用Mybatis的SQL支持能够将数据库访问的开发简单的多,主要是易于扩展。目前主流的框架与Mybatis紧密相关的是SSM,我目前主要做的是服务端,所以并没有View层,这里也不打算融入SpringMVC,本篇建立在上一篇的Spring5.x+CXF3.x构建REST服务的基础上。

      在使用Spring集成Mybatis之前,先讲一下直接使用Mybatis访问数据库是怎么做的

一、使用Mybatis加原生jdbc完成数据库的访问

首先我们需要的jar包

   

根据所使用的数据库选择jdbc驱动包:ojdbc{version}.jar(Oracle的驱动包)、mysql-connector-java-{version}.jar

Mybatis包:mybatis-{version}.jar

1.VO类

通常属性与数据库中的要操作的表字段一致

package iExcel.dao.test;

public class AccountVO {
    private String id;
    private String account;
    private String password;
    private String account_type;
    
    public String getId()
    {
        return this.id;
    }
    
    public void setId(String id)
    {
        this.id = id;
    }
    
    public String getAccount()
    {
        return this.account;
    }
    
    public void setAccount(String account)
    {
        this.account = account;
    }

}

2.dao层接口

这里体现了2种方式,通过注解方式可以不需要Mapper文件,但是不方便维护,这里写出来,但是本例实际使用的是配置Mapper文件的方式

package iExcel.dao.api;
import iExcel.dao.test.AccountVO;
import org.apache.ibatis.annotations.Select;

public interface IAccountDAO {
    @Select("select * from haesoadb.account_t where id =#{id}")
    public AccountVO getAccount();

}

3.Mapper文件


 
 
 
     
     
     
 

4.Mybatis的配置

主要是配置数据源,这里也可以配置连接池



 
     
         
             
             
             
                 
                 
                 
                 
             

         

     

     
     
         
         
     

     
 

5.测试

package iExcel.dao.test;

import java.io.InputStream;
import java.util.List;

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import iExcel.dao.api.IAccountDAO;

public class Test2 {
    public static void main(String[] args)
    {
        String resource = "mybatis.xml";
        InputStream is = Test1.class.getClassLoader().getResourceAsStream(resource);
        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(is);
        SqlSession session = sessionFactory.openSession();

       //此处传递的其实是Mapper文件的namespace,而不是包路径,因而namespace可以随便取,只要保证唯一且与这里的传值一致即可
        String statement = "iExcel.dao.impl.AccountDAO.getAccountVO";
        //ResultSet resultSet =(ResultSet)session.selectOne(statement, 1);
        List account = session.selectList(statement);
        //IAccountDAO iaccount = session.getMapper(IAccountDAO.class);
        /*
        try{
            while(resultSet.next())
            {
                System.out.println(resultSet.getString(1));
            }
        }catch(SQLException e){
            e.printStackTrace();
        }*/
        for(int i=0;i         {
            System.out.println(account.get(i).getAccount());
        }
        
    }

结果如图所示:

Spring5.x集成Mybatis构建REST服务_第1张图片

这个是查询表里的全部数据,如果要按条件查询,修改一下Mapper文件里面的接受的传参类型和SQL,eg:

然后修改一下测试部分的传参,eg:List account = session.selectList(statement,"0000000000000001");

测试结果:

Spring5.x集成Mybatis构建REST服务_第2张图片

可以看到非常简单和方便

二、Spring集成Mybatis

首先我们需要的jar包

mybatis本身的包:mybatis-{version}.jar

Spring与mybatis融合的包:mybatis-spring-{version}.jar

MySQL驱动包:mysql-connector-java-{version}.jar

1.web.xml配置

这里web.xml的配置与上一篇的REST服务没有任何改动,仍然需要Spring的监听


    contextConfigLocation
    /WEB-INF/spring.xml
 
 
 
    org.springframework.web.context.ContextLoaderListener
 

 
    cxf
    org.apache.cxf.transport.servlet.CXFServlet
    1
 

 
    cxf
    /ws/*
 

2.Spring配置文件增加mybatis访问配置


   
   
   
   
   
       
   

    
   
    
   
        
     
     
        
     
   

     
               
   
    
   
   
       
       
       
       
       
       
   

    
   
   
       
       
       
       
   

3.dao层接口

package com.weijie.dao.api;
import org.mybatis.spring.annotation.MapperScan;

import com.weijie.accounts.Account;
@MapperScan
public interface IAccountDAO {
    
    public Account fetchAccount(String uid);
    public void createAccount();
    public void deleteAccount();
}

4.Mybatis访问数据库的Mapper层

       这里的namespace取值和单纯使用Mybatis的不一样,因为用到了Spring的自动注入,没有像单纯使用Mybatis那样传namespace的参数,扫描Mapper文件,通过namespace确定其对应的接口文件,框架通过动态代理生成其实现,事实上在代码里我也并没有实现dao层接口。




   
   
           insert into iexcel.weijie_account_t(id,userid,password,telephonenumber,email,created_by,create_date)
           values(weijie_account_id_sequence_s.nextval,#{uid},#{password},#{telephone},#{email},'w00379336',sysdate)
   

   
           delete form iexcel.weijie_account_t where id=#{id}
   

5.数据库访问的service层

package com.weijie.dao.service;
import org.springframework.stereotype.Service;

import java.io.InputStream;
import java.util.List;

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import com.weijie.dao.api.IAccountDAO;

import com.weijie.accounts.Account;
import javax.annotation.Resource;


@Service(value = "AccountDAOService")
public class AccountDAOService {
    @Autowired
    //@Resource
    private IAccountDAO accountDao;
    public Account fetchAccount(String uid) {
        Account account = null;
        try{
            account = accountDao.fetchAccount(uid);
        }catch(Exception e){
            e.printStackTrace();
        }
         
        if(account !=null) return  account;
        else{
            Account ac = new Account();
            ac.setUid("测试用户名");
            ac.setPassword(System.currentTimeMillis()+"");
            ac.setEmail("测试邮箱名");
            return ac;
        }
    }    

}

      看完代码这里要注意几点,首先在类上添加了注解,这个在Spring的配置文件中有描述,我需要Spring帮我完成自动注入,因为在调用数据库查询返回结果时我使用的是接口对象调用方法,事实上这个接口我根本就没有实现,而我使用它去返回结果了。秘诀就在于我使用了@Autowired注解,并且在Spring的配置中配置过扫描以@Service注解的类,因而在实际调用时Spring会帮助我完成自动注入。为了快速验证,这里我只写了查询的服务。

       到这里事实上集成Mybatis的从接口到服务层已经完成了,接下来是要验证我的配置到开发是否生效了。从网上找的大量的帖子都是使用junit去完成测试,但实际上我使用junit没有调用通。话说回来,我的目的也不是通过junit直接去测试,我希望提供服务给用户使用,也就是标题提到的REST服务,我把数据的访问包装成接口,暴露REST接口给用户,完成数据库访问,系统内部的查询也可以使用相同的方式。

6.增加REST接口

package com.weijie.rest.api;
import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.core.MediaType;
import com.weijie.accounts.Account;

//

@Path("/public/account")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public interface IAccountService {
    @POST
    @Path("/fetch")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public String fetch(String uid);
    //public Account[] fetch(Account account);
    
    @POST
    @Path("/create")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Account create(Account account);
    
    @GET
    @Path("/hello/{name}")
    public String sayHello(@PathParam("name") String name);
    
    @GET
    @Path("/fetchAccount/{uid}")
    public Account fetchAccount(@PathParam("uid") String uid);

}
从代码可以看出来,相比上一篇的REST服务构建,REST接口这里仅仅只增加了一个方法,没有改动原来的方法声明。

7.REST接口实现

package com.weijie.rest.impl;
import com.weijie.rest.api.IAccountService;
import com.weijie.accounts.Account;
import com.weijie.accounts.AccountUtils;
import com.weijie.DBHelper.DBHelper;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;

import org.apache.log4j.Logger;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import com.weijie.dao.service.AccountDAOService;
import org.springframework.beans.factory.annotation.Autowired;

public class AccountService implements IAccountService{
    private Logger log = Logger.getLogger(AccountService.class);
    //private Logger log = Logger.getLogger("AccountService");
    @Autowired
    private AccountDAOService dao;
    public Account create(Account account)
    {
        if(AccountUtils.isEmpty(account)) return null;
        DBHelper helper = new DBHelper("iexcel_sit_url","iexcel_sit_user","iexcel_sit_pwd");
        String driver = "oracle.jdbc.driver.OracleDriver";
        String sql = "insert into weijie_account_t (id,userid,password,telephonenumber,email,created_by,create_date) "
                + "values(weijie_account_id_sequence_s.nextval,'"+account.getUid()+"','huawei','1008612','[email protected]','test3',sysdate)";
        String sql1 = "select * from weijie_account_t where userid = '" + account.getUid() +"'";
        helper.executeSQL(driver, sql);
        ResultSet result = helper.executeSQL(driver,sql1);
        Account temp = resultSet2Account(result)[0];
        return temp ;
    }
    
    public String sayHello(String name)
    {
        
        return "Hello World " + name;
    }
    
    public Account fetchAccount( String uid)
    {
        return dao.fetchAccount(uid);
        
    }
    
    public String fetch(String uid)
    {
        Account account = new Account();
        account.setUid(uid);
        Account[] result = null;
        Gson gson = new GsonBuilder().create();
        
        result = fetch(account);
        
        String ss = gson.toJson(result[0]);
        System.out.println(result[0].getEmail());    
        return ss;
    }
    public Account[] fetch(Account account)
    {
        log.info("entry AccountService");
        Account[] result ;
        result = new Account[10];
        try{
            Class.forName("oracle.jdbc.driver.OracleDriver");
            Connection conn = DBHelper.getConnectionByProp("iexcel_sit_url", "iexcel_sit_user", "iexcel_sit_pwd");
            String sql = "select * from iexcel.weijie_account_t where userid =?";
            String userid = account.getUid();
            PreparedStatement pstmt = null;
            ResultSet resultSet = null;            
            pstmt = conn.prepareStatement(sql);
            pstmt.setString(1, userid);
            resultSet = pstmt.executeQuery();
            if(resultSet != null)
            {
                int i = 0;
                Account temp = new Account();
                try{
                    while(resultSet.next())
                    {
                        
                        ResultSetMetaData metaData = resultSet.getMetaData();
                        String column4 = metaData.getColumnLabel(4);
                        temp.setTelephonenumber(resultSet.getString(column4));
                        temp.setEmail(resultSet.getString(metaData.getColumnLabel(5)));
                        result[i] = temp;
                        System.out.println(result[i].toString());
                        i++;
                    }
                }catch(SQLException e){
                    e.printStackTrace();
                }                            
            }
        }catch(ClassNotFoundException e){
            e.printStackTrace();
        }catch(SQLException e){
            e.printStackTrace();
        }
        return result; 
    }
    public static Account[] resultSet2Account(ResultSet result)
    {
        if(result == null) return null;
        Account[] account;
        int num = 0;/*
        try{
            result.last();
            num = result.getRow();
            result.first();
        }catch(SQLException e){
            e.printStackTrace();
        }*/
        
        account = new Account[10];//num
        try{
            if(result !=null)
            {
                int i=0;
                ResultSetMetaData metaData = result.getMetaData();
                while(result.next())
                {
                    account[i] = new Account();
                    account[i].setUid(result.getString(2));
                    account[i].setTelephonenumber(result.getString(metaData.getColumnLabel(4)));
                    account[i].setEmail(result.getString(metaData.getColumnLabel(5)));
                }
            }
        }catch(SQLException e){
            e.printStackTrace();
        }
        return account;
    }

}
      看一下代码,主要2个地方,第一,其实就是调用了前面提到的数据库访问的服务层,直接访问,并没有做任何其它处理;第二,我使用的是数据库访问服务层这个类的未初始化的对象,声明对象时也加了一个@Autowired注解。尤其是第二点,因为调用时我并没有修改访问服务层的结果,也没有多余的动作,其实这里我完全不需要显示定义一个对象,我用new AccountDAOService().fetchAccount(uid)一样可以完成调用,而且更简洁,那么为什么此处要多此一举。这个问题耗费我很久的时间,经过反复的测试总是抛异常,显示数据库访问服务层在通过未实例化的api的对象返回数据时报空指针异常。前面已经提到了,使用@Autowired注解的接口对象,在使用时Spring框架会帮助我注入而完成实例化,而在这里却显示空指针,我一度以为是我的配置有问题,我看过了几十篇的Spring集成Mybatis的帖子,各种各样的配置都试了,都没能解决这个问题。通过debug我发现这个接口对象确实是空,我查了很多mybatis接口对象注入的资料,也没有找到解决方案。直到后来我详细看了SSM框架的搭建,我注意到了Control层的实现,在Control层调用数据库访问服务时,除了在Control层的类上加了@Control注解外,还在调用时使用了数据访问服务的未实例化对象,并且在声明这个对象时加上了@Autowired注解,这个时候我尝试了一下修改了一下,发现控制层的未实例化对象此时不为空,数据库访问访问服务层里的未实例化接口对象也不为空了,可以成功返回数据,此时整个REST服务都调通了。

Spring5.x集成Mybatis构建REST服务_第3张图片

     到此服务搭建已经完成,实现的是一个GET方法的REST服务,直接在浏览器里面就可以测试了。剩下的问题是服务搭建是OK了,测试也通过了,但是Spring具体在什么时候触发自动注入还没有解决。Mybatis的Sqlsession我都没有在代码中去创建,都在Spring配置文件中去配置的,Spring是怎么驱动Mybatis去注入这些对象的,简单来说,我从来没有主动触发注入,那么框架都帮我完成了什么工作,在什么时候完成的。学习框架需要我们去深入了解框架的注入原理与过程,以便能够适应复杂多变的业务,最关键的是提高我们排查错误的效率。

你可能感兴趣的:(Spring5.x集成Mybatis构建REST服务)