手搓MyBatis框架(原理讲解)

 

你在学完MyBatis框架后会不会觉得很神奇,为什么我改一个配置文件就可以让程序识别和执行不同的sql语句操作数据库?

SqlSessionFactoryBuilder,SqlSessionFactory和SqlSession对象到底是怎样执行的?

如果你有这些问题看就完事了

没错,现在要做的就是手搓mybatis框架底层,简易版还原mybatis框架的执行原理

 

一.分析

你是站在一个设计框架者的角度来和未来使用你框架的人来进行一个对接的

刚开始毫无头绪,我们需要找一段代码作为参考

SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(Resources.getResourceAsStream("mybatis-config.xml"));
SqlSession sqlSession = factory.openSession();

这就是基本操作,通过SqlSessionFactoryBuilder对象的build方法,传进去配置文件的一个流来创建SqlSessionFactory对象。

然后通过SqlSessionFactory对象来创建SqlSession会话对象,最后通过SqlSession进行sql语句的调用和事务的管理

好吧,先估计一下大致有几个模块

1.SqlSessionFactoryBuilder模块,由于要传进去一个有关配置文件的流,所以这个模块里还要进行配置文件的解析 ,并把解析出来的东西

2.SqlSessionFactory模块,把SqlSessionFactoryBuilder来的模块进行封装和整合,毕竟这玩意一个环境一个,要经常使用的 

3.SqlSession模块,直接对sql语句进行操作并进行事务的管理等

 

二.SqlSessionFactoryBuilder类的设计

首先你需要一个SqlSessionFactoryBuilder类,同时需要一个build方法来构建SqlSessionFactory对象。

考虑到未来使用框架的人会给你提供一个mybatis-config.xml文件,这里用一个最简单的mybatis-config.xml文件作为模板

<?xml version="1.0" encoding="UTF-8" ?>

<configuration>
    <properties resource="jdbc.properties"/>
    <environments default="dev">
        <environment id="dev">
            <transactionManager type="JDBC"/>
            <dataSource type="UNPOOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/itcast"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="sqlMapper.xml"/>
    </mappers>

</configuration>

1.Resources工具类的设计

dxbatis框架需要使用者提供一个配置文件路径,dxbatis框架把这个配置文件通过流返回,所以要提供一个方法类把该文件作为流返回,私有化构造方法,通过类加载器ClassLoader将mybatis-config.xml文件作为流返回

import java.io.InputStream;

/**
 * dxbatis框架提供的一个工具类
 * 专门用来类路径中资源的加载
 *
 * @author 丁小喜
 * @version 1.0
 */

public class Resources {

    private Resources() {
    }

    public static InputStream getResourceAsStream(String resource) {
        return ClassLoader.getSystemClassLoader().getResourceAsStream(resource);
    }
}

现在我们得到了配置文件流了,接下来就是在SqlSessionFactoryBuilder解析这个配置文件了

2.build方法的设计

解析xml文件我们需要两个依赖,dom4j依赖和jaxen依赖,在pom文件中引入这两个依赖

    <!-- https://mvnrepository.com/artifact/org.dom4j/dom4j -->
    <dependency>
      <groupId>org.dom4j</groupId>
      <artifactId>dom4j</artifactId>
      <version>2.1.3</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/jaxen/jaxen -->
    <dependency>
      <groupId>jaxen</groupId>
      <artifactId>jaxen</artifactId>
      <version>1.1.6</version>
    </dependency>

 然后在build方法中进行解析

    public SqlSessionFactory build(InputStream in) {

        SqlSessionFactory factory = null;

        try {
            //解析godbatis-config.xml文件
            SAXReader reader = new SAXReader();
            //把文件读进来
            Document document = reader.read(in);
            //获取environments标签节点
            Element environments = (Element) document.selectSingleNode("/configuration/environments");
            //获取default属性值
            String defaultId = environments.attributeValue("default");
            //获取默认使用的environment节点
            Element environment = (Element) document.selectSingleNode("/configuration/environments/environment[@id='" + defaultId + "']");
            //获取transactionManager节点
            Element transactionElt = (Element) environment.element("transactionManager");
            //获取dataSource节点
            Element dataSourceElt = (Element) environment.element("dataSource");

        } catch (Exception e) {
            e.printStackTrace();
        }


        return factory;
    }

这里对解析xml文件进行一点简单的介绍:

dom4j解析
先创建一个SAXReader对象
再ClassLoader.getSystemClassLoader().getResourceAsStream("mybatis-config.xml");获取流
然后调用SAXReader对象的read方法获取document文档对象
Element 节点对象
document.getRootElement()获取根节点
document.selectSingleNode(xpath)再强转成Element就是获取指定xpath的节点
element.attributeValue("default")获取属性
element.element("transactionManager")获取子节点
element.elements()获取所有子节点
element.getTextTrim()获取标签中的内容

注:配置文件可以找到servlet而注解找不到,看一下web.xml文件的metadata-complete是true就只允许配置文件,false配置文件和注解都可以
 

解析完配置文件我们发现我们得到了数据源dataSource节点,事务管理transactionManager节点 ,所以我们接下来需要做的是处理transactionManager节点和dataSource节点

 3.sql语句集合的设计

咱不仅要解析mybatis-config.xml文件,咱想要执行sql语句,咱还必须得解析mapper映射文件才行,那什么容器来盛放这些sql语句才合适呢?

来想想mybatis框架是怎么做的,如何定位一个sql语句呢?

使用sqlId来定位一个sql标签的,sqlId又由namespace和标签id组成

所以我们自然而然的就想到Map集合,把sqlId作为key,把sql语句作为value

这是我们又想到sql语句标签不止有其中得sql语句,还有resultType属性,故我们还要设计一个存储sql标签的类MappedStatement。

这个集合就为 

 Map<String, MappedStatement> mappedStatements

4.MappedStatement类的设计

其实就是一个简单的pojo类,当标签时select时resultType的值为需要映射的pojo类型

而标签不为select时resultType属性为空

/**
 * 此类为一个pojo类
 * 用来封装mapper.xml映射文件中的一个sql标签
 * 如:select标签,insert标签等
 * 最后将其装入到一个mapper集合中作为解析mapper映射文件的结果
 *
 * @author 丁小喜
 * @version 1.0
 */

public class MappedStatement {

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

    /**
     * 如果此对象表示的标签为select
     * 表示他要封装的结果集
     * 其他标签此属性值为null
     */
    private String resultType;

    public MappedStatement() {
    }

    public MappedStatement(String sql, String resultType) {
        this.sql = sql;
        this.resultType = resultType;
    }

    /**
     * 获取
     *
     * @return sql
     */
    public String getSql() {
        return sql;
    }

    /**
     * 设置
     *
     * @param sql
     */
    public void setSql(String sql) {
        this.sql = sql;
    }

    /**
     * 获取
     *
     * @return resultType
     */
    public String getResultType() {
        return resultType;
    }

    /**
     * 设置
     *
     * @param resultType
     */
    public void setResultType(String resultType) {
        this.resultType = resultType;
    }

    public String toString() {
        return "MappedStatement{sql = " + sql + ", resultType = " + resultType + "}";
    }
}

5.mapper映射文件的解析及sql语句集合的获取

直接获取mybatis-config.xml文件mapper标签

获取该文件的解析对象

获取namespace属性和标签的id属性放入集合的key

再通过getTextTrim方法获取到MappedStatement放入value并返回

private Map<String, MappedStatement> getMappedStatements(List<String> sqlMapperXMLPath) {
        Map<String, MappedStatement> mappedStatements = new HashMap<>();
        sqlMapperXMLPath.forEach(XMLPath -> {
            try {
                SAXReader reader = new SAXReader();
                Document document = reader.read(Resources.getResourceAsStream(XMLPath));
                System.out.println(XMLPath);
                Element mapperElt = (Element) document.selectSingleNode("mapper");
                String namespace = mapperElt.attributeValue("namespace");
                List<Element> taps = mapperElt.elements();
                taps.forEach(tap -> {
                    String id = tap.attributeValue("id");
                    String resultType = tap.attributeValue("resultType");
                    String sql = tap.getTextTrim();
                    MappedStatement mappedStatement = new MappedStatement(sql, resultType);
                    String sqlId = namespace + '.' + id;
                    mappedStatements.put(sqlId, mappedStatement);
                });
            } catch (Exception e) {
                e.printStackTrace();
            }

        });
        return mappedStatements;
    }

 

三.DateSource数据源实现类的设计

1.分析

因为dateSource节点有可能有UNPOOLED,POOLED,JUDI三个属性所以需要设计三个实现类实现javax.sql.DataSource接口,并实现其中的抽象方法。 

Transaction进行事务管理时也需要获取连接对象故先进行此类的实现

2.UNPOOLED类的实现

因为驱动只用注册一次,所以就不用把driver作为属性了

只用在构造方法的时候注册以下驱动就行了

注:

@Override
    public Connection getConnection() throws SQLException {
        return this.getConnection(username,password);
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        Connection connection = DriverManager.getConnection(url, username, password);
        return connection;
    }

这两个方法最好都实现一下,第一次的时候我第一个getConnection()方法没有实现,最后测试的时候报了空指针异常,就是没有调用第二个getConnection()方法,直接返回了null

import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;

/**
 * 数据源的实现类:UnPooled
 * 不使用数据库连接池,每一次都创建新的connection对象
 *
 * @author 丁小喜
 * @version 1.0
 */

public class UnPooledDateSource implements javax.sql.DataSource {

    /**
     * 数据库驱动属性
     */


    private String url;
    private String username;
    private String password;

    public UnPooledDateSource() {
    }

    /**
     * 创建一个数据源对象
     *
     * @param driver   数据库驱动
     * @param url      统一资源定位符
     * @param username 用户名
     * @param password 密码
     */
    public UnPooledDateSource(String driver, String url, String username, String password) {
        try {
            Class.forName(driver);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        this.url = url;
        this.username = username;
        this.password = password;
    }

    @Override
    public Connection getConnection() throws SQLException {
        return this.getConnection(username,password);
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException        {
        Connection connection = DriverManager.getConnection(url, username, password);
        return connection;
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return null;
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }

    @Override
    public PrintWriter getLogWriter() throws SQLException {
        return null;
    }

    @Override
    public void setLogWriter(PrintWriter out) throws SQLException {

    }

    @Override
    public void setLoginTimeout(int seconds) throws SQLException {

    }

    @Override
    public int getLoginTimeout() throws SQLException {
        return 0;
    }

    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }
}
 
 

四.Transaction接口及其实现类的设计

因为Transaction节点可能有JDBC和Managed两个值,我们接收dxbatis框架的使用者的值为两个,所以要设计一个 JDBCTransaction和一个ManagedTransaction的实现类来实现Transaction接口。最后要用多态的方法,向Transaction属性传值(是在SqlSessionFactory对象中)

1.Transaction接口的设计 

你的事务管理器需要实现提交事务,回滚事务,关闭事务等功能,故设计了commit,rollback,close方法

import java.sql.Connection;

/**
 * 事务管理器接口
 * 封装用来管理事务的抽象方法
 * 所有事务都应该遵守此规范
 * jdbc事务管理器和managed事务管理器都要实现此接口
 *
 * @author 丁小喜
 * @version 1.0
 */

public interface Transaction {
    /**
     * 提交事务
     */
    void commit();

    /**
     * 回滚事务
     */
    void rollBack();

    /**
     * 关闭事务
     */
    void close();

    
}

 

2.JDBCTransaction实现类的实现

给JDBCTransaction添加一个属性connect用于直接调用对连接进行控制

这里面的connection没什么好说的直接调用

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

/**
 * JDBC事务管理器
 *
 * @author 丁小喜
 * @version 1.0
 */

public class JDBCTransaction implements Transaction {

    /**
     * 数据源属性
     * 因为下面的方法中都需要数据源connection
     * 故在SqlSessionFactory类中不需要数据源了
     * 因为可以通过Transaction属性去获取数据源再写一个就冗余了
     */
    private DataSource dataSource;

    /**
     * 自动提交属性
     * true为开启自动提交
     * false为关闭自动提交
     */
    private boolean autoCommit;


    private Connection connection;

    /**
     * 创建事务管理器对象
     *
     * @param dataSource 数据源
     * @param autoCommit 是否自动提交
     */
    public JDBCTransaction(DataSource dataSource, boolean autoCommit) {
        this.dataSource = dataSource;
        this.autoCommit = autoCommit;
    }

    @Override
    public void commit() {
        try {
            connection.setAutoCommit(this.autoCommit);
            connection.commit();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void rollBack() {
        try {
            connection.rollback();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void close() {
        try {
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

}

3.build方法的完善

设计完了DateSource类和Transaction接口,现在可以把xml文件中的dateSource节点和transactionManager节点转换成对应的对象了

(1).dataSource节点的转换

先获取dataSource节点下的所有子节点也就是property节点,遍历这些节点,取出这些节点的name,再根据name获取这些节点的value把他们塞到一个集合里面。

再获取数据源类型,是哪个就创建对应的数据源对象,最后再返回这个数据源对象

ps:这里只实现了UnPooledDateSource,写别的会错的。

    /**
     * 获取数据源对象
     *
     * @param dataSourceElt 数据源节点
     * @return 数据源对象
     */
    private DataSource getDataSource(Element dataSourceElt) {
        Map<String, String> map = new HashMap<>();
        DataSource dataSource = null;
        List<Element> propertys = dataSourceElt.elements("property");
        propertys.forEach(s -> {
            String name = s.attributeValue("name");
            String value = s.attributeValue("value");
            map.put(name, value);
        });

        String type = dataSourceElt.attributeValue("type").trim().toUpperCase();
        if (type.equals(Const.DATASOURCE_UN_POOLED)) {
            dataSource = new UnPooledDateSource(map.get("driver"), map.get("url"), map.get("username"), map.get("password"));
            System.out.println(dataSource);
        } else if (type.equals(Const.DATASOURCE_POOLED)) {
            dataSource = new PooledDataSource();
        } else if (type.equals(Const.DATASOURCE_JNDI)) {
            dataSource = new JNDIDataSource();
        } else {
            System.err.println("SqlSessionFactoryBuilder的getDataSource戳了");
        }
        return dataSource;
    }

 (2).transactionManager节点的转换

 这个就简单了,根据type属性创建对应的对象再返回就行了

    /**
     * 获取事务管理器对象
     *
     * @param transactionElt 事务管理器节点
     * @param dataSource     数据源对象
     * @return 事务管理器对象
     */
    private Transaction getTransaction(Element transactionElt, DataSource dataSource) {
        Transaction transaction = null;
        String type = transactionElt.attributeValue("type").trim().toUpperCase();

        if (Const.TRANSACTION_JDBC.equals(type)) {
            transaction = new JDBCTransaction(dataSource, false);//事务默认不会自动提交
        } else if (Const.TRANSACTION_MANAGED.equals(type)) {
            transaction = new ManagedTransaction();
        } else {
            System.out.println("SqlSessionFactoryBuilder的getTransaction戳了");
        }
        return transaction;
    }

(3).最终build方法

public SqlSessionFactory build(InputStream in) {

        SqlSessionFactory factory = null;

        try {
            //解析godbatis-config.xml文件
            SAXReader reader = new SAXReader();
            //把文件读进来
            Document document = reader.read(in);
            //获取environments标签节点
            Element environments = (Element) document.selectSingleNode("/configuration/environments");
            //获取default属性值
            String defaultId = environments.attributeValue("default");
            //获取默认使用的environment节点
            Element environment = (Element) document.selectSingleNode("/configuration/environments/environment[@id='" + defaultId + "']");
            //获取transactionManager节点
            Element transactionElt = (Element) environment.element("transactionManager");
            //获取dataSource节点
            Element dataSourceElt = (Element) environment.element("dataSource");

            //通过dataSource节点获取数据源
            DataSource dataSource = getDataSource(dataSourceElt);
            //通过transactionElt节点和dataSource数据源获取事务管理器
            Transaction transaction = getTransaction(transactionElt, dataSource);

            List<String> sqlMapperXMLPath = new ArrayList<>();
            List<Node> mapperElt = document.selectNodes("//mapper");
            mapperElt.forEach(s -> {
                Element mapperNode = (Element) s;
                String resource = mapperNode.attributeValue("resource");
                sqlMapperXMLPath.add(resource);
            });

            sqlMapperXMLPath.forEach(s-> System.out.println(s));
            Map<String, MappedStatement> mappedStatements = null;
            mappedStatements = getMappedStatements(sqlMapperXMLPath);

            factory = new SqlSessionFactory(transaction, mappedStatements);
        } catch (Exception e) {
            e.printStackTrace();
        }


        return factory;
    }

 

五.常量类Const的设计

常量类在项目设计中也是一种常用手段 ,可以增加代码可读性,常量便于管理

ps:还可以让你的代码看起来更高级(笑)

/**
 * 常量类
 *
 * @author 丁小喜
 * @version 1.0
 */
public class Const {
    public static final String DATASOURCE_UN_POOLED = "UNPOOLED";
    public static final String DATASOURCE_POOLED = "POOLED";
    public static final String DATASOURCE_JNDI = "JNDI";
    public static final String TRANSACTION_JDBC = "JDBC";
    public static final String TRANSACTION_MANAGED = "MANAGED";
}

六.SqlSessionFactory类的设计

到这就很简单了 ,明确SqlSessionFactory要干嘛

SqlSession sqlSession = factory.openSession();

要openSession创建SqlSession对象

那直接在openSession方法里new一个Session对象返回就完事了

SqlSession对象需要什么:

sql语句,事务管理器 ,连接

既然这样 ,就直接把 SqlSessionFactory在openSession方法中传给SqlSession,不管你open多少个Session,SqlSessionFactory都是一个

import java.util.Map;

/**
 * 一个数据库对象对应一个SqlSessionFactory对象
 * 而SqlSessionFactory对象可以构建SqlSession对象(开启会话)
 * 一个SqlSessionFactory对象可以开启多个SqlSession对象
 *
 * @author 丁小喜
 * @version 1.0
 */

public class SqlSessionFactory {

    /**
     * 创建SqlSessionFactory对象
     * @param transaction 事务管理器对象
     * @param mappedStatements sql语句对象集合
     */
    public SqlSessionFactory(Transaction transaction, Map<String, MappedStatement> mappedStatements) {
        this.transaction = transaction;
        MappedStatements = mappedStatements;
    }

    /**
     * Transaction是一个事务管理器接口
     * 实现类有jdbcTransaction和managedTransaction
     * 通过配置文件的切换使事务管理器可以灵活切换,就解耦合了
     */

    private Transaction transaction;


    /**
     * 这个属性表示的是mapper映射文件的所有sql语句的集合
     * key是能唯一标识sql语句的sqlId,其实就是namespace+sql标签的id
     * value是sql标签信息对象
     * 通过sqlId(key)可以唯一找到一个sql语句对象(value)
     */
    private Map<String, MappedStatement> MappedStatements;


    /**
     * 返回一个SqlSession会话对象
     * @return SqlSession对象
     */

    public SqlSession openSession(){
        SqlSession sqlSession = null;
        this.transaction.openConnection();
        //创建一个SqlSession对象,将SqlSessionFactory对象传进去
        //因为SqlSessionFactory对象需要执行sql语句所以需要connection和MappedStatements
        //又因为connection对象在transaction中,故直接将此对象传入其中;
        sqlSession = new SqlSession(this);

        return sqlSession;
    }

    public Transaction getTransaction() {
        return transaction;
    }

    public Map<String, MappedStatement> getMappedStatements() {
        return MappedStatements;
    }
}

七.SqlSession类的设计

终于到最后一步了

我们可以直面sql语句和jdbc了

先分析一波:框架使用者会怎么使用 SqlSession对象

其他不重要,SqlSession调用了insert方法,commit方法,close方法

当然,构造方法不能忘

咱们再在前面opensession的时候new过SqlSession对象传的是 SqlSessionFactory对象

故设计对应属性factory及其构造方法

     private SqlSessionFactory factory;

     public SqlSession(SqlSessionFactory factory) {
            this.factory = factory;
     }

1.insert方法的设计

首先咱得先把要执行的sql语句找出来

框架使用者在传入的时候传了个sqlId,直接通过这个获取一个对应的sql对象

然后通过正则把框架中的sql语句换成jdbc可识别的语句

通过反射机制分别传值

最后就是jdbc经典代码,不多赘述了

 public int insert(String sqlId, Object pojo) {
        int count = 0;
        try {
            Map<String, MappedStatement> mappedStatements = this.factory.getMappedStatements();
            //获取sql语句对象
            MappedStatement mappedStatement = mappedStatements.get(sqlId);
            //获取文件中未处理的sql语句
            String godSql = mappedStatement.getSql();
            //获取数据库连接对象
            Connection connection = this.factory.getTransaction().getConnection();
            String resultType = mappedStatement.getResultType();
            //将sql处理成PreparedStatement可以识别的的sql语句
            String sql = godSql.replaceAll("#\\{[a-z0-9A-Z_$]*}", "?");
            //获取PreparedStatement
            PreparedStatement ps = connection.prepareStatement(sql);
            //给第几个问号传什么值
            int fromIndex = 0;
            int index = 0;
            while (true) {
                int jIndex = godSql.indexOf('#', fromIndex);
                if (jIndex < 0) {
                    break;
                }
                index++;
                System.out.println(index);
                int rightIndex = godSql.indexOf('}', fromIndex);
                String inner = godSql.substring(jIndex + 2, rightIndex).trim();
                String getMethod = "get" + inner.toUpperCase().charAt(0) + inner.substring(1);
                Method method = pojo.getClass().getDeclaredMethod(getMethod);
                Object retValue = method.invoke(pojo);

                ps.setString(index, retValue.toString());
                fromIndex = rightIndex + 1;
            }
            count = ps.executeUpdate();


        } catch (Exception e) {
            e.printStackTrace();
        }
        return count;
    }

2.selectOne方法的设计

public Object selectOne(String sqlId,Object param){
        Object obj = null;
        try {
            //获取数据库连接对象
            Connection connection = this.factory.getTransaction().getConnection();
            //获取sql语句集对象
            MappedStatement mappedStatement = this.factory.getMappedStatements().get(sqlId);
            //获取框架中的sql语句
            String godSql = mappedStatement.getSql();
            //获取返回值类型
            String resultType = mappedStatement.getResultType();
            //获取jdbc可识别的sql
            String sql = godSql.replaceAll("#\\{[a-zA-Z0-9_$]*}","?");
            //得到PreparedStatement对象
            PreparedStatement ps = connection.prepareStatement(sql);
//            System.out.println(godSql);
            System.out.println(sql);
            //给占位符传值
            ps.setString(1,param.toString());
            ResultSet rs = ps.executeQuery();
            if(rs.next()){
                Class<?> pojo = Class.forName(resultType);
                obj = pojo.newInstance();
                //获取结果集源数据
                ResultSetMetaData rsmd = rs.getMetaData();
                int columnCount = rsmd.getColumnCount();
                //rsmd.getColumnName这玩意可能版本不一样这里是从1开始算的
                for (int i = 1; i <= columnCount; i++) {
                    String columnName = rsmd.getColumnName(i);
                    String setMethod = "set"+columnName.toUpperCase().charAt(0)+columnName.substring(1);
                    Method declaredMethod = pojo.getDeclaredMethod(setMethod, String.class);
                    declaredMethod.invoke(obj,rs.getString(columnName));
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return obj;
    }


3.事务方法的设计

调用factory属性获取Transaction中的方法执行就行了

    /**
     * 提交事务
     */
    public void commit() {
        this.factory.getTransaction().commit();
    }

    /**
     * 回滚事务
     */
    public void rollBack() {
        this.factory.getTransaction().rollBack();
    }

    /**
     * 关闭事务
     */
    public void close() {
        this.factory.getTransaction().close();
    }

最后SqlSession类是这样的

import java.lang.reflect.Method;
import java.sql.*;
import java.util.Map;

/**
 * 专门负责执行sql语句的会话对象
 *
 * @author 丁小喜
 * @version 1.0
 */
public class SqlSession {
    private SqlSessionFactory factory;

    public SqlSession(SqlSessionFactory factory) {
        this.factory = factory;
    }


    public Object selectOne(String sqlId,Object param){
        Object obj = null;
        try {
            //获取数据库连接对象
            Connection connection = this.factory.getTransaction().getConnection();
            //获取sql语句集对象
            MappedStatement mappedStatement = this.factory.getMappedStatements().get(sqlId);
            //获取框架中的sql语句
            String godSql = mappedStatement.getSql();
            //获取返回值类型
            String resultType = mappedStatement.getResultType();
            //获取jdbc可识别的sql
            String sql = godSql.replaceAll("#\\{[a-zA-Z0-9_$]*}","?");
            //得到PreparedStatement对象
            PreparedStatement ps = connection.prepareStatement(sql);
//            System.out.println(godSql);
            System.out.println(sql);
            //给占位符传值
            ps.setString(1,param.toString());
            ResultSet rs = ps.executeQuery();
            if(rs.next()){
                Class<?> pojo = Class.forName(resultType);
                obj = pojo.newInstance();
                //获取结果集源数据
                ResultSetMetaData rsmd = rs.getMetaData();
                int columnCount = rsmd.getColumnCount();
                //rsmd.getColumnName这玩意可能版本不一样这里是从1开始算的
                for (int i = 1; i <= columnCount; i++) {
                    String columnName = rsmd.getColumnName(i);
                    String setMethod = "set"+columnName.toUpperCase().charAt(0)+columnName.substring(1);
                    Method declaredMethod = pojo.getDeclaredMethod(setMethod, String.class);
                    declaredMethod.invoke(obj,rs.getString(columnName));
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return obj;
    }


    public int insert(String sqlId, Object pojo) {
        int count = 0;
        try {
            Map<String, MappedStatement> mappedStatements = this.factory.getMappedStatements();
            //获取sql语句对象
            MappedStatement mappedStatement = mappedStatements.get(sqlId);
            //获取文件中未处理的sql语句
            String godSql = mappedStatement.getSql();
            //获取数据库连接对象
            Connection connection = this.factory.getTransaction().getConnection();
            String resultType = mappedStatement.getResultType();
            //将sql处理成PreparedStatement可以识别的的sql语句
            String sql = godSql.replaceAll("#\\{[a-z0-9A-Z_$]*}", "?");
            //获取PreparedStatement
            PreparedStatement ps = connection.prepareStatement(sql);
            //给第几个问号传什么值
            int fromIndex = 0;
            int index = 0;
            while (true) {
                int jIndex = godSql.indexOf('#', fromIndex);
                if (jIndex < 0) {
                    break;
                }
                index++;
                System.out.println(index);
                int rightIndex = godSql.indexOf('}', fromIndex);
                String inner = godSql.substring(jIndex + 2, rightIndex).trim();
                String getMethod = "get" + inner.toUpperCase().charAt(0) + inner.substring(1);
                Method method = pojo.getClass().getDeclaredMethod(getMethod);
                Object retValue = method.invoke(pojo);

                ps.setString(index, retValue.toString());
                fromIndex = rightIndex + 1;
            }
            count = ps.executeUpdate();


        } catch (Exception e) {
            e.printStackTrace();
        }
        return count;
    }

    /**
     * 提交事务
     */
    public void commit() {
        this.factory.getTransaction().commit();
    }

    /**
     * 回滚事务
     */
    public void rollBack() {
        this.factory.getTransaction().rollBack();
    }

    /**
     * 关闭事务
     */
    public void close() {
        this.factory.getTransaction().close();
    }
    
}

好了,dxbatis搓完了

文章写得我欲仙欲死的(笑)

最后感谢一下老杜的视频,大家要是看不懂的的话可以去参考一下老杜的mybatis视频,讲的清晰明了。

这个搓完真的有种穿起来的感觉,好像基础夯实了不少。对我的debug能力也是一种提升,不得不说哥们现在找错找的还挺快的,好了我要去吃饭了,这文章写了我一下午,也算是某种复习吧。

 

对你有帮助的话点点赞吧😀

 

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值