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

发布时间:2025-05-15 08:51:01 作者:益华网络 来源:undefined 浏览量(1) 点赞(1)
摘要:为何要手写自定义持久层框架? 1.JDBC 编码的弊端 会造成硬编码问题(无法灵活切换数据库驱动) 频繁创建和释放数据库连接造成系统资源浪费 影响系统性能 sql 语句存在硬编码,造成代码不易维护,实际应用中 sql 变化可能较大,变动 sql 需要改 Java 代码 使用 pr

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

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

二维码

扫一扫,关注我们

声明:本文由【益华网络】编辑上传发布,转载此文章须经作者同意,并请附上出处【益华网络】及本页链接。如内容、图片有任何版权问题,请联系我们进行处理。

感兴趣吗?

欢迎联系我们,我们愿意为您解答任何有关网站疑难问题!

您身边的【网站建设专家】

搜索千万次不如咨询1次

主营项目:网站建设,手机网站,响应式网站,SEO优化,小程序开发,公众号系统,软件开发等

立即咨询 15368564009
在线客服
嘿,我来帮您!