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

JDBC问题分析

  1. 数据库配置信息存在硬编码问题。
  2. 频繁创建释放数据库链接
  3. sql语句、设置参数、获取结果集,存在硬编码问题
  4. 手动封装结果集,较为繁琐。

    解决方法

  5. 利用配置文件解决数据库硬编码问题。

  6. 利用连接池解决频繁创建释放问题。
  7. 利用配置文件解决sql语句、设置参数、获取结果集硬编码问题。
  8. 利用反射和内省解决繁琐的封装问题。

    思路

  9. 引入自定义持久层框架的jar包

  10. 提供两部分配置信息:数据库配置信息、sql配置信息(SQL语句,参数类型,返回值类型)
  11. 使用配置文件来提供这两部分配置信息:
    (1)sqlMapConfig.xml : 存放数据库配置信息
    (2)mapper.xml :存放sql配置信息
    自定义持久层框架:
    本质就是对JDBC代码进行了封装。
  12. 加载配置文件:根据配置文件的路径,加载配置文件成字节输入流,存储在内存中。创建Resources类,方法:InputSteam getResourceAsSteam(String path)
  13. 创建两个javaBean:存放的就是对配置文件解析出来的内容。
    Configuration:核心配置类,存放sqlMapConfig.xml解析出来的内容。
    MappedStatement:映射配置类,存放mapper.xml解析出来的内容。
  14. 解析配置文件(dom4j):
    创建类:SqlSessionFactoryBuilder,方法:build(InputSteam in)
    (1)使用dom4j解析配置文件,将解析出来的内容封装到容器对象中。
    (2)创建SqlSessionFactory对象;生产sqlSession(会话对象[工厂模式])
  15. 创建sqlSessionFactory接口及实现DefaultSqlSessionFactory。
    第一:openSession():生产sqlSession
  16. 创建SqlSession接口及实现DefaultSqlSession。定义对数据库CRUD的操作。
  17. 创建Executer接口及实现类SimpleExecuter实现类。
    Query(Configuration,MappedStatement,Object… params):执行的就是JDBC代码

编写测试类

  1. 引入自定义持久层框架的依赖
  1. <!--引入自定义持久层框架的依赖-->
  2. <dependencies>
  3. <dependency>
  4. <groupId>com.learn</groupId>
  5. <artifactId>IPersistence</artifactId>
  6. <version>1.0-SNAPSHOT</version>
  7. </dependency>
  8. </dependencies>
  1. 创建sqlMapConfig.xml
  1. <configuration>
  2. <!-- 数据库配置信息-->
  3. <dataSource>
  4. <propewrty name="driverClass" value="com.mysql.jdbc.Driver"></propewrty>
  5. <propewrty name="jdbcUrl" value="jdbc:mysql:///blog"></propewrty>
  6. <propewrty name="username" value="root"></propewrty>
  7. <propewrty name="password" value="root"></propewrty>
  8. </dataSource>
  9. <!-- 存放mapper.xml的全路径-->
  10. <mapper resource="UserMapper.xml"></mapper>
  11. </configuration>
  1. 创建UserMapper.xml
    注意:namespace.id作为映射xml的唯一标识。例如:user.selectUserList
  1. <mapper namespace="user">
  2. <select id="selectUserList" resultType="com.learn.pojo.User">
  3. select * from t_user
  4. </select>
  5. <select id="selectUserInfo" resultType="com.learn.pojo.User" paramterType="com.learn.pojo.User">
  6. select * from t_user where PK_ID= #{id} AND USER_NAME = #{userName}
  7. </select>
  8. </mapper>

Resources类定义

新建一个模块,封装JDBC的代码。
创建一个Resources类

  1. public class Resources {
  2. // 根据配置文件的路径,将配置文件加载成字节输入流,存储在内存中
  3. public static InputStream getResourceAsSteam(String path){
  4. InputStream resourceAsStream = Resources.class.getClassLoader().getResourceAsStream(path);
  5. return resourceAsStream;
  6. }
  7. }

容器对象定义

  1. 定义Configuration容器对象
  1. public class Configuration {
  2. private DataSource dataSource;
  3. /*
  4. * key: statementid value:封装好的mappedStatement对象
  5. * */
  6. Map<String,MappedStatement> mappedStatementMap = new HashMap<>();
  7. public DataSource getDataSource() {
  8. return dataSource;
  9. }
  10. public void setDataSource(DataSource dataSource) {
  11. this.dataSource = dataSource;
  12. }
  13. public Map<String, MappedStatement> getMappedStatementMap() {
  14. return mappedStatementMap;
  15. }
  16. public void setMappedStatementMap(Map<String, MappedStatement> mappedStatementMap) {
  17. this.mappedStatementMap = mappedStatementMap;
  18. }
  19. }
  1. 定义MappedStatement映射对象
  1. public class Configuration {
  2. private DataSource dataSource;
  3. /*
  4. * key: statementid value:封装好的mappedStatement对象
  5. * */
  6. Map<String,MappedStatement> mappedStatementMap = new HashMap<>();
  7. public DataSource getDataSource() {
  8. return dataSource;
  9. }
  10. public void setDataSource(DataSource dataSource) {
  11. this.dataSource = dataSource;
  12. }
  13. public Map<String, MappedStatement> getMappedStatementMap() {
  14. return mappedStatementMap;
  15. }
  16. public void setMappedStatementMap(Map<String, MappedStatement> mappedStatementMap) {
  17. this.mappedStatementMap = mappedStatementMap;
  18. }
  19. }

解析配置文件mapper.xml 和 数据库连接配置文件sqlMapConfig.xml

  1. 实现DefaultSqlSessionFactory的build()方法,返回一个SqlSessionFactory接口对象
  1. public class SqlSessionFactoryBuilder {
  2. public SqlSessionFactory build(InputStream in) throws DocumentException, PropertyVetoException {
  3. // 第一:使用dom4j解析配置文件,将解析出来的内容封装到Configuration中
  4. XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
  5. Configuration configuration = xmlConfigBuilder.parseConfig(in);
  6. // 第二:创建sqlSessionFactory对象:工厂类:生产sqlSession:会话对象
  7. DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration);
  8. return defaultSqlSessionFactory;
  9. }
  10. }
  1. 创建类XMLConfigBuilder,对sqlMapConfig.xml配置文件进行解析
  1. public class XMLConfigBuilder {
  2. private Configuration configuration;
  3. public XMLConfigBuilder() {
  4. this.configuration = new Configuration();
  5. }
  6. /**
  7. * 该方法就是使用dom4j对配置文件进行解析,封装Configuration
  8. */
  9. public Configuration parseConfig(InputStream inputStream) throws DocumentException, PropertyVetoException {
  10. Document document = new SAXReader().read(inputStream);
  11. //<configuration>
  12. Element rootElement = document.getRootElement();
  13. List<Element> list = rootElement.selectNodes("//property");
  14. Properties properties = new Properties();
  15. for (Element element : list) {
  16. String name = element.attributeValue("name");
  17. String value = element.attributeValue("value");
  18. properties.setProperty(name,value);
  19. }
  20. ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
  21. comboPooledDataSource.setDriverClass(properties.getProperty("driverClass"));
  22. comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));
  23. comboPooledDataSource.setUser(properties.getProperty("username"));
  24. comboPooledDataSource.setPassword(properties.getProperty("password"));
  25. configuration.setDataSource(comboPooledDataSource);
  26. //mapper.xml解析: 拿到路径--字节输入流---dom4j进行解析
  27. List<Element> mapperList = rootElement.selectNodes("//mapper");
  28. for (Element element : mapperList) {
  29. String mapperPath = element.attributeValue("resource");
  30. InputStream resourceAsSteam = Resources.getResourceAsSteam(mapperPath);
  31. XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
  32. xmlMapperBuilder.parse(resourceAsSteam);
  33. }
  34. return configuration;
  35. }
  36. }
  1. 创建类XMLMapperBuilder,对mapper.xml映射配置文件进行解析
  1. public class XMLMapperBuilder {
  2. private Configuration configuration;
  3. public XMLMapperBuilder(Configuration configuration) {
  4. this.configuration =configuration;
  5. }
  6. public void parse(InputStream inputStream) throws DocumentException {
  7. Document document = new SAXReader().read(inputStream);
  8. Element rootElement = document.getRootElement();
  9. String namespace = rootElement.attributeValue("namespace");
  10. List<Element> list = rootElement.selectNodes("//select");
  11. for (Element element : list) {
  12. String id = element.attributeValue("id");
  13. String resultType = element.attributeValue("resultType");
  14. String paramterType = element.attributeValue("paramterType");
  15. String sqlText = element.getTextTrim();
  16. MappedStatement mappedStatement = new MappedStatement();
  17. mappedStatement.setId(id);
  18. mappedStatement.setResultType(resultType);
  19. mappedStatement.setParamterType(paramterType);
  20. mappedStatement.setSql(sqlText);
  21. String key = namespace+"."+id;
  22. configuration.getMappedStatementMap().put(key,mappedStatement);
  23. }
  24. }
  25. }
  1. 创建sqlSessionFactory对象:工厂类:生产sqlSession:会话对象,返回一个SqlSession的对象
  1. public interface SqlSessionFactory {
  2. public SqlSession openSession();
  3. }
  1. public class DefaultSqlSessionFactory implements SqlSessionFactory {
  2. private Configuration configuration;
  3. public DefaultSqlSessionFactory(Configuration configuration) {
  4. this.configuration = configuration;
  5. }
  6. @Override
  7. public SqlSession openSession() {
  8. return new DefaultSqlSession(configuration);
  9. }
  10. }

会话对象sqlSession类定义

定义SqlSession接口并且实现接口DefaultSqlSession类
实现查询所有数据、根据条件查询耽搁数据、为Dao接口生成代理实现类

  1. public interface SqlSession {
  2. //查询所有
  3. public <E> List<E> selectList(String statementid,Object... params) throws Exception;
  4. //根据条件查询单个
  5. public <T> T selectOne(String statementid,Object... params) throws Exception;
  6. //为Dao接口生成代理实现类
  7. public <T> T getMapper(Class<?> mapperClass);
  8. }
  1. public class DefaultSqlSession implements SqlSession {
  2. private Configuration configuration;
  3. public DefaultSqlSession(Configuration configuration) {
  4. this.configuration = configuration;
  5. }
  6. @Override
  7. public <E> List<E> selectList(String statementid, Object... params) throws Exception {
  8. //将要去完成对simpleExecutor里的query方法的调用
  9. simpleExecutor simpleExecutor = new simpleExecutor();
  10. MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementid);
  11. List<Object> list = simpleExecutor.query(configuration, mappedStatement, params);
  12. return (List<E>) list;
  13. }
  14. @Override
  15. public <T> T selectOne(String statementid, Object... params) throws Exception {
  16. List<Object> objects = selectList(statementid, params);
  17. if(objects.size()==1){
  18. return (T) objects.get(0);
  19. }else {
  20. throw new RuntimeException("查询结果为空或者返回结果过多");
  21. }
  22. }
  23. @Override
  24. public <T> T getMapper(Class<?> mapperClass) {
  25. // 使用JDK动态代理来为Dao接口生成代理对象,并返回
  26. Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
  27. @Override
  28. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  29. // 底层都还是去执行JDBC代码 //根据不同情况,来调用selctList或者selectOne
  30. // 准备参数 1:statmentid :sql语句的唯一标识:namespace.id= 接口全限定名.方法名
  31. // 方法名:findAll
  32. String methodName = method.getName();
  33. String className = method.getDeclaringClass().getName();
  34. String statementId = className+"."+methodName;
  35. // 准备参数2:params:args
  36. // 获取被调用方法的返回值类型
  37. Type genericReturnType = method.getGenericReturnType();
  38. // 判断是否进行了 泛型类型参数化
  39. if(genericReturnType instanceof ParameterizedType){
  40. List<Object> objects = selectList(statementId, args);
  41. return objects;
  42. }
  43. return selectOne(statementId,args);
  44. }
  45. });
  46. return (T) proxyInstance;
  47. }
  48. }

最后调用jdbc底层执行sql,然后封装结果集,将数据进行返回。
创建执行器接口

  1. public interface Executor {
  2. public <E> List<E> query(Configuration configuration,MappedStatement mappedStatement,Object... params) throws Exception;
  3. }

实现query方法

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

解析sql的工具类

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

BoundSql工具类,存储xml中解析过后的sql字符串

  1. public class BoundSql {
  2. private String sqlText; //解析过后的sql
  3. private List<ParameterMapping> parameterMappingList = new ArrayList<>();
  4. public BoundSql(String sqlText, List<ParameterMapping> parameterMappingList) {
  5. this.sqlText = sqlText;
  6. this.parameterMappingList = parameterMappingList;
  7. }
  8. public String getSqlText() {
  9. return sqlText;
  10. }
  11. public void setSqlText(String sqlText) {
  12. this.sqlText = sqlText;
  13. }
  14. public List<ParameterMapping> getParameterMappingList() {
  15. return parameterMappingList;
  16. }
  17. public void setParameterMappingList(List<ParameterMapping> parameterMappingList) {
  18. this.parameterMappingList = parameterMappingList;
  19. }
  20. }

Client端运行测试

  1. 实体类User
  1. public class User {
  2. private Integer id;
  3. private String username;
  4. public Integer getId() {
  5. return id;
  6. }
  7. public void setId(Integer id) {
  8. this.id = id;
  9. }
  10. public String getUsername() {
  11. return username;
  12. }
  13. public void setUsername(String username) {
  14. this.username = username;
  15. }
  16. @Override
  17. public String toString() {
  18. return "User{" +
  19. "id=" + id +
  20. ", username='" + username + '\'' +
  21. '}';
  22. }
  23. }
  1. IUserDao类
  1. public interface IUserDao {
  2. //查询所有用户
  3. public List<User> findAll() throws Exception;
  4. //根据条件进行用户查询
  5. public User findByCondition(User user) throws Exception;
  6. }
  1. 测试运行
  1. public class IPersistenceTest {
  2. @Test
  3. public void test() throws Exception {
  4. InputStream resourceAsSteam = Resources.getResourceAsSteam("sqlMapConfig.xml");
  5. SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsSteam);
  6. SqlSession sqlSession = sqlSessionFactory.openSession();
  7. //调用
  8. User user = new User();
  9. user.setId(1);
  10. user.setUsername("张三");
  11. /* User user2 = sqlSession.selectOne("user.selectOne", user);
  12. System.out.println(user2);*/
  13. /* List<User> users = sqlSession.selectList("user.selectList");
  14. for (User user1 : users) {
  15. System.out.println(user1);
  16. }*/
  17. IUserDao userDao = sqlSession.getMapper(IUserDao.class);
  18. List<User> all = userDao.findAll();
  19. for (User user1 : all) {
  20. System.out.println(user1);
  21. }
  22. }
  23. }