为何要手写自定义持久层框架?
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 Map<String,MappedStatement> mappedStatementMap = 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