文章详情

randy
2022-10-15
3788
原创

手写MyBatis

1.导入依赖

dom4j和jaxen用于解析xml,junit用于搭建测试环境,mysql用于配置数据源,得到连接对象

  <dependencies>
        <dependency>
            <groupId>org.dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>2.1.3</version>
        </dependency>
        <dependency>
            <groupId>jaxen</groupId>
            <artifactId>jaxen</artifactId>
            <version>1.2.0</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.18</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

2.创建SqlSessionFactoryBuild类

该类的作用:通过build方法返回SqlSessionFactory对象

 public SqlSessionFactory build(InputStream in){
        //事务对象
	    Transaction transaction=null;
        //Map集合,key是sqlId, value是封装了SQL标签(sql语句和查询返回类型)的MapperStatement对象
        Map<String,MapperStatement> mapperStatementMap=new HashMap<>();
 		SqlSessionFactory sqlSessionFactory = new SqlSessionFactory(transaction,mapperStatementMap);
        return sqlSessionFactory;
 }

3.创建SqlSessionFactory类

该类的作用:通过openSession方法返回SqlSession对象

public class SqlSessionFactory {
    public SqlSession openSession(){
        SqlSession sqlSession = new SqlSession(this);sm
        return sqlSession;
    }
}

类中需要两个属性:事务属性Map属性

public class SqlSessionFactory {
    //1.需要事务属性(里面有dataSource属性)
    Transaction transaction;
    //2.需要Map集合属性(key:sqlid, value: sql标签对象)
    Map<String,MapperStatement> mapperStatementMap;
    public SqlSessionFactory(Transaction transaction, Map<String,MapperStatement> mapperStatementMap){
        this.transaction=transaction;
        this.mapperStatementMap=mapperStatementMap;
    }
}

分析SqlSessionFactory类需要什么属性

要明确这个问题,我们要先从MyBatis框架的用户使用流程来倒推.用户使用MyBatis,第一步就是编写全局配置文件mybatis-config.xmluserMpper.xml,mybatis-config.xml中主要包含事务和数据源两方面的配置,userMpper.xml包含select语句的标签,创建SqlSessionFactory对象的时候需要解析配置文件的内容,封装成对象传到SqlSessionFactory对象中.从两个配置文件可看出,要得到SqlSessionFactory对象,至少需要事务,数据源和sql标签等三方面数据.由此我们可以得出结论:创建SqlSessionFactory对象需要事务属性,数据源属性和sql标签属性.

数据源属性

作用:数据源的作用就是创建连接对象Connection

属性: 需要 url username password driver

private String url;
private String username;
private String password;
private Driver driver;

对象获取方式:实现javax.sql.DataSource接口(jdk的接口)

public class Unpooled implements DataSource {
    private String url;
    private String username;
    private String password;
    private Driver driver;
    public Unpooled(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 DriverManager.getConnection(url,username,password);
    }
}

常见的数据源对象(均实现了DataSource对象)

Unpooled: 不用数据库连接池,每一次都是创建新的连接对象(此番手写框架只实现这个类)

Pooled : 使用MyBatis实现的数据库连接池获取连接对象(不实现)

JNDI : 使用第三方的数据库连接池获取连接对象(不实现)

事务属性

作用:提供管理事务的相关方法,如commit,rollback,close等方法

对象获取方式:自定义事务接口Transcation,实现Transacton接口获取对应事务类

定义Transaction接口

public interface Transaction {//有JDBC和MANAGED两种事务选择,分别对应两个实现类
    void commit();
    void rollback();
    void close();
    Connection getConnection() throws SQLException;
}

事务实现类

public class JdbcTransaction implements Transaction{//调用JDBC的事务实现

    //需要Connection对象
    private DataSource dataSource;
    private Connection connection;
    public JdbcTransaction(DataSource dataSource){
        this.dataSource=dataSource;
    }

    public Connection getConnection() throws SQLException {
        if (connection == null) {
            connection=dataSource.getConnection();
        }
        return connection;
    }
    @Override
    public void commit() {
        try {
            connection.commit();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        //调用JDBC的Connection对象的commit方法
    }

    @Override
    public void rollback() {
        try {
            connection.rollback();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        //调用JDBC的Connection对象的rollback方法
    }

    @Override
    public void close() {
        try {
            connection.close();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        //调用JDBC的Connection对象的close方法
    }
}

常见事务实现类

JDBC: 通过借助jdbc的Connection对象调用commit,rollback,close等方法实现事务(本次框架实习这个类)

MANAGED: 通过第三方实现事务,一般而言通过Spring容器实现事务.(未实现)

观察事务实现类中可得知,事务的commit,rollback,close方法都是通过Connection对象来调用的,由此我们得知,事务对象中一定要有Connection对象,而要获取Connection对象,就必须要DataSource对象,要DataSource对象就必须从配置文件中获取到Drive,url,username,password等信息,同时由于事务对象中包含数据源对象,那么SqlSessionFactory中有事务属性就能获取到数据源属性.由此SqlSessionFactory中只需要事务属性和Sql标签属性.

深入Map属性(mapperStatementMap)

作用:封装存放所有的bean配置类.

为什么需要此类:在获取到连接对象后,我们要先获取sql语句,执行sql,返回sql处理结果,因此我们需要一个属性能得到sql语句,返回类.此时我们在返回配置文件,主配置文件只有一个,但是每个javabean类都有一个userMpper.xml的配置文件,里面编写对应bean的增删改查的sql语句和查询得到的结果集的返回值类型.由于每个bean的配置类都会加载到主配置类.我们考虑新建一个MapperStatement类,这类里面封装了sql语句和返回值类型,同时用一个Map集合存放所有的bean配置类,key是配置类的namespace+id的组合字符串,value就是MapperStatement对象.

MapperStatement类

public class MapperStatement {
    private String sql;
    private String resultType;
}

Map<String,MapperStatement> mapperStatementMap

 for (Element e :elements) {
                String id = e.attributeValue("id");
                String resultType = e.attributeValue("resultType");
                String sql = e.getTextTrim();
                String sqlId=namespace+"."+id;
                MapperStatement mapperStatement = new MapperStatement(sql, resultType);
                mapperStatementMap.put(sqlId,mapperStatement);
   }

解析配置文件

//解析两个配置文件获取配置文件中的信息,并封装为Transaction和map
        SAXReader saxReader = new SAXReader();
        Document document = saxReader.read(in);
        Element environments  = (Element) document.selectSingleNode("/configuration/environments");
        String defaultId = environments.attributeValue("default");//获取default的id值
        //获取与default的id一致的environment
        Element environElt=(Element)                                                   environments.selectSingleNode("/configuration/environments/environment[@id='"+defaultId+"']");
        Element transactionManagerElt = environElt.element("transactionManager");
        //事务类型
        String transcationType = transactionManagerElt.attributeValue("type").trim().toUpperCase();
        Element dataSourceElt = environElt.element("dataSource");
        //数据源类型及数据源4个数据
        String dataSourceType = dataSourceElt.attributeValue("type");
        List<Element> propertys = dataSourceElt.elements("property");
        Map<String ,String> dataMap=new HashMap<>();
        //把driver,url,username,password属性封装到map中
        for(Element e:propertys){
            String name = e.attributeValue("name");
            String value = e.attributeValue("value");
            dataMap.put(name,value);
        }
        //判断用户配置的数据源(UNPOOLED,POOLED,JNDI)
        if (Const.DATASOURCE_UNPOOLED.equals(dataSourceType)) {
            dataSource=new Unpooled(dataMap.get("driver"),dataMap.get("url"),dataMap.get("username"),dataMap.get("password"));
        }
        if (Const.DATASOURCE_POOLED.equals(dataSourceType)) {
            dataSource=new Pooled(dataMap.get("driver"),dataMap.get("url"),dataMap.get("username"),dataMap.get("password"));
        }
        if (Const.DATASOURCE_JNDI.equals(dataSourceType)) {
            dataSource=new JNDI(dataMap.get("driver"),dataMap.get("url"),dataMap.get("username"),dataMap.get("password"));
        }

        //判断用户配置的事务类型(JDBC,MANAGED)
        if (Const.TRANSACTION_JDBC.equals(transcationType)) {
            transaction=new JdbcTransaction(dataSource);
        }
        if (Const.TRANSACTION_MANAGED.equals(transcationType)) {
            transaction=new Managed(dataSource);
        }
        //封装mapperStatementMap
        List<Node> nodes = document.selectNodes("//mapper");
        for (Node node: nodes) {
            String resource = ((Element) node).attributeValue("resource");
            InputStream mapper = Resource.getResourceAsStream(resource);
            SAXReader saxReader1=new SAXReader();
            Document mapperDocument = saxReader1.read(mapper);
            Element mapperElt= (Element) mapperDocument.selectSingleNode("/mapper");
            String namespace = mapperElt.attributeValue("namespace");
            List<Element> elements = mapperElt.elements();
            for (Element e :elements) {
                String id = e.attributeValue("id");
                String resultType = e.attributeValue("resultType");
                String sql = e.getTextTrim();
                String sqlId=namespace+"."+id;
                MapperStatement mapperStatement = new MapperStatement(sql, resultType);
                mapperStatementMap.put(sqlId,mapperStatement);
            }

}

4.创建SqlSession对象

通过前期的努力,我们拿到了数据源对象,事务对象,map集合,有了这些,我们成功创建了SqlSessionFactory对象,通过该对象的openSession对象得到了SqlSession对象.而有了SqlSession对象我们就可以调用crud方法来实现数据库操作.

实现insert和select方法

  • insert(sqlId,bean)

    • sql中占位符需要用正则替换:#\{[a-zA-Z0-9_$]*}
    • 给?传值
      • 不知道有多少个?
        • 先获取到第一个井号的索引
        • 在获取}的索引
        • 然后substring进行裁串
        • 用一个变量来记录#个数,位置
      • 不知道要给bean的哪个属性赋值给哪个?
        • 有属性的名称,通过反射调用bean的get方法名称
          • 方法名:get+properName.toUpperCase().chartAt(0)+por.subString(1)
          • Obj obj=pojo.getClass().getDeclaredMethod(getMethodName).invoke(bean)
          • ps.setString(index,obj)
      • 都用ps.setString(),数据库中都是varchar
    public int insert(String sqlId,Object obj){//插入
            Connection connection=null;
            int count=0;
            try {
               connection = this.sqlSessionFactory.transaction.getConnection();
                MapperStatement mapperStatement = this.sqlSessionFactory.mapperStatementMap.get(sqlId);
                //insert inot User(id,name,age) values(#{id},#{name},#{age})
                String sql = mapperStatement.getSql();
                //insert into User(id,name,age) values(?,?,?);
                String newSql = sql.replaceAll("#\\{[A-Za-z0-9_$]*}", "?");
                PreparedStatement ps = connection.prepareStatement(newSql);
                //现在要做的事情,就是得到有多少个参数需要替换,每个参数如何调用对应Bean的get方法从bean中取值,并把值替换到sql中
                int index=0;
                while(true){
                    int jingHaoIndex = sql.indexOf("#");//第一个出现的井号下标
                    if (jingHaoIndex <0 ) {//说明没有#号了
                        break;
                    }
                    int youkuohaoIndex = sql.indexOf("}");//第一个右括号下标
                    String param = sql.substring(jingHaoIndex + 2, youkuohaoIndex).trim();//获取到的第一个参数名
                    String setMethodName="get"+param.toUpperCase().charAt(0)+param.substring(1);//得到参数对应的get方法名称:getId
                    Class<?> o = obj.getClass();
                    Method declaredMethod = o.getDeclaredMethod(setMethodName);
                    Object invoke = declaredMethod.invoke(obj);
                    index++;
                    sql=sql.substring(youkuohaoIndex+1);
                    ps.setString(index,invoke.toString());
    
                }
                count = ps.executeUpdate();
    
            } catch (Exception e) {
                e.printStackTrace();
            }
            return count;
        }
    
  • select

    • Class.forName(String resultType):获取结果集对象返回的类
    • 得到Obj对象: resultClass.newInstance(),无参构造,属性为空
    • 将查询结构的数据库的列名当作属性名:rs.getMetaData() 列名集合
    • 列名.getColumnCount得到有多少列
    • rsmd.gerColumnName(i):得到第i列的列名称
    • commit,rollabck,close:调用factory的commit,rollback,close方法
 public Object selectOne(String sqlId,String id){
        Connection connection=null;
        try {
           connection=this.sqlSessionFactory.transaction.getConnection();
           // select * from user where id= #{id}
            String sql = this.sqlSessionFactory.mapperStatementMap.get(sqlId).getSql();
            // select * from user where id= ?
            String newSql = sql.replaceAll("#\\{[a-zA-Z0-9_$]*}", "?");
            String resultType = this.sqlSessionFactory.mapperStatementMap.get(sqlId).getResultType();
            PreparedStatement preparedStatement = connection.prepareStatement(newSql);
            preparedStatement.setString(1,id);
            ResultSet resultSet = preparedStatement.executeQuery();
            Class<?> resultClass = Class.forName(resultType);
            Object o = resultClass.newInstance();
            if (resultSet.next()) {
                ResultSetMetaData metaData = resultSet.getMetaData();//获取数据库表中的所所有字段集合
                for (int i = 0; i < metaData.getColumnCount(); i++) {
                    String columnName = metaData.getColumnName(i+1);//遍历得到每个字段名称
                    String setMethod="set"+columnName.toUpperCase().charAt(0)+ columnName.substring(1);//得到每个字段的set方法名称
                    Method declaredMethod = o.getClass().getDeclaredMethod(setMethod,String.class);//得到set方法
                    declaredMethod.invoke(o, resultSet.getString(columnName));//调用set方法
                }
            }
            return o;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

评论区

  1. 目录
留言