设计思路

使用者

  • 引入自定义的框架jar
  • 编写配置文件
    1. dbconfig.xml:数据库配置信息,存放mapper.xml位置
    2. mapper.xml:sql配置信息

框架

  • 加载配置文件:以字节流存储在内存中
    • 创建Resources类:InputStrean getResourceAsStream(String path)
  • 创建两个Bean:容器对象,存放配置文件解析结果
    • Configuration:核心配置类,dbconfig.xml解析结果
    • MapperStatement:映射配置类,mapper.xml解析结果
  • 解析配置文件:dom4j
    • 创建SqlSessionFactoryBuilder:build(InputStream in)
    • 使用dom4j解析配置文件,将结果存放在容器对象
    • 创建SqlSessionFactory对象:生产SqlSession会话对象(工厂模式)
  • 创建SqlSessionFactory接口及实现类DefaultSqlSessionFactory
    • openSession():生产sqlSession
  • 创建SqlSession接口及实现类DefaultSqlSession
    • 定义crud操作
      • selectList()
      • selectOne()
      • update()
      • delete()
  • 创建Executor接口及实现类SimpleExecutor
    • query(Configuration, MapperStatement, Object… params):执行JDBC代码

实现

测试项目创建

  • 创建IPersistence_test项目

  • 在resources目录下新建dbconfig.xml配置文件,填写数据源信息
<!--dbconfig.xml-->
<configuration>
    <dataSource>
        <property name="driver-class" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql:localhost:3306/mydb"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </dataSource>
</configuration>
  • 然后编写mapper.xml保存具体的sql
<mapper namespace="user">

    <!--sql唯一标识:namespace.id组成:statementId-->
    <select id="findAll" resultType="com.chaofan.pojo.User">
        select * from user
    </select>

    <select id="findOne" paramType="com.chaofan.pojo.User" resultType="com.chaofan.pojo.User">
        select * from user where id=#{id} and username=#{username}
    </select>

</mapper>

使用namespace来区分不同的mapper.xml文件

在父标签内部,按照sql的不同,选择不同的标签来区别,再通过id属性来区分每一个标签,然后按照namespace.id组成每一个sql的唯一标识

  • 最后在dbconfig.xml中指定mapper.xml的位置
<mappers resource="UserMapper.xml"/>

使用端暂时结束,然后开始编写库

目前配置文件总览

<!--dbconfig.xml-->
<configuration>
    <dataSource>
        <property name="driver-class" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql:localhost:3306/mydb"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </dataSource>
    <!--mapper.xml位置-->
    <mappers resource="UserMapper.xml"/>
</configuration>
<!--UserMapper.xml-->
<mapper namespace="user">

    <!--sql唯一标识:namespace.id组成:statementId-->
    <select id="findAll" resultType="com.chaofan.pojo.User">
        select * from user
    </select>

    <select id="findOne" paramType="com.chaofan.pojo.User" resultType="com.chaofan.pojo.User">
        select * from user where id=#{id} and username=#{username}
    </select>

</mapper>

自定义框架的实现

创建项目

新建一个IPersistence模块

新建Resources类

编写Resources类

package com.chaofan.io;

import java.io.InputStream;

/**
 * @author Fairy
 * @date 2021/11/8
 */
public class Resources {

    /**
     * 根据path加载配置文件
     * @param path 配置文件路径
     * @return InputStream
     */
    public static InputStream getResourceAsStream(String path){
        return Resources.class.getClassLoader().getResourceAsStream(path);
    }
}

非常简单,获得当前类的类加载器然后加载配置文件获得输入流

容器对象定义

Configuration核心配置类

package com.chaofan.pojo;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * 核心配置类
 * @author Fairy
 * @date 2021/11/8
 * @since 1.0
 */
public class Configuration {

    private DataSource dataSource;

    /**
     * key:statementId
     * value:封装好的MappedStatement
     */
    Map<String, MappedStatement> mappedStatementMap = new HashMap<>();

    public DataSource getDataSource() {
        return dataSource;
    }

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public Map<String, MappedStatement> getMappedStatementMap() {
        return mappedStatementMap;
    }

    public void setMappedStatementMap(Map<String, MappedStatement> mappedStatementMap) {
        this.mappedStatementMap = mappedStatementMap;
    }
}

MappedStatement解析出来的Mapper对象

package com.chaofan.pojo;

/**
 * 解析出来的Mapper对象
 * @author Fairy
 * @date 2021/11/8
 * @since 1.0
 */
public class MappedStatement {

    /**
     * id标识
     */
    private String id;

    /**
     * 返回值类型
     */
    private String resultType;

    /**
     * 参数类型
     */
    private String paramType;

    /**
     * sql语句
     */
    private String sql;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getResultType() {
        return resultType;
    }

    public void setResultType(String resultType) {
        this.resultType = resultType;
    }

    public String getParamType() {
        return paramType;
    }

    public void setParamType(String paramType) {
        this.paramType = paramType;
    }

    public String getSql() {
        return sql;
    }

    public void setSql(String sql) {
        this.sql = sql;
    }
}

编写配置文件解析类

解析配置文件。保存在Configuration类的实例中

package com.chaofan.config;

import com.chaofan.io.Resources;
import com.chaofan.pojo.Configuration;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.util.List;
import java.util.Properties;

/**
 * @author Fairy
 * @date 2021/11/8
 */
public class XMLConfigBuilder {

    private final Configuration configuration;

    public XMLConfigBuilder() {
        this.configuration = new Configuration();
    }

    /**
     * 解析配置文件,封装Configuration
     * 使用dom4j
     * @param in InputStream
     * @return Configuration
     */
    @SuppressWarnings("unchecked")
    public Configuration parseConfig(InputStream in) throws Exception {
        Document document = new SAXReader().read(in);
        //<configuration>
        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 dataSource = new ComboPooledDataSource();
        dataSource.setJdbcUrl(properties.getProperty("url"));
        dataSource.setDriverClass(properties.getProperty("driver-class"));
        dataSource.setUser(properties.getProperty("username"));
        dataSource.setPassword(properties.getProperty("password"));
        configuration.setDataSource(dataSource);

        //解析mapper.xml
        //拿到路径
        List<Element> mapperList = rootElement.selectNodes("//mapper");
        //获得流并解析
        for (Element element : mapperList) {
            String mapperPath = element.attributeValue("resource");
            InputStream mapperStream = Resources.getResourceAsStream(mapperPath);
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
            xmlMapperBuilder.parse(mapperStream);
        }
        return configuration;
    }
}

使用xpath获取所有的<property>标签,然后遍历获取到的List,保存在Properties类的对象中

也可以用Map存储

Mapper解析类

解析mapper.xml的每一个sql语句,封装到MappedStatement对象中

package com.chaofan.config;

import com.chaofan.pojo.Configuration;
import com.chaofan.pojo.MappedStatement;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.util.List;

/**
 * @author Fairy
 * @date 2021/11/9
 */
public class XMLMapperBuilder {

    private final Configuration configuration;

    public XMLMapperBuilder(Configuration configuration) {
        this.configuration = configuration;
    }

    /**
     * 把Mappers存到Configuration中
     * @param inputStream mappers流
     */
    @SuppressWarnings("unchecked")
    public void parse(InputStream inputStream) throws Exception {
        Document document = new SAXReader().read(inputStream);
        Element rootElement = document.getRootElement();
        String namespace = rootElement.attributeValue("namespace");
        List<Element> selectList = rootElement.selectNodes("//select");
        for (Element element : selectList) {
            //获取每个属性
            String id = element.attributeValue("id");
            String resultType = element.attributeValue("resultType");
            String paramType = element.attributeValue("paramType");
            String sqlText = element.getTextTrim();
            //封装到MappedStatement
            MappedStatement statement = new MappedStatement();
            statement.setId(id);
            statement.setResultType(resultType);
            statement.setParamType(paramType);
            statement.setSql(sqlText);
            String key = namespace + "." + id;
            //保存在map中
            configuration.getMappedStatementMap().put(key, statement);
        }
    }
}

编写SqlSessionFactoryBuilder类

package com.chaofan.session;

import com.chaofan.config.XMLConfigBuilder;
import com.chaofan.pojo.Configuration;

import java.io.InputStream;

/**
 * @author Fairy
 * @date 2021/11/8
 */
public class SqlSessionFactoryBuilder {

    /**
     * 构建SqlSessionFactory
     * @param in 主配置文件流
     * @return DefaultSqlSessionFactory
     */
    public SqlSessionFactory build(InputStream in) throws Exception {
        //使用dom4j解析配置文件,封装到Configuration
        XMLConfigBuilder builder = new XMLConfigBuilder();
        Configuration configuration = builder.parseConfig(in);
        //创建SqlSessionFactory对象:生产SqlSession
        return new DefaultSqlSessionFactory(configuration);
    }
}

把InputStream传给XMLConfigBuilder类解析成Configuration对象,然后返回DefaultSqlSessionFactory对象

SqlSessionFactory接口

package com.chaofan.session;

/**
 * @author Fairy
 * @date 2021/11/8
 */
public interface SqlSessionFactory {

    /**
     * 生产一个SqlSession
     * @return SqlSession
     */
    SqlSession openSession();
}

生产SqlSession,然后编写默认实现类

DefaultSqlSessionFactory

package com.chaofan.session;

import com.chaofan.pojo.Configuration;

/**
 * @author Fairy
 * @date 2021/11/9
 */
public class DefaultSqlSessionFactory implements SqlSessionFactory {

    private final Configuration configuration;

    public DefaultSqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
    }

    @Override
    public SqlSession openSession() {
        return new DefaultSqlSession();
    }
}

这个工厂类生产SqlSession对象

SqlSession接口定义

package com.chaofan.session;

import java.util.List;

/**
 * @author Fairy
 * @date 2021/11/9
 */
public interface SqlSession {

    /**
     * 查询多个
     * @param statementId sql的id
     * @param params 参数列表
     * @param <E> 返回类型
     * @return List<E>
     */
    <E> List<E> selectList(String statementId, Object... params) throws Exception;

    /**
     * 按条件查询单个
     * @param statementId sql的id
     * @param params 参数列表
     * @param <T> 返回类型
     * @return T
     */
    <T> T selectOne(String statementId, Object... params) throws Exception;

    /**
     * 更新内容
     * @param statementId sql的id
     * @param params 参数列表
     * @return 受影响的行数
     */
    int update(String statementId, Object params) throws Exception;

    /**
     * 保存对象
     * @param statementId sql的id
     * @param params 参数列表
     * @return 受影响的行数
     */
    int save(String statementId, Object params) throws Exception;

    /**
     * 删除对象
     * @param statementId sql的id
     * @param params 参数列表
     * @return 受影响的行数
     */
    int delete(String statementId, Object params) throws Exception;

    /**
     * 关闭连接
     */
    void close();
}

编写SqlSession实现类DefaultSqlSession

package com.chaofan.session;

import com.chaofan.pojo.Configuration;
import com.chaofan.pojo.MappedStatement;

import java.util.List;

/**
 * @author Fairy
 * @date 2021/11/9
 */
public class DefaultSqlSession implements SqlSession {

    private final Configuration configuration;

    private Executor executor;

    public DefaultSqlSession(Configuration configuration) {
        this.configuration = configuration;
    }

    @Override
    public <E> List<E> selectList(String statementId, Object... params) throws Exception {
        //调动simpleExecutor的query方法
        executor = new SimpleExecutor();
        MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
        return executor.query(configuration, mappedStatement, params);
    }

    @Override
    public <T> T selectOne(String statementId, Object... params) throws Exception {
        List<T> t = selectList(statementId, params);
        if (t.size() == 1) {
            return t.get(0);
        } else if (t.size() > 1){
            throw new RuntimeException("返回结果过多");
        }
        throw new RuntimeException("返回结果为空");
    }

    @Override
    public int update(String statementId, Object params) throws Exception {
        executor = new SimpleExecutor();
        MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
        return executor.update(configuration, mappedStatement, params);
    }

    @Override
    public int save(String statementId, Object params) throws Exception {
        executor = new SimpleExecutor();
        MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
        return executor.update(configuration, mappedStatement, params);
    }

    @Override
    public int delete(String statementId, Object params) throws Exception {
        executor = new SimpleExecutor();
        MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
        return executor.update(configuration, mappedStatement, params);
    }

    @Override
    public void close() {
        try {
            executor.close();
        }catch(Exception ignore){}
    }

}

Executer接口执行sql

package com.chaofan.session;

import com.chaofan.pojo.Configuration;
import com.chaofan.pojo.MappedStatement;

import java.sql.SQLException;
import java.util.List;

/**
 * @author Fairy
 * @date 2021/11/10
 */
public interface Executor {

    /**
     * 查找
     * @param configuration 核心配置类
     * @param mappedStatement 已封装的sql
     * @param params 参数
     * @param <E> 返回值类型
     * @return 结果
     */
    <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception;

    /**
     * 更新
     * @param configuration 核心配置类
     * @param mappedStatement 已封装的sql
     * @param params 参数
     * @return 受影响的行数
     */
    int update(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception;

    /**
     * 关闭连接
     */
    void close();
}

编写实现类

package com.chaofan.session;

import com.chaofan.config.BoundSql;
import com.chaofan.pojo.Configuration;
import com.chaofan.pojo.MappedStatement;
import com.chaofan.utils.GenericTokenParser;
import com.chaofan.utils.ParameterMapping;
import com.chaofan.utils.ParameterMappingTokenHandler;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

/**L
 * @author Fairy
 * @date 2021/11/10
 */
public class SimpleExecutor implements Executor {
    private PreparedStatement statement;
    private Connection connection;
    @Override
    @SuppressWarnings({"unchecked", "Duplicates"})
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
        //注册驱动,获取连接
        connection = configuration.getDataSource().getConnection();
        //获取sql
        String sql = mappedStatement.getSql();
        //转换sql
        BoundSql boundSql = getBoundSql(sql);
        //获取预处理对象
        statement = connection.prepareStatement(boundSql.getSqlText());
        //设置参数
        String paramType = mappedStatement.getParamType();
        Class<?> type = getClassType(paramType);
        List<ParameterMapping> mappingList = boundSql.getParameterMappingList();
        for (int i = 0; i < mappingList.size(); i++) {
            ParameterMapping parameterMapping = mappingList.get(i);
            String content = parameterMapping.getContent();
            Field field = type.getDeclaredField(content);
            //开启暴力访问
            field.setAccessible(true);
            E o = (E)field.get(params[0]);
            //i从0开始,所以加一
            statement.setObject(i+1, o);
        }
        //执行sql
        ResultSet resultSet = statement.executeQuery();
        String resultType = mappedStatement.getResultType();
        Class<?> resultTypeClass = getClassType(resultType);
        Object o = resultTypeClass.getDeclaredConstructor().newInstance();
        //封装结果集
        List<Object> objects = new ArrayList<>();
        while (resultSet.next()) {
            ResultSetMetaData metaData = resultSet.getMetaData();
            for (int i = 0; i < metaData.getColumnCount(); i++) {
                //获取字段名
                String columnName = metaData.getColumnName(i+1);
                Object value = resultSet.getObject(columnName);
                //根据表和实体关系,完成封装,内省
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
                Method writeMethod = propertyDescriptor.getWriteMethod();
                writeMethod.invoke(o, value);
            }
            objects.add(o);
        }
        return (List<E>) objects;
    }

    @Override
    @SuppressWarnings("Duplicates")
    public int update(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
        connection = configuration.getDataSource().getConnection();
        String sql = mappedStatement.getSql();
        BoundSql boundSql = getBoundSql(sql);
        statement = connection.prepareStatement(boundSql.getSqlText());
        //设置参数
        String paramType = mappedStatement.getParamType();
        Class<?> type = getClassType(paramType);
        List<ParameterMapping> mappingList = boundSql.getParameterMappingList();
        for (int i = 0; i < mappingList.size(); i++) {
            ParameterMapping parameterMapping = mappingList.get(i);
            String content = parameterMapping.getContent();
            Field field = type.getDeclaredField(content);
            //开启暴力访问
            field.setAccessible(true);
            Object o = field.get(params[0]);
            //i从0开始,所以加一
            statement.setObject(i+1, o);
        }
        //执行sql
        return statement.executeUpdate();
    }

    @Override
    public void close() {
        try {
            statement.close();
            connection.close();
        } catch (SQLException ignored) {}
    }

    private Class<?> getClassType(String paramType) throws ClassNotFoundException {
        if (paramType != null) {
            return Class.forName(paramType);
        }
        throw new NullPointerException("paramType is null");
    }

    /**
     * 把 #{} 转换为 ?
     * 解析出#{}的值
     * @param sql 待处理的sql
     * @return 封装后的BoundSql
     */
    private BoundSql getBoundSql(String sql) {
        //标记处理类
        ParameterMappingTokenHandler tokenHandler = new ParameterMappingTokenHandler();
        GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", tokenHandler);
        //解析出来的sql
        String parseSql = genericTokenParser.parse(sql);
        //解析出来的参数名
        List<ParameterMapping> mappings = tokenHandler.getParameterMappings();
        return new BoundSql(parseSql, mappings);
    }
}

最后测试一下

InputStream inputStream = Resources.getResourceAsStream("dbconfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();

User user = new User();
user.setId("1");
user.setName("张三");
User result = sqlSession.selectOne("user.findOne", user);
System.out.println(result);

补充

根据接口动态生成实现类

@Override
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> mapperClass) {
    //jdk动态代理
    Object o = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, (proxy, method, args) -> {
        String methodName = method.getName();
        String className = method.getDeclaringClass().getName();
        String statementId = className + "." + methodName;
        //获取返回值类型
        Type genericReturnType = method.getGenericReturnType();
        if (genericReturnType instanceof ParameterizedType) {
            return selectList(statementId, args);
        }
        return selectOne(statementId, args);
    });
    return (T) o;
}

评论