mybatis学习笔记(一)(手写持久层框架)

mybatis学习笔记(手写持久层框架)

1.原生JDBC存在问题

1)数据库配置信息硬编码;频繁创建释放数据库连接

// 加载数据库驱动
 Class.forName("com.mysql.jdbc.Driver");
 // 通过驱动管理类获取数据库链接
 connection =DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?
characterEncoding=utf-8", "root", "root");

​ 解决:配置文件;连接池

2)sql,设置参数,获取结果集参数硬编码问题

// 定义sql语句?表示占位符
String sql = "select * from user where username = ?";
// 获取预处理statement
preparedStatement = connection.prepareStatement(sql);
// 设置参数,第⼀个参数为sql语句中参数的序号(从1开始),第⼆个参数为设置的参数值
preparedStatement.setString(1, "tom");
// 向数据库发出sql执⾏查询,查询出结果集
resultSet = preparedStatement.executeQuery();

​ 解决:配置文件

3)手动封装结果集,过程繁琐

​ 实体类属性较多的时候会影响

   while (resultSet.next()) {
       int id = resultSet.getInt("id");
       String username = resultSet.getString("username");
       // 封装User
       user.setId(id);
       user.setUsername(username);
   }

​ 解决:反射,内省(需要学习)*

反思:好的代码应该是抽象的,可复用的,这也是编程需要的重要思想

2.自定义持久层框架设计

​ 针对原生JDBC的问题以及提出的解决思路,对持久层框架进行设计。

1)使用端(项目):引入自定义持久层框架的jar包

1:提供两部分配置信息:数据库配置信息;sql配置信息、参数类型、返回值类型
使用配置文件提供
(1) .sqlMapConfig.xml:数据库配置信息
(2) . mapper.xml: 存放sql配置信息

2)框架本身(工程):本质是对jdbc进行封装

1:根据文件路径,加载配置文件成字节输入流,存储在内存中

​ 创建Resource类 方法:Inputstream getResourceAsStream(String path)

2:创建两个javaBean(容器对象):存放的是对配置文件解析出来的内容

​ Configuration:核心配置类:存放sqlMapConfig.xml解析内容

​ MappedStatement:映射配置类 :存放mapper.xml解析内容

3:解析配置文件 :dom4j

​ 创建类:SqlSessionFactoryBuilder 方法:build(InputStream in)

​ 第一:使用dom4j解析配置文件,讲解析出来的内容封装到容器对象中

​ 第二:创建SqlSessionFactory对象;生产Sqlsession;会话对象(工厂模式

4:创建SqlSessionFactory接口以及实现类DefaultSqlSessionFactory

​ openSession:生产SqlSession

5:创建SqlSession接口以及实现类DefaultSession

​ 定义对数据库的CURD操作:selectList(); selectone(); update(); delete()

6:创建Executor接口及实现类SimpleExecutor实现类

​ query(Configuration,MappedStatement,Object…params)(JDBC封装)


3.测试类编写

​ 根据分析思路编写配置文件

1) : sqlMapConfig.xml

<configuration>
    <!--数据库配置信息-->
    <dataSource>
        <property name="driverClass" value="com.mysql.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql:///zmx_mybatis"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </dataSource>

    <!--存放mapper.xml的全路径-->
    <mapper resource="UserMapper.xml"></mapper>

</configuration>

2): UserMapper.xml (关联实体类)

<mapper namespace ="user">
    <!--sql的唯一标识:由namespace.id 来决定-->
    <select id="selectList" resultType="com.zmx.pojo.User">
        select * from user
    </select>
    <!--
        User user = new User();
        user.setId(1);
        user.setName("zhangsan");
    -->
    <select id="selectOne" resultType="com.zmx.pojo.User" paramType="com.zmx.pojo.User">
        <!--select * from user where id= ? and  name = ?-->
        select * from user where id= #{id} and  name = #{name}
    </select>
</mapper>

4.Resource类的定义

​ 使用端:应用本身

​ 框架端:被使用端调用(mybatis)

1).新建module,创建类(框架端)

public class Resources {
    //根据配置文件路径 将配置文件加载成字节输入流 存储在内存中
    public static InputStream  getResourceAsStream(String path){
        InputStream  resourceAsStram  = Resources.class.getClassLoader().getResourceAsStream(path);
        return resourceAsStram;
    }
}

2).在使用端引入打包好的框架端,创建test类引入方法

public class IPersistenceTest {
    public  void test(){
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
    }
}

​ 反思:对maven的理解加深

​ 框架端的pom文件信息

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.zmx</groupId>
    <artifactId>IPersistence</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

</project>

​ 使用端引入打包的依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.zmx</groupId>
    <artifactId>Ipersistence_Test</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <!--引入自定义持久层框架的依赖-->
    <dependencies>
        <dependency>
            <groupId>com.zmx</groupId>
            <artifactId>IPersistence</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>


5.容器对象

​ 1).存放的配置信息对应文件(sqlMapConfig.xml)

public class Configuration {
    //数据源  存放数据库配置信息
    private DataSource dateResource;
    /**
    * key:statementId
    * value:封装好的mappedStatement对象
    * */
    Map<String,MappedStatement>  mappedStatementMap = new HashMap<>();
}

public class MappedStatement {
    //id标识
    private String id;
    //返回值类型
    private String resultType;
    //参数值类型
    private String paramType;
    //sql语句
    private String sql;
}


实体类:DataSource:数据库配置

​ MappedStatement :sql语句


6-7.解析配置文件sqlMapConfig.xml;解析映射配置文件mapper.xml

1).创建XMLConfigBuilder实体类解析配置文件

首先解析sqlMapConfig.xml
public class XMLConfigBuilder {
    private Configuration configuration;
    public XMLConfigBuilder() {
        this.configuration = new Configuration();
    }
    /*
    * 该方法解析配置文件,封装到Configuration
    * */
    public Configuration parseConfig(InputStream inputStream) throws DocumentException, PropertyVetoException {
        Document document = new SAXReader().read(inputStream);
        Element rootElement = document.getRootElement();
        List<Element> elements = rootElement.selectNodes("//property");
        Properties properties = new Properties();
        for (Element element : elements) {
            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.setDateResource(comboPooledDataSource);
        //解析mapper.xml 拿到路径-字节输入流  dom4j进行解析
        List<Element> mapperElements = rootElement.selectNodes("//mapper");
        Properties mappereProperties = new Properties();
        for (Element element : mapperElements) {
            String mapperPath = element.attributeValue("resource");
            InputStream resourceAsStream = Resources.getResourceAsStream(mapperPath);
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
            xmlMapperBuilder.parse(resourceAsStream);
        }
        return configuration;
    }
}


解析sqlMapConfig.xml之后解析mapper.xml
public class XMLMapperBuilder {
    private Configuration configuration;
    public XMLMapperBuilder(Configuration configuration) {
        this.configuration = configuration;
    }
    public void parse(InputStream inputStream) throws DocumentException {
        Document document = new SAXReader().read(inputStream);
        Element rootElement = document.getRootElement();
        String namespace = rootElement.attributeValue("namespace");
        List<Element> sqlElement = rootElement.selectNodes("//select");
        for (Element element : sqlElement) {
            String id = element.attributeValue("id");
            String resultType = element.attributeValue("resultType");
            String paramType = element.attributeValue("paramType");
            String sqlText = element.getTextTrim();
            MappedStatement mappedStatement = new MappedStatement();
            mappedStatement.setId(id);
            mappedStatement.setParamType(paramType);
            mappedStatement.setResultType(resultType);
            mappedStatement.setSql(sqlText);
            String key = namespace+"."+id;
            configuration.getMappedStatementMap().put("",mappedStatement);
        }
    }
}

反思:学习过程跟随思路走,重要理解其逻辑,因果关系


8-9.会话对象Sqlsession类定义以及方法定义

1).sqlSession类的定义

​ 用来创建会话对象,里边包含封装的jdbc方法(增删改查),根据传递过来的唯一标识以及 结果集/参数集 返回对应的结果。

public interface SqlSession {
    //查询所有
    public <E> List<E> selectList(String statementId,Object...params);
    //根据条件查询单个
    public <T> T selectOne(String statementId,Object...params);
}

2)方法的定义

​ 方法不止查询,也可以添加修改删除等操作 selectOne可以视作selectList的某种形式

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) {
        //完成对simpleExecutor里的query方法调用
        SimpleExecutor simpleExecutor = new SimpleExecutor();
        MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
        List<Object> query = simpleExecutor.query(configuration, mappedStatement, params);
        return (List<E>) query;
    }
    @Override
    public <T> T selectOne(String statementId, Object... params) {
        List<Object> objects = selectList(statementId, params);
        if(objects.size() == 1) {
            return (T) objects.get(0);
        }else{
            throw new RuntimeException("查询结果过多或结果为空");
        }
    }
}

3)创建SqlSessionFactory

public class sqlSessionFactoryBuilder {
    public SqlSessionFactory build(InputStream in) throws DocumentException, PropertyVetoException {
      //第一:使用dom4j解析配置文件,解析的内容封装到Configuration
        XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
        Configuration configuration = xmlConfigBuilder.parseConfig(in);
      //第二:创建sqlSessionFactory对象  工厂类:生产sqlSession会话对象
        DefaultSqlSessionFactory defaultSqlSessionFactory = 
            								new DefaultSqlSessionFactory(configuration);
        return defaultSqlSessionFactory;
    }
}

反思:在openSession时候需要先创建sqlSessionFactoryBuilder调用build方法创建SqlSessionFactory;SqlSessionFactory中包含了SqlSession接口,接口中封装jdbc方法进行调用。


10-12. 查询对象query定义–参数设置实现–封装返回结果集实现

内部封装JDBC

public class SimpleExecutor implements  Executor{


    @Override
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
        //1.注册驱动  获取链接
        Connection connection = configuration.getDateResource().getConnection();
        //2.获取sql语句   select * from user where id= #{id} and  name = #{name}
            //转换sql 需要对#{} 的值进行解析存储
        String sql = mappedStatement.getSql();
        BoundSql boundSql = getBoundSql(sql);
        //3.获取预处理对象 preparedStatement
        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
        //4.设置参数
        String paramType = mappedStatement.getParamType();
        Class<?> paramTypeClass =  getClassType(paramType);
        List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
        for (int i = 0; i < parameterMappingList.size(); i++) {
            ParameterMapping parameterMapping = parameterMappingList.get(i);
            String content = parameterMapping.getContent(); //对应#{}里边的值
            //反射
            Field declaredField = paramTypeClass.getDeclaredField(content);
            //暴力访问
            declaredField.setAccessible(true);
            Object o = declaredField.get(params[0]);
            preparedStatement.setObject(i+1,o);//下标从1开始
        }
        //5.执行sql
        ResultSet resultSet = preparedStatement.executeQuery();
        String resultType = mappedStatement.getResultType();
        Class<?> resultTypeClass = getClassType(resultType);
        Object o = resultTypeClass.newInstance();
        ArrayList<Object> objects = new ArrayList<>();
        //6.封装返回结果
        while(resultSet.next()){
            //元数据
            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>) o;
    }
    
    private Class<?> getClassType(String paramType) throws ClassNotFoundException {
        if(paramType != null){
            Class<?>  aClass = Class.forName(paramType);
            return aClass;
        }
        return null;
    }
    /*
    * 完成对#{}的解析
    * 1.将#{}使用?进行代替
    * 2.解析出#{} 内容进行存储
    * */
    private BoundSql getBoundSql(String sql){
        //标记处理类 配置标记解析器来完成对占位符的解析处理工作
        ParameterMappingTokenHandler parameterMappingTokenHandler = 
            					new ParameterMappingTokenHandler();
        GenericTokenParser genericTokenParser =
            					new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
        //解析出的sql
        String parseSql = genericTokenParser.parse(sql);
        //解析出来的参数名称
        List<ParameterMapping> parameterMappings = 	
            					parameterMappingTokenHandler.getParameterMappings();
        BoundSql boundSql = new BoundSql(parseSql,parameterMappings);
        return boundSql;
    }

}

public class BoundSql {

    private String sqlText; //sql语句

    private List<ParameterMapping> parameterMappingList ;  //#{}内部值

    public BoundSql(String sqlText, List<ParameterMapping> parameterMappingList) {
        this.sqlText = sqlText;
        this.parameterMappingList = parameterMappingList;
    }
 
}



13.运行测试

运行过程碰到很多问题。

1).target目录下没有resources路径下的文件

解决方法:pom文件中将 标签内容改为jar;之前为pom

2).java.lang.IllegalArgumentException: argument type mismatch 表中字段类型为integer,对象对应字段类型为string

解决方法:修改对象类型

3).java.lang.ClassCastException: class com.zmx.pojo.User cannot be cast to class java.util.List (com.zmx.pojo.User is in unnamed module of loader ‘app’; java.util.List is in module java.base of loader ‘bootstrap’)

类型转换异常 在返回结果集时,返回的是某个对象并不是list

解决方法:返回list

4).java.lang.RuntimeException: 查询结果过多或结果为空

结果集添加数据时,放进了set值得一步 导致多次添加数据,结果变多

解决方法:add方法放到循环外

解决完毕,程序正常运行!


14-15 功能扩展

1).存在问题

​ 1.Dao层使用自定义持久层框架,存在代码重读,操作过程模板重复(加载配置文件,创建sqlSessionFactory,生产sqlSession)

//此处为何要放在不同的jar包  为何不直接在框架包使用该流
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new sqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();

  1. statementId存在硬编码问题

    List<User> users = sqlSession.selectList("user.selectList");
    
    

    解决思路:使用代理模式生成Dao层接口的代理实现类

    2). 在DefaultSqlSession创建新的方法(invoke)

    代理对象调用接口中任意方法,都会执行invoke方法

    IUserDao userDao = sqlSession.getMapper(IUserDao.class);
    List<User> all = userDao.findAll();
    
    
 @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 {
                //proxy 当前代理对象的应用  method 当前被调用方法的引用   args 传递的参数
                //根据不同情况   选择调用selectList或者selectOne
                //准备参数 1.statementId sql语句唯一标识 namespace.id = 接口全限定名.方法名
                //方法名:findAll
                String name = method.getName();
                String className = method.getDeclaringClass().getName();
                String statementId =className+"."+name;
                //准备参数 2.params 传递的参数 Object[] args
                //获取被调用方法的返回值类型
                Type genericReturnType = method.getGenericReturnType();
                //判断是否进行了  泛型类型参数化
                if(genericReturnType instanceof ParameterizedType){
                   return selectList(statementId,args);
                }
                return selectOne(statementId,args);
            }
        });
        return (T) proxyInstance;
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值