Mybatis-接口代理方式Dao
接口代理方式实现Dao
案例项目骨架
继续使用之前Mybatis-传统实现方式Dao案例
什么是代理开发方式?
传统的方式实现Dao层,我们既要写接口,还要写实现类。而MyBatis框架可以帮我们省略编写Dao层接口实现类的步骤。程序员只要编写接口就行了,Mybatis框架自身可以根据接口的定义来创建接口的动态代理对象。
采用 Mybatis 的代理开发方式实现 DAO 层的开发,这种方式是我们后面进入企业的主流。
Mapper 接口开发(相当于Dao 接口)方法只需要程序员编写Mapper 接口,由Mybatis 框架根据接口定义创建接口的动态代理对象,代理对象的方法体同上边Dao接口实现类方法。
Mapper 接口开发需要遵循以下规范
(1) Mapper.xml文件中的namespace与mapper接口的全限定名(全类名)相同。
(2) Mapper接口方法名和Mapper.xml中定义的每个statement的id相同。
(3) Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql的parameterType的类型相同。
(4) Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同。
(1) Mapper.xml文件中的namespace与mapper接口的全限定名(全类名)相同。
StudentMapper.xml映射配置文件中的mapper标签的namespace属性值是StudentMapper接口的全类名。
(2) Mapper接口方法名和Mapper.xml中定义的每个statement的id相同。
在StudentMapper.xml中的id属性值为selectAll,则StudentMapper接口中的抽象方法名要与其一致。
(3) Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql的parameterType的类型相同。
在StudentMapper接口中抽象方法的参数类型是Integer,在其对应的映射配置文件中的parameterType的值也要是Integer。
(注:此处本来是java.lang.Integer,但是在Mybatis中写int框架会自动将其转为Integer)
(4) Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同。
编写StudentMapper接口
按照上面的描述,总的来说就如下图所示:
案例演示
我们使用上一个案例的代码进行修改就行。
(1)删除mapper层的接口实现类
(2)修改映射配置文件
全部代码如下所示:
(1)bean层的Student类不变
(2)controller层的StudentController类不变
(3)mapper层将impl包(里面是其实现类)删除,StudentMapper接口不变
(4)service层的StudentServiceImpl是主要修改的对象!!!
package com.itheima.service.impl;
import com.itheima.bean.Student;
import com.itheima.mapper.StudentMapper;
import com.itheima.service.StudentService;
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.List;
/*
业务层实现类
*/
public class StudentServiceImpl implements StudentService {
@Override
public List<Student> selectAll() {
InputStream is = null;
SqlSession sqlSession = null;
List<Student> list = null;
try {
// 1. 加载核心配置文件
is = Resources.getResourceAsStream("MyBatisConfig.xml");
// 2.获取SqlSessionFactory工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
// 3.通过工厂对象获取SqlSession对象
sqlSession = sqlSessionFactory.openSession(true);
// ***4.获取StudentMapper接口的实现类对象(通过Mybatis动态生成)
// getMapper() 里面要一个接口类型的class对象
// 等价于:StudentMapper mapper = new StudentMapperImpl();
// 只不过是MyBatis通过动态的形式为我们获取到的实现类对象
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
// 5.通过实现类对象调用方法,接受结果
list = mapper.selectAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 6.释放资源
if(sqlSession != null) {
sqlSession.close();
}
if(is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 7.返回结果
return list;
}
@Override
public Student selectById(Integer id) {
InputStream is = null;
SqlSession sqlSession = null;
Student stu = null;
try {
// 1. 加载核心配置文件
is = Resources.getResourceAsStream("MyBatisConfig.xml");
// 2.获取SqlSessionFactory工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
// 3.通过工厂对象获取SqlSession对象
sqlSession = sqlSessionFactory.openSession(true);
// ***4.获取StudentMapper接口的实现类对象(通过Mybatis动态生成)
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
// 5.通过实现类对象调用方法,接受结果
stu = mapper.selectById(id);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 6.释放资源
if(sqlSession != null) {
sqlSession.close();
}
if(is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 7.返回结果
return stu;
}
@Override
public Integer insert(Student stu) {
InputStream is = null;
SqlSession sqlSession = null;
Integer result = null;
try {
// 1. 加载核心配置文件
is = Resources.getResourceAsStream("MyBatisConfig.xml");
// 2.获取SqlSessionFactory工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
// 3.通过工厂对象获取SqlSession对象
sqlSession = sqlSessionFactory.openSession(true);
// ***4.获取StudentMapper接口的实现类对象(通过Mybatis动态生成)
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
// 5.通过实现类对象调用方法,接受结果
result = mapper.insert(stu);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 6.释放资源
if(sqlSession != null) {
sqlSession.close();
}
if(is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 7.返回结果
return result;
}
@Override
public Integer update(Student stu) {
InputStream is = null;
SqlSession sqlSession = null;
Integer result = null;
try {
// 1. 加载核心配置文件
is = Resources.getResourceAsStream("MyBatisConfig.xml");
// 2.获取SqlSessionFactory工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
// 3.通过工厂对象获取SqlSession对象
sqlSession = sqlSessionFactory.openSession(true);
// ***4.获取StudentMapper接口的实现类对象(通过Mybatis动态生成)
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
// 5.通过实现类对象调用方法,接受结果
result = mapper.update(stu);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 6.释放资源
if(sqlSession != null) {
sqlSession.close();
}
if(is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 7.返回结果
return result;
}
@Override
public Integer delete(Integer id) {
InputStream is = null;
SqlSession sqlSession = null;
Integer result = null;
try {
// 1. 加载核心配置文件
is = Resources.getResourceAsStream("MyBatisConfig.xml");
// 2.获取SqlSessionFactory工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
// 3.通过工厂对象获取SqlSession对象
sqlSession = sqlSessionFactory.openSession(true);
// ***4.获取StudentMapper接口的实现类对象(通过Mybatis动态生成)
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
// 5.通过实现类对象调用方法,接受结果
result = mapper.delete(id);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 6.释放资源
if(sqlSession != null) {
sqlSession.close();
}
if(is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 7.返回结果
return result;
}
}
(5)核心配置文件MyBatisConfig.xml不变
(6)映射配置文件StudentMapper.xml要改一下
里面的mapper标签下的namespace属性改为全类名
<?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属性:名称空间
-->
<mapper namespace="com.itheima.mapper.StudentMapper">
<!--
select:查询功能的标签
id属性:唯一标识
resultType属性:指定结果映射对象类型
parameterType属性:指定参数映射对象类型
-->
<select id="selectAll" resultType="student">
SELECT * FROM student
</select>
<select id="selectById" resultType="student" parameterType="int">
SELECT * FROM student WHERE id = #{id}
</select>
<!--
返回的是一个int类型的行数!所以可以省略resultType
但是,SQL语句的参数id、name、age是从学生对象中来所以,要有parameterType
-->
<insert id="insert" parameterType="student">
INSERT INTO student VALUES (#{id},#{name},#{age})
</insert>
<update id="update" parameterType="student">
UPDATE student SET name = #{name},age = #{age} WHERE id = #{id}
</update>
<!-- java.lang.Integer -> int-->
<delete id="delete" parameterType="int">
DELETE FROM student WHERE id = #{id}
</delete>
</mapper>
源码分析
分析动态代理对象如何生成的?
通过动态代理开发模式,我们只编写一个接口,不写实现类,我们通过 getMapper() 方法最终获取到 org.apache.ibatis.binding.MapperProxy 代理对象,然后执行功能,而这个代理对象正是 MyBatis 使用了 JDK 的动态代理技术,帮助我们生成了代理实现类对象。从而可以进行相关持久化操作。
获取StudentMapper接口的实现类对象(通过Mybatis动态生成),getMapper() 的底层如下:
getMapper()是来自SqlSeesion
<T> T getMapper(Class<T> var1);
SqlSeesion的实现类为DefaultSqlSession ,在里面重写了getMapper()方法
注意:里面的type就是StudentMapper接口的class对象,this就是SqlSession对象
然后又调用了configuration的getMapper方法
public <T> T getMapper(Class<T> type) {
return this.configuration.getMapper(type, this);
}
在configuration对象中的getMapper()方法,又调用mapperRegistry的getMapper方法
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return this.mapperRegistry.getMapper(type, sqlSession);
}
获取了映射代理工厂对象mapperProxyFactory,工厂又调用了newInstance
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}
创建了映射对象MapperProxy,调用了 newInstance方法将映射代理对象mapperProxy,进行传递
public T newInstance(SqlSession sqlSession) {
MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
最终是调用了Proxy.newProxyInstance(JDK底层提供的动态代理)
里面的三个参数分别是:类加载器、class类型的对象数组,代理规则
protected T newInstance(MapperProxy<T> mapperProxy) {
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
分析方法是如何执行的?
动态代理实现类对象在执行方法的时候最终调用了 mapperMethod.execute() 方法,这个方法中通过 switch 语句根据操作类型来判断是新增、修改、删除、查询操作,最后一步回到了 MyBatis 最原生的 SqlSession 方式来执行增删改查。
动态代理要执行方法首先是要借助invoke()方法,得到了mapperMethod对象,调用execute方法
通过switch对方法的类型进行判断!最后还是使用SqlSession对象的insert()方法!