自定义Mybatis

142 阅读8分钟

1.1分析JDBC操作数据库问题

public static void main(String[] args) {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {
            // 加载驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            // 通过驱动获取数据库链接
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/dev?characterEncoding=UTF-8",
                    "****", "****");
            // 定义sql语句,?标识占位符
            String sql = "select * from `user` where name = ?";
            // 获取预处理statement
            preparedStatement = connection.prepareStatement(sql);
            // 设置参数,从1开始
            preparedStatement.setString(1, "Jack");
            // 执行sql,查询数据获取结果
            resultSet = preparedStatement.executeQuery();
            User user = new User();
            // 遍历查询结果
            while (resultSet.next()) {
                long id = resultSet.getLong("id");
                String name = resultSet.getString("name");
                Integer age = resultSet.getInt("age");
                String email = resultSet.getString("email");
                // 封装user
                user.setId(id);
                user.setName(name);
                user.setAge(age);
                user.setEmail(email);
            }
            System.out.println(user);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 资源释放
            if (Objects.nonNull(resultSet)) {
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (Objects.nonNull(preparedStatement)) {
                try {
                    preparedStatement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (Objects.nonNull(connection)) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }

JDBC存在的问题

1、数据库配置信息存在硬编码(驱动、链接信息)

2、数据库连接创建、释放频繁造成系统资源浪费,从⽽影响系统性能

3、sql语句在代码中硬编码,造成代码不易维护,sql变动需要改变 java代码

4、 使⽤preparedStatement向占位符传参数存在硬编码,因为sql语句的where条件不确定,修改sql还要修改代码,系统不易维护

5、 对结果集解析存在硬编码(列名),sql变化导致解析代码变化,系统不易维护,如果能将数据 库记录封装成pojo对象解析⽐较⽅便

1.2JDBC问题解决思路

1、配置信息抽取到配置文件

2、使用连接池优化数据库连接

3、sql语句使用配置文件

4、参数设置和封装结果集使用反射、内省

1.3自定义ORM框架设计思路(本质就是对JDBC的封装)

  • 使用端提供两部分配置文件

    1、数据库配置信息(sqlMapConfig.xml)

    2、sql映射信息,包含参数类型、返回值类型(mapper.xml)

  • 自定义ORM框架

    1、加载配置文件,读取成流存储在内存中

    ​ 1)、创建Resources类型 方法getResourceAsStream(String resource)

    2、创建两个JavaBean(容器对象)存储配置文件解析出来的内容

    ​ 1)、Configuration 核心配置类,存放sqlMapConfig.xml解析出来的内容

    ​ 2)、MappedStatement 映射配置类,存放mapper.xml解析出来的内容

    3、解析配置文件(使用dom4j)

    ​ 创建SqlSessionFactoryBuilder类 方法builde(InputStream in)

    ​ 1)、使用dom4j解析配置文件,将解析出来的内容封装到容器对象中

    ​ 2)、创建解析sqlMapConfig.xml的构建类XMLConfigBuilder

    ​ 3)、创建解析mapper.xml的构建类XMLMapperBuilder

    4、创建SqlSessionFactory接口及默认实现类DefaultSqlSessionFactory 方法openSession() 生产sqlSession(会话对象)

    5、创建SqlSession接口及默认实现类DefaultSqlSession

    ​ (1)、定义CRUD的操作方法 selectList()、selectOne()、update()、delete()

    6、创建Executor接口及实现类SimpleExecutor 方法query(Configuration cf,MappedStatement ms,Object... params)执行jdbc代码

1.4自定义ORM实现

1.4.1使用端

sqlMapConfig.xml

<configuration>
    <!--数据库配置信息-->
    <dataSource>
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/dev?characterEncoding=UTF-8"></property>
        <property name="username" value="****"></property>
        <property name="password" value="****"></property>
    </dataSource>
    <!--存放mapper.xml的全路径-->
    <mapper resource="UserMapper.xml"></mapper>
</configuration>

mapper.xml

<mapper namespace="com.qiyuan.mapper.UserMapper">
    <!--sql的唯一标识:namespace.id来组成:statementId-->
    <select id="listUsers" resultType="com.qiyuan.entity.User">
        select * from user
    </select>
    <!--根据条件查询-->
    <select id="getUser" resultType="com.qiyuan.entity.User" parameterType="com.qiyuan.entity.User">
        select * from user where id = #{id} and name = #{name}
    </select>
    <insert id="insert" parameterType="com.qiyuan.entity.User">
        insert into user (name,age,email) values (#{name},#{age},#{email})
    </insert>
    <update id="update" parameterType="com.qiyuan.entity.User">
        update user
        set name = #{name},
        email = #{email}
        where id = #{id}
    </update>
    <delete id="delete" parameterType="com.qiyuan.entity.User">
        delete from user
        where id = #{id}
    </delete>
</mapper>
1.4.2自定义ORM框架端

Resources

public class Resources {
    /**
     * 根据配置文件路径加载配置文件,读取成流存储在内存中
     *
     * @param path
     * @return java.io.InputStream
     * @author qiyuan
     * @date 2022/1/2 21:16
     */
    public static InputStream getResourceAsStream(String path) {
        InputStream resourceAsStream = Resources.class.getClassLoader().getResourceAsStream(path);
        return resourceAsStream;
    }
}

Configuration

@Data
public class Configuration {
    /**
     * 数据源
     */
    private DataSource dataSource;
    /**
     * mapper映射配置类key:statementid value:封装好的mappedStatement对象
     */
    private Map<String,MappedStatement> mappedStatementMap = new HashMap<>(16);
}

MappedStatement

@Data
public class MappedStatement {
    /**
     * id标识
     */
    public String id;
    /**
     * 参数类型
     */
    public String parameterType;
    /**
     * 返回值类型
     */
    public String resultType;
    /**
     * sql语句
     */
    public String sql;
    /**
     * sql类型 select、insert、update、delete
     */
    public String sqlType;
}

SqlSessionFactoryBuilder

public class SqlSessionFactoryBuilder {

    public SqlSessionFactory build(InputStream inputStream) throws DocumentException {
        // 1.使用dom4j解析配置文件,将解析出来的内容封装到容器对象中
        XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
        Configuration configuration = xmlConfigBuilder.parse(inputStream);

        // 2.创建SqlSessionFactory 生产sqlSession会话对象
        DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration);
        return defaultSqlSessionFactory;
    }
}

XMLConfigBuilder

public class XMLConfigBuilder {

    private Configuration configuration;

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

    public Configuration parse(InputStream inputStream) throws DocumentException {
        // 1.解析sqlMapConfig.xml
        Document document = new SAXReader().read(inputStream);
        Element rootElement = document.getRootElement();
        // 解析出所有的property
        List<Node> propertyList = rootElement.selectNodes("//property");
        // 配置属性就是key-value形式
        Properties properties = new Properties();
        propertyList.forEach(node -> {
            Element element = (Element) node;
            String name = element.attributeValue("name");
            String value = element.attributeValue("value");
            properties.setProperty(name,value);
        });
        // 配置druid连接池
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(properties.getProperty("driverClass"));
        dataSource.setUrl(properties.getProperty("jdbcUrl"));
        dataSource.setUsername(properties.getProperty("username"));
        dataSource.setPassword(properties.getProperty("password"));
        configuration.setDataSource(dataSource);

        // 2.解析mapper.xml
        List<Node> mapperNodeList = rootElement.selectNodes("//mapper");
        for (Node node : mapperNodeList) {
            Element element = (Element) node;
            // 解析出来的mapper映射文件路径
            String mapperPath = element.attributeValue("resource");
            // 根据路径加载成流
            InputStream resourceAsStream = Resources.getResourceAsStream(mapperPath);
            // 创建mapper映射文件解析类
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
            // 解析mapper映射文件
            xmlMapperBuilder.parse(resourceAsStream);
        }
        return configuration;
    }
}

XMLMapperBuilder

public class XMLMapperBuilder {

    private Configuration configuration;

    private static final List<String> sqlTypeList = new ArrayList<>(Arrays.asList(Constant.SELECT, Constant.INSERT, Constant.UPDATE, Constant.DELETE));

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

    public void parse(InputStream inputStream) throws DocumentException {
        Document document = new SAXReader().read(inputStream);
        Element rootElement = document.getRootElement();
        // 获取namespace
        String namespace = rootElement.attributeValue("namespace");
        // mapper映射文件中所有查询配置信息,并构建MappedStatement
        sqlTypeList.forEach(sqlType -> {
            List<Node> nodeList = rootElement.selectNodes(sqlType);
            builderMappedStatement(namespace, nodeList, sqlType);
        });
    }

    /**
     * 构建MappedStatement
     *
     * @param namespace
     * @param selectNodeList
     * @param sqlType
     * @author qiyuan
     * @date 2022/1/2 23:24
     */
    private void builderMappedStatement(String namespace, List<Node> selectNodeList, String sqlType) {
        for (Node node : selectNodeList) {
            Element element = (Element) node;
            // id值,方法名(select id="xxx")
            String id = element.attributeValue("id");
            // 参数类型
            String parameterType = element.attributeValue("parameterType");
            // 返回值类型
            String resultType = element.attributeValue("resultType");
            // sql
            String sqlText = element.getTextTrim();
            // 绑定mappedStatement
            MappedStatement mappedStatement = new MappedStatement();
            mappedStatement.setId(id);
            mappedStatement.setParameterType(parameterType);
            mappedStatement.setResultType(resultType);
            mappedStatement.setSql(sqlText);
            mappedStatement.setSqlType(sqlType);
            String key = namespace + "." + id;
            configuration.getMappedStatementMap().put(key, mappedStatement);
        }
    }
}

SqlSessionFactory和DefaultSqlSessionFactory

public interface SqlSessionFactory {
    SqlSession openSession();
}
public class DefaultSqlSessionFactory implements SqlSessionFactory{

    private Configuration configuration;

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

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

SqlSession和DefaultSqlSession

public interface SqlSession {
    <E> List<E> selectList(String statementId, Object... params) throws Exception;

    <T> T selectOne(String statementId, Object... params) throws Exception;

    int insert(String statementId, Object... params) throws Exception;

    int update(String statementId, Object... params) throws Exception;

    int delete(String statementId, Object... params) throws Exception;

    <T> T getMapper(Class<?> aClass);
}
public class DefaultSqlSession implements SqlSession {

    private Configuration configuration;

    private SimpleExecutor simpleExecutor;

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

    @Override
    public <E> List<E> selectList(String statementId, Object... params) throws Exception {
        MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
        List<Object> list = simpleExecutor.query(configuration, mappedStatement, params);
        return (List<E>) list;
    }

    @Override
    public <T> T selectOne(String statementId, Object... params) throws Exception {
        List<Object> objects = selectList(statementId, params);
        if (Objects.isNull(objects) || objects.size() == 0) {
            return null;
        }
        if (objects.size() != 1) {
            throw new RuntimeException("数据返回结果集过多");
        }
        return (T) objects.get(0);
    }

    @Override
    public int insert(String statementId, Object... params) throws Exception {
        return update(statementId, params);
    }

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

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

    @Override
    public <T> T getMapper(Class<?> aClass) {
        // JDK动态代理为Mapper接口生成代理类
        Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{aClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 获取执行方法名
                String name = method.getName();
                // 获取代理类全限定类名
                String className = method.getDeclaringClass().getName();
                // statementId = className.name
                String statementId = className + "." + name;
                // 获取方法返回值
                Type genericReturnType = method.getGenericReturnType();
                // 获取mappedStatement
                MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
                switch (mappedStatement.getSqlType()) {
                    case Constant.SELECT:
                        // 判断返回值是否是泛型
                        if (genericReturnType instanceof ParameterizedType) {
                            List<Object> list = selectList(statementId, args);
                            return list;
                        }
                        Object obj = selectOne(statementId, args);
                        return obj;
                    case Constant.INSERT:
                        return insert(statementId, args);
                    case Constant.UPDATE:
                        return update(statementId, args);
                    case Constant.DELETE:
                        return delete(statementId, args);
                    default:
                        return null;
                }
            }
        });
        return (T) proxyInstance;
    }
}

Executor和SimpleExecutor

public interface Executor {

    <E> List<E> query(Configuration configuration, MappedStatement ms, Object... params) throws Exception;

    int update(Configuration configuration, MappedStatement ms, Object... params) throws Exception;

    int delete(Configuration configuration, MappedStatement ms, Object... params) throws Exception;
}
public class SimpleExecutor implements Executor {

    @Override
    public <E> List<E> query(Configuration configuration, MappedStatement ms, Object... params) throws Exception {
        // 1.获取预处理对象preparedStatement,并设置参数
        PreparedStatement preparedStatement = getPreparedStatement(configuration, ms, params);
        // 2.执行sql
        ResultSet resultSet = preparedStatement.executeQuery();

        // 3.封装返回结果集
        // 获取返回值Class
        Class<?> resultClassType = getClassType(ms.getResultType());
        List<Object> list = new ArrayList<>();
        while (resultSet.next()) {
            // 获取元数据
            ResultSetMetaData metaData = resultSet.getMetaData();
            // 获取返回类型实例
            Object obj = resultClassType.newInstance();
            for (int i = 1; i <= metaData.getColumnCount(); i++) {
                // 获取字段名称
                String columnName = metaData.getColumnName(i);
                // 获取字段值
                Object value = resultSet.getObject(columnName);
                // 使用内省,封装数据库表和实体的对应关系
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultClassType);
                Method writeMethod = propertyDescriptor.getWriteMethod();
                writeMethod.invoke(obj, value);
            }
            list.add(obj);
        }
        return (List<E>) list;
    }

    @Override
    public int update(Configuration configuration, MappedStatement ms, Object... params) throws Exception {
        // 1.获取预处理对象preparedStatement,并设置参数
        PreparedStatement preparedStatement = getPreparedStatement(configuration, ms, params);
        return preparedStatement.executeUpdate();
    }

    @Override
    public int delete(Configuration configuration, MappedStatement ms, Object... params) throws Exception {
        // 1.获取预处理对象preparedStatement,并设置参数
        PreparedStatement preparedStatement = getPreparedStatement(configuration, ms, params);
        return preparedStatement.executeUpdate();
    }

    /**
     * 获取预处理对象preparedStatement,并设置参数
     *
     * @param configuration
     * @param ms
     * @param params
     * @return java.sql.PreparedStatement
     * @author qiyuan
     * @date 2022/1/6 14:50
     */
    private PreparedStatement getPreparedStatement(Configuration configuration, MappedStatement ms, Object... params) throws Exception {
        // 1.获取数据库连接
        Connection connection = configuration.getDataSource().getConnection();
        // 2.获取原始sql,对原始sql进行解析转换,同时对#{}里面的值进行存储id,name
        // select * from user where id = #{id} and name = #{name}
        // select * from user where id = ? and name = ?
        String sql = ms.getSql();
        BoundSql boundSql = getBoundSql(sql);
        // 3.获取预处理对象preparedStatement
        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getContent());
        // 4.设置参数
        String parameterType = ms.getParameterType();
        // 获取参数Class
        Class<?> parameterClassType = getClassType(parameterType);
        // 获取参数集合
        List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
        for (int i = 0; i < parameterMappingList.size(); i++) {
            // 获取参数对象(#{}里面解析出来的值对象)
            ParameterMapping parameterMapping = parameterMappingList.get(i);
            // 获取参数(#{}里面解析出来的值)
            String content = parameterMapping.getContent();
            // 反射获取实例字段
            Field declaredField = parameterClassType.getDeclaredField(content);
            // 设置可以操作私有属性
            declaredField.setAccessible(true);
            // 获取实例属性值
            Object o = declaredField.get(params[0]);
            // 设置参数,下标从1开始
            preparedStatement.setObject(i + 1, o);
        }
        return preparedStatement;
    }

    /**
     * 完成对#{}的解析工作:1.将#{}使用?进行代替,2.解析出#{}里面的值进行存储
     *
     * @param sql
     * @return com.qiyuan.config.BoundSql
     * @author qiyuan
     * @date 2022/1/4 11:39
     */
    private BoundSql getBoundSql(String sql) {
        ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
        GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
        String parseSql = genericTokenParser.parse(sql);
        List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
        BoundSql boundSql = new BoundSql(parseSql, parameterMappings);
        return boundSql;
    }

    /**
     *
     * @author qiyuan
     * @date 2022/1/6 17:19
     * @param fullPath
     * @return java.lang.Class<?>
     */
    private Class<?> getClassType(String fullPath) throws ClassNotFoundException {
        if (fullPath == null) {
            return null;
        }
        return Class.forName(fullPath);
    }
}

BoundSql

@Data
@AllArgsConstructor
public class BoundSql {

    /**
     * 解析之后的sql
     */
    private String content;

    private List<ParameterMapping> parameterMappingList;

}

1.5自定义ORM优化

通过自定义ORM,我们解决了JDBC操作数据库带来的一些问题

  • 1、数据库配置信息存在硬编码(驱动、链接信息)

  • 2、数据库连接创建、释放频繁造成系统资源浪费,从⽽影响系统性能

  • 3、sql语句在代码中硬编码,造成代码不易维护,sql变动需要改变 java代码

  • 4、 使⽤preparedStatement向占位符传参数存在硬编码,因为sql语句的where条件不确定,修改sql还要修改代码,系统不易维护

  • 5、 对结果集解析存在硬编码(列名),sql变化导致解析代码变化,系统不易维护,如果能将数据 库记录封装成pojo对象解析⽐较⽅便

现在我们自定义ORM还有什么问题?

  • 1、mapper的实现类中存在重复的代码,整个操作的过程模板重复(创建sqlsession,调⽤sqlsession⽅法)

  • 2、mapper的实现类中存在硬编码,调⽤sqlsession的⽅法时,参数statementId硬编码

解决方案

是不是可以不需要mapper实现类?如果可以不就不存在以上问题了吗

使用代理模式,创建接口的代理对象就可以解决

在SqlSession里面添加getMapper方法

public interface SqlSession {
    <T> T getMapper(Class<?> aClass);
}

实现

@Override
public <T> T getMapper(Class<?> aClass) {
    // JDK动态代理为Mapper接口生成代理类
    Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{aClass}, new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // 获取执行方法名
            String name = method.getName();
            // 获取代理类全限定类名
            String className = method.getDeclaringClass().getName();
            // statementId = className.name
            String statementId = className + "." + name;
            // 获取方法返回值
            Type genericReturnType = method.getGenericReturnType();
            // 获取mappedStatement
            MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
            switch (mappedStatement.getSqlType()) {
                case Constant.SELECT:
                    // 判断返回值是否是泛型
                    if (genericReturnType instanceof ParameterizedType) {
                        List<Object> list = selectList(statementId, args);
                        return list;
                    }
                    Object obj = selectOne(statementId, args);
                    return obj;
                case Constant.INSERT:
                    return insert(statementId, args);
                case Constant.UPDATE:
                    return update(statementId, args);
                case Constant.DELETE:
                    return delete(statementId, args);
                default:
                    return null;
            }
        }
    });
    return (T) proxyInstance;
}

使用端

@Test
public void testMapperInsert() throws Exception {
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    User user = new User();
    user.setName("启元");
    user.setAge(18);
    user.setEmail("qiyuan@123.com");
    int count = userMapper.insert(user);
    System.out.println("影响行数:" + count);
}

本文由博客一文多发平台 OpenWrite 发布!