1.持久层框架设计实现及MyBatis 源码解析 - 自定义持久层框架

一、原生 JDBC 使用方式

public static void main(String[] args) {
    Connection conn = null;
    PreparedStatement preparedStatement = null;
    ResultSet resultSet = null;
    User user = new User();
    List<User> userList = new ArrayList<User>();
    try {
        // 1、加载数据库驱动
        Class.forName("com.mysql.jdbc.Driver");
        // 2、创建数据库连接
        conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8",
                "root","root");
        // 3、定义 SQL 语句
        String sql = "select * from user where username = ?";
        // 4、获取预处理statement
        preparedStatement = conn.prepareStatement(sql);
        // 5、设置参数,第⼀个参数为sql语句中参数的序号(从1开始),第⼆个参数为设置的参数值
        preparedStatement.setString(1,"zhangsan");
        // 6、向数据库发出sql执⾏查询,查询出结果集
        resultSet = preparedStatement.executeQuery();
        // 7、遍历查询结果
        while (resultSet.next()){
            // 8、封装对象
            user.setId(resultSet.getInt("id"));
            user.setUsername(resultSet.getString("username"));
            userList.add(user);
        }
        for (User user1 : userList) {
            System.out.println(user1.toString());
        }
    }catch (Exception e){
        e.printStackTrace();
    }finally {
        try {
            if (resultSet != null){
                resultSet.close();
            }
            if (preparedStatement != null){
                preparedStatement.close();
            }
            if (conn != null){
                conn.close();
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

二、原生 JDBC 存在的问题及解决思路

  1. 数据库配置信息存在硬编码问题:可以使用配置文件
  2. 频繁创建、释放数据库连接:可以使用连接池
  3. SQL 语句、设置参数、获取结果集参数均存在硬编码问题:可以使用配置文件
  4. 返回结果需要手动封装结果集,较为繁琐:可以使用反射、内省解决

三、自定义持久层框架设计

  1. 使用端
  • SqlMapConfig.xml:存放数据库配置信息和 mapper.xml 的全路径:数据库驱动、数据库连接URL、账户、密码
  • mapper.xml:存放SQL 配置信息:SQL 语句,参数类型,返回值类型
  1. 自定义持久层框架本身:本质就是对 JBDC 代码进行封装
  • 加载配置文件:根据配置文件的路径,加载配置文件成字节输入流,存储在内存中
    • 创建 Resource 类:使用方法 InputStream getResourceAsStream(String path)
  • 创建两个JavaBean(容器对象):存放的就是对配置文件解析出来的内容
    • Configuration:核心配置类,存放 SqlMapConfig.xml 解析出来的内容
    • MappedStatement:映射配置类,存放 mapper.xml 解析出来的内容
  • 解析配置文件:dom4J
    • 创建类:SqlSessionFactoryBuilder,方法 build(InputStream in)
    • 使用 dom4j 解析配置文件,将解析出来的内容封装到容器对象中
    • 创建 SqlSessionFactory 对象;生产 SqlSession 会话对象(工厂模式)
  • 创建 SqlSessionFactory 接口及实现类 DefaultSqlSessionFactory
    • openSession() 生产 SqlSession
  • 创建 SqlSession 接口及实现类 DefaultSqlSession ,定义对数据库的 CURD 操作
    • selectList()、selectOne()、update()、delete()
  • 创建 Executor 接口及实现类 SimpleExecutor
    • query():执行的就是 JDBC 代码

四、自定义框架实现使用端

1.使用端项目目录

在这里插入图片描述

2.数据库表结构

在这里插入图片描述

3.新建 Maven 工程

命名为 IPersistence_test,pom.xml 文件中导入 自定义框架持久层框架 的依赖

<dependencies>
    <dependency>
        <groupId>com.mfc</groupId>
        <artifactId>IPersistence</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
</dependencies>
4.编写实体类

编写实体类,用于封装数据

public class User {
    private int id;
    private String username;
    // set、get、toString 方法省略。。。
}
5.编写SqlMapConfig.xml:

存放数据库配置信息和 mapper.xml 的全路径:数据库驱动、数据库连接URL、账户、密码

<configuration>
    <!-- 数据库连接信息 -->
    <dataSource>
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
    </dataSource>
    <!-- 引入SQL 配置信息 -->
    <mapper resource="UserMapper.xml"></mapper>
</configuration>
6.编写mapper.xml

存放SQL 配置信息:SQL 语句,参数类型,返回值类型

<mapper namespace="user">
    <select id="selectList" resultType="com.mfc.entity.User" paramterType="com.mfc.entity.User">
        select * from user
    </select>
    <select id="selectOne" resultType="com.mfc.entity.User" paramterType="com.mfc.entity.User">
        select * from user where id=#{id} and username=#{username}
    </select>
</mapper>
7.测试代码
public class Test {
    public static void main(String[] args) throws Exception {
        InputStream inputStream = Resource.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        User user = new User();
        user.setId(1);
        user.setUsername("zhangsan");
        User user1 = sqlSession.selectOne("user.selectOne",user);
        System.out.println(user1);

        List<User> list = sqlSession.selectList("user.selectList");
        for (User user2 : list) {
            System.out.println(user2);
        }
        sqlSession.close();
    }
}
8.测试效果

在这里插入图片描述

五、自定义框架实现

1.自定义框架目录

在这里插入图片描述

2.新建Module

新建Module,命名为 IPersistence,pom.xml 文件中导入 相关依赖

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <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>
<dependencies>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.17</version>
    </dependency>
    <dependency>
        <groupId>c3p0</groupId>
        <artifactId>c3p0</artifactId>
        <version>0.9.1.2</version>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.12</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.10</version>
    </dependency>
    <dependency>
        <groupId>dom4j</groupId>
        <artifactId>dom4j</artifactId>
        <version>1.6.1</version>
    </dependency>
    <dependency>
        <groupId>jaxen</groupId>
        <artifactId>jaxen</artifactId>
        <version>1.1.6</version>
    </dependency>
</dependencies>
3.创建 Resource 类

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

public class Resource {
    public static InputStream getResourceAsStream(String path){
        InputStream inputStream = Resource.class.getClassLoader().getResourceAsStream(path);
        return inputStream;
    }
}
4.创建两个JavaBean(容器对象)

存放的就是对配置文件解析出来的内容

  1. Configuration:核心配置类,封装 SqlMapConfig.xml 解析出来的内容(数据库配置信息、mapper配置信息)

    public class Configuration {
        private DataSource dataSource;  // 封装数据库连接
        private Map<String,MappedStatement> mappedStatementMap = new HashMap<>();
        // set、get方法省略。。。
    }
    
  2. MappedStatement:映射配置类,封装 mapper.xml 解析出来的内容

    /**
    <select id="selectList" resultType="com.mfc.entity.User" paramterType="com.mfc.entity.User">
         select * from user
    </select>
    * */
    public class MappedStatement {
        // Id 值
        private String id;
        // SQL 语句
        private String sql;
        // 返回数据类型
        private Class<?> resultType;
        // 传入参数类型
        private Class<?> paramterType;
        // set、get方法省略。。。
    }
    
5.解析配置文件:dom4J
  1. 创建类:SqlSessionFactoryBuilder,编写方法 build(InputStream in),将封装好的 Configuration.java 的对象传递下去。创建 SqlSessionFactory 对象;生产 SqlSession 会话对象(工厂模式)

    public class SqlSessionFactoryBuilder {
        public SqlSessionFactory build(InputStream inputStream) throws Exception{
            // 读取并封装配置信息
            XmlConfigurationParse xmlConfigurationParse = new XmlConfigurationParse();
            Configuration configuration = xmlConfigurationParse.parseConfiguration(inputStream);
            // 创建 SqlSessionFactory
            SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration);
            return sqlSessionFactory;
        }
    }
    
  2. 使用 dom4j 解析配置文件,将解析出来的内容封装到容器对象中

    • 创建 XmlConfigurationParse,根据 Resource 读取的配置文件字节流解析配置文件SqlMapConfig.xml 和 Mapper.xml ,并将读取到的配置信息封装到 Configuration.java 的对象中

      public class XmlConfigurationParse {
      
          public Configuration parseConfiguration(InputStream inputStream) throws Exception{
              Configuration configuration = new Configuration();
      
              // 获取 SqlMapConfig.xml 文档
              Document document = new SAXReader().read(inputStream);
              // 获取 SqlMapConfig.xml 文档中的根标签<configuration>
              Element rootElement = document.getRootElement();
              // 获取根标签下的所有 <property> 标签
              List<Element> propertyElementList = rootElement.selectNodes("//property");
              Properties properties = new Properties();
              // 遍历 <property> 标签中的 name 和 value 属性,并获取他们的值,存放在properties集合中
              for (Element propertyElement : propertyElementList) {
                  String name = propertyElement.attributeValue("name");
                  String value = propertyElement.attributeValue("value");
                  properties.setProperty(name,value);
              }
              // 使用从 <property> 读取到的值(数据库连接信息)创建数据库连接池(这里用 C3P0)
              ComboPooledDataSource pool = new ComboPooledDataSource();
              pool.setDriverClass(properties.getProperty("driverClass"));
              pool.setJdbcUrl(properties.getProperty("url"));
              pool.setUser(properties.getProperty("user"));
              pool.setPassword(properties.getProperty("password"));
              // 将数据库连接池封装进 configuration
              configuration.setDataSource(pool);
      
              // 获取 SqlMapConfig.xml 文档中的根标签下的所有<mapper>标签
              List<Element> mapperElementList = rootElement.selectNodes("//mapper");
              // 遍历所有 <mapper> 标签,并解析 <mapper> 标签引入的 mapper.xml文件,然后封装到configuration
              for (Element mapperElement : mapperElementList) {
                  String mapperPath = mapperElement.attributeValue("resource");
                  InputStream mapperInputStream = Resource.getResourceAsStream(mapperPath);
                  XmlMappedStatementParse xmlMappedStatementParse = new XmlMappedStatementParse(configuration);
                  xmlMappedStatementParse.parseMapper(mapperInputStream);
              }
              return configuration;
          }
      }
      
    • 创建 XmlMappedStatementParse,解析 Mapper.xml ,并将读取到的配置信息封装到 Configuration.java 的对象中

      public class XmlMappedStatementParse {
      
          private Configuration configuration;
          public XmlMappedStatementParse(Configuration configuration){
              this.configuration = configuration;
          }
          public void parseMapper(InputStream inputStream) throws Exception{
              Document document = new SAXReader().read(inputStream);
              Element rootElement = document.getRootElement();
              String namespace = rootElement.attributeValue("namespace");
              List<Element> selectElementList = rootElement.selectNodes("//select");
              for (Element selectElement : selectElementList) {
                  String id = selectElement.attributeValue("id");
                  String sql = selectElement.getTextTrim();
                  String resultTypeStr = selectElement.attributeValue("resultType");
                  String paramterTypeStr = selectElement.attributeValue("paramterType");
                  Class<?> resultType = getClassType(resultTypeStr);
                  Class<?> paramterType = getClassType(paramterTypeStr);
                  MappedStatement mappedStatement = new MappedStatement();
                  mappedStatement.setSql(sql);
                  mappedStatement.setId(id);
                  mappedStatement.setResultType(resultType);
                  mappedStatement.setParamterType(paramterType);
                  // 使用 namespace.statementId 作为 唯一标识,区分Mapper.xml中的多个CRUD标签
                  String key = namespace + "." + id;
                  configuration.getMappedStatementMap().put(key,mappedStatement);
              }
          }
          private Class<?> getClassType(String name) throws Exception{
              return Class.forName(name);
          }
      }
      
6.创建 SqlSessionFactory

创建SqlSessionFactory 接口及实现类 DefaultSqlSessionFactory

  1. SqlSessionFactory 接口

    public interface SqlSessionFactory {
        public SqlSession openSession();
    }
    
  2. DefaultSqlSessionFactory 类实现 SqlSessionFactory 接口,使用 openSession() 生产 SqlSession 。将封装好的 Configuration.java 的对象传递下去(传递给 SqlSession)

    public class DefaultSqlSessionFactory implements SqlSessionFactory {
        private Configuration configuration;
        public DefaultSqlSessionFactory(Configuration configuration){
            this.configuration = configuration;
        }
        @Override
        public SqlSession openSession() {
            SqlSession sqlSession = new DefaultSqlSession(configuration);
            return sqlSession;
        }
    }
    
7.创建 SqlSession

创建 SqlSession 接口及实现类 DefaultSqlSession ,定义对数据库的 CURD 操作:selectList()、selectOne()、update()、delete()。这里 SqlSession 并不是真正干活的类,我们再创建 Executor 去获取数据库连接、执行SQL、封装返回结果

  1. SqlSession 接口

    public interface SqlSession {
        public <E> List<E> selectList(String statementId,Object... params);
        public <T> T selectOne(String statementId,Object... params);
        public void close() throws SQLException; //关闭数据库连接
    }
    
  2. DefaultSqlSession 类实现 SqlSession 接口。

    public class DefaultSqlSession implements SqlSession {
        private Configuration configuration;
        private Executor executor = new SimpleExecutor();
        public DefaultSqlSession(Configuration configuration){
            this.configuration = configuration;
        }
        @Override
        public <E> List<E> selectList(String statementId, Object... params) {
            List<E> list = executor.query(configuration,statementId,params);
            return list;
        }
        @Override
        public <T> T selectOne(String statementId, Object... params) {
            List<Object> list = selectList(statementId, params);
            if (list.size() == 1){
                return (T) list.get(0);
            }else{
                throw new RuntimeException("查询结果为空或者查询出多条结果");
            }
        }
        @Override
        public void close() throws SQLException {
            executor.close();
        }
    }
    
8.创建 Executor 接口

创建 Executor 接口及实现类 SimpleExecutor,query():执行的就是 JDBC 代码。其中 TokenHandler 接口、ParameterMappingTokenHandler 类、GenericTokenParser 类都是直接从 MyBatis 源码中 copy 出来的。

  1. Executor 接口

    	public interface Executor {
            public <E> List<E> query(Configuration configuration, String statementId, Object[] params);
            public void close() throws SQLException;
        }
    
  2. SimpleExecutor 类实现 Executor 接口

    public class SimpleExecutor implements Executor {
        private Connection connection = null;
        @Override
        public <E> List<E> query(Configuration configuration,
                                 String statementId, Object[] params){
            List<Object> returnList = new ArrayList<>();
            try {
                // 获取数据库连接
                connection = configuration.getDataSource().getConnection();
                // 获取 MappedStatement 对象
                MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
                // 获取 sql
                String sql = mappedStatement.getSql();
                // 解析 SQL(将Sql中的 #{id} 替换成 ?,将#{id}中的id封装起来)
                BoundSql boundSql = getBoundSql(sql);
                // 获取预处理对象 preparedStatement
                PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
                // 获取 sql 中的参数
                List<ParameterMapping> parameter = boundSql.getParameterMappings();
                // 入参类型
                Class<?> parameterType = mappedStatement.getParamterType();
                // 给预处理语句赋值
                for (int i = 0; i < parameter.size(); i++) {
                    // 通过反射获取参数属性
                    Field field = parameterType.getDeclaredField(parameter.get(i).getContent());
                    // 暴力修改
                    field.setAccessible(true);
                    // field.get(params[0]):方法返回指定对象上由此Field表示的字段的值
                    Object object = field.get(params[0]);
                    // 给解析后的 SQL 预处理对象 preparedStatement 的占位符赋值
                    preparedStatement.setObject(i+1,object);
                }
                // 获取返回结果
                ResultSet resultSet = preparedStatement.executeQuery();
                // 返回类型
                Class<?> resultType = mappedStatement.getResultType();
                // 封装返回结果集
                while (resultSet.next()){
                    // 获取返回数据的元数据,ResultSetMetaData中可以获取到ResultSet中列的名称和类型信息
                    ResultSetMetaData metaData = resultSet.getMetaData();
                    // 每条数据有几列
                    int cloumnCount = metaData.getColumnCount();
                    // 获取一个返回值的对象
                    Object o = resultType.newInstance();
                    for (int i = 0;i < cloumnCount; i++){
                        // 获取返回数据的属性名
                        String cloumnName = metaData.getColumnName(i + 1);
                        // 根据返回数据的列名获取这一列对应的值,属性值
                        Object cloumnValue = resultSet.getObject(cloumnName);
                        // 创建属性描述器,为属性生成读写方法
                        PropertyDescriptor propertyDescriptor = new PropertyDescriptor(cloumnName,resultType);
                        // 获取用于写入属性值的方法
                        Method method = propertyDescriptor.getWriteMethod();
                        // 执行用于写入属性值的方法,将属性值赋给返回值对象中的 cloumnName 属性
                        method.invoke(o,cloumnValue);
                    }
                    returnList.add(o);
                }
            }catch (Exception e){
                e.printStackTrace();
            }
            return (List<E>) returnList;
        }
    
        @Override
        public void close() throws SQLException {
            // 关闭数据库连接
            connection.close();
        }
    
        private BoundSql getBoundSql(String sql){
            /*
            * 标记处理类:主要是配合通用标记解析器GenericTokenParser类完成对配置文件等的
            * 解析工作,其中 TokenHandler 主要完成处理
            * */
            ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
            /*
            * GenericTokenParser:通用的标记解析器,完成了代码片段中占位符的解析,然后再根据给定的标记处理器(TokenHandler)来进行表达式的处理
            * 三个参数:
            *   openToken:开始标记
            *   closeToken:结束标记
            *   handler:标记处理器
            * */
            GenericTokenParser genericTokenParser = new GenericTokenParser("#{","}",parameterMappingTokenHandler);
            String sqlText = genericTokenParser.parse(sql);
            List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
            BoundSql boundSql = new BoundSql(sqlText,parameterMappings);
            return boundSql;
        }
    }
    
  3. SimpleExecutor 中使用的 BoundSql 对象,此对象封装解析后的 SQL 和 解析出来的参数

    public class BoundSql {
        // 解析后的 SQL
        private String sqlText;
        // 解析出来的参数
        private List<ParameterMapping> parameterMappings;
    
        public BoundSql(String sqlText, List<ParameterMapping> parameterMappings) {
            this.sqlText = sqlText;
            this.parameterMappings = parameterMappings;
        }
        // set、get方法省略。。。
    }
    
  4. ParameterMapping ,用于封装解析出来的参数名称,每一个 ParameterMapping 对象对应一个参数名称

    public class ParameterMapping {
        private String content;
    
        public ParameterMapping(String content) {
            this.content = content;
        }
        // set、get 方法省略
    }
    
  5. SimpleExecutor 中使用的 ParameterMappingTokenHandler:标记处理类,主要是配合通用标记解析器GenericTokenParser类完成对配置文件等的解析工作,其中 TokenHandler 主要完成处理

    • TokenHandler 接口

      public interface TokenHandler {
        String handleToken(String content);
      }
      
    • ParameterMappingTokenHandler 实现 TokenHandler 接口

      public class ParameterMappingTokenHandler implements TokenHandler {
         private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();
         // context是参数名称 #{id} #{username}
         public String handleToken(String content) {
            parameterMappings.add(buildParameterMapping(content));
            return "?";
         }
         private ParameterMapping buildParameterMapping(String content) {
            ParameterMapping parameterMapping = new ParameterMapping(content);
            return parameterMapping;
         }
         public List<ParameterMapping> getParameterMappings() {
            return parameterMappings;
         }
         public void setParameterMappings(List<ParameterMapping> parameterMappings) {
            this.parameterMappings = parameterMappings;
         }
      }
      
  6. SimpleExecutor 中使用的 GenericTokenParser:通用的标记解析器,完成了代码片段中占位符的解析,然后再根据给定的标记处理器(TokenHandler)来进行表达式的处理。GenericTokenParser 的构造器有三个参数(openToken:开始标记;closeToken:结束标记;handler:标记处理器)

    public class GenericTokenParser {
    
      private final String openToken; //开始标记
      private final String closeToken; //结束标记
      private final TokenHandler handler; //标记处理器
    
      public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
        this.openToken = openToken;
        this.closeToken = closeToken;
        this.handler = handler;
      }
    
      /**
       * 解析${}和#{}
       * @param text
       * @return
       * 该方法主要实现了配置文件、脚本等片段中占位符的解析、处理工作,并返回最终需要的数据。
       * 其中,解析工作由该方法完成,处理工作是由处理器handler的handleToken()方法来实现
       */
      public String parse(String text) {
        // 验证参数问题,如果是null,就返回空字符串。
        if (text == null || text.isEmpty()) {
          return "";
        }
    
        // 下面继续验证是否包含开始标签,如果不包含,默认不是占位符,直接原样返回即可,否则继续执行。
        int start = text.indexOf(openToken, 0);
        if (start == -1) {
          return text;
        }
    
       // 把text转成字符数组src,并且定义默认偏移量offset=0、存储最终需要返回字符串的变量builder,
        // text变量中占位符对应的变量名expression。判断start是否大于-1(即text中是否存在openToken),如果存在就执行下面代码
        char[] src = text.toCharArray();
        int offset = 0;
        final StringBuilder builder = new StringBuilder();
        StringBuilder expression = null;
        while (start > -1) {
         // 判断如果开始标记前如果有转义字符,就不作为openToken进行处理,否则继续处理
          if (start > 0 && src[start - 1] == '\\') {
            builder.append(src, offset, start - offset - 1).append(openToken);
            offset = start + openToken.length();
          } else {
            //重置expression变量,避免空指针或者老数据干扰。
            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] == '\\') {//如果结束标记前面有转义字符时
                // this close token is escaped. remove the backslash and continue.
                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) {
              // close token was not found.
              builder.append(src, start, src.length - start);
              offset = src.length;
            } else {
              //首先根据参数的key(即expression)进行参数处理,返回?作为占位符
              builder.append(handler.handleToken(expression.toString()));
              offset = end + closeToken.length();
            }
          }
          start = text.indexOf(openToken, offset);
        }
        if (offset < src.length) {
          builder.append(src, offset, src.length - offset);
        }
        return builder.toString();
      }
    }
    

六、自定义框架优化

1.自定义框架的问题

通过上面的自定义框架,解决了 JDBC 操作数据库带来的一些问题,例如频繁创建释放数据库连接,硬编码,手动封装返回结果集等问题。但是继续分析上面完成的自定义框架,还存在以下问题:

  1. 每一次调用 SqlSession 的方法时,都需要读取配置文件,获取 SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession。如果在 Dao 层的每一个持久化方法中都写以下模板代码,导致代码重复、臃肿。这一部分可以在使用端进行优化,将其封装成一个静态方法:

    InputStream inputStream = Resource.getResourceAsStream("SqlMapConfig.xml");
    SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
    SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    
  2. 在上面使用端中还是存在硬编码,在调用 SqlSession 的方法时,参数 statementId 还是写死的(硬编码)。这一部分可以使用代理模式来进行优化

    public class Test {
        public static void main(String[] args) throws Exception {
            InputStream inputStream = Resource.getResourceAsStream("SqlMapConfig.xml");
            SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
            SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
            SqlSession sqlSession = sqlSessionFactory.openSession();
            User user = new User();
            user.setId(1);
            user.setUsername("zhangsan");
            User user1 = sqlSession.selectOne("user.selectOne",user);
            System.out.println(user1);
    
            List<User> list = sqlSession.selectList("user.selectList");
            for (User user2 : list) {
                System.out.println(user2);
            }
            sqlSession.close();
        }
    }
    
2.解决自定义框架问题

在自定义持久层框架的项目中使用代理模式优化硬编码问题

  1. 在 SqlSession 接口中添加方法

    public <T> T getMapper(Class<?> clazz);
    
  2. 在 DefaultSqlSession 中添加方法的实现

    public <T> T getMapper(Class<?> clazz) {
        Object obj = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{clazz}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                String methodName = method.getName();
                String className = clazz.getName();
                String key = className + "." + methodName;
                Type type = method.getGenericReturnType();
                // 判断参数是否泛型化
                if (type instanceof ParameterizedType){
                    return selectList(key,args);
                }else{
                    return selectOne(key,args);
                }
            }
        });
        return (T) obj;
    }
    
3.在使用端修改并测试
  1. 此时需要编写一个 Dao 层的接口,无需实现类

    public interface UserDao {
        public List<User> selectList(User user);
        public User selectOne(User user);
    }
    
  2. mapper.xml 中 mapper 标签的 namespace 需要修改成 Dao 层接口 的全限定名

    <mapper namespace="com.mfc.dao.UserDao">
        <select id="selectList" resultType="com.mfc.entity.User" paramterType="com.mfc.entity.User">
            select * from user
        </select>
        <select id="selectOne" resultType="com.mfc.entity.User" paramterType="com.mfc.entity.User">
            select * from user where id=#{id} and username=#{username}
        </select>
    </mapper>
    
  3. 测试

    public static void main(String[] args) throws Exception {
        InputStream inputStream = Resource.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        User user = new User();
        user.setId(1);
        user.setUsername("zhangsan");
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        User userMapper = userDao.selectOne(user);
        System.out.println(userMapper);
        sqlSession.close();
    }
    
4.代理模式解析

在这里插入图片描述

在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值