一、JDBC回顾及问题分析

  1. CREATE TABLE `user` (
  2. `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  3. `username` varchar(50) DEFAULT NULL,
  4. `password` varchar(50) DEFAULT NULL,
  5. `birthday` varchar(50) DEFAULT NULL,
  6. PRIMARY KEY (`id`)
  7. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
  1. /**
  2. * jdbc测试类
  3. * @author lipengyu
  4. */
  5. public class JdbcTest {
  6. public static void main(String[] args) {
  7. Connection connection = null;
  8. PreparedStatement preparedStatement = null;
  9. ResultSet resultSet = null;
  10. try {
  11. // 加载数据库驱动
  12. Class.forName("com.mysql.jdbc.Driver");
  13. // 通过驱动管理类获取数据库链接
  14. connection = DriverManager.getConnection("jdbc:mysql:///mybatis1?characterEncoding=utf-8", "root", "");
  15. // 定义sql语句 ?表示占位符
  16. String sql = "select * from user where username = ?";
  17. // 获取预处理statement
  18. preparedStatement = connection.prepareStatement(sql);
  19. // 设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值
  20. preparedStatement.setString(1, "lucy");
  21. // 向数据库发出sql执行查询,查询出结果集
  22. resultSet = preparedStatement.executeQuery();
  23. // 遍历查询结果集
  24. while (resultSet.next()) {
  25. long id = resultSet.getLong("id");
  26. String username = resultSet.getString("username");
  27. //封装User
  28. User user = new User();
  29. user.setId(id);
  30. user.setUsername(username);
  31. System.out.println(user);
  32. }
  33. } catch (Exception e) {
  34. e.printStackTrace();
  35. } finally {
  36. // 释放资源
  37. if (resultSet != null) {
  38. try {
  39. resultSet.close();
  40. } catch (SQLException e) {
  41. e.printStackTrace();
  42. }
  43. }
  44. if (preparedStatement != null) {
  45. try {
  46. preparedStatement.close();
  47. } catch (SQLException e) {
  48. e.printStackTrace();
  49. }
  50. }
  51. if (connection != null) {
  52. try {
  53. connection.close();
  54. } catch (SQLException e) {
  55. e.printStackTrace();
  56. }
  57. }
  58. }
  59. }
  60. }
  • 数据库配置信息存在硬编码问题
    解决:配置文件
  • 频繁创建释放数据库连接
    解决:连接池
  • sql语句、设置参数、获取结果集均存在硬编码问题
    解决:配置文件
    (虽然数据库配置信息和sql配置信息都可以写入配置文件,但是因为数据库配置信息不常变化,而sql配置信息经常变化,所以建议配置文件分开)
  • 手动封装返回结果集,较为繁琐
    解决:反射、内省(https://blog.csdn.net/z714405489/article/details/84650307)

反射是在运行状态把Java类中的各种成分映射成相应的Java类,可以动态的获取所有的属性以及动态调用任意一个方法,强调的是运行状态。
内省(IntroSpector)是Java 语言针对 Bean 类属性、事件的一种缺省处理方法。主要使用方式就是通过类 Introspector 的 getBeanInfo方法 来获取某个对象的 BeanInfo 信息,然后通过 BeanInfo 来获取属性的描述器(PropertyDescriptor),通过这个属性描述器就可以获取某个属性对应的 getter/setter 方法,然后我们就可以通过反射机制来调用这些方法,这就是内省机制。

二、自定义持久层框架思路分析

  • 使用端:(项目):引入自定义持久层框架的jar包
    提供两部分配置信息:数据库配置信息、sql配置信息、参数类型、返回值类型。
    使用配置文件来提供这部分配置信息;
    • (1)sqlMapConfig.xml:存放数据库配置信息,存放mapper.xml的全路径
    • (2)mapper.xml:存放sql配置信息
  • 自定义持久层框架本身:(工程):本质就是对JDBC代码进行了封装
    • (1)加载配置文件:根据配置文件的路径,加载配置文件成字节输入流,存储在内存中。
      创建Resources类 方法:InputStream getResourceAs Stream(String path)
    • (2)创建两个javaBean:(容器对象):存放的就是对配置文件解析出来的内容
      Configuration: 核心配置类:村饭sqlMapperConfig.xml解析出来的内容
      MappedStatement: 映射配置类:存放mapper.xml解析出来的内容
    • (3)解析配置文件:dom4j
      创建类:SqlSessionFactoryBuilder 方法 build(InputStream in)
      • 第一:使用dom4j解析配置文件,将解析出来的内容封装到容器对象中
      • 第二:创建SqlSessionFactory对象;生产sqlSession; 会话对象(工厂模式)
    • (4)创建SqlSessionFactory接口及实现类DefaultSqlSessionFactory
      • 第一:openSession(): 生产sqlSession
    • (5)创建SqlSession接口及实现类DefaultSession
      定义对数据库的crud操作:
      selectList()
      selectOne()
      update()
      delete()
    • (6)创建Executor接口及实现类SimpleExecutor实现类
      query(Configuration, MappedStatement, Object… params); 执行的就是JDBC

      三、IPersistence_Test测试类编写

新建项目IPersistence_test
在resource下新建文件sqlMapConfig.xml

  1. <configuration>
  2. <datasource>
  3. <property name="driverClass" value="com.mysql.jdbc.Driver"/>
  4. <property name="user" value="root"/>
  5. <property name="password" value=""/>
  6. <property name="jdbcUrl" value="jdbc:mysql:///mybatis1"/>
  7. </datasource>
  8. <!-- 存放mapper.xml的全路径-->
  9. <mapper resource="mapper.xml">
  10. <mapper resource="UserMapper.xml"/>
  11. </configuration>

在resource下新建文件UserMapper.xml

  1. <mapper namespace="user">
  2. <!--sql的唯一表示:namespace.id来组成: statementId-->
  3. <select id="selectList" resultType="com.lpy.model.User">
  4. select * from user
  5. </select>
  6. <!--
  7. User user = new User;
  8. user.setId(1);
  9. user.setUsername("zhangsan")
  10. -->
  11. <select id="selectOne" parameterType="com.lpy.model.User" resultType="com.lpy.model.User">
  12. select * from user where id = #{id} and username = #{username}
  13. </select>
  14. </mapper>

创建User实体类

  1. /**
  2. * User实体类
  3. * @author lipengyu
  4. */
  5. @Getter
  6. @Setter
  7. @ToString
  8. public class User {
  9. private Long id;
  10. private String username;
  11. }

四、IPersistence自定义持久层框架编写

新建模块IPersistence
首先为了加载配置文件,在com.lpy.io下新建Resources,将文件读取成字节输入流

  1. /**
  2. * 获取资源类
  3. * @author lipengyu
  4. */
  5. public class Resources {
  6. /**
  7. * 根据配置文件的路径,将配置文件加载成字节输入流,存储在内存中
  8. */
  9. public static InputStream getResourceAsStream(String path) {
  10. return Resources.class.getClassLoader().getResourceAsStream(path);
  11. }
  12. }

修改IPersistence的pom.xml文件

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <modelVersion>4.0.0</modelVersion>
  6. <groupId>com.lpy</groupId>
  7. <artifactId>IPersistence</artifactId>
  8. <version>1.0-SNAPSHOT</version>
  9. <properties>
  10. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  11. <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
  12. <java.version>1.8</java.version>
  13. <maven.compiler.source>1.8</maven.compiler.source>
  14. <maven.compiler.target>1.8</maven.compiler.target>
  15. </properties>
  16. <dependencies>
  17. <dependency>
  18. <groupId>mysql</groupId>
  19. <artifactId>mysql-connector-java</artifactId>
  20. <version>5.1.17</version>
  21. </dependency>
  22. <dependency>
  23. <groupId>c3p0</groupId>
  24. <artifactId>c3p0</artifactId>
  25. <version>0.9.1.2</version>
  26. </dependency>
  27. <dependency>
  28. <groupId>log4j</groupId>
  29. <artifactId>log4j</artifactId>
  30. <version>1.2.12</version>
  31. </dependency>
  32. <dependency>
  33. <groupId>junit</groupId>
  34. <artifactId>junit</artifactId>
  35. <version>4.10</version>
  36. </dependency>
  37. <dependency>
  38. <groupId>dom4j</groupId>
  39. <artifactId>dom4j</artifactId>
  40. <version>1.6.1</version>
  41. </dependency>
  42. <dependency>
  43. <groupId>jaxen</groupId>
  44. <artifactId>jaxen</artifactId>
  45. <version>1.1.6</version>
  46. </dependency>
  47. <dependency>
  48. <groupId>org.projectlombok</groupId>
  49. <artifactId>lombok</artifactId>
  50. <version>1.16.14</version>
  51. </dependency>
  52. </dependencies>
  53. </project>

将IPersistence打包后引入IPersistence_test
修改IPersistence_test 的pom.xml文件

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <modelVersion>4.0.0</modelVersion>
  6. <groupId>com.lpy</groupId>
  7. <artifactId>IPersistence_test</artifactId>
  8. <version>1.0-SNAPSHOT</version>
  9. <!--引入自定义持久层框架的依赖-->
  10. <dependencies>
  11. <dependency>
  12. <groupId>org.projectlombok</groupId>
  13. <artifactId>lombok</artifactId>
  14. <version>1.16.14</version>
  15. </dependency>
  16. <dependency>
  17. <groupId>org.lpy</groupId>
  18. <artifactId>IPersistence</artifactId>
  19. <version>1.0-SNAPSHOT</version>
  20. </dependency>
  21. </dependencies>
  22. </project>

在模块IPersistence_test编写测试类com.lpy.test.IPersistenceTest,每完成一步都可以自测一下

  1. public class IPesistenceTest {
  2. public void test(){
  3. InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
  4. }
  5. }

为了将配置文件解析成对象,我们先创建两个javabean
在IPersistence com.lpy.pojo中新建MappedStatement,主要存放单个sql的信息

  1. /**
  2. * 将mapper.xml中sql标签解析成对象
  3. * @author lipengyu
  4. */
  5. @Getter
  6. @Setter
  7. public class MappedStatement {
  8. /**
  9. * id标识
  10. */
  11. private String id;
  12. /**
  13. * 返回值类型
  14. */
  15. private Class<?> parameterType;
  16. /**
  17. * 参数值类型
  18. */
  19. private Class<?> resultType;
  20. /**
  21. * sql语句
  22. */
  23. private String sql;
  24. public MappedStatement(String id, Class<?> parameterType, Class<?> resultType, String sql) {
  25. this.id = id;
  26. this.parameterType = parameterType;
  27. this.resultType = resultType;
  28. this.sql = sql;
  29. }
  30. }

com.lpy.pojo中新建Configuration,主要存放数据源信息和所有的sql信息

  1. /**
  2. * 将sqlMapConfig.xml解析成对象
  3. * @author lipengyu
  4. */
  5. @Getter
  6. @Setter
  7. public class Configuration {
  8. /**
  9. * 数据源
  10. */
  11. private DataSource dataSource;
  12. /**
  13. * key: statementId value: 封装好的mappedStatement对象
  14. */
  15. private Map<String, MappedStatement> mappedStatementMap = new HashMap<>();
  16. }

com.lpy.sqlSession中新建类SqlSessionFactoryBuilder
开始解析配置文件 (可以暂时忽略第二)

  1. /**
  2. * 解析配置文件以及生产sqlSession
  3. * @author lipengyu
  4. */
  5. public class SqlSessionFactoryBuilder {
  6. private Configuration configuration;
  7. public SqlSessionFactoryBuilder() {
  8. this.configuration = new Configuration();
  9. }
  10. public SqlSessionFactory build(InputStream in) throws DocumentException, PropertyVetoException, ClassNotFoundException {
  11. // 第一:使用dom4j解析配置文件,将解析出来的内容封装到Configuration中
  12. XMLConfigerBuilder builder = new XMLConfigerBuilder(configuration);
  13. Configuration configuration = builder.parseConfig(in);
  14. // 第二:创建sqlSessionFactory对象 工厂类:生产sqlSession:会话对象
  15. return new DefaultSqlSessionFactory(configuration);
  16. }
  17. }

com.lpy.config中新建类XMLConfigerBuilder
首先解析sqlMapConfig.xml

  1. /**
  2. * 解析sqlMapConfig.xml具体实现、解析mapper.xml
  3. * @author lipengyu
  4. */
  5. public class XMLConfigerBuilder {
  6. private Configuration configuration;
  7. public XMLConfigerBuilder(Configuration configuration) {
  8. this.configuration = configuration;
  9. }
  10. /**
  11. * 该方法就是使用dom4j对配置文件解析,封装Configuration
  12. *
  13. * 从当前节点的儿子节点中选择名称为 item 的节点。
  14. * SelectNodes("item")
  15. * 从根节点的儿子节点中选择名称为 item 的节点。
  16. * SelectNodes("/item")
  17. * 从任意位置的节点上选择名称为 item 的节点。要重点突出这个任意位置,它不受当前节点的影响,也就是说假如当前节点是在第 100 层(有点夸张),也可以选择第一层的名称为 item 的节点。
  18. * SelectNodes("//item")
  19. */
  20. public Configuration parseConfig(InputStream in) throws DocumentException, PropertyVetoException, ClassNotFoundException {
  21. Document read = new SAXReader().read(in);
  22. // <configuration>
  23. Element rootElement = read.getRootElement();
  24. List<Element> propertyElements = rootElement.selectNodes("//property");
  25. Properties properties = new Properties();
  26. propertyElements.forEach(element -> {
  27. properties.put(element.attributeValue("name"), element.attributeValue("value"));
  28. });
  29. // 连接池
  30. ComboPooledDataSource dataSource = new ComboPooledDataSource();
  31. dataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));
  32. dataSource.setDriverClass(properties.getProperty("driverClass"));
  33. dataSource.setUser(properties.getProperty("user"));
  34. dataSource.setPassword(properties.getProperty("password"));
  35. configuration.setDataSource(dataSource);
  36. // mapper.xml解析:拿到路径->字节输入流->dom4j进行解析
  37. List<Element> mapperElements = rootElement.selectNodes("//mapper");
  38. XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
  39. for (Element mapperElement : mapperElements) {
  40. String mapperPath = mapperElement.attributeValue("resource");
  41. InputStream resourceAsStream = Resources.getResourceAsStream(mapperPath);
  42. xmlMapperBuilder.parse(resourceAsStream);
  43. }
  44. return configuration;
  45. }
  46. }

com.lpy.config中新建类XMLMapperBuilder
解析mapper.xml

  1. /**
  2. * 解析mapper.xml具体实现
  3. * @author lipengyu
  4. */
  5. public class XMLMapperBuilder {
  6. public Configuration configuration;
  7. public XMLMapperBuilder(Configuration configuration) {
  8. this.configuration = configuration;
  9. }
  10. /**
  11. * 解析mapper.xml
  12. */
  13. public void parse(InputStream resourceAsStream) throws DocumentException, ClassNotFoundException {
  14. Document read = new SAXReader().read(resourceAsStream);
  15. Element rootElement = read.getRootElement();
  16. String namespace = rootElement.attributeValue("namespace");
  17. List<Element> selectElements = rootElement.selectNodes("select");
  18. Map<String, MappedStatement> mappedStatementMap = configuration.getMappedStatementMap();
  19. for (Element selectElement : selectElements) {
  20. String id = selectElement.attributeValue("id");
  21. String parameterType = selectElement.attributeValue("parameterType");
  22. String resultType = selectElement.attributeValue("resultType");
  23. String sql = selectElement.getTextTrim();
  24. // 获取参数类型
  25. Class<?> parameterTypeClass = getClassType(parameterType);
  26. // 获取返回结果类型
  27. Class<?> reusltClassType = getClassType(resultType);
  28. mappedStatementMap.put(namespace + "." + id, new MappedStatement(id, parameterTypeClass, reusltClassType, sql));
  29. }
  30. }
  31. private Class<?> getClassType(String classType) throws ClassNotFoundException {
  32. if (classType != null) {
  33. return Class.forName(classType);
  34. }
  35. return null;
  36. }
  37. }

解析完成将配置信息封装到Configuration中,接下来使用工厂生成sqlSession
在com.lpy.sqlSession下创建接口SqlSessionFactory

  1. /**
  2. * SqlSession工厂
  3. * @author lipengyu
  4. */
  5. public interface SqlSessionFactory {
  6. SqlSession openSession();
  7. }

以及实现类DefaultSqlSessionFactory

  1. /**
  2. * SqlSession工厂 默认实现
  3. * @author lipengyu
  4. */
  5. public class DefaultSqlSessionFactory implements SqlSessionFactory {
  6. private Configuration configuration;
  7. public DefaultSqlSessionFactory(Configuration configuration) {
  8. this.configuration = configuration;
  9. }
  10. @Override
  11. public DefaultSqlSession openSession() {
  12. return new DefaultSqlSession(configuration);
  13. }
  14. }

在com.lpy.sqlSession下创建接口SqlSession

  1. /**
  2. * SqlSession
  3. * @author lipengyu
  4. */
  5. public interface SqlSession {
  6. /**
  7. * 查询所有
  8. */
  9. <E> List<E> selectList(String statementId, Object ...param) throws Exception;
  10. /**
  11. * 查询单个
  12. */
  13. <T> T selectOne(String statementId, Object ...param) throws Exception;
  14. }

以及实现类DefaultSqlSession

  1. /**
  2. * SqlSession默认实现
  3. * @author lipengyu
  4. */
  5. public class DefaultSqlSession implements SqlSession {
  6. private Configuration configuration;
  7. public DefaultSqlSession(Configuration configuration) {
  8. this.configuration = configuration;
  9. }
  10. public Executor simpleExecutor = new SimpleExecutor();
  11. @Override
  12. public <E> List<E> selectList(String statementId, Object... param) throws Exception {
  13. MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
  14. return simpleExecutor.query(configuration, mappedStatement, param);
  15. }
  16. @Override
  17. public <T> T selectOne(String statementId, Object... param) throws Exception {
  18. List<Object> objects = selectList(statementId, param);
  19. if (objects.size() > 1) {
  20. throw new RuntimeException("返回结果过多");
  21. }
  22. return (T) objects.get(0);
  23. }
  24. }

这一步我们通过openSession方法创建了sqlSession以便我们操作数据库,简单封装了基本查询操作,还涉及了Executor,Executor接口就是对jdbc的实现。
在com.lpy.sqlSession下创建接口Executor

  1. /**
  2. * 执行器接口
  3. * @author lipengyu
  4. */
  5. public interface Executor {
  6. <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object[] param) throws Exception;
  7. }

以及实现SimpleExecutor

  1. public class SimpleExecutor implements Executor {
  2. /**
  3. * 查询
  4. */
  5. @Override
  6. public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object[] param) throws SQLException, NoSuchFieldException, IllegalAccessException, IntrospectionException, InstantiationException, InvocationTargetException {
  7. // 1. 注册驱动 获取连接
  8. Connection connection = configuration.getDataSource().getConnection();
  9. // 2. 获取sql语句:select * from user where id = #{id} and username = #{username}
  10. // 转换sql语句:select * from user where id = ? and username = ? , 转换的过程中,还需要对#{}里面的值进行解析存储
  11. String sql = mappedStatement.getSql();
  12. BoundSql boundSql = getBoundSql(sql);
  13. // 3. 获取预处理对象 preparedStatement
  14. PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getBoundSql());
  15. // 4. 设置参数
  16. // 获取参数类型
  17. Class<?> parameterType = mappedStatement.getParameterType();
  18. List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
  19. for (int i = 0; i < parameterMappingList.size(); i++) {
  20. ParameterMapping parameterMapping = parameterMappingList.get(i);
  21. // 获取当前参数的名字
  22. String content = parameterMapping.getContent();
  23. // 通过反射找到该属性
  24. Field declaredField = parameterType.getDeclaredField(content);
  25. // 暴力访问
  26. declaredField.setAccessible(true);
  27. // param[0] 指对象,从该对象取出该属性的值
  28. Object o = declaredField.get(param[0]);
  29. // 设置参数
  30. preparedStatement.setObject(i + 1, o);
  31. }
  32. // 5. 执行sql
  33. ResultSet resultSet = preparedStatement.executeQuery();
  34. // 6. 封装返回结果集
  35. // 获取元数据
  36. ResultSetMetaData metaData = resultSet.getMetaData();
  37. // 获取列的数量
  38. int columnCount = metaData.getColumnCount();
  39. // 获取返回值类型
  40. Class<?> resultType = mappedStatement.getResultType();
  41. List<E> results = new ArrayList<>();
  42. while (resultSet.next()) {
  43. E e = (E) resultType.newInstance();
  44. for (int i = 1; i < columnCount + 1; i++) {
  45. String columnName = metaData.getColumnName(i);
  46. // 使用反射或者内省,根据数据库和实体相关的对应关系,完成封装。
  47. PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultType);
  48. Method writeMethod = propertyDescriptor.getWriteMethod();
  49. // 执行写方法
  50. writeMethod.invoke(e, resultSet.getObject(columnName));
  51. }
  52. results.add(e);
  53. }
  54. return results;
  55. }
  56. /**
  57. * 完成对#{}的解析工作:
  58. * 1. 将#{}使用?进行代替。
  59. * 2. 解析出#{}里面的值进行存储
  60. */
  61. private BoundSql getBoundSql(String sql) {
  62. // 标记处理类:配置标记解析器来完成对占位符的解析处理工作
  63. ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler();
  64. GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
  65. // 解析出来的sql
  66. String parse = parser.parse(sql);
  67. // #{}里面解析出来的参数名称
  68. return new BoundSql(parse, handler.getParameterMappings());
  69. }
  70. }

在SimpleExecutor 中第二步需要转换sql,需要工具类GenericTokenParser、ParameterMapping、ParameterMappingTokenHandler、TokenHandler
在com.lpy.utils下面新建

  1. /**
  2. * @author Clinton Begin
  3. */
  4. public interface TokenHandler {
  5. String handleToken(String content);
  6. }
  1. /**
  2. * @author Clinton Begin
  3. */
  4. public class GenericTokenParser {
  5. /**
  6. * 开始标记
  7. */
  8. private final String openToken;
  9. /**
  10. * 结束标记
  11. */
  12. private final String closeToken;
  13. /**
  14. * 标记处理器
  15. */
  16. private final TokenHandler handler;
  17. public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
  18. this.openToken = openToken;
  19. this.closeToken = closeToken;
  20. this.handler = handler;
  21. }
  22. /**
  23. * 解析${}和#{}
  24. *
  25. * @param text
  26. * @return 该方法主要实现了配置文件、脚本等片段中占位符的解析、处理工作,并返回最终需要的数据。
  27. * 其中,解析工作由该方法完成,处理工作是由处理器handler的handleToken()方法来实现
  28. */
  29. public String parse(String text) {
  30. // 验证参数问题,如果是null,就返回空字符串。
  31. if (text == null || text.isEmpty()) {
  32. return "";
  33. }
  34. // 下面继续验证是否包含开始标签,如果不包含,默认不是占位符,直接原样返回即可,否则继续执行。
  35. int start = text.indexOf(openToken, 0);
  36. if (start == -1) {
  37. return text;
  38. }
  39. // 把text转成字符数组src,并且定义默认偏移量offset=0、存储最终需要返回字符串的变量builder,
  40. // text变量中占位符对应的变量名expression。判断start是否大于-1(即text中是否存在openToken),如果存在就执行下面代码
  41. char[] src = text.toCharArray();
  42. int offset = 0;
  43. final StringBuilder builder = new StringBuilder();
  44. StringBuilder expression = null;
  45. while (start > -1) {
  46. // 判断如果开始标记前如果有转义字符,就不作为openToken进行处理,否则继续处理
  47. if (start > 0 && src[start - 1] == '\\') {
  48. builder.append(src, offset, start - offset - 1).append(openToken);
  49. offset = start + openToken.length();
  50. } else {
  51. //重置expression变量,避免空指针或者老数据干扰。
  52. if (expression == null) {
  53. expression = new StringBuilder();
  54. } else {
  55. expression.setLength(0);
  56. }
  57. builder.append(src, offset, start - offset);
  58. offset = start + openToken.length();
  59. int end = text.indexOf(closeToken, offset);
  60. //存在结束标记时
  61. while (end > -1) {
  62. //如果结束标记前面有转义字符时
  63. if (end > offset && src[end - 1] == '\\') {
  64. // this close token is escaped. remove the backslash and continue.
  65. expression.append(src, offset, end - offset - 1).append(closeToken);
  66. offset = end + closeToken.length();
  67. end = text.indexOf(closeToken, offset);
  68. } else {//不存在转义字符,即需要作为参数进行处理
  69. expression.append(src, offset, end - offset);
  70. offset = end + closeToken.length();
  71. break;
  72. }
  73. }
  74. if (end == -1) {
  75. // close token was not found.
  76. builder.append(src, start, src.length - start);
  77. offset = src.length;
  78. } else {
  79. //首先根据参数的key(即expression)进行参数处理,返回?作为占位符
  80. builder.append(handler.handleToken(expression.toString()));
  81. offset = end + closeToken.length();
  82. }
  83. }
  84. start = text.indexOf(openToken, offset);
  85. }
  86. if (offset < src.length) {
  87. builder.append(src, offset, src.length - offset);
  88. }
  89. return builder.toString();
  90. }
  91. }
  1. @Getter
  2. @Setter
  3. public class ParameterMapping {
  4. private String content;
  5. public ParameterMapping(String content) {
  6. this.content = content;
  7. }
  8. }
  1. /**
  2. * @author Clinton Begin
  3. */
  4. public class ParameterMappingTokenHandler implements TokenHandler {
  5. private List<ParameterMapping> parameterMappings = new ArrayList<>();
  6. // content是参数名称 #{id} #{username}
  7. @Override
  8. public String handleToken(String content) {
  9. parameterMappings.add(buildParameterMapping(content));
  10. return "?";
  11. }
  12. private ParameterMapping buildParameterMapping(String content) {
  13. ParameterMapping parameterMapping = new ParameterMapping(content);
  14. return parameterMapping;
  15. }
  16. public List<ParameterMapping> getParameterMappings() {
  17. return parameterMappings;
  18. }
  19. public void setParameterMappings(List<ParameterMapping> parameterMappings) {
  20. this.parameterMappings = parameterMappings;
  21. }
  22. }

以及转换后封装的boundSql
在com.lpy.config 下面新建

  1. /**
  2. * sql及参数映射
  3. * @author lipengyu
  4. */
  5. @Getter
  6. @Setter
  7. public class BoundSql {
  8. /**
  9. * 解析过后的sql
  10. */
  11. private String boundSql;
  12. /**
  13. * 参数映射
  14. */
  15. private List<ParameterMapping> parameterMappingList;
  16. public BoundSql(String boundSql, List<ParameterMapping> parameterMappingList) {
  17. this.boundSql = boundSql;
  18. this.parameterMappingList = parameterMappingList;
  19. }
  20. }

五、Client端运行测试

  1. /**
  2. * 测试类
  3. * @author lipengyu
  4. */
  5. public class IPesistenceTest {
  6. public static void main(String[] args) throws Exception {
  7. SqlSessionFactory build = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("sqlMapConfig.xml"));
  8. SqlSession sqlSession = build.openSession();
  9. List<User> users = sqlSession.selectList("user.selectList");
  10. System.out.println(users);
  11. User param = new User();
  12. param.setId(1L);
  13. param.setUsername("lucy");
  14. User user = sqlSession.selectOne("user.selectOne", param);
  15. System.out.println(user);
  16. }
  17. }

此处我遇到了个问题,我把User的id属性设置成了Integer类型,结果报错 argument type mismatch,我看了一下我的数据库类型明明是int,去翻mysql的文档,发现unsigned int对应的是long。
image.png
修改后就没问题啦~

六、功能扩展-getMapper方法实现

测试通过后,我们想通常调用查询方法都是通过持久层结果的方式调用,所以我们在IPersistence_test 中 com.lpy.dao 创建IUserDao

  1. /**
  2. * @author lipengyu
  3. */
  4. public interface IUserDao {
  5. List<User> findAll() throws Exception;
  6. User findByCondition(User user) throws Exception;
  7. }

在 com.lpy.dao.impl中创建IUserDaoImpl

  1. /**
  2. * @author lipengyu
  3. */
  4. public class IUserDaoImpl implements IUserDao {
  5. @Override
  6. public List<User> findAll() throws Exception {
  7. SqlSessionFactory build = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("sqlMapConfig.xml"));
  8. SqlSession sqlSession = build.openSession();
  9. return sqlSession.selectList("user.selectList");
  10. }
  11. @Override
  12. public User findByCondition(User param) throws Exception {
  13. SqlSessionFactory build = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("sqlMapConfig.xml"));
  14. SqlSession sqlSession = build.openSession();
  15. return sqlSession.selectOne("user.selectOne", param);
  16. }
  17. }

通过IUserDao调用查询方法

  1. IUserDao dao = new IUserDaoImpl();
  2. System.out.println(dao.findAll());
  3. System.out.println(dao.findByCondition(param));

虽然可以调用成功,但有一些问题我们需要分析一下
自定义持久层框架问题分析

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

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

在SqlSession中新增方法getMapper

  1. <T> T getMapper(Class<?> mapperClass);

在DefaultSqlSession中新增方法实现

  1. @Override
  2. @SuppressWarnings("unchecked")
  3. public <T> T getMapper(Class<?> mapperClass) {
  4. return (T)Proxy.newProxyInstance(mapperClass.getClassLoader(), new Class[]{mapperClass}, (proxy, method, args) -> {
  5. String methodName = method.getName();
  6. String className = method.getDeclaringClass().getName();
  7. // 拼接key
  8. String key = className + "." + methodName;
  9. // 判断是否有泛型
  10. Type genericReturnType = method.getGenericReturnType();
  11. if (genericReturnType instanceof ParameterizedType) {
  12. return selectList(key, args);
  13. }
  14. return selectOne(key, args);
  15. });
  16. }

此处有两个需要注意的点

  1. 如果想使用getMapper方法,需要规范xxxMapper.xml,namespace必须是className,也就是全类名,这样整个statementId,可以通过代理的方式直接调用查询sql,不用额外写DAO层的实现了
  2. 上方判断是否有泛型是取巧的写法,因为查询多个会返回List,单个会返回实体,所以有泛型的就调用selectList,没有的就selectOne。(只是为了实现简单的功能)

之后我们可以注释掉之前写的IUserDaoImpl了,并且要修改一下UserMapper.xml,不过为了保留过程,我新增了NewUserMapper.xml

  1. <mapper namespace="com.lpy.dao.IUserDao">
  2. <select id="findAll" resultType="com.lpy.model.User">
  3. select * from user
  4. </select>
  5. <select id="findByCondition" parameterType="com.lpy.model.User" resultType="com.lpy.model.User">
  6. select * from user where id = #{id} and username = #{username}
  7. </select>
  8. </mapper>

不要忘记在sqlMapConfig.xml中加入配置,引用新的配置文件

  1. <mapper resource = "NewUserMapper.xml"/>

测试一下也是没问题的

  1. IUserDao userDao = sqlSession.getMapper(IUserDao.class);
  2. List<User> all = userDao.findAll();
  3. all.forEach(System.out::println);
  4. User byCondition = userDao.findByCondition(param);
  5. System.out.println(byCondition);

image.png