第一章 mybatis源码系列-手写mybatis

目录

一、分析jdbc

二、自定义框架设计

 2.1 使用端

 2.2 框架端

  2.2.1 读取配置文件

 2.2.2 解析配置文件 

 2.2.3 创建sqlSession接口及实现类

  2.2.4 创建Executor接口及实现类SimpleExecutor

三、框架端主要部分代码实现

  3.1 读取配置文件

 3.2 sqlSessionFactory 接口及D efaultSqlSessionFactory 实现类 

 3.3 sqlSession 接口的实现类 DefaultSqlSession

 3.4 Executor接口的实现类SimpleExecutor

四、客户端调用主要代码

 4.1 sqlMapConfig.xml

 4.2 创建user实体类、userdao接口以及mapper.xml 

 4.3 进行测试类的编写


一、分析jdbc

// 加载数据库驱动
Class.forName("com.mysql.jdbc.Driver");
// 通过驱动管理类获取数据库链接
Connection connection =
DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?
characterEncoding=utf-8", "root", "root");
// 定义sql语句?表示占位符
String sql = "select * from user where username = ?";
// 获取预处理statement
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// 设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值
preparedStatement.setString(1, "tom");
// 向数据库发出sql执行查询,查询出结果集
ResultSet resultSet = preparedStatement.executeQuery();
// 遍历查询结果集
while (resultSet.next()) {
int id = resultSet.getInt("id");
String username = resultSet.getString("username");
// 封装User
user.setId(id);
user.setUsername(username);
}

原始jdbc开发存在的问题如下:

  •  数据库连接创建、释放频繁造成系统资源浪费,从而影响系统性能。
  • Sql语句在代码中硬编码,造成代码不易维护,实际应用中sql变化的可能较大,sql变动需要改变java代码。
  • 使用preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不一定,可能多也可能少,修改sql还要修改代码,系统不易维护。
  •  对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据 库记录封装成pojo对象解析比较方便.

问题解决思路:

  • 使用数据库连接池初始化连接资源
  • 将sql语句抽取到xml配置文件中
  • 使用反射、内省等底层技术,自动将实体与表进行属性与字段的自动映射

二、自定义框架设计

 2.1 使用端


  提供核心配置文件:

  • sqlMapConfig.xml : 存放数据源信息,引入mapper.xml
  • Mapper.xml : sql语句的配置文件信息

   编写代码 :

  • pojo类
  • 针对个Mapper.xml生成Dao类

 2.2 框架端

  2.2.1 读取配置文件

     读取配置文件并可以创建javaBean来存储:

  • Configuration : 存放数据库基本信息(读取sqlMapConfig.xml)、以及Map<唯一标识,Mapper> MapperStatements存储读取Mapper.xml的MapperStatement信息。其中唯一标识=namespace(mapper级别) + "." +id(每一条sql id)。
  • MapperStatement:是mapper.xml中的每一条sql信息,包含了sql语句、statement类型、输入参数java类型、输出参数java类型

 2.2.2 解析配置文件 

创建sqlSessionFactoryBuilder类,包含构建sqlSessionFactory的build()方法:

  • 第一步:使用dom4j解析配置文件,将解析出来的内容封装到Configuration和MappedStatement中
  • 第二步:创建SqlSessionFactory的实现类DefaultSqlSession

 2.2.3 创建sqlSession接口及实现类

  • 调用sqlSessionFactory的openSession()方法,得到SqlSession对象
  • SqlSession中提供增删改查方法,以及getMapper方法(主要通过反射形式获取到要执行的mapperdao,然后调用对应的MapperStatement)
  • SqlSession实现类DefaultSqlSession 实现类中调用excutor接口真正实现sql的执行

  2.2.4 创建Executor接口及实现类SimpleExecutor

   在方法中进行真正增删该查的执行。

三、框架端主要部分代码实现

  3.1 读取配置文件

 /**
     * 该方法就是使用dom4j对配置文件进行解析,封装Configuration
     */
    public Configuration parseConfig(InputStream inputStream) throws DocumentException, PropertyVetoException {

        Document document = new SAXReader().read(inputStream);
        //<configuration>
        Element rootElement = document.getRootElement();
        List<Element> list = rootElement.selectNodes("//property");
        Properties properties = new Properties();
        for (Element element : list) {
            String name = element.attributeValue("name");
            String value = element.attributeValue("value");
            properties.setProperty(name,value);
        }

        ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
        comboPooledDataSource.setDriverClass(properties.getProperty("driverClass"));
        comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));
        comboPooledDataSource.setUser(properties.getProperty("username"));
        comboPooledDataSource.setPassword(properties.getProperty("password"));
        configuration.setDataSource(comboPooledDataSource);

        //mapper.xml解析: 拿到路径--字节输入流---dom4j进行解析
        List<Element> mapperList = rootElement.selectNodes("//mapper");

        for (Element element : mapperList) {
            String mapperPath = element.attributeValue("resource");
            InputStream resourceAsSteam = Resources.getResourceAsSteam(mapperPath);
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
            xmlMapperBuilder.parse(resourceAsSteam);
        }
        return configuration;
    }

   

//此方法主要对mapper.xml的解析
public void parse(InputStream inputStream) throws DocumentException {

        Document document = new SAXReader().read(inputStream);
        Element rootElement = document.getRootElement();

        String namespace = rootElement.attributeValue("namespace");

        List<Element> list = rootElement.selectNodes("//select");
        for (Element element : list) {
            String id = element.attributeValue("id");
            String resultType = element.attributeValue("resultType");
            String paramterType = element.attributeValue("paramterType");
            String sqlText = element.getTextTrim();
            MappedStatement mappedStatement = new MappedStatement();
            mappedStatement.setId(id);
            mappedStatement.setResultType(resultType);
            mappedStatement.setParamterType(paramterType);
            mappedStatement.setSql(sqlText);
            String key = namespace+"."+id;
            configuration.getMappedStatementMap().put(key,mappedStatement);

        }

    }

 3.2 sqlSessionFactory 接口及D efaultSqlSessionFactory 实现类 

public interface SqlSessionFactory {
public SqlSession openSession();
}
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
public SqlSession openSession(){
return new DefaultSqlSession(configuration);
}
}

 3.3 sqlSession 接口的实现类 DefaultSqlSession

public class DefaultSqlSession implements SqlSession {
    private Configuration configuration;

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

    @Override
    public <E> List<E> selectList(String statementid, Object... params) throws Exception {

        //将要去完成对simpleExecutor里的query方法的调用
        SimpleExecutor simpleExecutor = new SimpleExecutor();
        MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementid);
        List<Object> list = simpleExecutor.query(configuration, mappedStatement, params);

        return (List<E>) list;
    }

 @Override
    public <T> T getMapper(Class<?> mapperClass) {
        // 使用JDK动态代理来为Dao接口生成代理对象,并返回

        Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 底层都还是去执行JDBC代码 //根据不同情况,来调用selctList或者selectOne
                // 准备参数 1:statmentid :sql语句的唯一标识:namespace.id= 接口全限定名.方法名
                // 方法名:findAll
                String methodName = method.getName();
                String className = method.getDeclaringClass().getName();

                String statementId = className+"."+methodName;

                if(methodName.contains("insert")){
                    int result = insert(statementId, args);
                    return result;
                }
                if(methodName.contains("update")){
                    int result = update(statementId, args);
                    return result;
                }
                if(methodName.contains("delete")){
                    int result = delete(statementId, args);
                    return result;
                }
                // 准备参数2:params:args
                // 获取被调用方法的返回值类型
                Type genericReturnType = method.getGenericReturnType();
                // 判断是否进行了 泛型类型参数化
                if(genericReturnType instanceof ParameterizedType){
                    List<Object> objects = selectList(statementId, args);
                    return objects;
                }

                return selectOne(statementId,args);

            }
        });

        return (T) proxyInstance;
    }
}

 3.4 Executor接口的实现类SimpleExecutor

public class SimpleExecutor implements  Executor {

    @Override                                                                                
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
        // 1. 注册驱动,获取连接
        Connection connection = configuration.getDataSource().getConnection();

        // 2. 获取sql语句 : select * from user where id = #{id} and username = #{username}
            //转换sql语句: select * from user where id = ? and username = ? ,转换的过程中,还需要对#{}里面的值进行解析存储
        String sql = mappedStatement.getSql();
        BoundSql boundSql = getBoundSql(sql);

        // 3.获取预处理对象:preparedStatement
        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());

        // 4. 设置参数
            //获取到了参数的全路径
         String paramterType = mappedStatement.getParamterType();
         Class<?> paramtertypeClass = getClassType(paramterType);

        List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
        for (int i = 0; i < parameterMappingList.size(); i++) {
            ParameterMapping parameterMapping = parameterMappingList.get(i);
            String content = parameterMapping.getContent();

            //反射
            Field declaredField = paramtertypeClass.getDeclaredField(content);
            //暴力访问
            declaredField.setAccessible(true);
            Object o = declaredField.get(params[0]);

            preparedStatement.setObject(i+1,o);
        }


        // 5. 执行sql
        ResultSet resultSet = preparedStatement.executeQuery();
        String resultType = mappedStatement.getResultType();
        Class<?> resultTypeClass = getClassType(resultType);

        ArrayList<Object> objects = new ArrayList<>();

        // 6. 封装返回结果集
        while (resultSet.next()){
            Object o =resultTypeClass.newInstance();
            //元数据
            ResultSetMetaData metaData = resultSet.getMetaData();
            for (int i = 1; i <= metaData.getColumnCount(); i++) {

                // 字段名
                String columnName = metaData.getColumnName(i);
                // 字段的值
                Object value = resultSet.getObject(columnName);

                //使用反射或者内省,根据数据库表和实体的对应关系,完成封装
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
                Method writeMethod = propertyDescriptor.getWriteMethod();
                writeMethod.invoke(o,value);
            }
            objects.add(o);
        }
            return (List<E>) objects;
    }
    //省略增删改的实现代码
}

四、客户端调用主要代码

 4.1 sqlMapConfig.xml

〈configuration〉
<!--数据库连接信息-->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql:///zdy_mybatis"></property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
<! --引入sql配置信息-->
<mapper resource="mapper.xml"></mapper>
</configuration>

 4.2 创建user实体类、userdao接口以及mapper.xml 

 

public class User {
//主键标识
private Integer id;
//用户名
private String username;
 
public Integer getId() {
    return id;
}
public void setId(Integer id) {
    this.id = id;
}
public String getUsername() {
    return username;
}
public void setUsername(String username) {
    this.username = username;
}

}

 

public interface IUserDao {

    //查询所有用户
    public List<User> findAll() throws Exception;


    //根据条件进行用户查询
    public User findByCondition(User user) throws Exception;

    //根据条件进行用户查询
    public Integer insertUser(User user) throws Exception;

    //根据条件进行用户查询
    public Integer updateUser(User user) throws Exception;

    //根据条件进行用户查询
    public Integer deleteUser(User use) throws Exception;


}
<mapper namespace="com.lagou.dao.IUserDao">

    <!--sql的唯一标识:namespace.id来组成 : statementId-->
    <select id="findAll" resultType="com.lagou.pojo.User" >
        select * from user
    </select>

    <select id="findByCondition" resultType="com.lagou.pojo.User" paramterType="com.lagou.pojo.User">
        select * from user where id = #{id} and username = #{username}
    </select>

    <select id="insertUser"  resultType="java.lang.Integer" paramterType="com.lagou.pojo.User">
        insert into user(id, username) values(#{id}, #{username})
    </select>

    <select id="updateUser"  resultType="java.lang.Integer" paramterType="com.lagou.pojo.User">
        update user set username=#{username} where id=#{id}
    </select>

    <select id="deleteUser"  resultType="java.lang.Integer" paramterType="com.lagou.pojo.User">
        delete from user where id=#{id}
    </select>


</mapper>

 4.3 进行测试类的编写

public class IPersistenceTest {

    @Test
    public void test() throws Exception {
        InputStream resourceAsSteam = Resources.getResourceAsSteam("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsSteam);
        SqlSession sqlSession = sqlSessionFactory.openSession();

        //调用
        User user = new User();
        user.setId(1);
        user.setUsername("张三");
      
        IUserDao userDao = sqlSession.getMapper(IUserDao.class);

        List<User> all = userDao.findAll();
        for (User user1 : all) {
            System.out.println(user1);
        }

    }

    @Test
    public void testInsert() throws Exception {
        InputStream resourceAsSteam = Resources.getResourceAsSteam("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsSteam);
        SqlSession sqlSession = sqlSessionFactory.openSession();

        //调用
        User user = new User();
        user.setId(3);
        user.setUsername("mike");

        IUserDao userDao = sqlSession.getMapper(IUserDao.class);

        userDao.insertUser(user);

    }

    @Test
    public void testUpdate() throws Exception {
        InputStream resourceAsSteam = Resources.getResourceAsSteam("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsSteam);
        SqlSession sqlSession = sqlSessionFactory.openSession();

        //调用
        User user = new User();
        user.setId(3);
        user.setUsername("nike");

        IUserDao userDao = sqlSession.getMapper(IUserDao.class);

        userDao.updateUser(user);

    }

    @Test
    public void testDelete() throws Exception {
        InputStream resourceAsSteam = Resources.getResourceAsSteam("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsSteam);
        SqlSession sqlSession = sqlSessionFactory.openSession();

        //调用
        User user = new User();
        user.setId(3);
        IUserDao userDao = sqlSession.getMapper(IUserDao.class);

        userDao.deleteUser(user);

    }

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值