一、面向接口开发步骤
- 定义代理接口,将操作数据库的方法定义在代理接口中。
- 在SQL 映射文件中编写SQL 语句。
- 将SQL 映射文件注册在MyBatis 的全局配置文件中。
- 编写测试代码。
二、环境准备
数据库表结构:
DROP TABLE IF EXISTS `t_employee`;
CREATE TABLE `t_employee` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(30) DEFAULT NULL,
`gender` char(1) DEFAULT NULL,
`email` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
在pom.xml
中添加依赖:
<!-- MyBatis 的依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<!-- 数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.39</version>
</dependency>
<!-- log4j 的依赖,用于在控制台查看执行的SQL 语句 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
项目工程目录:
三、开发过程
这里就不提供log4j 相关的配置信息了,有兴趣的可以自行百度搜索。
员工类Employee
public class Employee {
private Integer id;
private String username;
private Character gender;
private String email;
/** 省略get 、set与toString 方法 */
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
接口EmployeeMapper
public interface EmployeeMapper {
/***
* 根据id 获取Employee 的信息
*
* @param id
* @return
*/
Employee getEmpById(Integer id);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
SQL 映射文件EmployeeMapper.xml
<?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">
<!--
namespace:EmployeeMapper 接口的全路径名
id:唯一标识,EmployeeMapper 接口对应的方法名
resultType:返回值类型,这里返回的是Employee 对象
-->
<mapper namespace="com.jas.mybatis.mapper.EmployeeMapper">
<select id="getEmpById" resultType="com.jas.mybatis.bean.Employee">
<!-- #{id} 表示要传进来的参数,类似于JDBC SQL 语句中的? -->
select * from t_employee where id = #{id}
</select>
</mapper>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
MyBatis 全局配置文件mybatis-config.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>
<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://localhost:3306/mybatis-study"/>
<property name="username" value="root"/>
<property name="password" value="1234"/>
</dataSource>
</environment>
</environments>
<!-- 注册SQL 映射文件 -->
<mappers>
<mapper resource="EmployeeMapper.xml"/>
</mappers>
</configuration>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
测试代码
public class MyBatisTest {
@Test
public void helloWorld() throws Exception{
String resource = "mybatis-config.xml";
InputStream is = Resources.getResourceAsStream(resource);
// 创建SqlSessionFactory 对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
// 创建SqlSession 对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 利用sqlSession.getMapper() 方法创建EmployeeMapper 对象
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
// 调用EmployeeMapper 中的getEmpById() 方法获取数据库信息
Employee employee = employeeMapper.getEmpById(2);
// 关闭sqlSession 对象
sqlSession.close();
System.out.println(employee);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
测试结果
不知道大家有没有一个疑问:EmployeeMapper
不是一个接口吗,为什么可以生成一个对象呢?原因是在调用sqlSession.getMapper()
方法时,底层利用JDK 的动态代理方式为EmployeeMapper
接口生成了一个代理对象。在下面会进行其底层的原理分析。
关于JDK 动态代理可参考博文:http://blog.csdn.net/codejas/article/details/79218302
四、为什么MyBatis要提供面向接口的开发方式
到这里我们知道MyBatis 提供了面向接口方式编程,但是为什么要这么做呢?这里就要提及一些关于设计模式方面的知识。我们知道一个设计原则:面向抽象编程,其实面向接口编程就是面向抽象编程。面向抽象编程可以降低代码之间的耦合,使代码更加具有弹性,从而提高程序扩展性。
五、sqlSession.getMapper()
方法创建接口代理对象原理解析
这里采用DUBUG 的方式深入MyBatis 的源码内部去探索sqlSession.getMapper(EmployeeMapper.class)
创建对象的过程。
1.执行sqlSession.getMapper(EmployeeMapper.class)
时,调用的是DefaultSqlSession
中的getMapper(Class<T> type)
方法。
/** DefaultSqlSession 中的方法 */
@Override
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
- 1
- 2
- 3
- 4
- 5
2.DefaultSqlSession
中的getMapper(Class<T> type)
方法调用了Configuration
中的
getMapper(Class<T> type, SqlSession sqlSession)
方法。
/** Configuration 中的方法 */
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
- 1
- 2
- 3
- 4
3.接着调用MapperRegistry
类中的getMapper(Class<T> type, SqlSession sqlSession)
方法,在这个方法中创建了一个MapperProxyFactory
对象,然后调用MapperProxyFactory
类的newInstance(sqlSession)
方法。
/** MapperRegistry 中的方法 */
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
4.下面是在MapperProxyFactory
类中关于newInstance(sqlSession)
方法的调用过程,在这个方法中首先创建了一个MapperProxy<T>
对象。
/** MapperProxyFactory 中的方法 */
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
- 1
- 2
- 3
- 4
- 5
5.下面是创建MapperProxy<T>
对象的过程,仔细看你会发现MapperProxy<T>
类实现了InvocationHandler
接口,这个InvocationHandler
接口是Proxy
的一个辅助类,用于帮助Proxy
创建代理对象。
/** MapperProxy<T> 类的构造函数 */
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
- 1
- 2
- 3
- 4
- 5
- 6
6.创建完MapperProxy<T>
对象后,接着调用MapperProxyFactory
自身类中的另一个newInstance(MapperProxy<T> mapperProxy)
方法,在newInstance(MapperProxy<T> mapperProxy)
中最终完成代理类的创建。然后一步步返回,把代理对象的引用赋给employeeMapper
。到这里EmployeeMapper
接口的代理对象就算是创建完成了。
/** MapperProxyFactory 中的方法 */
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
- 1
- 2
- 3
- 4
建议大家自己动手DEBUG 一下,只有动手了才是自己的。
最终执行结果图:
调用过程时序图:
六、总结
这篇博客前半部分主要介绍了MyBatis 接口式编程的使用方法,后半部分对MyBatis 接口式编程创建接口代理对象的过程进行了深入探究。如果你对后半部分的调用过程不是很理解,大可不必先急于了解其中的过程,首先会使用就可以了。学习不可一蹴而就,慢慢积累,过了一段时间,你自然就会理解了。希望这篇博客可以为你提供帮助。