mybatis的核心是通过解析核心配置xml(获取数据源、存放mapper.xml文件的路径 ),根据mapper.xml路径解析出sql的类型(crud哪一种)、返回值类型(结果集是什么类型的)、参数类型(带不带参数就行sql操作)这些东西存放在一个Configuration 类里面(用一个map保存key为:接口全限定名.方法名。)。最后利用动态代理(jdk)去生成一个代理对象。代理对象根据Configuration类里面的存放的sql信息,利用jdbc操作数据库。
<dependencies>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.12version>
<scope>providedscope>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.19version>
dependency>
<dependency>
<groupId>c3p0groupId>
<artifactId>c3p0artifactId>
<version>0.9.1.2version>
dependency>
<dependency>
<groupId>dom4jgroupId>
<artifactId>dom4jartifactId>
<version>1.6.1version>
dependency>
<dependency>
<groupId>jaxengroupId>
<artifactId>jaxenartifactId>
<version>1.1.6version>
dependency>
dependencies>
SqlMapConfig.xml
<configuration>
<dataSource>
<property name="driverClass" value="com.mysql.jdbc.Driver">property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/book?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true">property>
<property name="username" value="root">property>
<property name="password" value="123">property>
dataSource>
<mapper resource="UserMapper.xml">mapper>
configuration>
UserMapper.xml
<mapper namespace="com.lihua.mapper.UserMapper">
<select id="queryUserAll" resultType="com.lihua.pojo.User">
select * from user
select>
mapper>
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<Element> 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<Element> 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<Element> 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类型,crud
private 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<String ,MappedStatement> 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解析配置文件,将解析出来的内容封装到Configuration
XMLConfigBuilder 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;
}
@Override
public 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集合
*/
<E> List<E> selectList(String statementId, Object... params) throws Exception;
/**
* 根据条件查询单个
* @param statementId sql唯一id
* @param params sql有可能十四模糊查询,传可变参数
* @param 泛型
* @return 某一对象
*/
<T> T selectOne(String statementId, Object... params) throws Exception;
/**
* 为Dao层接口生成代理实现类(获取代理对象,执行crud)
* @param mapperClass 字节码
* @param 泛型
* @return 某一对象
*/
<T> 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();
}
}
@Override
public <E> List<E> selectList(String statementId, Object... params) throws Exception {
return null;
}
@Override
public <T> T selectOne(String statementId, Object... params) throws Exception {
return null;
}
@Override
public <T> T getMapper(Class<?> mapperClass) throws Exception {
// 使用JDK动态代理来为Dao层接口生成代理对象,并返回。
Object proxyInstance = Proxy.newProxyInstance(mapperClass.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
@Override
public 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;i<fields.length;i++){
fields[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;i<args.length;i++){
statement.setObject(i+1,args[i]);
}
return statement.executeUpdate();
}
else if("select".equals(mappedStatement.getSqlType())){
if (args!=null){
for (int i=0;i<args.length;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;i<fields.length;i++){
fields[i].setAccessible(true);//设置属性是可以访问的,避免private
fields[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<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
@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 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;
}
@UsesJava7
private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)
throws Throwable {
final Constructor<MethodHandles.Lookup> 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
<mapper namespace="com.lihua.mapper.IUserMapper">
<select id="queryUserAll" resultType="com.lihua.pojo.User">
select * from user
select>
mapper>
SqlMapConfig.xml
<configuration>
<dataSource>
<property name="driverClass" value="com.mysql.jdbc.Driver">property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/book?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true">property>
<property name="username" value="root">property>
<property name="password" value="123">property>
dataSource>
<mapper resource="IUserMapper.xml">mapper>
configuration>
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<User> 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.Test
public 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();
}
}
}