手敲Mybatis(三)-DataSource的解析创建和使用

本章节主要是解决通过xml配置可处理sql语句,所以最主要的就是解析xml,一个解析并放入对应数据源对象DataSourceFactory和事务对象TransactionFactory,一个处理Sql并放入对应的Sql类对象MapperStateMent,最终得到这些基础信息以后怎么使用呢,数据源肯定是要连接数据库最后才能执行各种库操作,所以就需要DataSourceFactory,那么想要执行就需要Sql语句,就需要MapperStateMent,所以贯穿所有中心枢纽就是Configuration,既会得到DataSourceFactory也会得到MapperStateMent,通过此传入SqlSeeion里,这样就能执行sql语句啦

1.类图

2.代码

Transaction类:定义事务接口,定义获取连接、提交、回滚、关闭方法,这样就可以不同的事务方式由不同的具体实现类去实现。

package df.middleware.mybatis.transaction
/**
 * @Author df
 * @Date 2022/5/26 11:19
 * @Version 1.0
 * 事务接口,定义获取连接,提交,回滚,关闭连接
 */
public interface Transaction {
    Connection getConnection() throws SQLException;

    void commit() throws SQLException;

    void rollback() throws SQLException;

    void close() throws SQLException;
}

JdbcTransaction类:为Transaction的具体实现类,封装JDBC方式的事务实现,包括获取连接以及提交回滚,关闭。

package df.middleware.mybatis.transaction.jdbc
/**
 * @Author df
 * @Date 2022/5/26 11:22
 * @Version 1.0
 * JDBC事务,直接利用JDBC的commit,rollback,依赖于数据源获得的连接来管理事务范围
 */
public class JdbcTransaction implements Transaction {
    protected Connection connection;
    protected DataSource dataSource;
    // 事务隔离级别
    protected TransactionIsolationLevel level = TransactionIsolationLevel.NONE;
    protected boolean autoCommit;

    public JdbcTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
        this.dataSource = dataSource;
        this.level = level;
        this.autoCommit = autoCommit;
    }

    public JdbcTransaction(Connection connection) {
        this.connection = connection;
    }

    @Override
    public Connection getConnection() throws SQLException {
        connection = dataSource.getConnection();
        connection.setTransactionIsolation(level.getLevel());
        connection.setAutoCommit(autoCommit);
        return connection;
    }

    @Override
    public void commit() throws SQLException {
        if (connection != null && !connection.getAutoCommit()) {
            connection.commit();
        }
    }

    @Override
    public void rollback() throws SQLException {
        if (connection != null && !connection.getAutoCommit()) {
            connection.rollback();
        }
    }

    @Override
    public void close() throws SQLException {
        if (connection != null && !connection.getAutoCommit()) {
            connection.close();
        }
    }

TransactionFactory类:主要目的定义从事务工厂能够获得事务基本功能Transaction

package df.middleware.mybatis.transaction
/**
 * @Author df
 * @Date 2022/5/26 14:13
 * @Version 1.0
 * 事务工厂
 */
public interface TransactionFactory {

    /**
     * 根据 Connection 创建Transaction
     *
     * @param conn Existing database connection
     * @return Transaction
     */
    Transaction newTransaction(Connection conn);

    /**
     *根据数据源和事务隔离级别创建Transaction
     * @param dataSource
     */
    Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);
}

JdbcTransactionFactory类:为TransactionFactory的实现类,通过此种实现可以已不同方式获取事务,此实现是通过JDBC获取事务。

package df.middleware.mybatis.transaction.jdbc
/**
 * @Author df
 * @Date 2022/5/26 14:27
 * @Version 1.0
 * JdbcTransaction 工厂
 */
public class JdbcTransactionFactory implements TransactionFactory {
    @Override
    public Transaction newTransaction(Connection conn) {
        return new JdbcTransaction(conn);
    }

    @Override
    public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
        return new JdbcTransaction(dataSource, level, autoCommit);
    }
}

Environment类:环境类,mybatis的提供数据库连接的环境,Environment类主要是通过xml的用户配置数据库环境信息再映射到此对象中使用,可配置多个数据库环境,根据环境id区分,除此之外还有事务的工厂对象,数据库的数据源等等。

package df.middleware.mybatis.mapping
public class Environment {
    // 环境id,不同的id表示不同的环境
    private final String id;
    // 创建事务的工厂
    private final TransactionFactory transactionFactory;
    // 数据源
    private final DataSource dataSource;


    public Environment(String id, TransactionFactory transactionFactory, DataSource dataSource) {
        this.id = id;
        this.transactionFactory = transactionFactory;
        this.dataSource = dataSource;
    }

    public static class Builder {
        private String id;
        private TransactionFactory transactionFactory;
        private DataSource dataSource;

        public Builder(String id) {
            this.id = id;
        }

        public Builder transactionFactory(TransactionFactory transactionFactory) {
            this.transactionFactory = transactionFactory;
            return this;
        }

        public Builder dataSource(DataSource dataSource) {
            this.dataSource = dataSource;
            return this;
        }

        public String id() {
            return this.id;
        }

        public Environment Builder() {
            return new Environment(this.id, this.transactionFactory, this.dataSource);
        }
    }
  // 省略set/get
}

Configuration类:Configuration这一章节添加如下,添加了Environment对象,最终在xml里通过Configuration的setEnvironment得到环境信息,再注册机里添加jdbc和druid事务工厂,这样在xml里解析时可以按key值标识对应的事务工厂。

package df.middleware.mybatis.session
    //环境
    protected Environment environment;
    // 类型别名注册机
    protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();

    public Configuration() {
        typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
        typeAliasRegistry.registerAlias("DRUID", DruidDataSourceFactory.class);
    }

     public Environment getEnvironment() {
        return environment;
    }

    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

BaseBuilder类:修改点如下,添加构造方法并以Configuration方式传入,这样子类XMLConfigBuilder的类可以直接使用此配置来处理或添加配置。

package df.middleware.mybatis.builder
protected final TypeAliasRegistry typeAliasRegistry;
public BaseBuilder(Configuration configuration){
        this.configuration = configuration;
        this.typeAliasRegistry = configuration.getTypeAliasRegistry();
}

XMLConfigBuilder类:添加数据库环境解析方法,来解析xml中的配置,获取dataSource配置信息,获取事务信息,并解析生成对应的事务工厂和数据源对象

并把上期加入的解析mapperElement方法更改Sql的结构部分所以也要把这个设置部分更改掉,把有关Sql方面的全部存储到BoundSql实体映射里,再通过MappedStatement包装起来

package df.middleware.mybatis.builder.xml
    /**
     * 解析配置:类型别名、插件、对象工厂、对象包装工厂、设置、环境、类型转换、映射器
     */
    public Configuration parse() {
        try {
            // 环境解析
            environmentsElement(root.element("environments"));
            // 解析映射器
            mapperElement(root.element("mappers"));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return configuration;
    }

    // 解析数据源,事务等,并最终存储到环境类里
    private void environmentsElement(Element context) throws Exception {
        String enviroment = context.attributeValue("default");
        List<Element> enviromentList = context.elements("environment");
        for (Element e : enviromentList) {
            String id = e.attributeValue("id");
            if (enviroment.equals(id)) {
                // 数据源
                Element dataSourceElement = e.element("dataSource");
                // 从类型注册机里找到DRUID名字的并得到类DataSourceFactory
                DataSourceFactory dataSourceFactory = (DataSourceFactory) typeAliasRegistry.resolveAlias(dataSourceElement.attributeValue("type")).newInstance();
                // 获取xml中数据源的属性数据
                List<Element> propertiesList = dataSourceElement.elements("property");
                Properties properties = new Properties();

                for (Element prop : propertiesList) {
                    properties.setProperty(prop.attributeValue("name"), prop.attributeValue("value"));
                }
                // 设置数据源属性对象
                dataSourceFactory.setProperties(properties);
                // 设置DruidDataSource数据源并返回
                DataSource dataSource = dataSourceFactory.getDataSource();
                // 找到类型注册机里JDBC事务管理器
                TransactionFactory txFactory = (TransactionFactory) typeAliasRegistry.resolveAlias(e.element("transactionManager").attributeValue("type")).newInstance();

                // 构建环境-通过建造者模式
                Environment.Builder environmentBuilder = new Environment.Builder(id).dataSource(dataSource)
                        .transactionFactory(txFactory);

                // 将环境信息存储到配置里configuration供其他需要的地方使用
                configuration.setEnvironment(environmentBuilder.Builder());
            }
        }
    }

    // 将mapper中的xml配置解析出来存储到对应的实体类中
    private void mapperElement(Element mappers) throws Exception {
       // ...省略,上一章节有
       // 添加BoundSql并把有关sql解析的存储在这里
       BoundSql boundSql = new BoundSql(sql, paramter, parameterType, resultType);

       MappedStatement mappedStatement = new 
       MappedStatement.Builder(configuration, msId, sqlCommandType,
                        boundSql).build();
    }

BoundSql类:此类存储的是xml里的sql,如parameterType还有resultType,以及将参数处理成?等等都存放到BoundSql实体类

<mapper namespace="df.middleware.mybatis.dao.IUserDao">
    <select id="queryUserInfoById" parameterType="java.lang.Long" resultType="df.middleware.mybatis.po.User">
        SELECT id, userId, userHead,userName
        FROM user
        where id = #{id}
    </select>
</mapper>
package df.middleware.mybatis.mapping
/**
 * @Author df
 * @Date 2022/5/26 16:09
 * @Version 1.0
 * 绑定的SQL,是从SqlSource而来,将动态内容都处理完成得到的SQL语句字符串,其中包括?,还有绑定的参数
 */
public class BoundSql {
    private String sql;
    private String parameterType;
    private String resultType;
    private Map<Integer, String> parameterMappings;

    public BoundSql(String sql,Map<Integer, String> parameterMappings,String parameterType,String resultType){
        this.sql = sql;
        this.parameterMappings = parameterMappings;
        this.parameterType = parameterType;
        this.resultType = resultType;
    }

    // set/get
}

MappedStatement类:这个和Environment的处理比较像,建造器模式,然后里边存储的主要是xml里的id还有namespace,以及对应的BoundSql类对象,这样Configuration类里就可以直接MappedStatement就可以获取其他相关的实体类信息。

<mapper namespace="df.middleware.mybatis.dao.IUserDao">
    <select id="queryUserInfoById">
       
    </select>
</mapper>
package df.middleware.mybatis.mapping
public class MappedStatement {
    private Configuration configuration;
    private String id;
    private SqlCommandType sqlCommandType;

    private BoundSql boundSql;


    //public MappedStatement(){}

    public static class Builder {
        private MappedStatement mappedStatement = new MappedStatement();

        public Builder(Configuration configuration, String id, SqlCommandType sqlCommandType,
                       BoundSql boundSql) {
            mappedStatement.configuration = configuration;
            mappedStatement.id = id;
            mappedStatement.sqlCommandType = sqlCommandType;

            mappedStatement.boundSql = boundSql;

        }

        public MappedStatement build() {
            assert mappedStatement.configuration != null;
            assert mappedStatement.id != null;
            return mappedStatement;
        }
    }

    public Configuration getConfiguration() {
        return configuration;
    }

    public String getId() {
        return id;
    }

    public SqlCommandType getSqlCommandType() {
        return sqlCommandType;
    }

    public void setConfiguration(Configuration configuration) {
        this.configuration = configuration;
    }

    public void setSqlCommandType(SqlCommandType sqlCommandType) {
        this.sqlCommandType = sqlCommandType;
    }

    public BoundSql getBoundSql() {
        return boundSql;
    }
}

DefaultSqlSession类:DefaultSqlSession里添加对Environment环境的使用可以取出对应的数据源连接数据库,也可以通过MappedStatement得到Sql信息,参数和结果信息

这样的化就去连接数据库,拼接传参数执行sql语句得到结果通过反射去对应对象即可,暂时是写死的后期会更改

@Override
    public <T> T selectOne(String statement, Object parameter) {
        try {
            MappedStatement mappedStatement = configuration.getMappedStatement(statement);
            Environment environment = configuration.getEnvironment();
            Connection connection = null;

            connection = environment.getDataSource().getConnection();


            BoundSql boundSql = mappedStatement.getBoundSql();

            PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSql());
            preparedStatement.setLong(1, Long.parseLong(((Object[]) parameter)[0].toString()));
            ResultSet resultSet = preparedStatement.executeQuery();
            List<T> list = resultSet2Obj(resultSet, Class.forName(boundSql.getResultType()));
            return list.get(0);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


    private <T> List<T> resultSet2Obj(ResultSet resultSet, Class<?> clazz) {
        List<T> list = new ArrayList();
        try {
            ResultSetMetaData metaData = resultSet.getMetaData();
            int columnCount = metaData.getColumnCount();

            while (resultSet.next()) {
                // 新建实例
                T obj = (T) clazz.newInstance();
                for (int i = 1; i <= columnCount; i++) {
                    Object value = resultSet.getObject(i);
                    String columnName = metaData.getColumnName(i);
                    String setMethod = "set" + columnName.substring(0, 1).toUpperCase() + columnName.substring(1);

                    // clazz.getMethod(setMethod, value.getClass());
                    // 参数传入实体的方法名称,和方法对应的参数class
                    Method method;
                    if (value instanceof Timestamp) {
                        method = clazz.getMethod(setMethod, Date.class);
                    } else {
                        method = clazz.getMethod(setMethod, value.getClass());
                    }
                    // 反射调用方法
                    method.invoke(obj, value);
                }
                list.add(obj);
            }

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

测试准备

在resources下添加mybatis-config-datasource.xml文件

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

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="UNPOOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis_demo?useUnicode=true"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>

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

</configuration>

在resources/mapper下添加User_Mapper.xml文件,内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<mapper namespace="df.middleware.mybatis.dao.IUserDao">
    <select id="queryUserInfoById" parameterType="java.lang.Long" resultType="df.middleware.mybatis.po.User">
        SELECT id, userId, userHead,userName
        FROM user
        where id = #{id}
    </select>
</mapper>

在单元测试类里添加如下内容

@Test
public void test_SqlSessionFactory() throws IOException {
    // 1. 从SqlSessionFactory中获取SqlSession
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config-datasource.xml"));
    SqlSession sqlSession = sqlSessionFactory.openSession();
    
    // 2. 获取映射器对象
    IUserDao userDao = sqlSession.getMapper(IUserDao.class);
    
    // 3. 测试验证
    User user = userDao.queryUserInfoById(1L);
    logger.info("测试结果:{}", JSON.toJSONString(user));
}

单元测试结果如下,得到数据库数据

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值