您现在的位置是:亿华云 > 应用开发

长文干货|手写自定义持久层框架!

亿华云2025-10-03 11:41:28【应用开发】0人已围观

简介为何要手写自定义持久层框架?1.JDBC 编码的弊端会造成硬编码问题(无法灵活切换数据库驱动) 频繁创建和释放数据库连接造成系统资源浪费 影响系统性能 sql 语句存在硬编码,造成代码不

 为何要手写自定义持久层框架?长文

1.JDBC 编码的弊端

会造成硬编码问题(无法灵活切换数据库驱动) 频繁创建和释放数据库连接造成系统资源浪费 影响系统性能 sql 语句存在硬编码,造成代码不易维护,实际应用中 sql 变化可能较大,变动 sql 需要改 Java 代码 使用 preparedStatement 向占有位符号传参数存在硬编码, 因 sql 语句的 where 条件不确定甚至没有where条件,修改 sql 还要修改代码 系统不易维护 对结果集解析也存在硬编码, sql变化导致解析代码变化

2.更有助于读 mybatis 持久层框架源码

JDBC代码

public class jdbcConnection {      private static Connection connection = null;     private static PreparedStatement preparedStatement = null;     private static ResultSet resultSet = null;     public static void main(String[] args) {          try {              // 加载数据库驱动             Class.forName("com.mysql.jdbc.Driver");             // 通过驱动管理类获取数据库连接             connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/huodd", "root", "1234");             // 定义sql语句 ? 表示占位符             String sql = "select id,username from user where id = ?";             // 获取预处理对象 statement             PreparedStatement preparedStatement = (PreparedStatement) connection.prepareStatement(sql);             // 设置参数 第一个参数为 sql 语句中参数的序号(从1开始) 第二个参数为 设置的参数值             preparedStatement.setInt(1, 1);             // 向数据库发出sql执行查询 查询出结果集             resultSet = preparedStatement.executeQuery();             // 遍历查询结果集             while (resultSet.next()) {                  int id = resultSet.getInt("id");                 String username = resultSet.getString("username");                 // 封装对象                 User user = new User();                 user.setId(id);                 user.setUsername(username);                 System.out.println(user);             }         } catch (Exception ex) {              ex.printStackTrace();         } finally {              try {                  // 释放资源                 if (resultSet != null) {                      resultSet.close();                 }                 if (preparedStatement != null) {                      preparedStatement.close();                 }                 if (connection != null) {                      connection.close();                 }             } catch (Exception ex) {                  ex.printStackTrace();             }         }     } }

解决问题的思路

数据库频繁创建连接、释放资源 -> 连接池 sql语句及参数硬编码 -> 配置文件 手动解析封装结果集 -> 反射、干货内省

编码前思路整理

1.创建、手写读取配置文件

sqlMapConfig.xml 存放数据库配置信息 userMapper.xml :存放sql配置信息 根据配置文件的自定路径,加载配置文件成字节输入流,义持存储在内存中Resources#getResourceAsStream(String path) 创建两个JavaBean存储配置文件解析出来的久层内容 Configuration :核心配置类 ,存放 sqlMapConfig.xml解析出来的框架内容 MappedStatement:映射配置类:存放mapper.xml解析出来的内容

2.解析配置文件(使用dom4j)

创建类:SqlSessionFactoryBuilder#build(InputStream in) -> 设计模式之构建者模式 使用dom4j解析配置文件,将解析出来的长文内容封装到容器对象(JavaBean)中

3.创建 SqlSessionFactory 接口及实现类DefaultSqlSessionFactory

SqlSessionFactory对象,生产sqlSession会话对象 -> 设计模式之工厂模式

4.创建 SqlSession接口及实现类DefaultSqlSession

定义对数据库的干货CRUD操作 selectList() selectOne() update() delete()

5.创建Executor接口及实现类SimpleExecutor实现类

query(Configuration configuration, MappedStatement mapStatement, Object... orgs) 执行的就是JDBC代码

6.测试代码

用到的设计模式

构建者模式 工厂模式 代理模式

进入编码

1.创建、读取配置文件

sqlMapConfig.xml 存放数据库配置信息

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

userMapper.xml 存放sql配置信息

<mapper namespace="user">     <!-- sql 的手写唯一标识: namespace.id 组成 => statementId 如 当前的为 user.selectList -->     <select id="selectList" resultType="com.huodd.pojo.User" paramterType="com.huodd.pojo.User">         select * from user     </select>     <select id="selectOne" paramterType="com.huodd.pojo.User" resultType="com.huodd.pojo.User">         select * from user where id = #{ id} and username =#{ username}     </select> </mapper>

User.java

public class User {      private Integer id;     private String username;     ... 省略getter setter 方法     ... 省略 toString 方法 } 

pom.xml 中引入依赖

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

创建两个JavaBean对象 用于存储解析的配置文件的内容(Configuration.java、MappedStatement.java)

public class Configuration {      // 数据源     private DataSource dataSource;     //map集合 key:statementId value:MappedStatement     private MapmappedStatementMap = new HashMap<>();     ... 省略getter setter 方法 }

public class MappedStatement {      // id     private String id;     // sql 语句     private String sql;     // 参数值类型     private Class<?自定> paramterType;     // 返回值类型     private Class<?> resultType;    ... 省略getter setter 方法 } 

创建Resources工具类 并编写静态方法getResourceAsSteam(String path)

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

2.解析配置文件(使用dom4j)

创建 SqlSessionFactoryBuilder类 并添加 build 方法

public class SqlSessionFactoryBuilder {      public SqlSessionFactory build (InputStream in) throws DocumentException, PropertyVetoException, ClassNotFoundException {          // 1. 使用 dom4j 解析配置文件 将解析出来的内容封装到Configuration中         XMLConfigerBuilder xmlConfigerBuilder = new XMLConfigerBuilder();         // configuration 是已经封装好了sql信息和数据库信息的对象         Configuration configuration = xmlConfigerBuilder.parseConfig(in);         // 2. 创建 SqlSessionFactory 对象  工厂类 主要是生产sqlSession会话对象         DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration);         return defaultSqlSessionFactory;     } }  public class XMLConfigerBuilder {      private Configuration configuration;     public XMLConfigerBuilder() {          this.configuration = new Configuration();     }     /**      * 该方法 使用dom4j对配置文件进行解析 封装Configuration      * @param in      * @return      */      public Configuration parseConfig (InputStream in) throws DocumentException, PropertyVetoException, ClassNotFoundException {           Document document = new SAXReader().read(in);          // <configuation>          Element rootElement = document.getRootElement();          List<Element> propertyElements = rootElement.selectNodes("//property");          Properties properties = new Properties();          for (Element propertyElement : propertyElements) {               properties.setProperty(propertyElement.attributeValue("name"), propertyElement.attributeValue("value"));          }          // 连接池          ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();          comboPooledDataSource.setDriverClass(properties.getProperty("driverClass"));          comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));          comboPooledDataSource.setUser(properties.getProperty("user"));          comboPooledDataSource.setPassword(properties.getProperty("password"));          // 填充 configuration          configuration.setDataSource(comboPooledDataSource);          // mapper 部分  拿到路径 -> 字节输入流 -> dom4j进行解析          List<Element> mapperElements = rootElement.selectNodes("//mapper");          XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);          for (Element mapperElement : mapperElements) {               String mapperPath = mapperElement.attributeValue("resource");              InputStream resourceAsStream = Resources.getResourceAsStream(mapperPath);              xmlMapperBuilder.parse(resourceAsStream);          }          return configuration;      }  public class XMLMapperBuilder {      private Configuration configuration;     public XMLMapperBuilder(Configuration configuration) {          this.configuration = configuration;     }     public void parse(InputStream inputStream) throws DocumentException, ClassNotFoundException {          Document document = new SAXReader().read(inputStream);         // <mapper>         Element rootElement = document.getRootElement();         String namespace = rootElement.attributeValue("namespace");         List<Element> select = rootElement.selectNodes("//select");         for (Element element : select) {              // 获取 id 的香港云服务器值             String id = element.attributeValue("id");             String paramterType = element.attributeValue("paramterType");             String resultType = element.attributeValue("resultType");             // 输入参数 class             Class<?> paramterTypeClass = getClassType(paramterType);             // 返回结果 class             Class<?> resultTypeClass = getClassType(resultType);             // sql 语句             String sqlStr = element.getTextTrim();             // 封装 mappedStatement             MappedStatement mappedStatement = new MappedStatement();             mappedStatement.setId(id);             mappedStatement.setParamterType(paramterTypeClass);             mappedStatement.setResultType(resultTypeClass);             mappedStatement.setSql(sqlStr);             // statementId             String key = namespace + "." + id;             // 填充 configuration             configuration.getMappedStatementMap().put(key, mappedStatement);         }     }     private Class<?> getClassType(String paramterType) throws ClassNotFoundException {          Class<?> aClass = Class.forName(paramterType);         return aClass;     } }

3.创建 SqlSessionFactory 接口及实现类DefaultSqlSessionFactory

public interface SqlSessionFactory {      SqlSession openSession(); }

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

4. 创建 SqlSession接口及实现类DefaultSqlSession

public interface SqlSession {      <E> List<E> selectList(String statementId, Object... param) throws Exception;     <T> T selectOne(String statementId, Object... params) throws Exception;     void close() throws SQLException; }  public class DefaultSqlSession implements SqlSession {      private Configuration configuration;     // 处理器对象     private Executor simpleExcutor = new SimpleExecutor();     public DefaultSqlSession(Configuration configuration) {          this.configuration = configuration;     }     @Override     public <E> List<E> selectList(String statementId, Object... param) throws Exception {          // 完成对 simpleExcutor里的query方法的调用         MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);         List<E> list = simpleExcutor.query(configuration, mappedStatement, param);         return 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 void close() throws SQLException {          simpleExcutor.close();     } }

5.创建Executor接口及实现类SimpleExecutor实现类

public interface Executor {      <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... param) throws Exception;     void close() throws SQLException; }  public class SimpleExecutor implements Executor {      private Connection connection = null;     @Override     public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... param) throws Exception {          // 注册驱动 获取连接         connection = configuration.getDataSource().getConnection();         // select * from user where id = #{ id} and username = #{ username}         String sql = mappedStatement.getSql();         // 对 sql 进行处理         BoundSql boundSql = getBoundSql(sql);         // select * from where id = ? and username = ?         String finalSql = boundSql.getSqlText();         // 获取传入参数类对象         Class<?> paramterTypeClass = mappedStatement.getParamterType();         // 获取预处理 preparedStatement 对象         PreparedStatement preparedStatement = connection.prepareStatement(finalSql);         // 设置参数         List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();         for (int i = 0; i < parameterMappingList.size(); i++) {              ParameterMapping parameterMapping = parameterMappingList.get(i);             String name = parameterMapping.getContent();             // 反射  获取某一个属性对象             Field declaredField = paramterTypeClass.getDeclaredField(name);             // 设置暴力访问             declaredField.setAccessible(true);             // 参数传递的值             Object o = declaredField.get(param[0]);             // 给占位符赋值             preparedStatement.setObject(i + 1, o);         }         // 执行sql         ResultSet resultSet = preparedStatement.executeQuery();         // 封装返回结果集         // 获取返回参数类对象         Class<?> resultTypeClass = mappedStatement.getResultType();         ArrayList<E> results = new ArrayList<>();         while (resultSet.next()) {              // 取出 resultSet的元数据             ResultSetMetaData metaData = resultSet.getMetaData();             E o = (E) resultTypeClass.newInstance();             int columnCount = metaData.getColumnCount();             for (int i = 1; i <= columnCount; 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);             }             results.add(o);         }         return results;     }     /**      * 转换sql语句 完成对 #{ } 的解析工作      * 1. 将 #{ } 使用?进行代替      * 2. 解析出 #{ } 里面的值进行存储      *      * @param sql 转换前的原sql      * @return      */     private BoundSql getBoundSql(String sql) {          // 标记处理类: 主要是配合通用解析器 GenericTokenParser 类完成对配置文件等的解析工作 其中TokenHandler 主要完成处理         ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();         // GenericTokenParser: 通用的标记解析器 完成了代码片段中的占位符的解析 然后根据给定的标记处理器( TokenHandler ) 来进行表达式的处理         // 三个参数: 分别为 openToken (开始标记)、 closeToken (结束标记)、义持 handler (标记处理器)         GenericTokenParser genericTokenParse = new GenericTokenParser("#{ ",久层 "}", parameterMappingTokenHandler);         // 解析出来的sql         String parseSql = genericTokenParse.parse(sql);         // #{ } 里面解析出来的参数名称         List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();         BoundSql boundSql = new BoundSql(parseSql, parameterMappings);         return boundSql;     }     @Override     public void close() throws SQLException {          connection.close();     } }  public class BoundSql {      // 解析过后的 sql 语句     private String sqlText;     // 解析出来的参数     private List<ParameterMapping> parameterMappingList = new ArrayList<>();     // 有参构造方便创建时赋值     public BoundSql(String sqlText, List<ParameterMapping> parameterMappingList) {          this.sqlText = sqlText;         this.parameterMappingList = parameterMappingList;     }    ... 省略getter setter 方法 }

6.测试代码

public class IPersistenceTest {      @Test     public void test () throws Exception {          InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");         SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);         SqlSession sqlSession = sessionFactory.openSession();         User user = new User();         user.setId(1);         user.setUsername("bd2star");         User res = sqlSession.selectOne("user.selectOne", user);         System.out.println(res);         // 关闭资源        sqlSession.close()     } }

运行结果如下

User{ id=1, username=bd2star}

测试通过 调整代码

创建 接口 Dao及实现类

public interface IUserDao {      // 查询所有用户     public List<User> selectList() throws Exception;     // 根据条件进行用户查询     public User selectOne(User user) throws Exception; }  public class UserDaoImpl implements IUserDao {      @Override     public List<User> findAll() throws Exception {          InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");         SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);         SqlSession sqlSession = sessionFactory.openSession();         List<User> res = sqlSession.selectList("user.selectList");         sqlSession.close();         return res;     }     @Override     public User findByCondition(User user) throws Exception {          InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");         SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);         SqlSession sqlSession = sessionFactory.openSession();         User res = sqlSession.selectOne("user.selectOne", user);         sqlSession.close();         return res;     } }

调整测试方法

public class IPersistenceTest {      @Test     public void test () throws Exception {          User user = new User();         user.setId(1);         user.setUsername("bd2star");         IUserDao userDao = new UserDaoImpl();         User res = userDao.findByCondition(user);         System.out.println(res);     } }

运行结果如下

User{ id=1, username=bd2star} 

测试通过

7.补充

huodd.sql

--新建数据库 CREATE DATABASE huodd; --使用数据库 use huodd; --创建表 CREATE TABLE `user`  (   `id` int(11) NOT NULL,   `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,   PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact; -- 插入测试数据 INSERT INTO `user` VALUES (1, bd2star); INSERT INTO `user` VALUES (2, bd3star);

用到的工具类

GenericTokenParser.java

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

ParameterMapping.java

public class ParameterMapping {      private String content;     public ParameterMapping(String content) {          this.content = content;     }     ... 省略getter setter 方法 }

ParameterMappingTokenHandler.java

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

TokenHandler.java

public interface TokenHandler {    String handleToken(String content); }

继续优化自定义框架

通过上述自定义框架,我们解决了JDBC操作数据库带来的一些问题,例如频繁创建释放数据库连接,硬编码,手动封装返回结果等问题

但从测试类可以发现新的问题

dao 的实现类存在重复代码 整个操作的过程模板重复 (如创建 SqlSession 调用 SqlSession方法 关闭 SqlSession) dao 的实现类中存在硬编码,如调用 sqlSession 方法时 参数 statementId 的源码库硬编码

解决方案

通过代码模式来创建接口的代理对象

1.添加getMapper方法

删除dao的实现类 UserDaoImpl.java 我们通过代码来实现原来由实现类执行的逻辑

在 SqlSession 中添加 getMapper 方法

public interface SqlSession {     <T> T getMapper(Class<?> mapperClass); } 

2. 实现类实现方法

DefaultSqlSession 类中实现 getMapper 方法

@Override public <T> T getMapper(Class<?> mapperClass) {      // 使用 JDK 动态代理 来为 Dao 接口生成代理对象 并返回     Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{ mapperClass}, new InvocationHandler() {          /**          *          * @param proxy 当前代理对象的引用          * @param method 当前被调用方法的引用          * @param args 传递的参数          * @return          * @throws Throwable          */         @Override         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {              // 底层都还是去执行 JDBC 代码  -> 根据不同情况 调用 selectList() 或者 selectOne()             // 准备参数  1. statmentId sql语句的唯一标识  namespace.id = 接口全限定名.方法名             //          2. params -> args             // 拿到的是方法名 findAll             String methodName = method.getName();             // 拿到该类的全限定类名 com.huodd.dao.IUserDao             String className = method.getDeclaringClass().getName();             String statmentId = className + "." + methodName;             // 获取被调用方法的返回值类型             Type genericReturnType = method.getGenericReturnType();             // 判断是否进行了 泛型类型参数化             if (genericReturnType instanceof ParameterizedType) {                  List<Object> list = selectList(statmentId, args);                 return list;             }             return selectOne(statmentId, args);         }     });     return (T) proxyInstance; }

3.调整mapper.xml配置文件

这里要注意两点

namespace 与 dao 接口的全限定类名保持一致

id 与 dao 接口中定义的方法名保持一致

<mapper namespace="com.huodd.dao.IUserDao">     <!-- sql 的唯一标识: namespace.id 组成 => statementId 如 当前的为 Userselect.List -->     <select id="findAll" resultType="com.huodd.pojo.User" paramterType="com.huodd.pojo.User">         select * from user     </select>     <select id="findByCondition" paramterType="com.huodd.pojo.User" resultType="com.huodd.pojo.User">         select * from user where id = #{ id} and username =#{ username}     </select> </mapper>

4. 进入测试

public class IPersistenceTest {      @Test     public void test () throws Exception {          InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");         SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);         SqlSession sqlSession = sessionFactory.openSession();         User user = new User();         user.setId(1);         user.setUsername("bd2star");   // 此时返回的 userDao 就是代理对象 所以它的类型就是 Proxy         IUserDao userDao = sqlSession.getMapper(IUserDao.class);         // userDao 是代理对象  调用了接口中的 findAll()  代理对象调用接口中任意方法 都会执行 invoke()         List<User> users = userDao.findAll();         System.out.println(users);         User res = userDao.findByCondition(user);         System.out.println(res);     } }

运行结果如下

[User{ id=1, username=bd2star}, User{ id=2, username=bd3star}] User{ id=1, username=bd2star} 

目录结构调整

将代码分为两个模块

提供端(自定义持久层框架-本质就是对JDBC代码的封装) 使用端 (引用持久层框架的jar ) 包含数据库配置信息 包含sql配置信息 包含sql语句 参数类型 返回值类型

项目目录结构最终为

提供端

使用端

源码地址 https://gitee.com/bx2star/mybatis-learning.git

很赞哦!(99734)