在上一篇博客写了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
//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());
}
}
结果如图所示:
这个是查询表里的全部数据,如果要按条件查询,修改一下Mapper文件里面的接受的传参类型和SQL,eg:
然后修改一下测试部分的传参,eg:List
测试结果:
可以看到非常简单和方便
二、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的监听
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服务都调通了。
到此服务搭建已经完成,实现的是一个GET方法的REST服务,直接在浏览器里面就可以测试了。剩下的问题是服务搭建是OK了,测试也通过了,但是Spring具体在什么时候触发自动注入还没有解决。Mybatis的Sqlsession我都没有在代码中去创建,都在Spring配置文件中去配置的,Spring是怎么驱动Mybatis去注入这些对象的,简单来说,我从来没有主动触发注入,那么框架都帮我完成了什么工作,在什么时候完成的。学习框架需要我们去深入了解框架的注入原理与过程,以便能够适应复杂多变的业务,最关键的是提高我们排查错误的效率。