一、前言
本文将进一步探讨在之前“「Mybatis实战八」:传统开发方式下的Mybatis DAO层构建”所奠定的基础之上,如何运用Mybatis的接口代理开发模式来优化持久层的设计与实现,解决上文中的问题。
二、代理开发方式简介
Mybatis提供的基于接口的代理开发方式是现代企业级应用中广泛采用的标准实践。这种方式允许开发者仅需专注于定义Mapper接口,而无需编写其实现类。Mybatis框架会在运行时依据这些接口自动生成代理对象,负责处理数据访问操作。为确保正确映射,遵循以下规范:
- Mapper.xml映射文件的namespace属性应与对应的Mapper接口的全限定名一致。
- Mapper接口的方法名必须与Mapper.xml中的每个SQL statement的id相匹配。
- Mapper接口方法的输入参数类型应当与XML中定义的SQL语句的parameterType类型相同。
- Mapper接口方法返回值类型(或输出参数类型)应与XML中定义的SQL语句的resultType类型保持一致。
通过这种方式,开发人员仅需关注业务逻辑相关的Mapper接口设计,具体的数据库交互由Mybatis自动完成。
三、代码演示
-
移除UserMapper接口原有的实现类UserMapperImpl。
-
调整UserMapper.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"> <mapper namespace="mapper.UserMapper"> <!-- 查询所有用户 --> <select id="findAll" resultType="user"> select * from user </select> <!--根据id查找用户--> <select id="findUserById" parameterType="int" resultType="user"> select * from user where id=#{id} </select> <!--新增用户--> <!--#{} : mybatis中的占位符,等同于JDBC中的 parameterType :指定接收到的参数类型 --> <insert id="save" parameterType="domain.User"> insert into user(username, birthday, sex, address) values (#{username}, #{birthday}, #{sex}, #{address}) </insert> <!-- 更新用户 --> <update id="update" parameterType="domain.User"> update user set username=#{username},birthday=#{birthday},sex=#{sex},address=#{address} where id=#{id} </update> <!--删除用户 java.lang.Integer--> <delete id="delete" parameterType="int"> delete from user where id = #{id} </delete> </mapper>
-
修改SqlMapConfig.xml文件,加载映射配置 即 mapper标签 需要更新
注意 UserMapper 的接口类 需要 和 UserMapper.xml文件层级保持一致
SqlMapConfig.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> <properties resource="jdbc.properties"></properties> <typeAliases> <!--方式一:给单个实体起别名--> <!-- <typeAlias type="domain.User" alias="user"></typeAlias>--> <!--方式二:批量起别名 别名就是类名,且不区分大小写--> <package name="domain"/> </typeAliases> <!--环境配置--> <environments default="mysql"> <!--使用mysql环境--> <environment id="mysql"> <!--使用jdbc事务管理亲--> <transactionManager type="JDBC"></transactionManager> <!-- 使用连接池--> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments> <!--加载映射配置--> <mappers> <mapper resource="mapper/UserMapper.xml"></mapper> </mappers> </configuration>
-
修改测试类,将依赖从原来的实现类改为直接使用Mapper接口。
package test; import domain.User; import mapper.UserMapper; 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 org.junit.Test; import java.io.InputStream; public class MybatisTest { @Test public void testFindUserById() throws Exception { InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); SqlSession sqlSession = sqlSessionFactory.openSession(); //获取Mapper代理对象 UserMapper mapper = sqlSession.getMapper(UserMapper.class); User user = mapper.findUserById(1); System.out.println(user); sqlSession.close(); } }
-
测试结果
四、Mybatis基于接口代理方式的内部执行原理
当我们采用接口代理方式进行开发后,虽然表面上只保留了不包含具体实现的Mapper接口,但实际的数据查询工作是由Mybatis框架巧妙地通过动态代理机制完成的:
-
Mybatis为每个定义的Mapper接口创建一个代理对象,该代理对象由MapperProxy类生成。
-
当调用Mapper接口的某个方法时,实际上触发的是MapperProxy的invoke方法。
-
MapperProxy 实现了 InvocationHandler,也就是采用了 JDK动态代理,那么真正执行的方法在实现的invoke接口方法中
-
invoke方法
-
-
在invoke方法内部,进一步调用了mapperMethod.execute(sqlSession, args)来执行具体的数据库操作。
-
最终,真正与数据库交互的核心组件SqlSession被调用,它根据映射规则执行相应的SQL,并将结果映射回Java对象,从而实现了透明化的数据访问服务。