Mybatis Mapper接口和xml绑定的多种方式、内部实现原理和过程解析
作者:say荣帅
一、绑定方式
1. XML文件方式
在Mybatis中,我们需要创建一个与实体类对应的Mapper接口,然后在该接口上添加方法,这些方法对应着SQL语句。然后,我们需要创建一个XML文件,这个文件中包含了SQL语句和映射关系。
例如,我们有一个User实体类和一个UserMapper接口:
public interface UserMapper { User getUserById(int id); }
然后,我们可以创建一个名为UserMapper.xml的文件,内容如下:
<mapper namespace="com.example.dao.UserMapper"> <select id="getUserById" resultType="com.example.entity.User"> SELECT * FROM user WHERE id = #{id} </select> </mapper>
在这个XML文件中,namespace属性指定了Mapper接口的全限定名,id属性指定了SQL语句的唯一标识符,resultType属性指定了查询结果的类型。
2. 注解方式
Mybatis也支持通过注解的方式来进行映射。首先,需要在Mapper接口上添加@Mapper注解,然后在方法上添加@Select、@Insert、@Update、@Delete等注解来指定SQL语句。
例如,我们可以将上面的UserMapper接口改为注解方式:
import org.apache.ibatis.annotations.*; @Mapper public interface UserMapper { @Select("SELECT * FROM user WHERE id = #{id}") User getUserById(int id); }
在这个例子中,@Mapper注解表示这是一个Mapper接口,@Select注解表示这是一个查询语句,#{id}是参数占位符。
3. 需要注意
启动类上添加@MapperScan注解指定扫描路径
@SpringBootApplication @MapperScan("com.example.mapper") // 指定扫描路径 public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
不用@MapperScan注解,也可以在mybatis-config.xml中配置mapper映射文件的位置和命名空间
<configuration> <mappers> <mapper resource="com/example/mapper/UserMapper.xml"/> // 指定Mapper映射文件的位置和名称 </mappers> </configuration>
二、实现原理
1. 原理
Mybatis的Mapper接口和xml绑定的原理主要依赖于JDK动态代理技术。
而Mybatis中MapperProxy代理类是mybatis实现Mapper接口和xml绑定的核心类之一。它实现了InvocationHandler接口,用于拦截Mapper接口方法的调用,并将方法名和参数传递给SqlSession对象执行相应的SQL语句。
2. MapperProxy代理类的实现过程
- 首先,通过JDK动态代理技术生成一个MapperProxy代理类实例。这个代理类实现了Mapper接口,并重写了接口中的方法。
- 在重写的方法中,MapperProxy会拦截方法调用,并将方法名和参数传递给SqlSession对象执行相应的SQL语句。
- SqlSession对象会根据Mapper接口的namespace值找到对应的mapper.xml文件,并根据id值找到对应的SQL语句。然后,根据返回值类型和参数类型等信息,生成相应的Java代码。这些Java代码会包含对SqlSession的操作,例如查询、更新等操作。最终,SqlSession对象会将这些Java代码编译成字节码,并加载到JVM中运行。
- 当SQL语句执行完毕后,SqlSession对象会将结果返回给MapperProxy代理类。然后,MapperProxy代理类会将结果映射为Java对象,并返回给调用者。
3. MapperProxy代理类源码
下面是MapperProxy代理class的核心方法实现:mybatis3.5.9
public class MapperProxy<T> implements InvocationHandler, Serializable { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else { return cachedInvoker(method).invoke(proxy, method, args, sqlSession); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } private MapperMethodInvoker cachedInvoker(Method method) throws Throwable { try { return MapUtil.computeIfAbsent(methodCache, method, m -> { if (m.isDefault()) { try { if (privateLookupInMethod == null) { return new DefaultMethodInvoker(getMethodHandleJava8(method)); } else { return new DefaultMethodInvoker(getMethodHandleJava9(method)); } } catch (IllegalAccessException | InstantiationException | InvocationTargetException | NoSuchMethodException e) { throw new RuntimeException(e); } } else { return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())); } }); } catch (RuntimeException re) { Throwable cause = re.getCause(); throw cause == null ? re : cause; } } private MethodHandle getMethodHandleJava9(Method method) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { final Class<?> declaringClass = method.getDeclaringClass(); return ((Lookup) privateLookupInMethod.invoke(null, declaringClass, MethodHandles.lookup())).findSpecial( declaringClass, method.getName(), MethodType.methodType(method.getReturnType(), method.getParameterTypes()), declaringClass); } private MethodHandle getMethodHandleJava8(Method method) throws IllegalAccessException, InstantiationException, InvocationTargetException { final Class<?> declaringClass = method.getDeclaringClass(); return lookupConstructor.newInstance(declaringClass, ALLOWED_MODES).unreflectSpecial(method, declaringClass); } }
到此这篇关于Mybatis Mapper接口和xml绑定的多种方式、内部实现原理和过程的文章就介绍到这了,更多相关Mybatis Mapper接口和xml绑定内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!