mybatis的核心是通过解析核心配置xml(获取数据源、存放mapper.xml文件的路径 ),根据mapper.xml路径解析出sql的类型(crud哪一种)、返回值类型(结果集是什么类型的)、参数类型(带不带参数就行sql操作)这些东西存放在一个Configuration 类里面(用一个map保存key为:接口全限定名.方法名。)。最后利用动态代理(jdk)去生成一个代理对象。代理对象根据Configuration类里面的存放的sql信息,利用jdbc操作数据库。
junit junit 4.12 org.projectlombok lombok 1.18.12 provided mysql mysql-connector-java 8.0.19 c3p0 c3p0 0.9.1.2 dom4j dom4j 1.6.1 jaxen jaxen 1.1.6
SqlMapConfig.xml
UserMapper.xml
package com.lihua.custommybatis;import java.io.InputStream;/*** @author lihua* @date 2022/12/6 17:28** 利用 ClassLoader加载配置*/
public class Resources {/*** 根据配置文件的路径,将配置文件加载成字节输入流,存储在内存中。* @param path 文件路径* @return 字节流*/public static InputStream getResourceAsStream(String path) {InputStream resourceAsStream = Resources.class.getClassLoader().getResourceAsStream(path);return resourceAsStream;}}
package com.lihua.custommybatis;import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;import java.beans.PropertyVetoException;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;/*** @author lihua* @date 2022/12/6 16:09* 用于读取mybatis核心xml文件,对mybatis核心配置文件进行读取。*/
public class XMLConfigBuilder {private Configuration configuration;public XMLConfigBuilder() {this.configuration = new Configuration();}/*** 该方法就是使用dom4j对配置文件进行解析,封装成Configuration对象* @param in 字节输入流* @return Configuration*/public Configuration parseConfig(InputStream in) throws DocumentException, PropertyVetoException {Document document = new SAXReader().read(in);// Element rootElement = document.getRootElement();List list = rootElement.selectNodes("//property");Properties properties = new Properties();for (Element element : list) {String name = element.attributeValue("name");String value = element.attributeValue("value");properties.setProperty(name, value);}ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();comboPooledDataSource.setDriverClass(properties.getProperty("driverClass"));comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));comboPooledDataSource.setUser(properties.getProperty("username"));comboPooledDataSource.setPassword(properties.getProperty("password"));configuration.setDataSource(comboPooledDataSource);// mapper.xml解析:拿到路径--字节输入流--dom4j进行解析List mapperList = rootElement.selectNodes("//mapper");for (Element element : mapperList) {String mapperPath = element.attributeValue("resource");InputStream resourceAsStream = Resources.getResourceAsStream(mapperPath);XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);xmlMapperBuilder.parse(resourceAsStream);}return configuration;}
}
package com.lihua.custommybatis;import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;import java.io.InputStream;
import java.util.List;/*** @author lihua* @date 2022/12/6 16:11*/
public class XMLMapperBuilder {private Configuration configuration;public XMLMapperBuilder(Configuration configuration) {this.configuration = configuration;}public void parse(InputStream in) throws DocumentException {Document document = new SAXReader().read(in);Element rootElement = document.getRootElement();String namespace = rootElement.attributeValue("namespace");List list = rootElement.selectNodes("//select");for (Element element : list) {String id = element.attributeValue("id");String resultType = element.attributeValue("resultType");String parameterType = element.attributeValue("parameterType");String sqlText = element.getTextTrim();MappedStatement mappedStatement = new MappedStatement();mappedStatement.setId(id);mappedStatement.setResultType(resultType);mappedStatement.setParameterType(parameterType);mappedStatement.setSql(sqlText);mappedStatement.setSqlType(element.getName());String key = namespace + "." + id;configuration.getMappedStatement().put(key, mappedStatement);}}}
解析xml后,将结果放到一个Configuration 类里存放。在前面的解析步骤中,需要解析两个配置文件。
核心配置文件:存放了我们连接数据库的数据源信息。我们用一个javax.sql.DataSource;下的DataSource类存放。
mapper.xml:存放了我们编写的sql语句。我们创建一个MappedStatement类存放sql语句的信息,比如:sql类型(insert\select),参数类型,结果集类型,具体的sql。因为一个mapper中有很多sql语句,因此我们需要用一个map存放,key为:接口全限定名.方法名。value为:MappedStatement
MappedStatement
package com.lihua.custommybatis;import lombok.Data;/*** @author lihua* @date 2022/12/6 15:28* 用于存放解析后的sql语句**/
@Data
public class MappedStatement {// id标识private String id;//sql类型,crudprivate String sqlType;// 返回值类型private String resultType;// 参数值类型private String parameterType;// sql语句private String sql;
}
Configuration
package com.lihua.custommybatis;import lombok.Data;import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;/*** @author lihua* @date 2022/12/6 15:24* 自定义config,用于存放读取到的xml文件*/
@Data
public class Configuration {private DataSource dataSource;private Map mappedStatement = new HashMap<>();
}
使用建造者模式创建一个SqlSessionFactory 对象。一般以Builder 结尾的都是建造者模式。建造者模式使用多个简单的对象一步一步构建成一个复杂的对象。(与工厂模式一样,是创建型模式,用于屏蔽复杂对象的构建过程)。
package com.lihua.custommybatis;import org.dom4j.DocumentException;import java.beans.PropertyVetoException;
import java.io.InputStream;/*** @author lihua* @date 2022/12/6 16:07* 建造者模式创建SqlSessionFactory 工厂对象*/
public class SqlSessionFactoryBuilder {public SqlSessionFactory build(InputStream in) {// 1.使用dom4j解析配置文件,将解析出来的内容封装到ConfigurationXMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();Configuration configuration = null;try {configuration = xmlConfigBuilder.parseConfig(in);} catch (DocumentException e) {e.printStackTrace();} catch (PropertyVetoException e) {e.printStackTrace();}// 2.创建SqlSessionFactory对象:工厂类:生产SqlSession:会话对象DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration);return defaultSqlSessionFactory;}
}
SqlSessionFactory 是简单工厂模式(工厂模式),也是用于构建一个对象。这里用来构建SqlSession。
SqlSessionFactory
public interface SqlSessionFactory {/*** 通过工厂模式获取session** @return*/DefaultSqlSession openSession();
}
默认的实现类
package com.lihua.custommybatis;/*** @author lihua* @date 2022/12/6 15:48* 默认会话工厂实现*/
public class DefaultSqlSessionFactory implements SqlSessionFactory {private Configuration configuration;/*** 通过构造器注入* @param configuration*/public DefaultSqlSessionFactory(Configuration configuration) {this.configuration = configuration;}@Overridepublic DefaultSqlSession openSession() {return new DefaultSqlSession(configuration);}
}
在SqlSession中就使用了动态代理(jdk动态代理)模式创建一个代理类去操作数据库。
package com.lihua.custommybatis;import java.util.List;/*** @author lihua* @date 2022/12/6 15:56*/
public interface SqlSession {/*** 查询所有* @param statementId sql唯一id* @param params sql有可能十四模糊查询,传可变参数* @param 泛型* @return List集合*/ List selectList(String statementId, Object... params) throws Exception;/*** 根据条件查询单个* @param statementId sql唯一id* @param params sql有可能十四模糊查询,传可变参数* @param 泛型* @return 某一对象*/ T selectOne(String statementId, Object... params) throws Exception;/*** 为Dao层接口生成代理实现类(获取代理对象,执行crud)* @param mapperClass 字节码* @param 泛型* @return 某一对象*/ T getMapper(Class> mapperClass) throws Exception;
}
package com.lihua.custommybatis;import java.lang.reflect.*;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;/*** @author lihua* @date 2022/12/6 15:49* 会话*/
public class DefaultSqlSession implements SqlSession {private Configuration configuration;private Connection connection;public DefaultSqlSession(Configuration configuration) {this.configuration = configuration;try {//操作数据库的连接this.connection= configuration.getDataSource().getConnection();} catch (SQLException throwables) {throwables.printStackTrace();}}@Overridepublic List selectList(String statementId, Object... params) throws Exception {return null;}@Overridepublic T selectOne(String statementId, Object... params) throws Exception {return null;}@Overridepublic T getMapper(Class> mapperClass) throws Exception {// 使用JDK动态代理来为Dao层接口生成代理对象,并返回。Object proxyInstance = Proxy.newProxyInstance(mapperClass.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {/*** 底层都还是去执行JDBC代码* 根据不同情况来调用findAll或者findByCondition方法* 准备参数:* 1.statementId: sql语句的唯一标识 nnamespace.id = 接口全限定名.方法名*/// 方法名String methodNme = method.getName();String className = method.getDeclaringClass().getName();String statementId = className + "." + methodNme;System.out.println(methodNme);MappedStatement mappedStatement = configuration.getMappedStatement().get(statementId);//根据方法名得到它的环境信息PreparedStatement statement = connection.prepareStatement(mappedStatement.getSql());//根据sql得到预编译语句if ("insert".equals(mappedStatement.getSqlType())){//假设传入一个参数String paramType = mappedStatement.getParameterType();//得到参数类型Class> clazz = args[0].getClass();Field[] fields = clazz.getDeclaredFields();for(int i=0;ifields[i].setAccessible(true);statement.setObject(i+1,fields[i].get(args[0]));}return statement.executeUpdate();}else if("delete".equals(mappedStatement.getSqlType())){for (int i=0;istatement.setObject(i+1,args[i]);}return statement.executeUpdate();}else if("select".equals(mappedStatement.getSqlType())){if (args!=null){for (int i=0;i//替换占位符的参数statement.setObject(i+1,args[i]);}}ResultSet resultSet = statement.executeQuery();//执行查询语句得到返回集合List list=new ArrayList();while (resultSet.next()){//遍历返回集合Class> clazz = Class.forName(mappedStatement.getResultType());//通过反射机制,根据返回值类型创建实例Object object = clazz.newInstance();Field[] fields = clazz.getDeclaredFields();//通过反射机制,该类型的所有属性for (int i=0;ifields[i].setAccessible(true);//设置属性是可以访问的,避免privatefields[i].set(object,resultSet.getObject(fields[i].getName()));//给每个属性赋值}list.add(object);//把对象放在list集合中}return list;}return null;}});return (T) proxyInstance;}
}
MyBatis 框架中的代理类是这样的
/*** Copyright 2009-2017 the original author or authors.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/
package org.apache.ibatis.binding;import java.io.Serializable;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;import org.apache.ibatis.lang.UsesJava7;
import org.apache.ibatis.reflection.ExceptionUtil;
import org.apache.ibatis.session.SqlSession;/*** @author Clinton Begin* @author Eduardo Macarron*/
public class MapperProxy implements InvocationHandler, Serializable {private static final long serialVersionUID = -6424540398559729838L;private final SqlSession sqlSession;private final Class mapperInterface;private final Map methodCache;public MapperProxy(SqlSession sqlSession, Class mapperInterface, Map methodCache) {this.sqlSession = sqlSession;this.mapperInterface = mapperInterface;this.methodCache = methodCache;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, args);} else if (isDefaultMethod(method)) {return invokeDefaultMethod(proxy, method, 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;}@UsesJava7private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)throws Throwable {final Constructor constructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);if (!constructor.isAccessible()) {constructor.setAccessible(true);}final Class> declaringClass = method.getDeclaringClass();return constructor.newInstance(declaringClass,MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED| MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC).unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);}/*** Backport of java.lang.reflect.Method#isDefault()*/private boolean isDefaultMethod(Method method) {return ((method.getModifiers()& (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC)&& method.getDeclaringClass().isInterface();}
}
使用方式和Mybatis基本一样,只是功能简单。
IUserMapper
SqlMapConfig.xml
IUserMapper
package com.lihua.mapper;import com.lihua.pojo.User;import java.util.List;/*** @author lihua* @date 2022/12/6 16:22*/
public interface IUserMapper {List queryUserAll();}
User
package com.lihua.pojo;import lombok.Data;
import lombok.ToString;/*** @author lihua* @date 2022/12/6 15:17* user*/
@Data
@ToString
public class User {private long id;private String account;private String password;private String email;
}
test
package com.lihua.test;import com.lihua.custommybatis.DefaultSqlSession;
import com.lihua.custommybatis.Resources;
import com.lihua.custommybatis.SqlSessionFactory;
import com.lihua.custommybatis.SqlSessionFactoryBuilder;
import com.lihua.mapper.UserMapper;
import com.lihua.pojo.User;import java.io.InputStream;/*** @author lihua* @date 2022/12/6 15:20* test*/
public class Test {@org.junit.Testpublic void test(){InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);DefaultSqlSession sqlSession = sqlSessionFactory.openSession();try {UserMapper mapper = sqlSession.getMapper(UserMapper.class);for (User user : mapper.queryUserAll()) {System.out.println(user.toString());}} catch (Exception e) {e.printStackTrace();}}
}
手写mybatis 1
手写mybatis 2
上一篇:Python 学习笔记(更新中)
下一篇:概率论介绍