「JAVA查询mapper」java查询树形结构
今天给各位分享JAVA查询mapper的知识,其中也会对java查询树形结构进行解释,如果能碰巧解决你现在面临的问题,别忘了关注本站,现在开始吧!
本文目录一览:
- 1、Java Web中的mapper,service,controller,model究竟分别是什么作用?
- 2、Mybatis Mapper接口是如何找到实现类的-源码分析
- 3、如何通过接口查找对应的mapper.xml及方
Java Web中的mapper,service,controller,model究竟分别是什么作用?
java web中mapper是对象持久化映射层,一般会继承ibatis或者mybatis servive是一些业务逻辑的处理层,controller是控制层,相当于mvc的c层,model是数据模型层相当于mvc的m层。
Java是一种可以撰写跨平台应用程序的面向对象的程序设计语言。
Java 不同于一般的编译执行计算机语言和解释执行计算机语言。它首先将源代码编译成二进制字节码(bytecode),然后依赖各种不同平台上的虚拟机来解释执行字节码,从而实现了“一次编译、到处执行”的跨平台特性。
不过,每次的编译执行需要消耗一定的时间,这同时也在一定程度上降低了 Java 程序的运行效率。但在 J2SE 1.4.2 发布后,Java 的执行速度有了大幅提升。
与传统程序不同,Sun 公司在推出 Java 之际就将其作为一种开放的技术。全球数以万计的 Java 开发公司被要求所设计的 Java 软件必须相互兼容。
“Java 语言靠群体的力量而非公司的力量”是 Sun 公司的口号之一,并获得了广大软件开发商的认同。这与微软公司所倡导的注重精英和封闭式的模式完全不同。
Mybatis Mapper接口是如何找到实现类的-源码分析
KeyWords: Mybatis 原理,源码,Mybatis Mapper 接口实现类,代理模式,动态代理,Java动态代理,Proxy.newProxyInstance,Mapper 映射,Mapper 实现
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。我们在使用 Mybaits 进行 ,通常只需要定义几个 Mapper 接口,然后在编写一个 xml 文件,我们在配置文件中写好 sql , Mybatis 帮我们完成 Mapper 接口道具体实现的调用。以及将结果映射到 model bean 中。
我们在项目中所编写的众多的 Mapper 类只是一个接口(interface ),根据 Java 的多态性我们知道,可以使用接口接口作为形参,进而在运行时确定具体实现的对象是什么。但是,对于 Mapper 接口,我们并没有编写其实现类!Mybatis是如何找到其实现类,进而完成具体的 CRUD 方法调用的呢?原理何在?
为了弄清楚 Mapper 接口是如何找到实现类的,我们先回忆一下 Mybatis 是怎么使用的,根据实际的例子,进而一点点的去分析。这里的使用指的是Mybatis 单独使用,而不是整合 spring , 因为整合 spring 的话,还需要涉及 Mapper dao 装载到 spring 容器的问题,spring 帮忙创建数据源配置等问题。
通常我们使用 Mybatis 的主要步骤是:
从一段代码看起
上面我们概括了使用 Mybatis 的4个步骤。这4个步骤看起来很简单,但是用代码写出来就很多。我们不妨先记着这4个步骤,再去看代码,会容易点。
在这块代码中,第 1 部分我们使用了 Java 编码的形式来实现 SqlSessionFactory ,也可以使用 xml 。如果使用xml的话,上面的第一部分代码就是这样的:
我们本次的目标是弄清楚 “ Mapper 是如何找到实现类的 ”,我们注意上面代码 3 , 4 的位置:
这里 mapper 可以调用selectBlog(1) 这个方法,说明 mapper 是个对象,因为对象才具有方法行为实现啊。BlogMapper接口是不能实例化的,更没有具体方法实现。我们并没有定义一个类,让它实现BlogMapper接口,而在这里它只是通过调用session.getMapper() 所得到的。由此,我们可以推断:肯定是session.getMapper() 方法内部产生了BlogMapper的实现类。有什么技术可以根据BlogMapper 接口生成了一个实现类呢?想到这里,对于有动态代理 使用经验的程序员来说,很容易想到,这背后肯定是基于动态代理技术,具体怎么实现的呢?下面我们来根据源码一探究竟。
Mapper 接口的注册
从上面的代码中,我们知道 BlogMapper 接口的实现类是从session.getMapper中得来的,大概是基于动态代理技术实现。我们既然能够从SqlSession中得到BlogMapper接口的,那么我们肯定需要先在哪里把它放进去了,然后 SqlSession 才能生成我们想要的代理类啊。上面代码中有这么一行:
跟着这个 addMapper 方法的代码实现是这样的:
我们看到这里 mapper 实际上被添加到 mapperRegissry 中。继续跟进代码:
看到这里我们知道上面所执行的configuration.addMapper(BlogMapper.class); 其实最终被放到了HashMap中,其名为knownMappers ,knowMappers是MapperRegistry 类的一个私有属性,它是一个HashMap 。其Key 为当前Class对象,value 为一个MapperProxyFactory 实例。
这里我们总结一下: 诸如BlogMapper 之类的Mapper接口被添加到了MapperRegistry 中的一个HashMap中。并以 Mapper 接口的 Class 对象作为 Key , 以一个携带Mapper接口作为属性的MapperProxyFactory 实例作为value 。MapperProxyFacory从名字来看,好像是一个工厂,用来创建Mapper Proxy的工厂。我们继续往下看。
Mapper接口的动态代理类的生成
上面我们已经知道,Mapper 接口被到注册到了MapperRegistry中——放在其名为knowMappers 的HashMap属性中,我们在调用Mapper接口的方法的时候,是这样的:
这里,我们跟踪一下session.getMapper() 方法的代码实现,这里 SqlSession 是一个接口,他有两个实现类,一个是DefaultSqlSession,另外一个是SqlSessionManager,这里我们用的是DefaultSqlSession. 为什么是DefaultSqlSession呢?因为我们在初始化SqlSessionFactory的时候所调用的SqlSessionFactoryBuilder的build()方法里边配置的就是DefaultSqlSession, 所以,我们进入到DefaultSession类中,看看它对session.getMapper(BlogMapper.class)是怎么实现的:
如代码所示,这里的 getMapper 调用了 configuration.getMapper , 这一步操作其实最终是调用了MapperRegistry,而此前我们已经知道,MapperRegistry是存放了一个HashMap的,我们继续跟踪进去看看,那么这里的get,肯定是从这个hashMap中取数据。我们来看看代码:
我们调用的session.getMapper(BlogMapper.class);最终会到达上面这个方法,这个方法,根据BlogMapper的class对象,以它为key在knowMappers 中找到了对应的value —— MapperProxyFactory(BlogMapper) 对象,然后调用这个对象的newInstance()方法。根据这个名字,我们就能猜到这个方法是创建了一个对象,代码是这样的:
看到这里,就清楚了,最终是通过Proxy.newProxyInstance产生了一个BlogMapper的代理对象。Mybatis 为了完成 Mapper 接口的实现,运用了代理模式。具体是使用了JDK动态代理,这个Proxy.newProxyInstance方法生成代理类的三个要素是:
代理模式中,代理类(MapperProxy)中才真正的完成了方法调用的逻辑。我们贴出MapperProxy的代码,如下:
我们调用的 Blog blog = mapper.selectBlog(1); 实际上最后是会调用这个MapperProxy的invoke方法。这段代码中,if 语句先判断,我们想要调用的方法是否来自Object类,这里的意思就是,如果我们调用toString()方法,那么是不需要做代理增强的,直接还调用原来的method.invoke()就行了。只有调用selectBlog()之类的方法的时候,才执行增强的调用——即mapperMethod.execute(sqlSession, args);这一句代码逻辑。
而mapperMethod.execute(sqlSession, args);这句最终就会执行增删改查了,代码如下:
再往下一层,就是执行JDBC那一套了,获取链接,执行,得到ResultSet,解析ResultSet映射成JavaBean。
至此,我们已经摸清楚了Blog blog = mapper.selectBlog(1); 中,BlogMapper接口调用到得到数据库数据过程中,Mybaitis 是如何为接口生成实现类的,以及在哪里出发了最终的CRUD调用。实际上,如果我们在调用Blog blog = mapper.selectBlog(1);之前,把从slqSession中得到的 mapper 对象打印出来就会看到,输出大概是这样的:
动态代理没错吧,Java动态代理实在是太美妙了。
上面我们用层层深入的方式摸清楚了 Mapper接口是如何找到实现类的。我们分析了 Mapper接口是如何注册的,Mapper接口是如何产生动态代理对象的,Maper接口方法最终是如何执行的。总结起来主要就是这几个点:
如何通过接口查找对应的mapper.xml及方
在使用mybatis的时候,有一种方式是
BookMapper bookMapper = SqlSession().getMapper(BookMapper.class)
获取接口,然后调用接口的方法。只要方法名和对应的mapper.xml中的id名字相同,就可以执行sql。
那么接口是如何与mapper.xml对应的呢?
首先看下,在getMapper()方法是如何操作的。
在DefaultSqlSession.java中调用了configuration.getMapper()
public T T getMapper(ClassT type) {
return configuration.TgetMapper(type, this);
}
在Configuration.java中调用了mapperRegistry.getMapper(type, sqlSession);
public T T getMapper(ClassT type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
下面重点来了,在MapperRegistry.java中实现了动态代理
public T T getMapper(ClassT type, SqlSession sqlSession) {
final MapperProxyFactoryT mapperProxyFactory = (MapperProxyFactoryT) 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);
}
}
这个函数分两部分来看,首先是从map集合中获取接口代理,map集合的来源,第二部分获取代理后实例化,获取接口的方法,执行sql。
对于第一部分:集合的来源。
这个MapperRegistry.java中有个方法是addMappers();共有两个重载。
public void addMappers(String packageName, Class? superType) {
ResolverUtilClass? resolverUtil = new ResolverUtilClass?();
//通过包名,查找该包下所有的接口进行遍历,放入集合中
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
SetClass? extends Class? mapperSet = resolverUtil.getClasses();
for (Class? mapperClass : mapperSet) {
addMapper(mapperClass);
}
}
//解析包名下的接口
public void addMappers(String packageName) {
addMappers(packageName, Object.class);
}
往上追溯该方法的调用是在SqlSessionFactory.build();时对配置文件的解析,其中对节点mappers的解析,这里先不赘述,
mapperElement(root.evalNode("mappers"));
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
//使用package节点进行解析配置
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
//注册包下的接口
configuration.addMappers(mapperPackage);
} else {
//使用mapper节点
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null url == null mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null url != null mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null url == null mapperClass != null) {
Class? mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
这是调用addMapper()的顺序。
同时在改方法中还有一个方法很重要
public T void addMapper(ClassT type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactoryT(type));
//根据接口名寻找同包下同名的xml或者mapper的namespace是该接口的xml
//找到对用的xml后进行解析mapper节点里面的节点
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
这是通过接口的全路径来查找对应的xml。这里有两种方式解析,也就是我们平常xml文件放置位置的两种写法。
第一种是不加namespace,把xml文件放在和接口相同的路径下,同时xml的名字与接口名字相同,如接口名为Student.java,xml文件为Student.xml。在相同的包下。这种当时可以不加namespace.
第二种是加namespace,通过namespace来查找对应的xml.
到这就是接口名和xml的全部注册流程。
下面再说下第二部分就是通过动态代理获取接口名字来对应xml中的id。
主要有两个类MapperProxyFactory.java和MapperProxy.java
对于MapperProxyFactory.java
public class MapperProxyFactoryT {
private final ClassT mapperInterface;
private MapMethod, MapperMethod methodCache = new ConcurrentHashMapMethod, MapperMethod();
//构造函数,获取接口类
public MapperProxyFactory(ClassT mapperInterface) {
this.mapperInterface = mapperInterface;
}
public ClassT getMapperInterface() {
return mapperInterface;
}
public MapMethod, MapperMethod getMethodCache() {
return methodCache;
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxyT mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
//供外部调用
public T newInstance(SqlSession sqlSession) {
final MapperProxyT mapperProxy = new MapperProxyT(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
在MapperProxy.java中进行方法的执行
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (Object.class.equals(method.getDeclaringClass())) { try { return method.invoke(this, args); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } final MapperMethod mapperMethod = cachedMapperMethod(method);
//方法的执行
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) { MapperMethod mapperMethod = methodCache.get(method); if (mapperMethod == null) { mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()); methodCache.put(method, mapperMethod); } return mapperMethod;
}
至此,就是mybatis所有接口和xml的加载,以及通过动态代理来进行接口的执行的过程。
关于JAVA查询mapper和java查询树形结构的介绍到此就结束了,不知道你从中找到你需要的信息了吗 ?如果你还想了解更多这方面的信息,记得收藏关注本站。
发布于:2022-11-22,除非注明,否则均为
原创文章,转载请注明出处。