基于注解实现简易版Mybatis框架

基于注解实现简易版Mybatis框架

          • 传统开发模式----JDBC
          • 解决问题,直击痛点
          • :one:定义注解
            • 参数注解: @param
            • 查询注解:@Select
            • 插入注解:@Insert
          • :two:定义bean对象和相应Mapper接口信息
            • 前置信息
            • 用户对象信息
            • UserMapper接口信息
          • :three:实现对Mapper接口的代理,生成MapperProxy代理对象
            • 前置介绍
            • 流程分析
            • 接口映射处理
            • 所需工具类
        • :four:执行SQL语句
          • JDBCUTILS工具类
        • :five:读取配置文件工具类信息
        • :six:小结

MyBatis 是一款优秀的持久层框架,是一款半 ORM(对象关系映射)框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects)为数据库中的记录。

基本思路:

1.自定义注解

2.实现对Mapper接口的代理,生成MapperProxy代理对象

3.利用反射绑定参数

4.执行SQL语句


本文重点

1.模拟实现mybatis框架基于注解式的开发**

2.梳理mybatis运行时执行流程

3.巩固反射,JDBC的基本细节


传统开发模式----JDBC

在基于传统的JDBC开发过程中我们会频繁创建数据库连接对象、释放,容易造成系统资源浪费,影响系统性能。

这个问题我们可以引入连接池来解决,但是使用JDBC需要自己实现连接池。Sql语句定义、参数设置、结果集处理存在硬编码。同时在实际项目中Sql语句变化的可能性较大,一旦发生变化,需要修改java代码,系统需要重新编译,重新发布,不好维护。

为了进一步提升性能我们用PrepareStatement对象进行Sql语句的预编译处理,其可以提升Sql语句的执行效率、避免Sql注入的攻击等一系列优势。然而,使用PrepareStatement占有位符号传参数已然存在硬编码的问题,虽然使但是当面对多条件查询时PreparedStatement语句也显得相形见绌。因为Sql语句的where条件拼接的个数不一定确定,可能多也可能少,此时预留占位符就成了一个新的问题。此时修改Sql还要修改代码,系统不易维护。

执行Sql语句后会获得到结果集ResultSet,原始的方法致使我们面临一个问题:结果集操作大同小异,唯一不同的就是每次拼接的对象实例不同。最初我们根据我们所想返回对象的不同来进行单独处理,面对小规模系统设计这完全没什么问题,但是当系统规模变得庞大,我们不断重复书写遍历ResulSet的逻辑就显得有些不理智了,相当于重复在书写相同的代码逻辑,而最终结果仅是返回所需实体类。

解决问题,直击痛点

面临上述种种的问题,Mybatis的诞生皆在解决上述问题,Mybatis核心功能的实现基本就是:**反射、JDBC操作。**这些都是java基础范畴内容,自己在闲暇之余也可以利用所学进行抽象、总结。

本文所说手写框架并不是去完整的构建出一个框架,而是学习框架的一种设计思路,设计思想。发现框架中的核心有价值内容。进行模仿、应用。从而提升能力。

本文皆在叙述如何手工实现Mybatis基于注解形式的开发, 模拟框架的总体执行流程,当然其中还有很多细节并不是短短数字就叙述清楚的。

项目结构:
基于注解实现简易版Mybatis框架_第1张图片

1️⃣定义注解

基于注解式开发的好处在于避免了繁琐的XML文档信息的编写,将待执行的Sql语句写至注解之中,从而达到简化开发的目的。

目前所需注解:@Param,@Select,@Insert

@Param:主要作用在于绑定注解参数信息,在Mybatis框架中,参数信息的保存一般使用一个Map集合来保存对应信息,如果不指定@Param信息,将以参数所在位置作为key来作为传入参数的索引值项如果不使用@Param指定参数名则可通过 #{param1}获取对应参数的位置

@Select:主要作用在于定义查询的Sql语句信息

@Insert:主要作用在于定义插入的Sql语句信息

param(index)的形式获取信息 index:表示参数所在位置
//实例:
    // 接口层代码信息
      User findById(Integer id);
	// mapper层的映射
    <select id="findById" parameterType="Integer" resultMap="userMap">
        select * from user  where id = #{
      param1}
    </select>
参数注解: @param
package mybatisAnnioation.annotation;

import java.lang.annotation.*;

/**参数注解信息
 * @program: XMind
 * @description
 * @author: yihang
 **/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
@Documented
public @interface Param {
     
    String value() default "";
}

查询注解:@Select
package mybatisAnnioation.annotation;

import java.lang.annotation.*;

/**查询信息
 * @program: XMind
 * @description
 * @author: yihang
 **/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface Select {
     
    String value() default "";
}

插入注解:@Insert
package mybatisAnnioation.annotation;

import java.lang.annotation.*;

/**
 * @program: XMind
 * @description
 * @author: yihang
 **/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface Insert {
     
    // 插入数据注解
    String value() default "";
}

2️⃣定义bean对象和相应Mapper接口信息

在mybatis框架中,底层框架binding模块会将用户自定义的 Mapper 接口与映射配置文件关联起来在本模拟过程中将该过程简化为注解同方法间的映射。以此完成Sql语句同方法的映射。

前置信息
#数据库
create database mybatis;
#所需用户表
CREATE TABLE `stu` (
  `id` int(11) DEFAULT NULL,
  `username` varchar(50) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

/*Data for the table `stu` */
# 测试数据信息  可自行添加
insert  into `stu`(`id`,`username`) values (1,'张三'),(2,'李四'),(NULL,'张三'),(NULL,'张三'),(NULL,'张三'),(NULL,'张三'),(NULL,'张三'),(NULL,'张三'),(NULL,'张三'),(NULL,'张三'),(23,'张三'),(23,'张三');
用户对象信息
public class User {
     
    private Integer id;
    private String username;
	// 出于精简略去get/set方法
}
UserMapper接口信息
package mybatisAnnioation.mapper;


import mybatisAnnioation.annotation.Insert;
import mybatisAnnioation.annotation.Param;
import mybatisAnnioation.annotation.Select;
import mybatisAnnioation.bean.User;

/**
 * @program: XMind
 * @description
 * @author: yihang
 **/
public interface UserMapper {
     

    // 查询信息
    @Select("select id,username from stu where id = #{id} and username = #{name}")
    User selectUser(@Param("id")Integer id , @Param("name")String name);

    // 插入数据信息
    @Insert("insert into stu(id,username) values (#{id},#{username})")
    int insertUser(@Param("id")Integer id , @Param(value="username")String username);
}
3️⃣实现对Mapper接口的代理,生成MapperProxy代理对象
前置介绍

在为对应Mapper接口生成代理对象的MapperProxy的本质核心在于:java动态代理的应用,由于UserMapper属于接口,根据java语法规范接口中并允许有方法的具体实现,所以Mybatis利用动态代理的方式,对Mapper接口中的方法进行了增强。

此时要补充一点的就是:Mybatis在生成MapperProxy时所用到的代理形式,其实并不是一种“正统的动态代理”,一般动态,都会有一个具体的实际对象作为增强对象信息,但是由于Mapper属于接口层面,其并没有办法进行实例化,所以其动态代理的形式就退化成了如下形式。

基于注解实现简易版Mybatis框架_第2张图片

基于注解实现简易版Mybatis框架_第3张图片

流程分析

以查询方法为例进行分析

基于注解实现简易版Mybatis框架_第4张图片

核心方法解析

作用:返回待查询对象信息
参数类型:(Method method ,Select query, Object [] args) 
getResult逻辑
        1.获取sql语句信息   String sql = query.value();
        2.获取sql参数信息  此时sql 信息为主注解上的形如:"select id,username from stu where id = #{id} and username = #{name}"
        List<Object> paramNames = SQLUtils.getSelectParams(sql);
		4.替换sql信息,将占位符#{
      xxx} 替换为预编译SQL中的?
        // 形如select id,username from stu where id = ? and username = ?
        5.获取方法实参信 形式为 key -value ,@Param中指定的value值作为key
        ConcurrentHashMap<String, Object> paramMap = getMethodParam(method, args);
		6.参数进行封装处理,将实参信息重新拼接值sql中
        List<Object> paramValueList = new ArrayList<>();
		7.执行sql,返回结果集
        8.利用反射,获取方法返回值类型信息,遍历结果集封装对象信息
        9.返回信息
接口映射处理
package mybatisAnnioation.handler;

import mybatisAnnioation.annotation.Insert;
import mybatisAnnioation.annotation.Param;
import mybatisAnnioation.annotation.Select;
import mybatisAnnioation.utils.JDBCUtils;
import mybatisAnnioation.utils.SQLUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @program: XMind
 * @description
 * @author: yihang
 **/
public class UserMapperInvocationHandler implements InvocationHandler {
     
    private Class userMapperClazz;

    public UserMapperInvocationHandler(Class userMapperClazz) {
     
        this.userMapperClazz = userMapperClazz;
    }



    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws IllegalAccessException, SQLException, InstantiationException {
     

        // 获取查询注解信息
        Select query = method.getAnnotation(Select.class);
        if(query != null) {
     
            return getResult(method,query,args);
        }
        Insert insert = method.getAnnotation(Insert.class);
        if ( insert != null) {
     
            String insertSql = insert.value();
            //插入参数
            String[] insertParam = SQLUtils.getInsertParams(insertSql);
            //参数绑定
            ConcurrentHashMap<String, Object> paramMap = getMethodParam(method, args);
            System.out.println("paramList"+ Arrays.toString(insertParam));
            // 进行值的封装
            List<Object> paramValueList = new ArrayList<>();
            //将参数值装入list集合
            for (String param : insertParam) {
     
                Object paramValue = paramMap.get(param.trim());
                paramValueList.add(paramValue);
            }
            insertSql = SQLUtils.replaceParam(insertSql, insertParam);
            //更优化的处理在于再次设定一个类型转换
            return JDBCUtils.insert(insertSql, false, paramValueList);
        }

        return null;
    }


    /**
     * 返回结果集对象信息
     * @param method
     * @param query
     * @param args
     * @return
     */
    private Object getResult(Method method ,Select query, Object [] args) throws IllegalAccessException, SQLException, InstantiationException {
     
        // 获取sql语句信息
        String sql = query.value();
        // 获取sql参数信息
        List<Object> paramNames = SQLUtils.getSelectParams(sql);
        // 将sql语句进行替换,替换成预编译的sql
        // 形如select id,username from stu where id = ? and username = ?
        sql = SQLUtils.replaceParam(sql,paramNames);
        //获取方法参数,绑定值。
        ConcurrentHashMap<String, Object> paramMap = getMethodParam(method, args);


        List<Object> paramValueList = new ArrayList<>();
        //将参数值装入list集合
        for (Object param : paramNames) {
     
            Object paramValue = paramMap.get(param);
            paramValueList.add(paramValue);
        }
        // 生成预编译sql为对应位置设定对应值信息
        ResultSet rs = JDBCUtils.query(sql,paramValueList);
        if( rs == null) {
     
            System.out.println("userMapper null");
        }
        // 处理异常情况
        if (!rs.next()) {
     
            return null;
        }
        // 获取到方法的返回值类型信息
        Class<?> returnTypeClazz = method.getReturnType();
        // 实例化一个返回值的对象
        Object obj = returnTypeClazz.newInstance();
        // 光标往前移动一位
        rs.previous();
        while (rs.next()) {
     
            Field[] fields = returnTypeClazz.getDeclaredFields();
            for (Field field : fields) {
     
                String fieldName = field.getName();
                String fieldValue = rs.getString(fieldName);
                Type type = field.getGenericType();
                field.setAccessible(true);
                // 简单的处理类型映射
                if ( type.toString().equals("class java.lang.Integer")){
     
                    field.set(obj, Integer.valueOf(fieldValue));
                }else{
     
                    field.set(obj, fieldValue);
                }

            }
        }
        return obj;
    }

    /***
     * 获取方法上参数值的信息
     * @param method
     * @param args
     * @return
     */
    private ConcurrentHashMap getMethodParam(Method method, Object[] args) {
     
        ConcurrentHashMap paramMap = new ConcurrentHashMap();
        Parameter[] parameters = method.getParameters();
        // 获取所有注解信息
        Annotation [][] annotations = method.getParameterAnnotations();
        for( int i = 0 ; i < parameters.length ; i ++ ) {
     
            for ( Annotation annotation : annotations[i]){
     
                if ( annotation instanceof Param) {
     
                    String key = ((Param)annotation).value();
                    paramMap.put(key,args[i]);
                }
            }
        }

        return paramMap;
    }

}

所需工具类

SQLUTILS

package mybatisAnnioation.utils;

import org.apache.ibatis.jdbc.SQL;

import java.util.ArrayList;
import java.util.List;

/**
 * @program: XMind
 * @description
 * @author: yihang
 **/
public class SQLUtils {
     


    // where 关键字 and关键字
    private  static final int IGNORE_WHERE = 5;
    private static  final int IGNORE_AND = 3;
    private static  final int IGNORE_VALUES = 6;
    /***
     * 替换参数信息
     * #{param} ---> ?
     */

    public static String replaceParam(String sql , List<Object> params){
     
        for ( int i = 0 ; i < params.size() ; i ++ ) {
     
            // 获取参数名
            Object paramName = params.get(i);
            // 替换信息
            sql = sql.replace("#{" + paramName +"}","?" );
        }
        return sql;
    }

    /***
     * 用于处理insert语句时所需
     * @param sql
     * @param parameterName
     * @return
     */
    public static String replaceParam(String sql, String[] parameterName) {
     
        for (int i = 0; i < parameterName.length; i++) {
     
            String string = parameterName[i].trim();
            sql = sql.replace("#{" + string + "}", "?");
        }
        return sql;
    }


    /***
     *
     *获取sql的中的参数信息名
     * sql形式:select id,username from stu where id = #{id} and username = #{name}
     * @param sql
     * @return
     */
    public static List<Object> getSelectParams(String sql) {
     
        // 存放信息
        List<Object> paramList = new ArrayList<>();
        //定位到where的位置信息
        int startIndex = sql.indexOf("where");
        // 跳过where关键字 截取到where关键字的内容信息
        String whereAfter = sql.substring(startIndex + IGNORE_WHERE);
        // 通过and分隔
        String  [] params = whereAfter.split("and");
        for(String param : params) {
     
            // id = #{id}
            // 通过定位#{ 和}的位置来截取信息
            int start = param.indexOf("{");
            int end = param.indexOf("}");
            String paramName = param.substring(start + 1 , end);
            System.out.println(paramName.trim());
            //加入到集合 利用trim避免多用空信息
            paramList.add(paramName.trim());
        }
        return paramList;
    }

    /***
     * 处理insert的sql语句信息
     * @param insertSql
     * @return
     */
    public static String[] getInsertParams(String insertSql) {
     
        int startIndex = insertSql.indexOf("values");
        insertSql = insertSql.substring(startIndex + IGNORE_VALUES);
        //(#{id},#{username})
        // 转变为 id,username 随后根据进行分隔处理
        insertSql = insertSql.replaceAll("#\\{","")
                .replaceAll("\\}","")
                .replaceAll("\\(","").replaceAll("\\)","");
        // 注意此处,截取后的信息会多个接连空格信息!!!! 因为用空字符进行了替换
        // 在处理映射是需要注意去掉多用空格信息
        return insertSql.split(",");
    }
}

TIPS:

 public static String[] getInsertParams(String insertSql) {
     
        int startIndex = insertSql.indexOf("values");
        insertSql = insertSql.substring(startIndex + IGNORE_VALUES);
        //(#{id},#{username})
        // 转变为 id,username 随后根据进行分隔处理
        insertSql = insertSql.replaceAll("#\\{","")
                .replaceAll("\\}","")
                .replaceAll("\\(","").replaceAll("\\)","");
        // 注意此处,截取后的信息会多个接连空格信息!!!! 因为用空字符进行了替换
        // 在处理映射是需要注意去掉多用空格信息
        return insertSql.split(",");
    }

以上方法分析:
    	作用:用于拆解insert插入语句信息
    插入语句形如:"insert into stu(id,username) values (#{id},#{username})"
    基本拆解逻辑
    	1.定位values的位置信息
    	2.截取values信息后的内容信息
    		注意此时获取到的信息为: " (#{id},#{username})"
    	 此处处理一定要注意这个多余的空格信息!
        3.利用正则进行替换处理替换后的新信息为:" id,username"
    	4.分隔处理返回String数组作为参数信息
    注:如果不加处理此时在含有id这个字符串的内容中会包含有一个多余空格~~~~,所以在使用时必须去掉多用的空格信息

4️⃣执行SQL语句

此处就是基本SQL语句的执行,数据库基本JDBC语句的编写,以查询为例

 public static ResultSet query(String sql, List<Object> paramValueList) throws SQLException {
      
        //执行sql语句
        // 仅执行查询语句信息
        if (sql == null || sql.trim().isEmpty()
                || !sql.trim().toLowerCase().startsWith("select")) {
      
            throw new RuntimeException("你的SQL语句为空或不是查询语句");
        }
        ResultSet rs = null;
        // 说明执行带参数信息
        if( paramValueList.size() > 0 && paramValueList != null) {
      
            // 生成预编译sql
            PreparedStatement preparedStatement = prepare(sql,false);
            // 为sql设定内容
            for(int i = 0 ; i < paramValueList.size() ; i ++ ) {
      
                // 这里注意位置从1开始计算
               preparedStatement.setObject(i+1,paramValueList.get(i));
            }
            // 执行
            rs = preparedStatement.executeQuery();
        }else{
      
            // 无参方法
            Statement statement = createStatement();
            // 执行查询语句信息
            try {
      
                rs = statement.executeQuery(sql);
            } catch (SQLException e) {
      
                System.out.println("执行无参SQL失败: " + e.getMessage());
            }
        }

        return  rs;
    }
JDBCUTILS工具类
package mybatisAnnioation.utils;

import com.mysql.jdbc.Driver;

import java.sql.*;
import java.util.List;

/**
 * @program: XMind
 * @description
 * @author: yihang
 **/
public class JDBCUtils {
     
    private static String connect;
    private static String driverClassName;
    private static String URL;
    private static String username;
    private static String password;
    private static boolean autoCommit;
    // 连接超时时间
    private static final int TIME_OUT = 3;

    /** 声明一个 Connection类型的静态属性,用来缓存一个已经存在的连接对象 */
    private static Connection conn;

    static {
     
        initConfig();
    }

    /***
     * 加载初始信息
     */
    private static void initConfig(){
     
        // 设定信息
        driverClassName = PropertiesUtil.getProperty("jdbc.driver","");
        URL = PropertiesUtil.getProperty("jdbc.url","");
        username = PropertiesUtil.getProperty("jdbc.user","");
        password = PropertiesUtil.getProperty("jdbc.password","");
        // 自动提交
        autoCommit = false;
    }

    /***
     * 获取连接
     * @return
     */
    public static Connection getConnection(){
     
        // 在连接失效的情况下获取连接
        if ( isValid() ){
     
            try {
     
                //加载驱动
                Class.forName(driverClassName);
                // 创建连接
                conn = DriverManager.getConnection(URL,username,password);
            } catch (Exception e) {
     
                e.printStackTrace();
            }
        }
        return conn;
    }
    /**
     * 判断连接是否有效
     *  失效则返回 true
     *  有效返回 false
     * @return true / false
     */
    private static boolean isValid() {
     
        if (conn != null) {
     
            try {
     
                // 超时 或者关闭都认定为连接失效
                //isValid方法是判断Connection是否有效,如果连接尚未关闭并且仍然有效,
                if (conn.isClosed() || !conn.isValid(TIME_OUT)) {
     
                    return true;
                }
            } catch (SQLException e) {
     
                e.printStackTrace();
            }
             //conn 既不是 null 且也没有关闭 ,且 isValid 返回 true,说明是可以使用的
            // 返回 false
            return false;
        }
        // 连接为空 为获取连接
        return true;
    }

    /**
     * 事务控制
     * 设置是否自动提交事务
     */
    private static void transaction() {
     
        try {
     
            conn.setAutoCommit(autoCommit);
        } catch (SQLException e) {
     
            System.out.println("设置事务的提交方式为 : " + (autoCommit ? "自动提交" : "手动提交") + " 时失败: " + e.getMessage());
        }

    }

    /***
     * 事务提交
     * @param con
     */
    private static void commit(Connection con) {
     
        if (con != null && !autoCommit) {
     
            try {
     
                con.commit();
            } catch (SQLException e) {
     
                e.printStackTrace();
            }
        }
    }

    /***
     * 事务回滚
     * @param con
     */
    private static void rollback(Connection con) {
     
        if (con!= null && !autoCommit) {
     
            try {
     
                con.rollback();
            } catch (SQLException e) {
     
                e.printStackTrace();
            }
        }
    }

    /***
     * 执行查询语句
     * @param sql
     * @param paramValueList
     * @return
     * @throws SQLException
     */
    public static ResultSet query(String sql, List<Object> paramValueList) throws SQLException {
     
        //执行sql语句
        // 仅执行查询语句信息
        if (sql == null || sql.trim().isEmpty()
                || !sql.trim().toLowerCase().startsWith("select")) {
     
            throw new RuntimeException("你的SQL语句为空或不是查询语句");
        }
        ResultSet rs = null;
        // 说明执行带参数信息
        if( paramValueList.size() > 0 && paramValueList != null) {
     
            // 生成预编译sql
            PreparedStatement preparedStatement = prepare(sql,false);
            // 为sql设定内容
            for(int i = 0 ; i < paramValueList.size() ; i ++ ) {
     
                // 这里注意位置从1开始计算
               preparedStatement.setObject(i+1,paramValueList.get(i));
            }
            // 执行
            rs = preparedStatement.executeQuery();
        }else{
     
            // 无参方法
            Statement statement = createStatement();
            // 执行查询语句信息
            try {
     
                rs = statement.executeQuery(sql);
            } catch (SQLException e) {
     
                System.out.println("执行无参SQL失败: " + e.getMessage());
            }
        }

        return  rs;
    }

    /***
     * 处理insert语句信息
     * @param insertSql
     * @param autoGeneratedKeys 是否获取生成的键集
     * @param paramValueList 参数信息
     * @return
     */
    public static Object insert(String insertSql, boolean autoGeneratedKeys, List<Object> paramValueList) {
     
        // 记录键信息
        int keys = -1;
        if (insertSql == null || insertSql.trim().isEmpty()) {
     
            throw new RuntimeException("你没有指定SQL语句,请检查是否指定了需要执行的SQL语句");
        }
        // 如果不是 insert 开头开头的语句
        if (!insertSql.trim().toLowerCase().startsWith("insert")) {
     
            System.out.println(insertSql.toLowerCase());
            throw new RuntimeException("你指定的SQL语句不是插入语句,请检查你的SQL语句");
        }

        if( paramValueList != null && paramValueList.size() > 0){
     
            PreparedStatement ps = prepare(insertSql,false);
            // 设定参数
            for (int i = 0 ; i < paramValueList.size() ; i ++ ) {
     
                try {
     
                    ps.setObject(i+1,paramValueList.get(i));
                } catch (SQLException e) {
     
                    e.printStackTrace();
                }
            }
            // 数据保存
            try {
     
                int count =  ps.executeUpdate();
                // 如果希望获得数据库产生的键
                if (autoGeneratedKeys) {
     
                    // 获得数据库产生的键集
                    ResultSet rs = ps.getGeneratedKeys();
                    // 因为是保存的是单条记录,因此至多返回一个键
                    // 获得值并赋值给 var 变量
                    if (rs.next()) {
     
                        keys = rs.getInt(1);
                    }
                } else {
     
                    // 如果不需要获得,则将受SQL影像的记录数赋值给 keys 变量
                    keys = count;
                }
                // 提交
                commit(conn);
            } catch (SQLException e) {
     
                System.out.println("数据保存失败");
                rollback(conn);
            }
        }else{
     
            // insert语句未提供参数信息
            Statement st = createStatement();
            try {
     
                keys = st.executeUpdate(insertSql);
                commit(conn);
            } catch (SQLException e) {
     
                System.out.println("检查inser语句的参数情况,并未提供参数占位信息!!!");
                rollback(conn);
            }
        }
        return keys;
    }

    /***
     * 创建无参statment语句信息
     * @return
     */
    private static Statement createStatement() {
     
        Statement statement = null;
        // 创建连接
        getConnection();
        transaction();
        try {
     
            statement = conn.createStatement();
        } catch (SQLException e) {
     
            e.printStackTrace();
        }
        return statement;
    }


    /**
     * 根据给定的带参数占位符的SQL语句,创建 PreparedStatement 对象
     *
     * @param sql
     *            带参数占位符的SQL语句
     * @return 返回相应的 PreparedStatement 对象
     */
    private static PreparedStatement prepare(String sql, boolean autoGeneratedKeys) {
     
        // 创建连接
        getConnection();
        // 设置事务控制信息
        transaction();
        PreparedStatement ps = null;
        /* 设置事务的提交方式 */
        try {
     
            if (autoGeneratedKeys) {
     
                //使用 PreparedStatement.RETURN_GENERATED_KEYS  可以获取刚刚插入自增ID值
                ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
            } else {
     
                ps = conn.prepareStatement(sql);
            }
        } catch (SQLException e) {
     
            System.out.println("创建 PreparedStatement 对象失败: " + e.getMessage());
        }

        return ps;
    }
}

5️⃣读取配置文件工具类信息

为了避免出现SQL信息的编码处理,将连接数据库所需的信息全部存放到配置文件db.properties中便于维护

package mybatisAnnioation.utils;

import org.apache.commons.lang3.StringUtils;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

/****
 * 装载配置信息
 *
 * prop文件的初始化借助于
 * 1. 实例化对象
 * 2. 通过load装载内容信息
 */

/**
 * @program: XMind
 * @description
 * @author: yihang
 **/
public class PropertiesUtil {
     


    private static Properties props;

    // 静态初始化
    static {
     
        String fileName = "JdbcConfig.properties";
        // 实例化一个对象后,装载内容
        props = new Properties();
        InputStream inputStream = PropertiesUtil.class.getClassLoader().getResourceAsStream(fileName);
        try {
     
            props.load(inputStream);
        } catch (IOException e) {
     
            e.printStackTrace();
        }
    }
    public static String getProperty(String key,String defaultValue){
     

        String value = props.getProperty(key.trim());
        if(StringUtils.isBlank(value)){
     
            value = defaultValue;
        }
        return value.trim();
    }


    private static String getProperty(String key) {
     
        String value = props.getProperty(key.trim());
        if (StringUtils.isBlank(value)) {
     
            return null;
        }
        return value.trim();
    }
}

6️⃣小结

Mybatis框架在解析参数时,所做远比本文实现过程相对复杂。具体可参见源码SqlSourceBuilder 在经过其SqlNode.apply()方法的解析之后,SQL 语句会被传递到 SqlSourceBuilder 中进行进一步的解析。
SqlSourceBuilder 主要完成了两方面的操作。

	一方面是解析 SQL 语句中的“#{}”占位符 中定义的属性,格式类似于#{frc item_ 0, javaType= int, jdbcType=NUMERIC, typeHandler=MyTypeHandler }--->这种解析主要正对标签参数信息解析
	一方面是将 SQL 语句中的中#{}。”占位符替换成“? ” 占位符。

如果有兴趣可以参见源码,本文仅仅是利用正则替换的形式对不同的sql语句的占位符信息进行了提取替换和提取。实现相对简单,但是基本完整实现了注解开发的基本流程。

参考资料:

​ 《Mybatis技术内幕》
博客:纯手写mybatis(注解版)
文中项目地址:https://github.com/ThansCode/Mybatis-Annotation

你可能感兴趣的:(mybatis,java,java)