Mybatis(一)——自定义持久层框架

1. 主要流程

  • 使用端:
    项目: 引入自定义持久层框架的Jar包
    提供两部分配置信息数据库配置信息、sql配置信息:SQL语句、参数类型、返回值类型
    使用配置文件来提供这两份不配置信息:
    (1)sqlMapConfig.xml:存放数据库配置信息,存放mapper.xml的全路径
    (2)mapper.xml:存放sql配置信息

  • 自定义持久层框架本身:
    工程:本质就是对JDBC代码进行了封装
    (1)加载配置文件:根据文件的路径,加载配置文件成字节输入流,存储在内存中,创建Resource类 方法: InputSteam getResourceAsSteam(String path)
    (2)创建两个javaBean:(容器对象)存放的就是对配置文件解析出来的内容
    configuration:核心配置类:存放sqlMapConfig.xml解析出来的内容
    MappedStatement:映射配置类:存放mapper.xml解析出来的内容
    (3)解析配置文件:dom4j
    创建类:SqlSessionFactoryBuilder 方法build(InputSteam in)
    第一:使用dom4j解析配置文件,将解析出来的内容封装在对象中
    第二:创建SqlSessionFacroty对象;生产salSession:对话对象(工厂模式)
    (4)创建SqlSessionFactory接口及实现类DefaultSqlSessionFactory
    第一:openSession():生产sqlSession
    (5)创建SqlSession接口及实现类DefaultSession
    定义对数据库的crud操作:selectList() selectOne() update()
    delete()
    (6)创建Executor接口及实现类SimpleExecutor实现类
    query(Configuration, MappedStatement, Object… params):执行的就是JDBC代码。

  • 常用pom.xml配置

<properties>
        <project.build.sourceEnding>UTF-8</project.build.sourceEnding>
        <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
        <java.version>1.8</java.version>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
  • 数据库配置信息
    <dataSource>
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/zdy_mybatis?characterEncoding=utf8&amp;serverTimezone=UTC&amp;useSSL=false"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </dataSource>
  • 添加依赖
<dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.5</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>

2. 项目实现

  • jar包结构
    在这里插入图片描述
    使用端:
    在这里插入图片描述
  • 主线
//主线
public class SqlSessionFactoryBuilder {

    public SqlSessionFactory build(InputStream in) throws DocumentException, PropertyVetoException {
        //第一:使用dom4j解析配置文件,将解析出来的内容封装到Configuration中
        XMLCongfigBuilder xmlCongfigBuilder = new XMLCongfigBuilder();
        Configuration configuration = xmlCongfigBuilder.paseConfig(in); //解析配置文件

        //第二步:创建sqlSessionFactory对象(工厂类):生产sqlSession:会话对象  增删改查
        DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration);

        return defaultSqlSessionFactory;


    }
}
  • pojo配置
    主要配置文件:
public class Configuration {

    private DataSource dataSource; //连接数据库(连接池)

    //key: statementid  存储的是sql语句
    Map<String, MappedStatement> mappedStatementMap = new HashMap<>();

    public DataSource getDataSource() {
        return dataSource;
    }

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public Map<String, MappedStatement> getMappedStatementMap() {
        return mappedStatementMap;
    }

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

配置sql文件

public class MappedStatement {
    //id标识
    private String id;
    //返回值类型(全路径)
    private String resultType;
    //参数类型
    private String paramterType;
    //sql语句
    private String sql;

    public String getId() {
        return id;
    }

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

    public String getResultType() {
        return resultType;
    }

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

    public String getParamterType() {
        return paramterType;
    }

    public void setParamterType(String paramterType) {
        this.paramterType = paramterType;
    }

    public String getSql() {
        return sql;
    }

    public void setSql(String sql) {
        this.sql = sql;
    }
}
  • 根据配置文件的路径,将配置文件加载成字节输入流,存储在内存中
public class Resources {

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

}

2.1 第一:使用dom4j解析配置文件,将解析出来的内容封装到Configuration中

  • 加载文件成字节输入流
public class Resources {

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

}
public class XMLCongfigBuilder {

    private Configuration configuration;

    public XMLCongfigBuilder(){
        this.configuration = new Configuration();
    }


    //封装一层,解析文件(解析连接数据库的主要信息,并解析sql语句所在的路径).
     public Configuration paseConfig(InputStream inputStream) throws DocumentException, PropertyVetoException {
         Document document = new SAXReader().read(inputStream); //字节输入流,解析成树结构的document
         //<configuration>
         Element rootElement = document.getRootElement(); //获取xml根路径
         List<Element> list = rootElement.selectNodes("//property"); //获取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"); //获取mapper这个属性,对于具体的配置sql语句进行解析

         for (Element element : mapperList) {
             //从同一个配置文件中拿到另一个配置文件路径,然后进行解析
             String mapperPath = element.attributeValue("resource"); //获取路径resource这个标签内的值
             InputStream resourceAsSteam = Resources.getResourceAsSteam(mapperPath);

             //dom4j 进行解析 SQL语句跟本类一样需要解析
             XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration); //将
             xmlMapperBuilder.parse(resourceAsSteam);
         }


         return configuration;
     }

}
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");  //获取标签内的属性值使用attributeValue


        List<Element> list = rootElement.selectNodes("//select"); //获取所有select的子标签,这个是Xpath方式


        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);

        }
    }


}

2.2 第二步:创建sqlSessionFactory对象(工厂类):生产sqlSession:会话对象 增删改查

  • 创建会话工厂接口
public interface SqlSessionFactory {
    public SqlSession openSession();
}
  • 实现会话工厂接口:调用会话openSession回返回一个会话对象
public class DefaultSqlSessionFactory implements SqlSessionFactory{

    private Configuration configuration;

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

    @Override
    public SqlSession openSession() {
        //返回一个绘画对象
        return new DefaultSqlSession(configuration);
    }
}
  • 会话对象接口:这里面定义有哪些操作(CRUD)
public interface SqlSession {

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

    public <T> T selectOne(String statementid, Object... params) throws Exception;
}
  • 实现会话接口:
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

        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 selectOne(String statementid, Object... params) throws Exception {
        List<Object> objects = selectList(statementid, params);
        if(objects.size() == 1){
            return (T) objects.get(0);
        } else {
            throw new RuntimeException("查询结果为空或者返回结果过多!");
        }
    }
}

最后返回了一个默认会话工厂对象

使用端:
在这里插入图片描述

  • 测试
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("张三");
//        User user2 = sqlSession.selectOne("user.selectOne", user);
//        System.out.println(user2);

        //调用会话中的方法(CRUD)
        List<User> users = sqlSession.selectList("user.selectList");
        for(User user1 : users){
            System.out.println(user1);
        }
    }
}

3. 在进行封装

定义接口:

public interface IUserDao {

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

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

}

接口实现:

public class UserDaoImpl implements IUserDao{
    @Override
    public User findByCondition(User user) throws Exception {

        //传入配置文件
        InputStream resourceAsSteam = Resources.getResourceAsSteam("sqlMapConfig.xml");

        //会话工厂建立
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsSteam);

        //工厂对象返回一个会话对象
        SqlSession sqlSession = sqlSessionFactory.openSession();

        //存在硬编码问题,还需要进一步封装
        User user2 = sqlSession.selectOne("user.selectOne", user);
        return user2;
    }

    @Override
    public List<User> findAll() throws Exception {
        //传入配置文件
        InputStream resourceAsSteam = Resources.getResourceAsSteam("sqlMapConfig.xml");

        //会话工厂建立
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsSteam);

        //工厂对象返回一个会话对象
        SqlSession sqlSession = sqlSessionFactory.openSession();

        //调用会话中的方法(CRUD)
        List<User> users = sqlSession.selectList("user.selectList");
        return users;
    }
}

3. 自定义持久层框架问题分析

  • 自定义持久层框架,存在代码重复,整个操作的过程模板重复(加载配置文件、创建sqlSessionFactory、 生产sqlSession)
  • statementId存在硬编码问题

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

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

public interface IUserDao {

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

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

}

代理接口:

public interface SqlSession {

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

    public <T> T selectOne(String statementid, Object... params) throws Exception;

    //为Dao接口生成代理实现类
    public <T> T getMapper(Class<?> mapperClass);

}

代理接口实现

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

        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 selectOne(String statementid, Object... params) throws Exception {
        List<Object> objects = selectList(statementid, params);
        if(objects.size() == 1){
            return (T) objects.get(0);
        } else {
            throw new RuntimeException("查询结果为空或者返回结果过多!");
        }
    }

    @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   findByCondition
                String methodName = method.getName();
                String className = method.getDeclaringClass().getName(); //该类的全限定名

                String statementId = className+"."+methodName;

                // 准备参数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;
    }


}

注意配置文件中的namespace要写接口的全限定名,select中的id要写接口中的方法名

<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>

</mapper>

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值