天天看點

手寫Mybatis架構

一、簡介

Mybatis是一款優秀的持久層架構,在網際網路公司的項目開發中使用非常廣泛。通過對MyBatis源碼的學習,可以更好的了解Mybatis的使用,同時也可以借鑒其中優秀的程式設計方式和設計模式。學習是一個抽象歸納然後再運用的過程,通過對Mybatis源碼核心部分的抽象,手寫一個Mybatis架構,來更好的學習Mybatis。

二、處理流程

Mybatis核心流程分為三步:

1.讀取XML配置檔案和注解中的配置資訊,建立配置對象,并完成各個子產品的初始化工作;

2.封裝iBatis的程式設計模式,使用mapper接口開發的初始化工作;

3.通過SqlSession完成SQL的解析,參數的映射、SQL的執行、結果的反射解析過程;

三、手寫Mybatis具體實作

1. 實體相關類

Dept.java:

package com.taoxi.mybatis.mysimple.entity;

public class Dept {
    private Integer deptNo;
    private String dName;
    private String loc;

    public Integer getDeptNo() {
        return deptNo;
    }

    public void setDeptNo(Integer deptNo) {
        this.deptNo = deptNo;
    }

    public String getDname() {
        return dName;
    }

    public void setDname(String dname) {
        this.dName = dname;
    }

    public String getLoc() {
        return loc;
    }

    public void setLoc(String loc) {
        this.loc = loc;
    }

}      

DeptMapper.java:

package com.taoxi.mybatis.mysimple.mapper;

import com.taoxi.mybatis.mysimple.entity.Dept;

public interface DeptMapper {

    Dept deptFindById(Integer deptId);

}      

DeptMapper.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.taoxi.mybatis.mysimple.mapper.DeptMapper">
 
 <select id="deptFindById" resultType="com.taoxi.mybatis.mysimple.entity.Dept">
        select * from dept where deptno= ?
 </select>
   
</mapper>      

2.配置相關類

Configuration.java:

package com.taoxi.mybatis.mysimple.config;

import java.util.HashMap;
import java.util.Map;

public class Configuration {

    private String jdbcDriver;
    private String jdbcUrl;
    private String jdbcUsername;
    private String jdbcPassword;

    private Map<String, MappedStatement> mappedStatments = new HashMap<String, MappedStatement>();

    public String getJdbcDriver() {
        return jdbcDriver;
    }

    public void setJdbcDriver(String jdbcDriver) {
        this.jdbcDriver = jdbcDriver;
    }

    public String getJdbcUrl() {
        return jdbcUrl;
    }

    public void setJdbcUrl(String jdbcUrl) {
        this.jdbcUrl = jdbcUrl;
    }

    public String getJdbcUsername() {
        return jdbcUsername;
    }

    public void setJdbcUsername(String jdbcUsername) {
        this.jdbcUsername = jdbcUsername;
    }

    public String getJdbcPassword() {
        return jdbcPassword;
    }

    public void setJdbcPassword(String jdbcPassword) {
        this.jdbcPassword = jdbcPassword;
    }

    public Map<String, MappedStatement> getMappedStatments() {
        return mappedStatments;
    }

    public void setMappedStatments(Map<String, MappedStatement> mappedStatments) {
        this.mappedStatments = mappedStatments;
    }
}      

MappedStatemant.java:

package com.taoxi.mybatis.mysimple.config;

public class MappedStatement {

    private String namespace;
    private String sourceId;
    private String resultType;
    private String sql;

    public String getNamespace() {
        return namespace;
    }

    public void setNamespace(String namespace) {
        this.namespace = namespace;
    }

    public String getSourceId() {
        return sourceId;
    }

    public void setSourceId(String sourceId) {
        this.sourceId = sourceId;
    }

    public String getResultType() {
        return resultType;
    }

    public void setResultType(String resultType) {
        this.resultType = resultType;
    }

    public String getSql() {
        return sql;
    }

    public void setSql(String sql) {
        this.sql = sql;
    }
}      

3.SqlSessionFactory工廠類

SqlSessionFactory.java:

package com.taoxi.mybatis.mysimple.session;

import com.taoxi.mybatis.mysimple.config.Configuration;
import com.taoxi.mybatis.mysimple.config.MappedStatement;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.List;
import java.util.Properties;

public class SqlSessionFactory {

    private final Configuration configuration = new Configuration();

    public SqlSessionFactory() {
        loadDbInfo();
        loadMappersInfo();
    }

    public SqlSession openSession() {
        return new DefaultSqlSession(configuration);
    }

    //記錄mapper xml檔案存放的位置
    public static final String MAPPER_CONFIG_LOCATION = "mappers";
    //記錄資料庫連接配接資訊檔案存放位置
    public static final String DB_CONFIG_FILE = "db.properties";

    //加載資料庫配置資訊
    private void loadDbInfo() {
        //加載資料庫資訊配置檔案
        InputStream dbIn = SqlSessionFactory.class.getClassLoader().getResourceAsStream(DB_CONFIG_FILE);
        Properties properties = new Properties();
        try {
            properties.load(dbIn);
        } catch (IOException e) {
            throw new RuntimeException("load db info error:", e);
        }
        //将資料庫配置資訊寫入configuration對象
        configuration.setJdbcDriver(properties.get("jdbc.driver").toString());
        configuration.setJdbcPassword(properties.get("jdbc.password").toString());
        configuration.setJdbcUrl(properties.get("jdbc.url").toString());
        configuration.setJdbcUsername(properties.get("jdbc.username").toString());
    }


    //加載指定檔案夾下的所有mapper.xml
    private void loadMappersInfo() {
        URL resources = null;
        resources = SqlSessionFactory.class.getClassLoader().getResource(MAPPER_CONFIG_LOCATION);
        File mappers = new File(resources.getFile());
        if (mappers.isDirectory()){
            File[] listFiles = mappers.listFiles();
            //周遊檔案夾下所有的mapper.xml,解析資訊後,注冊到configuration對象中
            for (File file :
                    listFiles) {
                loadMapperInfo(file);
            }
        }

    }

    //加載指定的mapper.xml檔案
    private void loadMapperInfo(File file) {
        //建立saxReader對象
        SAXReader reader = new SAXReader();
        //通過read方法讀取一個檔案,轉換成Document對象
        Document document = null;
        try {
            document = reader.read(file);
        } catch (DocumentException e) {
            throw new RuntimeException("load mapper info error:", e);
        }
        //擷取根節點元素對象<mapper>
        Element root = document.getRootElement();
        //擷取命名空間
        String namespace = root.attribute("namespace").getData().toString();
        //擷取select子節點清單
        List<Element> selects = root.elements("select");
        //周遊select節點,将資訊記錄到MappedStatement對象,并登記到configuration對象中
        for (Element element :
                selects) {
            MappedStatement mappedStatement = new MappedStatement();//執行個體化mappedStatement
            String id = element.attribute("id").getData().toString();//讀取id屬性
            String resultType = element.attribute("resultType").getData().toString();//讀取resultType屬性
            String sql = element.getData().toString();//讀取SQL語句資訊
            String sourceId = namespace + "." + id;
            //給mappedStatement屬性指派
            mappedStatement.setSourceId(sourceId);
            mappedStatement.setResultType(resultType);
            mappedStatement.setSql(sql);
            mappedStatement.setNamespace(namespace);
            //注冊到configuration對象中
            configuration.getMappedStatments().put(sourceId, mappedStatement);
        }

    }

}      

4.SqlSession相關類

SqlSession.java接口:

package com.taoxi.mybatis.mysimple.session;

import java.util.List;

public interface SqlSession {

    public <T> T selectOne(String statement, Object parameter);

    public <E> List<E> selectList(String statement, Object parameter);

    public <T>  T getMapper(Class<T> type);

}      

DefaultSqlSession.java預設實作類:

package com.taoxi.mybatis.mysimple.session;

import com.taoxi.mybatis.mysimple.binding.MapperProxy;
import com.taoxi.mybatis.mysimple.config.Configuration;
import com.taoxi.mybatis.mysimple.config.MappedStatement;
import com.taoxi.mybatis.mysimple.executor.DefaultExecutor;
import com.taoxi.mybatis.mysimple.executor.Executor;

import java.lang.reflect.Proxy;
import java.util.List;

public class DefaultSqlSession implements SqlSession{

    private final Configuration configuration;

    private Executor executor;

    public DefaultSqlSession(Configuration configuration) {
        super();
        this.configuration = configuration;
        executor = new DefaultExecutor(configuration);
    }

    public <T> T selectOne(String statement, Object parameter) {
        //執行Sql語句擷取查詢結果
        List<Object> selectList = this.selectList(statement, parameter);
        //查詢結果為空傳回null
        if (selectList == null || selectList.size()==0) {
            return null;
        }
        if (selectList.size()==1) {
            return (T)selectList.get(0);
        }else{
            throw new RuntimeException("Too Many Results!");
        }

    }

    public <E> List<E> selectList(String statement, Object parameter) {
        try {
            MappedStatement ms = configuration.getMappedStatments().get(statement);
            return executor.query(ms, parameter);
        } catch (Exception e) {
            throw new RuntimeException("select list error:", e);
        }

    }

    public <T> T getMapper(Class<T> type) {
        MapperProxy mapperProxy = new MapperProxy(this);

        return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, mapperProxy);
    }
}      

MapperProxy.java代理處理類:

package com.taoxi.mybatis.mysimple.binding;

import com.taoxi.mybatis.mysimple.session.SqlSession;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Collection;

public class MapperProxy implements InvocationHandler {

    private SqlSession session;

    public MapperProxy(SqlSession session) {
        super();
        this.session = session;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (Collection.class.isAssignableFrom(method.getReturnType())){
            return session.selectList(method.getDeclaringClass().getName()+"."+method.getName(),
                    args==null?null:args[0]);
        }else {
            return session.selectOne(method.getDeclaringClass().getName()+"."+method.getName(),
                    args==null?null:args[0]);
        }
    }
}      

5.Executor執行器相關類

Executor.java執行器接口:

package com.taoxi.mybatis.mysimple.executor;

import com.taoxi.mybatis.mysimple.config.MappedStatement;

import java.util.List;

public interface Executor {
    public <E> List<E> query(MappedStatement ms, Object parameter) throws Exception;
}      

DefaultExecutor.java執行器預設實作類:

package com.taoxi.mybatis.mysimple.executor;

import com.taoxi.mybatis.mysimple.config.Configuration;
import com.taoxi.mybatis.mysimple.config.MappedStatement;
import com.taoxi.mybatis.mysimple.util.ReflectionUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

public class DefaultExecutor implements Executor{

    private static Logger logger = LoggerFactory.getLogger(DefaultExecutor.class);

    private final Configuration configuration;

    public DefaultExecutor(Configuration configuration) {
        super();
        this.configuration = configuration;
    }

    public <E> List<E> query(MappedStatement ms, Object parameter) throws SQLException{
        ArrayList<E> ret = new ArrayList<E>();//定義傳回結果集
        try {
            Class.forName(configuration.getJdbcDriver());//加載驅動程式
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;

        try {
            //擷取連接配接,從Configuration中擷取資料庫資訊
            connection = DriverManager.getConnection(configuration.getJdbcUrl(), configuration.getJdbcUsername(),
                    configuration.getJdbcPassword());
            //建立prepareStatement,從MappedStatement中擷取sql語句
            preparedStatement = connection.prepareStatement(ms.getSql());
            //處理sql語句中的占位符
            parameterize(preparedStatement, parameter);
            //執行查詢操作擷取resultSet
            resultSet = preparedStatement.executeQuery();
            //将結果集通過反射技術,填充到list中
            handlerResultSet(resultSet, ret, ms.getResultType());

        } catch (SQLException e) {
            throw e;
        }finally {
            try {
                resultSet.close();
                preparedStatement.close();
                connection.close();
            } catch (SQLException e) {
                throw e;
            }
        }

        return ret;
    }


    //對prepareStatement中的占位符進行處理
    private void parameterize(PreparedStatement preparedStatement, Object parameter) throws SQLException {
        if (parameter instanceof Integer) {
            preparedStatement.setInt(1,((Integer) parameter).intValue());
        } else if (parameter instanceof Long) {
            preparedStatement.setLong(1, ((Long) parameter).longValue());
        } else if (parameter instanceof String) {
            preparedStatement.setString(1, (String)parameter);
        }
    }

    //讀取resultset中的資料,并轉換成目标對象
    private <E> void handlerResultSet(ResultSet resultSet, List<E> ret, String className) {
        Class<E> clazz = null;
        try {
            //通過反射擷取類對象
            clazz = (Class<E>)Class.forName(className);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        try {
            while (resultSet.next()) {
                Object entity = clazz.newInstance();
                //使用反射工具将resultSet中的資料填充到entity中
                ReflectionUtil.setPropToBeanFromResult(entity, resultSet);
                //對象加入傳回集合中
                ret.add((E)entity);

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


}      

ReflectionUti.java反射工具類:

package com.taoxi.mybatis.mysimple.util;


import java.lang.reflect.Field;
import java.sql.ResultSet;

public class ReflectionUtil {

    public static void setPropToBean(Object bean, String propName, Object value) {
        Field field;
        try {
            field = field = bean.getClass().getDeclaredField(propName);
            field.setAccessible(true);
            field.set(bean, value);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        }

    }

    public static void setPropToBeanFromResult(Object entity, ResultSet resultSet) throws Exception {
        Field[] declaredFields = entity.getClass().getDeclaredFields();
        for (int i = 0;i<declaredFields.length;i++) {
            if (declaredFields[i].getType().getSimpleName().equals("String")){
                setPropToBean(entity, declaredFields[i].getName(), resultSet.getString(declaredFields[i].getName()));
            } else if (declaredFields[i].getType().getSimpleName().equals("Integer")){
                setPropToBean(entity, declaredFields[i].getName(), resultSet.getInt(declaredFields[i].getName()));
            } else if (declaredFields[i].getType().getSimpleName().equals("Long")){
                setPropToBean(entity, declaredFields[i].getName(), resultSet.getLong(declaredFields[i].getName()));
            }
        }
    }


    public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        Class<?> clazz = Class.forName("com.taoxi.mybatis.mysimple.entity.Dept");
        Object dept = clazz.newInstance();
        ReflectionUtil.setPropToBean(dept, "dName", "zhongguo");
        System.out.println(dept);
    }

}      

源碼實作工程位址:https://github.com/taoxibj/study/tree/master/mysimple

DeptMapper      
上一篇: 達克效應