手写Mybatis-XML简单版

过去的,未来的
2020-01-14 / 0 评论 / 0 点赞 / 679 阅读 / 0 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2020-01-14,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

MyBatis核心流程
1、读取xml配置文件和注解中的配置信息。
2、构建sqlsessionFactory
3、打开sqlSession
4、获取mapper接口对象,通过SqlSession完成SQL解析,参数的映射,SQL的执行,结果的反射解析过程
这个案例写的不是很完善,我们重在理解原理。现在开始上代码

一、加入必要的jar

	<!-- 数据库驱动-->
	<dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.22</version>
        </dependency>
        <!-- 依赖dom4j来解析mapper.xml -->
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>

二、在resources配置相关配置文件(mybatis-config.xml)

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://xxx:3306/base?useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=true&amp;serverTimezone=UTC"/>
                <property name="username" value="xxx"/>
                <property name="password" value="xxx"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="com/spring/mapper/UserMapper.xml"/>
    </mappers>
</configuration>

说明:这里的配置文件,主要是数据库的链接和Mapper的存放位置

三、我们先写个案例,然后跟着这个案例,一步一步完成。

这个案例是根据id,查询用户信息。
1、先写一个接口

public interface UserMapper {

    User getUserById(Integer id);
}

2、再来一个mapper

<?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.spring.mapper.UserMapper">
    <select id="getUserById"  parameterType="java.lang.Integer" resultType="com.spring.mode.User">
    select * from user where id = #{id}
  </select>
</mapper>

四、我们根据上面的案例,看看,我们怎么实现

主要思想:首先根据配置文件获得数据库链接,这样我们就可以拿到访问数据库的方式,然后加载所有的Mapper中的sql到一个Map中,最后SqlSession完成SQL解析,参数的映射,SQL的执行,结果的反射解析过程。

1、主流程
	//第一步,加载配置文件
        InputStream inputStream=Main.class.getClassLoader().getResourceAsStream("mybatis-config.xml");
        //第二步构建sqlsessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        //第三步 打开sqlSession
        SqlSession session = sqlSessionFactory.openSession();
        //第四步 获取mapper接口对象
        UserMapper userMapper= session.getMapper(UserMapper.class);
        //第五步 调用mapper接口对象操作数据库
        User user = userMapper.getUserById(1);
        System.out.println(user);
        //第六步 业务处理
2、加载配置文件,创建SqlSessionFactory
public class SqlSessionFactoryBuilder {
    public SqlSessionFactory build(InputStream inputStream){
	/**
注意这里  这个地方去读取配置文件的信息,封装到Configuration里面
*/
        XmlUtil xmlUtil=new XmlUtil();
        Configuration configuration=xmlUtil.loadConfig(inputStream);
        return new SqlSessionFactory(configuration);
    }
}
public class SqlSessionFactory {

    private Configuration configuration;

    public SqlSessionFactory(Configuration configuration){
        this.configuration=configuration;
    }

    public SqlSession openSession() {
        Executor executor=new Executor(configuration);
        return new SqlSession(configuration,executor);
    }
}
  • 我们来看下Configuration里面有什么
public class Configuration {
    //主要放置数据库链接信息
    private Environment environment;
    //所有sql
    private Map<String,MapperStatement> mapperStatementMap;
}

/**
 * 数据库的信息
 */
public class Environment {
    private String driver;
    private String url;
    private String username;
    private String password;

}

/**
 * Mapper里面的主要信息
 */
public class MapperStatement {


    private String sourceId;
    private String namespace;
    private String id;
    private String sql;
    private String parameterType;
    private String resultType;

}
  • 解析配置文件,写的比较简陋,仅供参考
/**
 * 主要解析配置文件
 */
public class XmlUtil {

     Map<String,MapperStatement> mapperStatementMap=new ConcurrentHashMap<String, MapperStatement>();
     Environment environment=new Environment();


    public Configuration loadConfig(InputStream inputStream){
        Configuration configuration=new Configuration();
        //创建SAXReader对象
        SAXReader saxReader = new SAXReader();
        //通过read方法读取一个文件,转换成Document对象
        Document document = null;
        try {
            document = saxReader.read(inputStream);
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        Element rootElement = document.getRootElement();
         Element environments = rootElement.element("environments").element("environment").element("dataSource");
        List<Element> property = environments.elements("property");
        for(Element el:property){
            String name = el.attribute("name").getData().toString();
            String value = el.attribute("value").getData().toString();
            if("driver".equals(name)){
                environment.setDriver(value);
            }else if("url".equals(name)){
                environment.setUrl(value);
            }else if("username".equals(name)){
                environment.setUsername(value);
            }else if("password".equals(name)){
                environment.setPassword(value);
            }
        }
        Element mappers = rootElement.element("mappers");
        List<Element> mapperList =mappers.elements("mapper");
        for(Element el:mapperList){
            String name = el.attribute("resource").getData().toString();
            URL   resource = XmlUtil.class.getClassLoader().getResource(name);
            File file = new File(resource.getFile());
            loadMapper(file);
        }
        configuration.setEnvironment(environment);
        configuration.setMapperStatementMap(mapperStatementMap);
        return configuration;
    }
/*    private void loadMappersInfo() {
        URL resource = null;
        resource = XmlUtil.class.getClassLoader().getResource(MAPPER_CONFIG_LOCATION);
        //获取指定文件夹信息
        File file = new File(resource.getFile());
        if (file.isDirectory()) {
            File[] mappers = file.listFiles();
            //遍历文件夹下所有的mapper.xml文件,解析后,注册到configuration中
            for (File mapper : mappers) {
                loadMapper(mapper);
            }
        }
    }*/

    /**
     * //TODO 对mapper.xml文件解析
     *
     * @param mapper
     * @return void
     **/
    private void loadMapper(File mapper) {
        //创建SAXReader对象
        SAXReader saxReader = new SAXReader();
        //通过read方法读取一个文件,转换成Document对象
        Document document = null;
        try {
            document = saxReader.read(mapper);
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        //获取根节点元素对象<mapper>
        Element rootElement = document.getRootElement();
        //获取命名空间
        String namespace = rootElement.attribute("namespace").getData().toString();
        //获取子节点<select>标签
        List<Element> selects = rootElement.elements("select");
        //遍历select节点,将信息记录到MappedStatement对象,并登记到Configuration对象中
        for (Element element : selects) {
            MapperStatement statement = new MapperStatement();
            String id = element.attribute("id").getData().toString();
            String resultType = element.attribute("resultType").getData().toString();
            String parameterType = element.attribute("parameterType").getData().toString();

            //读取sql语句信息
            String sql = element.getData().toString();

            String sourceId = namespace + "." + id;
            //给MappedStatement对象赋值
            statement.setSourceId(sourceId);
            statement.setNamespace(namespace);
            statement.setResultType(resultType);
            statement.setSql(sql);
            statement.setParameterType(parameterType);
            statement.setId(id);
            mapperStatementMap.put(sourceId,statement);
        }

    }

}
3、sqlSession,这里主要是写了数据库连接池,还有sql执行器
  • 通过数据库链接获得数据源
public interface MyDataSourceInterface extends DataSource {
    @Override
    default Connection getConnection() throws SQLException {
        return null;
    }

    @Override
    default Connection getConnection(String username, String password) throws SQLException {
        return null;
    }

    @Override
    default <T> T unwrap(Class<T> iface) throws SQLException {
        return null;
    }

    @Override
    default boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }

    @Override
    default PrintWriter getLogWriter() throws SQLException {
        return null;
    }

    @Override
    default void setLogWriter(PrintWriter out) throws SQLException {

    }

    @Override
    default void setLoginTimeout(int seconds) throws SQLException {

    }

    @Override
    default int getLoginTimeout() throws SQLException {
        return 0;
    }

    @Override
    default Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }
}

自定义数据库链接

public class MyDataSource implements MyDataSourceInterface {
    private Environment environment;
    private List<Connection> pool;
    private Connection conn=null;
    private static MyDataSource instance=null;
    private static final int POOL_SIZE=2;
    private  MyDataSource(Environment environment){
        this.environment=environment;
        pool=new ArrayList<Connection>(POOL_SIZE);
        this.createConnection();
    }
    public static MyDataSource getInstance(Environment environment){
        if(instance==null){
            instance=new MyDataSource(environment);
        }
        return instance;
    }
    private void createConnection(){
        for(int i=0;i<POOL_SIZE;i++){
            try {
                Class.forName(environment.getDriver());
                conn= DriverManager.getConnection(environment.getUrl(),environment.getUsername(),environment.getPassword());
                pool.add(conn);
            }catch (Exception e){

            }
        }
    }
    @Override
    public synchronized Connection getConnection(){
        if(pool.size()>0){
            Connection conn=pool.get(0);
            pool.remove(conn);
            return conn;
        }else {
            return null;
        }
    }
    public synchronized void release(Connection conn){
        pool.add(conn);
    }

    public synchronized  void closePool(){
        for(int i=0;i<POOL_SIZE;i++){
            try {
                conn=(Connection)pool.get(i);
                conn.close();
                pool.remove(i);
            }catch (Exception e){

            }
        }
    }
}

通过SqlSession查询数据

public class SqlSession {

    private Configuration configuration;
    private Executor executor;

    public SqlSession(Configuration configuration, Executor executor) {
        this.configuration = configuration;
        this.executor = executor;
    }


    public <T> T getMapper(Class<T> clazz){
        MapperProxy mapperProxy=new MapperProxy(this);
        return (T)Proxy.newProxyInstance(clazz.getClassLoader(),new Class<?>[]{clazz},mapperProxy);
    }

    public <T> T selectOne(String statementKey,Object args) {
        MapperStatement mapperStatement = configuration.getMapperStatementMap().get(statementKey);
        List<T> resultList= executor.query(mapperStatement,args);
        if(resultList!=null&&resultList.size()>1){
          throw  new RuntimeException("不止一个");
        }else{
            if(resultList!=null&&resultList.size()>0){
                return resultList.get(0);
            }else{
                return null;
            }

        }
    }

    public <T> T selectList(Object[] args) {
        return null;
    }

    public <T> T selectMap(Object[] args) {
        return null;
    }
}

可以看出其实真正执行查询的是executor,我们来研究下executor,‘

public class Executor {
    private Configuration configuration;

    private DataSource dataSource;

    public Executor(Configuration configuration) {
        this.configuration = configuration;
        dataSource=MyDataSource.getInstance(configuration.getEnvironment());
    }

    public <T> List<T> query(MapperStatement mapperStatement, Object parameter) {
        Connection connection=null;
        PreparedStatement preparedStatement=null;
        ResultSet resultSet=null;
        List<T> resultList=new ArrayList<>();
        try {
            connection=dataSource.getConnection();
            String sql=MyBatisUtil.parse(mapperStatement.getSql()).trim();
            System.out.println("sql:"+sql);
           preparedStatement=connection.prepareStatement(sql);
            if(parameter instanceof Integer){
                preparedStatement.setInt(1,(Integer)parameter);
            }else if(parameter instanceof Double){
                preparedStatement.setDouble(1,(Double)parameter);
            }else if(parameter instanceof String ){
                preparedStatement.setString(1,(String)parameter);
            }
            resultSet=preparedStatement.executeQuery();
            handlerResultSet(resultSet,resultList,mapperStatement.getResultType());
        }catch (Exception e){

        }finally {
            if(resultSet !=null){//关闭记录集
                try{
                    resultSet.close();
                } catch (SQLException e){
                    e.printStackTrace();
                }
            }

        }
        return resultList;
    }
    private <T> void handlerResultSet(ResultSet resultSet,List<T> resultList,String resultType){
        try {
            Class<T>clazz=(Class<T>)Class.forName(resultType);
            ReflectionUtil.setProToBeanFromResult(resultSet,clazz,resultList);
        }catch (Exception e){

        }
    }
}

可以看出底层是JDBC在工作,这里采用预编译sql,所以要处理sql

/**
 * 将sql处理成预编译sql
 */
public class MyBatisUtil {

    private static final String openToken="#{";
    private static final String closeToken="}";
    public static String parse(String text){
        if(text == null || text.isEmpty()){
            return null;
        }
        int start=text.indexOf(openToken);
        if(start==-1){
            return text;
        }
        char [] src=text.toCharArray();
        int offset =0;
        final StringBuilder builder=new StringBuilder();
        StringBuilder expression=null;
        while (start>-1){
            if(start>0&&src[start-1]=='\\'){
                builder.append(src,offset,start-offset-1).append(openToken);
                offset=start+openToken.length();

            }else {
                if(expression ==null){
                    expression=new StringBuilder();
                }else {
                    expression.setLength(0);
                }
                builder.append(src,offset,start-offset);
                offset=start+openToken.length();
                int end=text.indexOf(closeToken,offset);
                while (end>-1){
                    if(end>offset&&src[end-1]=='\\'){
                        expression.append(src,offset,end-offset-1).append(closeToken);
                        offset=end+closeToken.length();
                        end=text.indexOf(closeToken,offset);

                    }else {
                        expression.append(src,offset,end-offset);
                        offset=end+closeToken.length();
                        break;
                    }
                }
                if(end==-1){
                    builder.append(src,start,src.length-start);
                    offset=src.length;
                }else{
                    builder.append("?");
                    offset=end+closeToken.length();
                }
            }
            start=text.indexOf(openToken,offset);
        }
        if(offset<src.length){
            builder.append(src,offset,src.length-offset);
        }
        return builder.toString();
    }

}
4、获取mapper接口对象

根据上面分析可以看出,可以通过动态代理完成接口数据的查询

UserMapper userMapper= session.getMapper(UserMapper.class);
public <T> T getMapper(Class<T> clazz){
        MapperProxy mapperProxy=new MapperProxy(this);
        return (T)Proxy.newProxyInstance(clazz.getClassLoader(),new Class<?>[]{clazz},mapperProxy);
    }

我们看下代理类

public class MapperProxy implements InvocationHandler {

    private SqlSession sqlSession;

    public MapperProxy(SqlSession sqlSession) {
        this.sqlSession = sqlSession;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Class<?> returnType = method.getReturnType();
        if(Collection.class.isAssignableFrom(returnType)){
            return sqlSession.selectList(args);

        }else if(Map.class.isAssignableFrom(returnType)){
            return sqlSession.selectMap(args);
        }else{
            String statementKey=method.getDeclaringClass().getName()+"."+method.getName();
            return sqlSession.selectOne(statementKey,null==args?null:args[0]);
        }
    }
}
5、调用mapper接口对象操作数据库
        User user = userMapper.getUserById(1);
6、测试下
  public static void main(String[] args) {
        //第一步,加载配置文件
        InputStream inputStream=Main.class.getClassLoader().getResourceAsStream("mybatis-config.xml");
        //第二步构建sqlsessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        //第三步 打开sqlSession
        SqlSession session = sqlSessionFactory.openSession();
        //第四步 获取mapper接口对象
        UserMapper userMapper= session.getMapper(UserMapper.class);
        //第五步 调用mapper接口对象操作数据库
        User user = userMapper.getUserById(1);
        System.out.println(user);
        //第六步 业务处理
    }
7、结果

20200112140234

五、源代码地址

https://gitee.com/fengpt/my-mybatis

0

评论区