1、深入浅出MyBatis-快速入门 来源http://blog.youkuaiyun.com/hupanfeng/article/details/9068003 http://www.ibm.com/developerworks/cn/java/j-lo-ibatis-principle/
简介
MyBatis的前身叫iBatis,本是apache的一个开源项目, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis。MyBatis是支持普通SQL查询,存储过程和高级映射的优秀持久层框架。MyBatis消除了几乎所有的JDBC代码和参数的手工设置以及结果集的检索。MyBatis使用简单的XML或注解用于配置和原始映射,将接口和Java的POJOs(Plan Old Java Objects,普通的Java对象)映射成数据库中的记录。
Mybatis的功能架构分为三层(图片借用了百度百科):
1) API接口层:提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。
2) 数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。
3) 基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。
快速入门
Mybatis的整体流程图
SqlSessionFactoryBuilder
每一个MyBatis的应用程序的入口是SqlSessionFactoryBuilder,它的作用是通过XML配置文件创建Configuration对象(当然也可以在程序中自行创建),然后通过build方法创建SqlSessionFactory对象。没有必要每次访问Mybatis就创建一次SqlSessionFactoryBuilder,通常的做法是创建一个全局的对象就可以了。示例程序如下:
private static SqlSessionFactoryBuilder sqlSessionFactoryBuilder;
private static SqlSessionFactory sqlSessionFactory;
private static void init() throws IOException {
String resource = "mybatis-config.xml";
Reader reader = Resources.getResourceAsReader(resource);
sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
sqlSessionFactory = sqlSessionFactoryBuilder.build(reader);
SqlSessionFactory
SqlSessionFactory对象由SqlSessionFactoryBuilder创建。它的主要功能是创建SqlSession对象,和SqlSessionFactoryBuilder对象一样,没有必要每次访问Mybatis就创建一次SqlSessionFactory,通常的做法是创建一个全局的对象就可以了。SqlSessionFactory对象一个必要的属性是Configuration对象,它是保存Mybatis全局配置的一个配置对象,通常由SqlSessionFactoryBuilder从XML配置文件创建。这里给出一个简单的示例:
- <?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>
- <!-- 配置别名 -->
- <typeAliases>
- <typeAlias type="org.iMybatis.abc.dao.UserDao" alias="UserDao" />
- <typeAlias type="org.iMybatis.abc.dto.UserDto" alias="UserDto" />
- </typeAliases>
- <!-- 配置环境变量 -->
- <environments default="development">
- <environment id="development">
- <transactionManager type="JDBC" />
- <dataSource type="POOLED">
- <property name="driver" value="com.mysql.jdbc.Driver" />
- <property name="url" value="jdbc:mysql://127.0.0.1:3306/iMybatis?characterEncoding=GBK" />
- <property name="username" value="iMybatis" />
- <property name="password" value="iMybatis" />
- </dataSource>
- </environment>
- </environments>
- <!-- 配置mappers -->
- <mappers>
- <mapper resource="org/iMybatis/abc/dao/UserDao.xml" />
- </mappers>
- </configuration>
要注意XML头部的声明,需要用来验证XML文档正确性。typeAliases 元素是包含所有typeAlias(别名)的列表,别名用来替换完整类名,这样在需要完整类名的地方就可以用别名来代替。environment元素体中包含对事务管理和连接池的环境配置。mappers元素是包含所有mapper(映射器)的列表,这些mapper的XML文件包含SQL代码和映射定义信息。当然,在XML配置文件中还有很多可以配置的,上面的示例指出的则是最关键的部分,其他配置请参考Mybatis的官方文档。
SqlSession
SqlSession对象的主要功能是完成一次数据库的访问和结果的映射,它类似于数据库的session概念,由于不是线程安全的,所以SqlSession对象的作用域需限制方法内。SqlSession的默认实现类是DefaultSqlSession,它有两个必须配置的属性:Configuration和Executor。Configuration前文已经描述这里不再多说。SqlSession对数据库的操作都是通过Executor来完成的,Executor的具体功能在下一小节在描述。
到目前为止,我们看到的都是mybatis的流程,我们的应用程序在什么地方插入到这个流程中并获得我们想要的结果呢?就是SqlSession这里。SqlSession有一个重要的方法getMapper,顾名思义,这个方式是用来获取Mapper对象的。什么是Mapper对象?根据Mybatis的官方手册,应用程序除了要初始并启动Mybatis之外,还需要定义一些接口,接口里定义访问数据库的方法,存放接口的包路径下需要放置同名的XML配置文件。SqlSession的getMapper方法是联系应用程序和Mybatis纽带,应用程序访问getMapper时,Mybatis会根据传入的接口类型和对应的XML配置文件生成一个代理对象,这个代理对象就叫Mapper对象。应用程序获得Mapper对象后,就应该通过这个Mapper对象来访问Mybatis的SqlSession对象,这样就达到里插入到Mybatis流程的目的。示例代码如下:
- SqlSession session= sqlSessionFactory.openSession();
- UserDao userDao = session.getMapper(UserDao.class);
- UserDto user = new UserDto();
- user.setUsername("iMybatis");
- List<UserDto> users = userDao.queryUsers(user);
对应的接口:
- public interface UserDao {
- public List<UserDto> queryUsers(UserDto user) throws Exception;
- }
对应的配置文件:
- <?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="org.iMybatis.abc.dao.UserDao">
- <select id="queryUsers" parameterType="UserDto" resultType="UserDto"
- useCache="false">
- <![CDATA[
- select * from t_user t where t.username = #{username}
- ]]>
- </select>
- </mapper>
Executor
Executor对象在创建Configuration对象的时候创建,并且缓存在Configuration对象里。Executor对象的主要功能是调用StatementHandler访问数据库,并将查询结果存入缓存中(如果配置了缓存的话)。
StatementHandler
StatementHandler是真正访问数据库的地方,并调用ResultSetHandler处理查询结果。
ResultSetHandler
处理查询结果。
2、mybatis源代码分析之binding包 来源:http://www.cnblogs.com/sunzhenchao/archive/2013/05/13/3075854.html
在使用ibatis执行数据库访问时,会调用形如
getSqlMapClientTemplate().queryForObject("getCityByCityId", cityId);
这样的代码。这样的形式要求调用方选择需要使用的函数(queryForObject、queryForList、update),还需要告诉这个函数具体执行哪一个statement(上文中是“getCityByCityId”),在这个过程中如果有一个地方选择错误或者拼写错误,不仅没有办法达到自己的期望值,可能还会出现异常,并且这种错误只有在运行时才能够发现。
mybatis对此进行了改进,只要先声明一个接口,就可以利用IDE的自动完成功能帮助选择对应的函数,简化开发的同时增加了代码的安全性:
SqlSession session = sqlSessionFactory.openSession(); try { BlogMapper mapper = session.getMapper(BlogMapper.class); Blog blog =mapper.selectBlog(101
); } finally { session.close(); }
这个功能就是在利用java的动态代理在binding包中实现的。对动态代理不熟悉的读者可以查看(http://www.cnblogs.com/jqyp/archive/2010/08/20/1805041.html)。
一、binding包整体介绍
这个包中包含有四个类:
- BindingException 该包中的异常类
- MapperProxy 实现了InvocationHandler接口的动态代理类
- MapperMethod 代理类中真正执行数据库操作的类
- MapperRegistry mybatis中mapper的注册类及对外提供生成代理类接口的类
二、MapperMethod
MapperProxy关联到了这个类,MapperRegistry又关联到了MapperProxy,因而这个类是这个包中的基础类,我们首先介绍这个类的主要功能以及该类主要解决了那些问题。
这个类包含了许多属性和多个函数,我们首先来了解下它的构造函数。
//declaringInterface 已定义的mapper接口 //method 接口中具体的一个函数 //sqlSession 已打开的一个sqlSession public MapperMethod(Class<?> declaringInterface, Method method, SqlSession sqlSession) { paramNames = new ArrayList<String>(); paramPositions = new ArrayList<Integer>(); this.sqlSession = sqlSession; this.method = method; this.config = sqlSession.getConfiguration();//当前的配置 this.hasNamedParameters = false; this.declaringInterface = declaringInterface; setupFields();//确定这个方法在mapper中的全配置名:declaringInterface.getName() + "." + method.getName(); setupMethodSignature();//下文进行详细介绍 setupCommandType();//设置命令类型,就是确定这个method是执行的那类操作:insert、delete、update、select validateStatement(); }
在构造函数中进行了基本属性的设置和验证,这里面稍微复杂的操作是setupMethodSignature,我们来看其具体的功能:
//在具体实现时利用了Java的反射机制去获取method的各项属性 private void setupMethodSignature() { //首先判断方法的返回类型,这里主要判断三种:是否有返回值、返回类型是list还是map if( method.getReturnType().equals(Void.TYPE)){ returnsVoid = true; } //isAssignableFrom用来判定两个类是否存在父子关系;instanceof用来判断一个对象是不是一个类的实例 if (List.class.isAssignableFrom(method.getReturnType())) { returnsList = true; } if (Map.class.isAssignableFrom(method.getReturnType())) { //如果返回类型是map类型的,查看该method是否有MapKey注解。如果有这个注解,将这个注解的值作为map的key final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class); if (mapKeyAnnotation != null) { mapKey = mapKeyAnnotation.value(); returnsMap = true; } } //确定函数的参数 final Class<?>[] argTypes = method.getParameterTypes(); for (int i = 0; i < argTypes.length; i++) { //是否有为RowBounds类型的参数,如果有设置第几个参数为这种类型的 if (RowBounds.class.isAssignableFrom(argTypes[i])) { if (rowBoundsIndex == null) { rowBoundsIndex = i; } else { throw new BindingException(method.getName() + " cannot have multiple RowBounds parameters"); } } else if (ResultHandler.class.isAssignableFrom(argTypes[i])) {//是否有为ResultHandler类型的参数,如果有设置第几个参数为这种类型的 if (resultHandlerIndex == null) { resultHandlerIndex = i; } else { throw new BindingException(method.getName() + " cannot have multiple ResultHandler parameters"); } } else { String paramName = String.valueOf(paramPositions.size()); //如果有Param注解,修改参数名;如果没有,参数名就是其位置 paramName = getParamNameFromAnnotation(i, paramName); paramNames.add(paramName); paramPositions.add(i); } } }
前面介绍了MapperMethod类初始化相关的源代码,在初始化后就是向外提供的函数了,这个类向外提供服务主要是通过如下的函数进行:
public Object execute(Object[] args) { Object result = null; //根据初始化时确定的命令类型,选择对应的操作 if (SqlCommandType.INSERT == type) { Object param = getParam(args); result = sqlSession.insert(commandName, param); } else if (SqlCommandType.UPDATE == type) { Object param = getParam(args); result = sqlSession.update(commandName, param); } else if (SqlCommandType.DELETE == type) { Object param = getParam(args); result = sqlSession.delete(commandName, param); } else if (SqlCommandType.SELECT == type) {//相比较而言,查询稍微有些复杂,不同的返回结果类型有不同的处理方法 if (returnsVoid && resultHandlerIndex != null) { executeWithResultHandler(args); } else if (returnsList) { result = executeForList(args); } else if (returnsMap) { result = executeForMap(args); } else { Object param = getParam(args); result = sqlSession.selectOne(commandName, param); } } else { throw new BindingException("Unknown execution method for: " + commandName); } return result; }
这个函数整体上还是调用sqlSession的各个函数进行相应的操作,在执行的过程中用到了初始化时的各个参数。
三、MapperProxy
这个类继承了InvocationHandler接口,我们主要看这个类中的两个方法。一是生成具体代理类的函数newMapperProxy,另一个就是实现InvocationHandler接口的invoke。我们先看newMapperProxy方法。
public static <T> T newMapperProxy(Class<T> mapperInterface, SqlSession sqlSession) { //先初始化生成代理类所需的参数 ClassLoader classLoader = mapperInterface.getClassLoader(); Class<?>[] interfaces = new Class[]{mapperInterface}; MapperProxy proxy = new MapperProxy(sqlSession);//具体要代理的类 //利用Java的Proxy类生成具体的代理类 return (T) Proxy.newProxyInstance(classLoader, interfaces, proxy); }
我们再来看invoke方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //并不是任何一个方法都需要执行调用代理对象进行执行,如果这个方法是Object中通用的方法(toString、hashCode等)无需执行 if (!OBJECT_METHODS.contains(method.getName())) { final Class<?> declaringInterface = findDeclaringInterface(proxy, method); //生成MapperMethod对象 final MapperMethod mapperMethod = new MapperMethod(declaringInterface, method, sqlSession); //执行MapperMethod对象的execute方法 final Object result = mapperMethod.execute(args); //对处理结果进行校验 if (result == null && method.getReturnType().isPrimitive() && !method.getReturnType().equals(Void.TYPE)) { throw new BindingException("Mapper method '" + method.getName() + "' (" + method.getDeclaringClass() + ") attempted to return null from a method with a primitive return type (" + method.getReturnType() + ")."); } return result; } return null; }
四、MapperRegistry
这个类会在Configuration类作为一个属性存在,在Configuration类初始化时进行初始化:
protected MapperRegistry mapperRegistry = new MapperRegistry(this);
从类名就可以知道这个类主要用来mapper的注册,我们就首先来看addMapper函数:
public void addMapper(Class<?> type) { //因为Java的动态代理只能实现接口,因而在注册mapper时也只能注册接口 if (type.isInterface()) { //如果已经注册过了,则抛出异常,而不是覆盖 if (knownMappers.contains(type)) { throw new BindingException("Type " + type + " is already known to the MapperRegistry."); } boolean loadCompleted = false; try { //先将这个mapper添加到mybatis中,如果加载过程中出现异常需要再将这个mapper从mybatis中删除 knownMappers.add(type); // It's important that the type is added before the parser is run // otherwise the binding may automatically be attempted by the // mapper parser. If the type is already known, it won't try. //下面两行代码其实做了很多的事,由于涉及的东西较多,在此不做过多的描述,留待以后专门进行介绍 MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } }
下面我们来看下其向外提供生成代理对象的函数:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { //如果不存在这个mapper,则直接抛出异常 if (!knownMappers.contains(type)) throw new BindingException("Type " + type + " is not known to the MapperRegistry."); try { //返回代理类 return MapperProxy.newMapperProxy(type, sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } }
五、代理类生成的顺序图
我们在开篇时提到了如下的代码:
SqlSession session = sqlSessionFactory.openSession(); try { BlogMapper mapper = session.getMapper(BlogMapper.class); Blog blog =mapper.selectBlog(101
); } finally { session.close(); }