mybatis源码深入学习-01

如果我们没有mybatis下,怎么用java语言去实现,学一个东西得明白这个东西干嘛用,解决什么问题,有结果推向过程,最后才能更好的明白过程。

1、mybatis干嘛用?解决了什么痛点?

MyBatis是一流的持久性框架,支持自定义SQL,存储过程和高级映射。MyBatis消除了几乎所有的JDBC代码以及参数的手动设置和结果检索。MyBatis可以使用简单的XML或注释进行配置,并将图元,映射接口和Java POJO(普通的旧Java对象)映射到数据库记录。

mybatis是一个orm框架,也就是把字段映射为对象的属性。

2、首先复习下java的jdbc

JDBC(Java DataBase Connectivity)是Java和数据库之间的一个桥梁,是一个规范而不是一个实现,能够执行SQL语句。它由一组用Java语言编写的类和接口组成。各种不同类型的数据库都有相应的实现,本文中的代码都是针对MySQL数据库实现的。

JDBC编程步骤
(1)、导入专用的jar包(不同的数据库需要的jar包不同)

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.39</version>
</dependency>

(2)、初始化驱动

  Class.forName("com.mysql.jdbc.Driver");	

(3)、建立JDBC和数据库之间的Connection连接

      String url = "jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8";
            String name = "root";
            String password = "123456";
            // 获取数据库连接
          Connection  connection = DriverManager.getConnection(url, name, password);

(4)、创建Statement或者PreparedStatement接口,执行SQL语句
使用Statement接口
Statement中使用字符串拼接的方式,该方式存在句法复杂,容易犯错等缺点,且存在sql注入风险

Statement s = conn.createStatement();
// 准备sql语句
// 注意: 字符串要用单引号'
String sql = "insert into t_courses values(null,"+"'数学')";
//在statement中使用字符串拼接的方式,这种方式存在诸多问题
s.execute(sql);
System.out.println("执行插入语句成功");

使用PreparedStatement接口
PreparedStatement也是用来执行sql语句的与创建Statement不同的是,需要根据sql语句创建PreparedStatement。除此之外,还能够通过设置参数,指定相应的值,而不是Statement那样使用字符串拼接。

  	String sql = "select * from user  where user_name=?";
	// 创建Statement对象(每一个Statement为一次数据库执行请求)
	PreparedStatement preparedStatement = connection.prepareStatement(sql);
    // 设置传入参数
    preparedStatement.setString(1, "zhangsan");

(5)、执行SQL语句

  resultSet = preparedStatement.executeQuery();

(6)、处理查询结果(将查询结果转换成List格式)

ResultSetMetaData metaData = resultSet.getMetaData();
int columnCount = metaData.getColumnCount();

while (resultSet.next()) {
    Map map = new HashMap();
    for (int i = 0; i < columnCount; i++) {
        String columnName = metaData.getColumnName(i + 1);
        map.put(columnName, resultSet.getString(columnName));
    }
    resultList.add(map);
}

(7)、释放资源

/**
	 * 封装三个关闭方法
	 * @param pstmt
	 */
	public static void close(PreparedStatement pstmt){
		if(pstmt != null){						//避免出现空指针异常
			try{
				pstmt.close();
			}catch(SQLException e){
				e.printStackTrace();
			}
			
		}
	}
	
	public static void close(Connection conn){
		if(conn != null){
			try {
				conn.close();
			} catch (SQLException e) {
				// TODO: handle exception
				e.printStackTrace();
			}
		}
	}
	
	public static void close(ResultSet rs){
		if (rs != null) {
			try {
				rs.close();
			} catch (SQLException e) {
				// TODO: handle exception
				e.printStackTrace();
			}
		}
	}

完整代码

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

public class JdbcTest {
    public static void main(String[] args) {
        List<Map<String, Object>> user = getUser();
        System.out.println(user);
    }

    private static List<Map<String, Object>> getUser() {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        List<Map<String, Object>> resultList = new ArrayList<Map<String, Object>>();
        try {
            // 加载JDBC驱动
            Class.forName("com.mysql.jdbc.Driver").newInstance();
            String url = "jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8";
            String name = "root";
            String password = "123456";
            // 获取数据库连接
            connection = DriverManager.getConnection(url, name, password);
            String sql = "select * from user  where user_name=?";

            // 创建Statement对象(每一个Statement为一次数据库执行请求)
            preparedStatement = connection.prepareStatement(sql);
            // 设置传入参数
            preparedStatement.setString(1, "zhangsan");
            // 执行SQL语句
            resultSet = preparedStatement.executeQuery();
            // 处理查询结果(将查询结果转换成List<Map>格式)
            ResultSetMetaData metaData = resultSet.getMetaData();
            int columnCount = metaData.getColumnCount();

            while (resultSet.next()) {
                Map map = new HashMap();
                for (int i = 0; i < columnCount; i++) {
                    String columnName = metaData.getColumnName(i + 1);
                    map.put(columnName, resultSet.getString(columnName));
                }
                resultList.add(map);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                // 关闭结果集
                if (resultSet != null) {
                    resultSet.close();
                    resultSet = null;
                }
                // 关闭执行
                if (preparedStatement != null) {
                    preparedStatement.close();
                    preparedStatement = null;
                }
                if (connection != null) {
                    connection.close();
                    connection = null;
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return resultList;
    }
}

3、mybatis

mybatis的基本构成

1)、SqlSessionFactoryBuilder(构造器):根据配置信息或代码来生成SqlSessionFactory
2)、SqlSessionFactory:依靠工厂来生成SqlSession(会话)
3)、SqlSession:发送SQL去执行并返回结果,也可以获取mapper的接口(Executor 才是真正执行sql)。
4)、 Mapper:它是由一个Java接口和XML文件(注解)构成的,需要给出对应的SQL和映射规则。它负责发送sql去执行,并返回结果。
mybatis的基本构成的生命周期:
1)、SqlSessionFactoryBuilder:在方法内部有效,只用于生成SqlSessionFactory
2)、SqlSessionFactory:用于生成SqlSession(会话)整个mybatis应用生命周期(单例)
3)、SqlSession:相当于一个Connection,一个请求数据库的
4)、Mapper:是一个接口,发送sql返回需要的值,最大生命跟SqlSession范围一样大。

(1)、导jar包

		<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
		<dependency>
		    <groupId>org.mybatis</groupId>
		    <artifactId>mybatis</artifactId>
		    <version>3.5.4</version>
		</dependency>

(2)、测试和应用

mysql表语句

/*
Navicat MySQL Data Transfer

Source Server         : 127.0.0.1
Source Server Version : 50640
Source Host           : localhost:3306
Source Database       : test

Target Server Type    : MYSQL
Target Server Version : 50640
File Encoding         : 65001

Date: 2020-06-03 00:21:16
*/

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(32) NOT NULL AUTO_INCREMENT,
  `userName` varchar(32) NOT NULL,
  `passWord` varchar(50) NOT NULL,
  `realName` varchar(32) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', 'aa', '11', '11');

实体类

import java.io.Serializable;
import java.sql.Date;

public class User implements Serializable {
    private long id;
    private String userName;
    private String passWord;
    private Date createTime;
    private String name;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassWord() {
        return passWord;
    }

    public void setPassWord(String passWord) {
        this.passWord = passWord;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", userName='" + userName + '\'' +
                ", passWord='" + passWord + '\'' +
                ", createTime=" + createTime +
                ", name='" + name + '\'' +
                '}';
    }
}

XML文件构建SqlSessionFactory实现CURD

每个MyBatis应用程序都以SqlSessionFactory实例为中心。可以使用SqlSessionFactoryBuilder获取SqlSessionFactory实例。SqlSessionFactoryBuilder可以从XML配置文件或Configuration类的自定义准备好的实例中构建SqlSessionFactory实例。

从XML文件构建SqlSessionFactory实例非常简单。建议您为该配置使用类路径资源,但可以使用任何InputStream实例,包括从文字文件路径或file:// URL创建的实例。MyBatis包含一个称为资源的实用程序类,该实用程序类包含许多方法,这些方法使从类路径和其他位置加载资源更加容易。

mapper文件

import pojo.User;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface UserMapper {
    List<User> selectUserList();
}
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new 					SqlSessionFactoryBuilder().build(inputStream);

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>
<properties resource="db.properties"></properties>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mapper/UserMapper.xml"></mapper>
    </mappers>
</configuration>

基于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="dao.UserMapper">
    <select id="selectUserList" resultType="pojo.User">
        select * from user
    </select>
</mapper>

SqlSession调用的基于XML的映射语句

        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> users = mapper.selectUserList();
        for (User user : users) {
            System.out.println(user);
        }

不使用XML的情况下构建SqlSessionFactory实现CURD

映射语句根本不需要与XML映射。相反,他们可以使用Java注释。

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import pojo.User;
import java.util.List;
@Mapper
public interface UserMapper {
    @Select("select * from user")
    List<User> selectUserList();
}
  public static void main(String[] args) {

        SqlSession sqlSession = null;
        try {
            sqlSession = getSqlSessionFactory().openSession();
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            List<User> users = mapper.selectUserList();
            for (User user : users) {
                System.out.println(user);
            }

        } catch (Exception e) {
            System.out.println("执行失败");
        } finally {
            if (sqlSession != null) {
                sqlSession.close();
            }
        }

    }


 private static SqlSessionFactory getSqlSessionFactory() {
        //创建线程池
        PooledDataSource dataSource = new PooledDataSource();
        dataSource.setDriver("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");
        //构建数据库事物
        TransactionFactory transactionFactory = new JdbcTransactionFactory();
        //构建数据库✅环境
        Environment evironment = new Environment("development", transactionFactory, dataSource);
        //构建Configuration对象
        Configuration configuration = new Configuration(evironment);
        //注册一个别名
        configuration.getTypeAliasRegistry().registerAlias("user", User.class);
        //添加映射器
        configuration.addMapper(UserMapper.class);
        /**
         * 以上是Mybatis配置信息的处理过程正常开发中主要通过xml配置的
         * 以下是创建sqlSessionFactory
         */
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
        return sqlSessionFactory;
    }

动手模拟一次Mybatis的动态代理

代码实例:

import dao.UserMapper;
import org.apache.ibatis.annotations.Select;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.HashMap;

public class ProxyMapperTest {
    public static void main(String[] args) {
        UserMapper userMapper = (UserMapper) Proxy.newProxyInstance(ProxyMapperTest.class.getClassLoader(), new Class<?>[]{UserMapper.class}, new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("被动态代理类回调执行, 代理类 proxyClass =" + proxy.getClass()
                        + " 方法名: " + method.getName() + "方法. 方法返回类型:" + method.getReturnType()
                        + " 接口方法入参数组: " + (args == null ? "null" : Arrays.toString(args)));

                Select annotation = method.getAnnotation(Select.class);
                //  赋值到#
                Parameter[] parameters = method.getParameters();

                HashMap map = getParametersMap(parameters, args);

                if (annotation != null) {
                    String[] value = annotation.value();
                    String sql = value[0];
                    String sqlStr = getSql(sql, map);
                    System.out.println(sqlStr);
                }

                return null;
            }
        });
        userMapper.selectUserById(1);
    }

    private static String getSql(String sql, HashMap map) {
        StringBuilder sb = new StringBuilder();
        int length = sql.length();
        for (int i = 0; i < length; i++) {
            char a = sql.charAt(i);
            if (a == '#') {
                int nextIndex = i + 1;
                if ('{' != sql.charAt(nextIndex)) {
                    throw new RuntimeException(String.format("这里应该为{%s,index%s", sb.toString(), nextIndex));
                }
                StringBuilder sbs = new StringBuilder();
                i = parSqlarg(sbs, sql, nextIndex);
                Object argValue = map.get(sbs.toString());
                sb.append(argValue);
                continue;
            }
            sb.append(a);
        }
        return sb.toString();
    }

    private static int parSqlarg(StringBuilder sbs, String sql, int nextIndex) {
        nextIndex++;
        for (int i = nextIndex; i < sql.length(); i++) {
            char b = sql.charAt(nextIndex++);
            if ('}' != b) {
                sbs.append(b);
                continue;
            }
            if ('}' == b) {
                return nextIndex;
            }
        }
        throw new RuntimeException(String.format("这里应该为}%s,index%s", sbs.toString(), nextIndex));
    }

    private static HashMap getParametersMap(Parameter[] parameters, Object[] args) {
        HashMap map = new HashMap();
        int index[] = {0};
        Arrays.asList(parameters).forEach(parameter -> {
            String name = parameter.getName();
            map.put(name, args[index[0]]);
            index[0]++;
        });
        System.out.println(Arrays.toString(parameters));
        return map;
    }

}

jdk1.8提供了获取参数名的方法
但是在编译的时候要加上–parameters参数,如果不加这个参数会得到参数名为arg0…

Parameter[] parameters = methods.getParameters();
System.out.println(parameters[0].getName());
但是如果使用maven,只需要在编译插件上加上一个配置

<build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <compilerArgument>-parameters</compilerArgument>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

果使用IDEA打开javac设置
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值