自定义Mybatis持久层框架

news/2024/5/18 23:11:33 标签: 持久层框架, mvc, jdbc, 自定义

自定义持久层框架

1.回顾JDBC

创建自定义持久层框架前,让我们先来回顾一下,JDBC的操作:

public static void main(String[] args) {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {
            //加载数据库驱动
            Class.forName("com.mysql.jdbc.Driver");
            //通过驱动管理类获取数据库链接
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?" +
                    "characterEncoding = utf-8", "root", "root");
            //定义sq1语句?表示 占位符
            String sql = "select * from user where username = ?";
            //获取预处理statement
            preparedStatement = connection.prepareStatement(sql);
            //设置参数,第一个参数为sq1语句中参数的序号(从1开始),第二个参 数为设置的参数值
            preparedStatement.setString(1, "zhangsan");
            //向数据库发出sq1执行查询,查询出结果集
            resultSet = preparedStatement.executeQuery();
            //遍历查询结果集
            while (resultSet.next()) {
                int id = resultSet.getInt("id");
                String username = resultSet.getString("username");
                user.setId(id);
                user.setUsername(username);
            }
            System.out.println(user);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //释放资源
            if (resultSet != null) {
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (preparedStatement != null) {
                try {
                    preparedStatement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

2.自定义框架设计

jMZxW4

在开始上手之前,我先把我之前创建过的整个目录结构,给大家看一下,方便大家整体的去了解

20201106C1TfrD
2.1 创建使用端项目
创建一个Maven项目,直接点击下一步
202011060EcUWW

项目名称为:MyFrame_test,点击完成
2020110615945643385678
在使用端项目中创建配置配置文件
在resources下创建sqlMapConfig.xml

<configuration>
    <dataSource>
        <property name="driverClass" valaue="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" valaue="jdbc:mysql:///demo2"></property>
        <property name="username" valaue="root"></property>
        <property name="password" valaue="1q2w3e4r"></property>
    </dataSource>

    <mapper resource="UserMapper.xml"> </mapper>
</configuration>

在resources下创建UserMapper.xml

<mapper namespace="user">
    <select id="selectList" resultType="com.rick.pojo.User">
        select * from user
    </select>
    <select id="selectOne" resultType="com.rick.pojo.User" paramterType="com.rick.pojo.User">
        select * from user where id = #{id} and username = #{username}
    </select>
</mapper>

在com.rick.pojo包下创建User实体类

package com.rick.pojo;

/**
 * @author rickqi
 */
public class User {

    private Integer id;

    private String username;

    public Integer getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                '}';
    }
}


再创建一个Maven子工程并且导入需要用到的依赖坐标

创建步骤如下:

20201106nmXq5F
20201106c1wd70
20201106DiPpCj

将MyFrame引入到MyFrame_test
20201106xxMzO5

修改MyFrame下的pom文件添加依赖

<dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.17</version>
        </dependency>
        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.12</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
        </dependency>
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>
        <dependency>
            <groupId>jaxen</groupId>
            <artifactId>jaxen</artifactId>
            <version>1.1.6</version>
        </dependency>
    </dependencies>
    

在MyFrame_test项目下的com.rick.pojo包下,创建一个Configuration类,用于存放数据库配置信息

public class Configuration {
    private DataSource dataSource;

    Map<String, MappedStatement> mappedStatementMap = new HashMap<String, MappedStatement>();

    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类,用来存放存放SQL配置信息

public class MappedStatement {
    //id标识
    private String id;
    //返回值类型
    private String resultType;
    //参数值类型
    private String paramterType ;
    //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 getParamterType() {
        return paramterType;
    }

    public void setParamterType(String paramterType) {
        this.paramterType = paramterType;
    }

    public String getSql() {
        return sql;
    }

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

在MyFrame项目下的com.rick.io包中创建一个Resources类,作用是:
根据配置文件的路径,将配置文件加载成字节输入流,存储在内存中

public class Resources {
    /**
     * 根据配置文件的路径,将配置文件加载成字节输入流,存储在内存中
     */
    public static InputStream getResourcesAsSteam(String path){
        InputStream resourceAsStream = Resources.class.getClassLoader().getResourceAsStream(path);
        return resourceAsStream;
    }
}

上面我们完成了第一步和第二部

接下来。我们开始进行解析

在MyFrame项目下com.rick.sqlsession包创建SqlSessionFactoryBuilder类

public class SqlSessionFactoryBuilder{
    public SqlSessionFactory build(InputStream in) throws DocumentException, PropertyVetoException {
        //第一:使用dom4j解析配置文件,将解析出来的内容封装到Configuration中
        XmlConfigBuilder xmlConfigBuilder = new XmlConfigBuilder();
        Configuration configuration = xmlConfigBuilder.parseConfig(in);

        //第二:创建sqlSessionFactory对象:工厂类,主要生产sqlsession:会话对象
        DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration);

        return defaultSqlSessionFactory;

    }
}

然后我们来在MyFrame项目下的com.rick.config包中创建这个XmlConfigBuilder类

创建XmlConfigBuilder类

public class XmlConfigBuilder {

    private Configuration configuration;

    /**
     * 先执行无参构造函数
     * 相当于给Configuation赋值
     */
    public XmlConfigBuilder() {
        this.configuration = new Configuration();
    }

    /**
     * 该方法就是使用dom4j将配置文件进行解析,封装Configuration
     */
    public Configuration parseConfig(InputStream inputStream) throws DocumentException, PropertyVetoException {

        Document document = new SAXReader().read(inputStream);
        //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 valaue = element.attributeValue("valaue");
            properties.setProperty(name, valaue);
        }

        //数据库连接池
        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 resourcesAsSteam = Resources.getResourcesAsSteam(mapperPath);
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
            xmlMapperBuilder.parse(resourcesAsSteam);
        }

        return configuration;
    }
}

创建XMLMapperBuilder类

public class XMLMapperBuilder {

    private Configuration configuration;

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

    public void parse(InputStream inputStream) throws DocumentException {
        //拿到文档对象
        Document document = new SAXReader().read(inputStream);
        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 paramterType = element.attributeValue("paramterType");
            String sql = element.getTextTrim();
            MappedStatement mappedStatement = new MappedStatement();
            mappedStatement.setId(id);
            mappedStatement.setParamterType(paramterType);
            mappedStatement.setResultType(resultType);
            mappedStatement.setSql(sql);
            String key = namespace+"."+id;
            configuration.getMappedStatementMap().put(key,mappedStatement );
        }
    }
}

在MyFrame项目下的com.rick.session包中创建sqlSessionFactory接口及DefaultSqlSessionFactory

public interface SqlSessionFactory {
    public 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接口及实现类DefaultSession

SqlSession接口

public interface SqlSession {
    /**
     * 查询所有
     * @param statementid
     * @param params
     * @param <E>
     * @return
     */
    public <E> List<E> selectList(String statementid,Object... params) throws Exception;

    /**
     * 查询单个
     * @param statementid
     * @param params
     * @param <T>
     * @return
     */
    public <T> T selectOne(String statementid,Object... params) throws Exception;


}

DefaultSession类

public class DefaultSqlSession implements SqlSession {

    private Configuration configuration;

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

    @Override
    public <E> List<E> selectList(String statementid, Object... params) throws Exception {
        //要去完成对simpleExecutor里的query方法的调用
        MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementid);
        SimpleExecutor simpleExecutor = new SimpleExecutor();
        List<Object> query = simpleExecutor.query(configuration, mappedStatement, params);
        return (List<E>) query;
    }

    @Override
    public <T> T selectOne(String statementid, Object... params) throws Exception {
        List<Object> objects = selectList(statementid, params);
        Object object = new Object();
        if (objects.size() == 1){
            object = objects.get(0);
            return (T) object;
        }else {
            throw new  RuntimeException("查询结果为空或者存在多个返回结果");

        }
    }
}

第五步完成

然后我们创建Executor接口及实现类SimpleExecutor实现类,主要执行的就是JDBC的代码了

Executor接口

public interface Executor {
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws SQLException, Exception;
}

SimpleExecutor类

public class SimpleExecutor implements Executor {

    /**
     * 其实就是JDBC的代码
     *
     * @param configuration
     * @param mappedStatement
     * @param params
     * @param <E>
     * @return
     */
    @Override
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {

        //1.注册驱动,获得链接
        Connection connection = configuration.getDataSource().getConnection();
        //2.(1)获取sql语句,因为存在#{}格式占位符不会被识别,还要进行(2)转换sql语句,转换成?形式的sql。并且对{}里面的值进行解析存储
        String sql = mappedStatement.getSql();
        BoundSql boundSql = getBoundSql(sql);
        //3.获取预处理对象 preparedStatement
        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
        //4.设置参数
        //获取参数全路径
        String paramterType = mappedStatement.getParamterType();
        //根据全路径获得class对象
        Class<?> paramterTypeClass = getClassType(paramterType);
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        for (int i = 0; i < parameterMappings.size(); i++) {
            ParameterMapping parameterMapping = parameterMappings.get(i);
            //解析出来的就是{}里面的值
            String content = parameterMapping.getContent();
            //使用反射
            //获取属性对象
            Field declaredField = paramterTypeClass.getDeclaredField(content);
            //拿到对象的值,防止是私有的将参数设置为true,暴力访问
            declaredField.setAccessible(true);
            Object o = declaredField.get(params[0]);
            preparedStatement.setObject(i + 1, o);
        }
        //5.执行sql
        ResultSet resultSet = preparedStatement.executeQuery();
        //6.封装返回结果集
        String resultType = mappedStatement.getResultType();
        Class<?> resultTypeClass = getClassType(resultType);

        ArrayList<Object> objects = new ArrayList<>();
        while (resultSet.next()) {
            Object o = resultTypeClass.newInstance();

            //获取元数据,其中包含字段的名称
            ResultSetMetaData metaData = resultSet.getMetaData();
            //metaData.getColumnCount()为列数
            for (int i = 1; i <= metaData.getColumnCount(); i++) {
                //获取字段名
                String columnName = metaData.getColumnName(i);
                //根据名称获取到对应的值
                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;
    }

    private Class<?> getClassType(String paramterType) throws ClassNotFoundException {
        if (Objects.nonNull(paramterType)) {
            Class<?> aClass = Class.forName(paramterType);
            return aClass;
        }
        return null;
    }

    /**
     * 1.完成#{}的解析工作,使用?进行代替
     * 2.解析出{}里面的值进行存储
     *
     * @param sql
     * @return
     */
    private BoundSql getBoundSql(String sql) {
        //需要导入util工具类

        //标记处理类:配置标记解析器来完成对占位符的解析处理工作
        ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
        GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
        //返回一个解析过后的sql
        String parseSql = genericTokenParser.parse(sql);
        //#{}解析出来的参数名称
        List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();

        BoundSql boundSql = new BoundSql(parseSql, parameterMappings);

        return boundSql;


    }
}

其中在com.rick.config包下还有一个BoundSql类,用来解析SQL。解析#{}这种占位符的sql变成?这种占位符的sql,并将{}里面的值解析存储

/**
 * 解析#{}这种占位符的sql变成?这种占位符的sql,并将{}里面的值解析存储
 * @author rickqi
 */
public class BoundSql {
    private String sqlText;  //解析过后的sql

    private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();

    public BoundSql(String sqlText, List<ParameterMapping> parameterMappings) {
        this.sqlText = sqlText;
        this.parameterMappings = parameterMappings;
    }

    public String getSqlText() {
        return sqlText;
    }

    public void setSqlText(String sqlText) {
        this.sqlText = sqlText;
    }

    public List<ParameterMapping> getParameterMappings() {
        return parameterMappings;
    }

    public void setParameterMappings(List<ParameterMapping> parameterMappings) {
        this.parameterMappings = parameterMappings;
    }
}

在解析sql的过程中,还需要在com.rick.utils中引入一些工具类
下面我提供一下整个自定义持久层框架的源码,可以进去自己找一下

https://ruiqi01.oss-cn-beijing.aliyuncs.com/uPic/自定义持久层框架.zip


http://www.niftyadmin.cn/n/608777.html

相关文章

给WIN2003 IIS SQL服务器安全加固

转自&#xff1a; http://www.12986.com/n3765c48.aspx1.将<systemroot>\System32\cmd.exe转移到其他目录或更名&#xff1b; 2.系统帐号尽量少&#xff0c;更改默认帐户名&#xff08;如Administrator&#xff09;和描述&#xff0c;密码尽量复杂&#xff1b; 3.拒绝通过…

如果您忘记了WINDOWS的登录密码,怎么办?

经常见到有人遗忘了系统的管理员密码来求助的&#xff0c;而网上针对此类的答案可谓五花八门&#xff0c;但经实践发现其中绝大多数都是没有用的&#xff0c;有些以讹传讹的方法&#xff08;例如在winxp系统下删除sam文件等等&#xff09;还会造成系统的彻底崩溃。 相比之下&am…

导入Excel电子表格数据到SQL Sever数据库

<%On Error Resume Next导入Excel电子表格数据到SQL Sever数据库 By Dicky 2004-12-27 16:41:12 QQ:25941Function Open_Conn(SqlDatabaseName,SqlPassword,SqlUsername,SqlLocalName,SqlConn)创建数据库连接对象并打开数据库连接Dim ConnStrSQL Server数据库连接参数&#…

Builder构建者模式model

Builder模式的定义是“将一个复杂对象的构建与它的表示分离&#xff0c;使得同样的构建过程可以创建不同的表示。”它属于创建类模式&#xff0c;一般来说&#xff0c;如果一个对象的构建比较复杂&#xff0c;超出了构造函数所能包含的范围&#xff0c;就可以使用工厂模式和 Bu…

一个制作无边窗的方案

1、打开记事本&#xff0c;加入下面程序&#xff0c;保存为 "mywindow.js" 注意&#xff1a;名称为 mywindow 后缀是.js ,保存在 “img”文件夹里 /* CHROMELESS WINDOWS v.30.1Generate a chromeless window on IE4,IE5.x,IE6 on WIN32 and a regular one on the …

经典搞笑

夫妻圆房暗语 先生很正经的说&#xff1a;“我是来杀你的&#xff0c;那你呢&#xff1f;” 太太很害羞的回答&#xff1a;“我是来自杀的。” 女的很风骚的说.......... 有个小山村&#xff0c;有对夫妻&#xff0c;女的长的漂亮&#xff0c;男的长的不咋的 &#xff0c;女的…

工厂模式model

在 Mybatis中比如 SqlSession Factory使用的是工厂模式&#xff0c;该工厂没有那么复杂的逻辑&#xff0c;是一个简单工厂模式。 简单工厂模式&#xff08; Simple Factory Pattern&#xff09;&#xff1a;又称为静态工厂方法&#xff08; &#xff08;Static Factory Method模…

讨MM欢心的高难动作(组图)

转自&#xff1a;http://bbs.ccqtv.com/discuz/viewthread.php?tid4306&extrapage%3D6我想一辈子牵住你的手&#xff0c;永不言弃&#xff01;愛してる&#xff01;