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文件

<?xml version="1.0" encoding="UTF-8" ?>
 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 <!-- 为这个mapper指定一个唯一的namespace,namespace的值习惯上设置成包名+sql映射文件名,这样就能够保证namespace的值是唯一的
 例如namespace="iExcel.dao.impl.AccountDAO"就是iExcel.dao.impl(包名)+AccountDAO(AccountDAO.xml文件去除后缀)
  -->
 <mapper namespace="iExcel.dao.impl.AccountDAO">
     <!-- 在select标签中编写查询的SQL语句, 设置select标签的id属性为getUser,id属性值必须是唯一的,不能够重复
     使用parameterType属性指明查询时使用的参数类型,resultType属性指明查询返回的结果集类型
     resultType="iExcel.dao.test.AccountDAO"就表示将查询结果封装成一个AccountDAO类的对象返回
     User类就是users表所对应的实体类
     -->
     <!-- 根据id查询得到一个user对象-->
     <select id="getAccountVO"  resultType="iExcel.dao.test.AccountVO">
         select * from haesoadb.account_t  
     </select>
 </mapper>

4.Mybatis的配置

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

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
 <configuration>
     <environments default="development">
         <environment id="development">
             <transactionManager type="JDBC" />
             <!-- 配置数据库连接信息 -->
             <dataSource type="POOLED">
                 <property name="driver" value="com.mysql.jdbc.Driver" />
                 <property name="url" value="在此省略" />
                 <property name="username" value="在此省略" />
                 <property name="password" value="在此省略" />
             </dataSource>
         </environment>
     </environments>
     
     <mappers>
         <!-- 注册userMapper.xml文件, 
         AccountDAO.xml位于iExcel.dao.impl这个包下,所以resource写成iExcel/dao/impl/AccountDAO.xml-->
         <mapper resource="iExcel/dao/impl/AccountDAO.xml"/>
     </mappers>
     
 </configuration>

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<AccountVO> 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<account.size();i++)
        {
            System.out.println(account.get(i).getAccount());
        }
        
    }

结果如图所示:

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

<select id="getAccountVO"  parameterType="java.lang.String" resultType="iExcel.dao.test.AccountVO">
         select * from haesoadb.account_t where id =#{id} 
     </select>

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

测试结果:

可以看到非常简单和方便

二、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的监听

<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/spring.xml</param-value><!--  -->
  </context-param>  
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
  <servlet>
    <servlet-name>cxf</servlet-name>
    <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>cxf</servlet-name>
    <url-pattern>/ws/*</url-pattern>
  </servlet-mapping>

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

<!-- 与Mybatis整合 -->
   <!-- 自动扫描(自动注入),扫描me.gacl.service这个包以及它的子包的所有使用@Service注解标注的类 -->
   <context:component-scan base-package="com.weijie.dao" /><!-- .service -->
   
   <!-- 引入数据源配置文件 -->
   <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location" value="classpath:config/jdbc_mysql.properties"/>
    </bean>
    
    <!-- spring自带的数据源 --><!-- Oracle数据源 
    <bean id="dataSource"     
      class="org.springframework.jdbc.datasource.DriverManagerDataSource">     
    <property name="driverClassName" value="${driverClassName}" />  
    <property name="url" value="iexcel_sit_url" />  
    <property name="username" value="iexcel_sit_user" />     
    <property name="password" value="iexcel_sit_pwd" />  
    </bean> -->
    
    <!-- spring自带的数据源 --><!-- mysql数据源 -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">     
    <property name="driverClassName" value="${driverClassName}" />  
    <property name="url" value="${url}" /> 
    <property name="username" value="${user}" />     
    <property name="password" value="${pwd}" />  
    </bean>
     
               
    <!--数据库连接池配置--><!--dbcp的连接池已经不推荐使用了
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">  
        <property name="driverClassName" value="${driverClassName}"/>
        <property name="url" value="${iexcel_sit_url}"/>
        <property name="username" value="${iexcel_sit_user}"/>
        <property name="password" value="${iexcel_sit_pwd}"/>
    </bean>
    -->
    
    <!-- spring和MyBatis完美整合 -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 指定数据源 -->
        <property name="dataSource" ref="dataSource"/>
        <!-- 具体指定xml文件,可不配 -->
        <!--  <property name="configLocation" value="classpath:mybatis.xml"/>-->
        <!-- 自动扫描mapping.xml文件,**表示迭代查找 ,,也可在mybatis-config.xml中单独指定xml文件 -->
        <property name="mapperLocations" value="classpath:com/weijie/dao/mapper/*.xml"/>
    </bean>
    
    <!-- 自动扫描com/weijie/dao/api下的所有dao层接口,并实现这些接口,可直接在程序中使用dao接口,不用再获取sqlsession对象 -->
    <bean id="MapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--basePackage 属性是映射器接口文件的包路径。 你可以使用分号或逗号 作为分隔符设置多于一个的包路径-->
        <property name="basePackage" value="com.weijie.dao.api"/><!-- com/weijie/dao/api -->
        <!--
                 因为会自动装配 SqlSessionFactory和SqlSessionTemplate
                 所以没 有 必 要 去 指 定 SqlSessionFactory或 SqlSessionTemplate
                 因此可省略不配置;
                 但是,如果你使 用了一个 以上的 DataSource,那么自动装配可能会失效。
                 这种 情况下,你可以使用sqlSessionFactoryBeanName或sqlSessionTemplateBeanName属性来设置正确的 bean名称来使用;
        -->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    </bean>

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层接口。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.weijie.dao.api.IAccountDAO">
   <select id="fetchAccount" parameterType="java.lang.String" resultType="com.weijie.accounts.Account">
           select * from s_mail.weijie_account_t where userid=#{uid}
   </select>
   <insert id="createAccount" parameterType="com.weijie.accounts.Account">
           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)
   </insert>
   <delete id="deleteAccount" parameterType="String">
           delete form iexcel.weijie_account_t where id=#{id}
   </delete>
</mapper>

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','10086@140.com','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服务都调通了。

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值