手写mybatis

一,手写的mybatis实现的功能:

  • 连接数据库的初始化工作
  • 建立dao接口和xml配置文件的关系,使得我们调用接口的方法就可以执行xml文件中的SQL语句

二,目录介绍:

  1. pom.xml配置文件(有点多,有些是写springMVC框架引入的)
  2. user类和IUserDao接口:懂的都懂,不解释
  3. Test类:测试测试是否可以操作数据库
  4. DaoWrapper类:存储xml文件中的:SQL类型,方法名,参数类型,返回值类型的一个POJO类(用于记录他们之间的联系)
  5. DataSourceFactory类:生成数据源的工厂,可以根据参数名来选择实例化的数据源(hikari,Druid)
  6. Session类:会话类,我们想要与数据库交互,需要建立会话,会话携带数据源才可以连接数据库。该类里面有事物的相关方法(开始会话,提交会话,回滚会话)
  7. SessionFactory类:创建session实例对象的工厂(为什么要使用工厂来创建,因为可以把session复杂的创建过程影藏,每当我们使用session对象时,只需要调用它里面的创建方法就可以创建了)
  8. SQLInnovationHandler类:为了使用jdk动态代理创建的类,负责对具体SQL语句执行
  9. userMapper.xml:编写具体的SQL语句
  10. druid.properties:druid数据源连接数据库的信息
  11. hikari.properties:hikari连接数据库的信息
  12. mybatis-config.mxl:存储数据源的名字,和userDao.xml文件的路径(用于对dao.xml文件的管理)

手写mybatis_第1张图片

 

三,执行流程://代码里面有注解

/**
 * SessionFactory sessionFactory=new SessionFactory("mybatis-config.xml");
 * 1,创建一个sessionFactory对象,用来创建session对象,需要闯入参数mybatis-config.xml
 * 2,sessionFactory对象初始化时掉用loadxml(config)方法,config就是mybatis-config.xml
 * 3,loadxml(config)用来对mybatis-config.xml解析,
 * 获取它里面配置的数据源名字,使用DataSourceFactory创建对应的数据源。
 * 获取需要加载dao.xml文件的路径,遍历路径去解析对应的dao.xml配置文件。
 * 存储dao.xml的命名空间,每个方法的SQL类型,参数类型,返回值类型,参数类型。
 * SessionFactory初始化配置信息完成,dao方法与dao.xml配置文件的关系建立,这些信息是属于工厂
 *
 * Session session= sessionFactory.openSession();
 * 4,使用sessionFactory创建session,工厂的openSession()方法会将它的配置信息与session对象进行绑定。
 * IUserDao userDao=session.getMapper(IUserDao.class);
 * 5,session的getMapper方法使用了动态代理机制,闯入的接口IUserDao中的所有方法进行了增强,是它可以执行SQL语句
 * getMapper方法中具体执行SQL语句的方法是SqlInvocationHandler类中的invoke方法
 * 它从session上绑定的信息,调用响应的方法
 * userDao.saveUser(new User(12,"A","B"));
 * 6,进行方法的调用。
 *
 */

 

 

四,具体的代码:(从上到下)

pom.xml文件




  4.0.0

  SpringMVC
  SpringMVC
  1.0-SNAPSHOT
  war

  SpringMVC Maven Webapp
  
  http://www.example.com

  
    UTF-8
    10.0.1
    10.0.1
  

  
    
      junit
      junit
      4.12
      test
    

    
      javax.servlet
      servlet-api
      2.5
      provided
    

    
      dom4j
      dom4j
      1.6.1
    

    
      org.apache.commons
      commons-lang3
      3.5
    

    
      org.projectlombok
      lombok
      1.18.18
      provided
    

    
      com.fasterxml.jackson.core
      jackson-databind
      2.8.1
    

    
      cglib
      cglib
      3.3.0
    



    
      mysql
      mysql-connector-java
      5.1.47
    

    
      dev.tuxjsql
      hikaricp-cp
      2.1
    

    
      com.alibaba
      druid
      1.2.2
    

    
      org.slf4j
      slf4j-nop
      1.7.2
    

  



  
    SpringMVC
    
      
        
          maven-clean-plugin
          3.1.0
        
        
        
          maven-resources-plugin
          3.0.2
        
        
          maven-compiler-plugin
          3.8.0
        
        
          maven-surefire-plugin
          2.22.1
        
        
          maven-war-plugin
          3.2.2
        
        
          maven-install-plugin
          2.5.2
        
        
          maven-deploy-plugin
          2.8.2
        

      
    
    
      
        org.apache.maven.plugins
        maven-compiler-plugin
        3.7.0
        
          10.0.1
          10.0.1
          
            -parameters
          
          UTF-8
        
      
    

    
      
        src/main/java
        
          **/*.xml
        
      
      
        src/main/resources
        
          **/*.*
        
      
    

  

 

1,user类和IUserDao接口:懂的都懂,不解释

package com.bruce.POJO;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private Integer uid;
    private String userName;
    private String password;
}
package com.bruce.dao;

import com.bruce.POJO.User;

import java.util.List;

/**
 * service层掉用该接口的方法
 * 该方法应该要与userMapper.xml配置文件中的sql语句一一对应,并且可以传入参数,返回查询的内容
 * 因此需要有一个类对两者进行绑定
 */
public interface IUserDao {
    void saveUser(User user);

    List findAllUser();

    User getUserById(Integer id);
}

2,Test类:测试测试是否可以操作数据库

package com.bruce;

import com.bruce.POJO.User;
import com.bruce.dao.IUserDao;
import com.mybatis.Session;
import com.mybatis.SessionFactory;

public class Test {
    public static void main(String[] args) {
//根据配置文件mybatis-config.xml创建session工厂
        SessionFactory sessionFactory=new SessionFactory("mybatis-config.xml");
        Session session= sessionFactory.openSession();//创建session
        System.out.println(session);//查看创建成功了吗
        IUserDao userDao=session.getMapper(IUserDao.class);
//使用session初始化配置信息,建立dao接口与dao.xml配置文件的关系
        userDao.saveUser(new User(11,"陈建江","123"));//掉用相关的方法
        
    }
}

3,DaoWrapper类:存储xml文件中的:SQL类型,方法名,参数类型,返回值类型的一个POJO类(用于记录他们之间的联系)

package com.mybatis;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.ToString;

import java.io.Serializable;

/**
 * 用来存储dao执行SQL语句的基本信息
 */
@AllArgsConstructor
@Data
@ToString
public class DaoWrapper implements Serializable {
    private String type;    //SQL语句的类型 insert  delete   select
    private String resultMap;//返回值类型
    private String paramType;//传入的参数类型
    private String sql;//sql语句
}

 

4,DataSourceFactory类:生成数据源的工厂,可以根据参数名来选择实例化的数据源(hikari,Druid)

package com.mybatis;

import com.alibaba.druid.pool.DruidDataSource;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

import javax.sql.DataSource;
import java.io.IOException;
import java.util.Properties;

/**
 * 数据源工厂,使用工厂设计模式,创建数据源对象
 * 把数据源对象复杂的创建过程交给工厂,如果需要数据源对象时,直接向工厂拿就行了
 */
public class DataSourceFactory {

    public static DataSource createDataSource(String type){
        DataSource dataSource=null;
        Properties properties=new Properties();//用来加载properties配置文件
        if ("hikari".equals(type)){//如果闯入的参数是hikari
            try {//加载hikari.properties配置文件
                properties.load(DataSourceFactory.class.getClassLoader().getResourceAsStream("hikari.properties"));
            } catch (IOException e) {
                e.printStackTrace();
            }
            HikariConfig hikariConfig=new HikariConfig(properties);//创建数据源对象,需要在pom.xml中导入数据源的包
            dataSource = new HikariDataSource(hikariConfig);
        }else if ("druid".equals(type)){
            try {
                properties.load(DataSourceFactory.class.getClassLoader().getResourceAsStream("druid.properties"));
            } catch (IOException e) {
                e.printStackTrace();
            }
            DruidDataSource druidDataSource=new DruidDataSource();
            druidDataSource.configFromPropety(properties);
            dataSource =druidDataSource;
        }
     return dataSource;
    }
}

 

5,Session类:会话类,我们想要与数据库交互,需要建立会话,会话携带数据源才可以连接数据库。该类里面有事物的相关方法(开始会话,提交会话,回滚会话)

package com.mybatis;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Session {
    private Connection connection;//每个会话都持有一个链接
    private Map> env;
    //开始会话
    public void begin(){
        try {
            connection.setAutoCommit(false);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    //提交会话
    public void commit(){
        try {
            connection.commit();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    //回滚会话
    public void rollback(){
        try {
            connection.rollback();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 参数是dao接口,用来根据dao接口名 来找到dao.xml文件,读取里面信息,最后把两者绑定起来
     * @param clazz
     * @param 
     * @return
     */
    public  T getMapper(Class clazz){
        //传入dao层的接口:例如userDao(处理器中需要闯入的参数是  连接,环境集合(所有与SQL语句相关的东西))
        T t = (T)Proxy.newProxyInstance(Session.class.getClassLoader(), new Class[]{clazz}, new SqlInvocationHandler(connection,env.get(clazz.getName())));
        return t;
    }
}

 

6,SessionFactory类:创建session实例对象的工厂(为什么要使用工厂来创建,因为可以把session复杂的创建过程影藏,每当我们使用session对象时,只需要调用它里面的创建方法就可以创建了)

package com.mybatis;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.*;

/**
 * 每次要打开一个会话,我都去取一个数据源放到session中
 * session对象创建的时候,需要实现将dao接口和dao.xml文件中的基本信息绑定存储在Map中
 * 因此session对象初始化的过程比较复杂,使用工厂来创建session,实现session对象的创建和使用的分离
 */
public class SessionFactory {
    private static DataSource dataSource;
    private static Map> env=new HashMap<>(8);//存储配置文件信息的环境
    //               nameSpace   方法名   参数类型,返回值类型,SQL语句的集合

    public SessionFactory(String config){//传入的参数是一个配置文件名,例如:mybatis-config.xml
        loadxml(config);//初始化,参数是mybatis-config.xml配置文件的名字
    }
    //打开一个会话,绑定一个数据源
    public  Session openSession(){
        Connection connection=null;
        try {
            connection=dataSource.getConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return new Session(connection,env);//会话绑定一个数据源
    }

    //解析mybatis-config.xml文件,初始化资源
    public void loadxml(String config){
        SAXReader reader=new SAXReader();//解析xml文件的对象
        //得到mybatis-config.xml文件的二进制流
        InputStream source= SessionFactory.class.getClassLoader().getResourceAsStream(config);
        Document configDom = null;
        if (source!=null){
            System.out.println("xml流文件不为空");
            try {
                configDom = reader.read(source);//xml解析对象,解析.xml文件的二进制流
                Element rootElement = configDom.getRootElement();//得到mybatis-config.xml文件根节点,
                String dataSourceName = rootElement.element("dataSource").getTextTrim();//得到数据源的名字
                dataSource = DataSourceFactory.createDataSource(dataSourceName);//使用工厂创建数据源
                List mappers = rootElement.elements("mapper");//获得mapper标签集合
                List mapperPaths=new ArrayList<>();
                for (Object element: mappers){//遍历标签得到里面的dao.xml文件的映射路径
                    Element mapper= (Element)element;
                    mapperPaths.add(mapper.getTextTrim());//存储路径
                }

                for (String mapperPath:mapperPaths){//遍历所有的mapperDao.xml对它们的数据进行封装。
                    Map wrapper=new HashMap<>(8);
                    Document document = reader.read(Session.class.getClassLoader().getResourceAsStream(mapperPath));
                    Element root = document.getRootElement();//获取根节点mapper
                    String namespace = root.attribute("namespace").getValue();//得到命名空间
                    Iterator iterator = root.elementIterator();
                    while (iterator.hasNext()){//通过迭代器获取mapper标签下的其他标签,把它们的相关数据封装到我DaoWrapper中
                        Element el = (Element)iterator.next();//获取每个标签
                        String type = el.getName();//获取到标签的名字,也就是SQL语句的类型
                        String id = el.attribute("id").getValue();//获取id值,也就是方法名
                        String resultMap="";
                        String paramType ="";
                        //如果参数
                        if (el.attribute("resultType")!=null){
                            resultMap= el.attribute("resultType").getValue();//获取返回值类型
                        }
                        if (el.attribute("parameterType")!=null){
                            paramType=el.attribute("parameterType").getValue();//获取返回值类型
                        }
                        String sql = el.getTextTrim();//得到SQL语句的字符串
                        wrapper.put(id, new DaoWrapper(type,resultMap,paramType,sql));
                    }
                    env.put(namespace,wrapper);//最终封装好的环境数据

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

    }

}

 

7,SQLInnovationHandler类:为了使用jdk动态代理创建的类,负责对具体SQL语句执行

package com.mybatis;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class SqlInvocationHandler implements InvocationHandler {
    private Connection connection;
    private Map env;

    public SqlInvocationHandler(Connection connection,Map env){
        this.connection=connection;
        this.env=env;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String name = method.getName();//得到dao层要调用的方法名
        DaoWrapper daoWrapper = env.get(name);//根据方法名得到它的环境信息
        PreparedStatement statement = connection.prepareStatement(daoWrapper.getSql());//根据sql得到预编译语句

        if ("insert".equals(daoWrapper.getType())){
            //假设传入一个参数
            String paramType = daoWrapper.getParamType();//得到参数类型
            Class clazz = args[0].getClass();
            Field[] fields = clazz.getDeclaredFields();
            for(int i=0;i clazz = Class.forName(daoWrapper.getResultMap());//通过反射机制,根据返回值类型创建实例
                Object object = clazz.newInstance();
                Field[] fields = clazz.getDeclaredFields();//通过反射机制,该类型的所有属性
                for (int i=0;i

 

8,userMapper.xml:编写具体的SQL语句





    

    
        insert into t_user(uid,userName,password) values (?,?,?);
    

 

10,druid.properties:druid数据源连接数据库的信息

druid.driverClassName=com.mysql.jdbc.Driver
druid.url=jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf-8
druid.username=root
druid.password=111

 

11,hikari.properties:hikari连接数据库的信息

jdbcUrl=jdbc:mysql://localhost:3306/test
username=test
password=123456
maximumPoolSize=30
minimumIdle=5
connectionTestQuery=SELECT 1
autoCommit=true
dataSource.cachePrepStmts=true
dataSource.prepStmtCacheSize=250
dataSource.prepStmtCacheSqlLimit=2048
dataSource.useServerPrepStmts=true
dataSource.useLocalSessionState=true
dataSource.useLocalTransactionState=true
dataSource.rewriteBatchedStatements=true
dataSource.cacheResultSetMetadata=true
dataSource.cacheServerConfiguration=true
dataSource.elideSetAutoCommits=true
dataSource.maintainTimeStats=false

 

12,mybatis-config.xml:存储数据源的名字,和userDao.xml文件的路径(用于对dao.xml文件的管理)

 

 


    druid
    mapper/userMapper.xml
    mapper/studentMapper.xml

成功页面:

手写mybatis_第2张图片

 

五,资源

源码:里面有SpringMVC和mybatis手写框架

链接:https://pan.baidu.com/s/1iGEUs2l0v6jSDtoPEI-Kwg 
提取码:uxet 
复制这段内容后打开百度网盘手机App,操作更方便哦

视频:https://www.bilibili.com/video/BV1NT4y1P73V?from=search&seid=9842311074072221898

 

 

扩展:简易版smm

我已经实现SpringMVC框架,和mybatis框架的基本功能的编写,并且实例化对象也是放在一个类似于spring容器中的,使用注解的方式实现对象的注入

目前service层,controller层对象注入完成,还需要实现dao接口的注入。由于注入是在

目标:

  • 完成dao接口的  “实例化”
  • 实现dao对象的注入。
  • service层调用到层的方法。

问题:Method threw 'java.lang.NullPointerException' exception. Cannot evaluate com.sun.proxy.$Proxy8.toString()

  • 代理接口对象,不能放在ioc容器中,在测试方法中使用同样代码得到的对象却可以用
  • 分析:正确参数是:interface com.bruce.dao.IUserDao
  • 调用时传入的参数是:interface com.bruce.dao.IUserDao

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(mybatis)