一.mybatis简介
Java 提供了JDBC来操作数据库,但是JDBC本身访问数据库的操作比较复杂,具体来说1.对于每次的sql查询都需要重复编写样板代码 2.查询过程中需要手动处理各种异常 3.查询结束后需要手动关闭各种资源(ResultSet、Statement、Connection)。这种情况下就出现了对JDBC进行封装的ORM框架来简化访问数据库的操作。所谓的ORM指的是对象关系映射,简单的来说指的是可以通过ORM提供的配置方便的将数据库的数据映射成简单Java对象(POJO),从而简化数据库访问过程。mybatis是一个半自动的ORM框架,所谓半自动是和全自动的Hibernate框架对应的;MyBatis消除了几乎所有的JDBC模板代码和参数的手工设置以及结果集的检索。使用简单的XML或注解用于配置和原始映射,将数据库的数据映射为JavaPOJO对象及将接口的方法和具体的SQL进行绑定。
从mybatis官网下载mybatis的jar包并将其导入项目的classpath,就可以使用mybatis了。
二.主要的功能类
Mybatis的功能架构分为三层:
1) API接口层:提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。
2) 数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。
3) 基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。
这里主要介绍下API接口层,即mybatis提供给外部使用的几个主要类:
1)SqlSessionFactoryBuilder(构造器):根据配置或代码生成SqlSessionFactory(工厂接口)
2)SqlSessionFactory:依靠工厂来生成SqlSession(会话)
3)Sqlsession:是一个既可以发送SQL获取返回结果也可以获取Mapper的接口
4)SQL Mapper:是mybatis新设计的组件,它由一个Java接口和XML文件(或注解)组成,给出具体SQL和对应的映射规则。它负责发送SQL去执行并获取返回结果。
下面通过一个简单的实例来具体了解下这几个类的作用;下面的实例使用mybatis从数据库中查出一个银行支行的数据(包括支行编号,名称,地址等信息)。
代码的目录结构如下所示:
其中 mybatis-config.xml是mybatis的主配置文件,branchMapper.xml是BranchMapper接口的映射文件,Branch是一个简单的POJO类用来映射数据库中查询出的结果。
首先来看下mybatis-config.xml,作为主配置文件里面定义了具体的数据源信息、mapper配置文件路径、POJO别名等信息,SqlSessionFactoryBuilder类就是通过这个配置文件
来生成SqlSessionFactory工厂对象的。下面是主要的配置内容:
<?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>
<!--为POJO对象定义别名,定义的别名可以在mapper配置文件中使用-->
<typeAliases>
<typeAlias alias="branch" type="com.sankuai.longkaili.domain.Branch"/>
</typeAliases>
<!--定义数据库信息,默认使用development数据库构建环境-->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/bank"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!--定义映射器,Mapper接口中的方法和具体SQL语句的映射关系-->
<mappers>
<mapper resource="mapper/branchMapper.xml"/>
</mappers>
</configuration>
这里面的mappers属性中配置了branchMapper.xml作为mapper文件,mapper文件的作用是将具体的sql和上面的BranchMapper接口中的方法相关联起来。下面是具体的配置内容
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.sankuai.longkaili.mapper.BranchMapper">
<select id="getBranchById" parameterType="long" resultType="branch">
select * from branch where branch_id=#{id}
</select>
</mapper>
首先是通过namespace来指定了这个mapper文件对应的Mapper接口,上面是对应到了BranchMapper接口上去,BranchMapper接口的内容如下所示:
public interface BranchMapper {
public Branch getBranchById(long id);
}
其实里面就只有一个通过id查询Branch信息的方法。在上面的mapper文件配置了一个select元素,这个元素的id就是这个方法名,通过这种方式将这个方法与具体的sql关联了起来。
可以看到select中还配置了parameterType表示参数的类型,然后参数是通过#{id}的方式传入的;另外resultType表示返回值的类型,这里配置的branch是用的上面mybatis-config.xml中配置的别名,
因为返回的数据库字段和定义的Branch对象中字段完全一致,所以不需要再定义具体字段间的映射关系,mybatis会自动完成查询结果和POJO对象的映射。
介绍完配置文件以后,来具体看下怎么使用这些配置文件构建对象进行数据库操作。先不考虑生产环境下的应用,在一个main函数中完成整个数据库操作过程:
public static void main(String[] args) throws Exception {
String resource = "mybatis-config.xml";
//获得配置文件的输入流
InputStream inputStream = Resources.getResourceAsStream(resource);
//SqlSessionFactoryBuilder通过配置文件构建SqlSessionFactory对象
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//从SqlSessionFactory对象中获取SqlSession
SqlSession sqlSession = sessionFactory.openSession();
try {
//通过SqlSession对象获取BranchMapper接口的代理对象,并通过代理对象执行sql获取结果
BranchMapper branchMapper = sqlSession.getMapper(BranchMapper.class);
Branch branch = branchMapper.getBranchById(1l);
System.out.println(branch.toString());
} catch (Exception e) {
sqlSession.rollback();
System.out.println(e.getLocalizedMessage());
} finally {
if (sqlSession != null) {
sqlSession.close();
}
}
}
最后好像是可以直接调用BranchMapper对象的getBranchById()方法就得到了查询的结果,但是我们知道BranchMapper是一个接口,本身是不能产生对象的;实际上这里是SqlSession.getMapper()方法中通过branchMapper.xml的配置为BranchMapper接口生成了一个代理对象(用到的是Java的动态代理技术),并且这个代理对象中的getBranchById()方法也根据配置文件与指定的sql进行了关联,所以执行这个方法的时候本质上是触发了相应的sql语句,从而得到了结果。
上面这段代码基本上描述了通过mybatis操作数据库的流程,但是还是很不实用因为没有考虑异常的情况,也没考虑资源的复用–像SqlSessionFactory这种代表一个数据库的对象是不可能每次都产生一个,这样的话资源消耗就太多了。在对上面的代码进行进一步改进之前,先考虑下上面提到的几个mybatis类的生命周期:
SqlSessionFactoryBuilder对象,这个本质上只是一个工具类,它的作用就是通过配置文件(或Configuration对象)来产生SqlSessionFactory对象,生命周期应该是用完就丢弃。
SqlSessionFactory对象,这个对象的作用是用来参数SqlSession,其代表的其实是整个数据库,对一个指定数据库应该保证唯一并且因为创建耗费资源比较大,应该保证自第一次初始化之后就一直能够存在,直到应用重启。所以下面使用了单例模式来保持SqlSessionFactory对象的唯一性。
SqlSession对象,表示与数据库的一次会话,与JDBC中的Connection类似,每Open一个SqlSession对象都是需要占用一定资源的,所以SqlSession在使用完毕之后需要关闭,如果不进行关闭就会导致数据库可用的资源越来越少。
Mapper对象,就是SqlSession的getMapper()方法返回的接口代理对象,在mybatis中这个对象实际上执行sql获取结果,相当于JDBC中的Statement对象,但这个对象不需要我们手动关闭。
所以需要额外关注的对象是SqlSessionFactory和SqlSession对象,下面的代码中会将他们封装在一个Util里进行处理。
package com.sankuai.longkaili.Utils;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Logger;
//SqlSessionFactoryUtil作为一个工具类,封装了SqlSessionFactory和SqlSession的一些操作
public class SqlSessionFactoryUtil {
private static final Class LOCK = SqlSessionFactoryUtil.class;
private static SqlSessionFactory sqlSessionFactory = null;
//
public static SqlSessionFactory init() {
if (sqlSessionFactory != null) {
return sqlSessionFactory;
}
InputStream inputStream;
try {
inputStream = Resources.getResourceAsStream("mybatis-config.xml");
} catch (IOException e) {
return null;
}
synchronized (LOCK) {
if (sqlSessionFactory == null) {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
}
return sqlSessionFactory;
}
public static SqlSession getSqlSession() {
if (sqlSessionFactory == null) {
init();
}
if (sqlSessionFactory == null) {
return null;
}
return sqlSessionFactory.openSession();
}
}
其中SqlSessionFactory对象使用了单例模式保证全局唯一。这样包装以后,在代码里就可以比较方便使用而不用每次都写一堆配置解析代码了。
package com.sankuai.longkaili.Service;
import com.sankuai.longkaili.Utils.SqlSessionFactoryUtil;
import com.sankuai.longkaili.domain.Branch;
import com.sankuai.longkaili.mapper.BranchMapper;
import org.apache.ibatis.session.SqlSession;
public class BranchQueryService {
public Branch getBranchInfoById(long id) {
SqlSession sqlSession = SqlSessionFactoryUtil.getSqlSession();
if (sqlSession == null) {
return null;
}
Branch branch;
try {
BranchMapper branchMapper = sqlSession.getMapper(BranchMapper.class);
branch = branchMapper.getBranchById(id);
} finally {
//保证sqlSession正确关闭
sqlSession.close();
}
return branch;
}
}