自定义持久层框架

自定义持久层框架

下图是JDBC引起的一系列问题以及解决办法:

自定义持久层框架

自定义持久层框架设计思路:


使用端(项目):引入自定义持久层框架jar包。

提供两部分配置信息:1,数据库配置信息;2,sql配置信息--(sql语句、参数类型、返回值类型) 解决办法:使用配置文件来提供两部分配置信息: <1>sqlMapConfig.xml:放数据库配置信息;在sqlMapConfig.xml中,其实也可以存放mapper.xml的全路径,方法getResourceAsSteam()可以一次性全部读取; <2>mapper.xml:存放sql配置信息;

自定义持久层框架本身(工程):本质上就是对JDBC代码进行了封装。

(1)加载配置文件,根据配置文件的路径,加载配置文件成字节输入流,存储在内存中;     创建Resource类          方法:getResourceAsSteam(String path)返回 InputSteam;
(2)创建两个javaBean(容器对象):存放的就是对配置文件解析出来的内容,如下:     Configruation核心配置类:存放sqlMapConfig.xml解析出来的内容;     MappedStatement映射配置类:存放mapper.xml解析出来的内容;    
(3)解析配置文件,可以采用dom4j对配置文件进行解析;     创建类:SqlSessionFactoryBuilder,方法:build(InputSteam is)         <1>使用dom4j解析配置文件,将解析出来的内容封装到容器对象中;         <2>创建SqlSessionFactory对象;主要作用就是利用工厂模式生产sqlSession(会话对象)
(4)基于开闭原则创建SqlSessionFactory接口及实现类DefaultSqlSessionFactory     <1>生产sqlSession【openSqlSession()】
(5)创建SqlSession接口及实现类DefaultSession  定义对数据库的crud操作:selectList()selectOne()update()delete()
(6)创建Exeutor接口及实现类SimpleExeutor实现类,执行的就是JDBC代码;     query(Configruation,MappedStatement,Object ... params);

 

创建两个maven工程IPersistence和IPersistence_test

--IPersistence_test引入IPersistence依赖-- ​      <groupId>com.yun</groupId>     <artifactId>IPersistence_test</artifactId>     <version>1.0-SNAPSHOT</version> ​     <!--引入自定义持久层框架依赖-->     <dependencies>         <dependency>             <groupId>com.yun</groupId>             <artifactId>IPersistence</artifactId>             <version>1.0-SNAPSHOT</version>         </dependency>     </dependencies>

--IPersistence_test-->sqlMapConfig.xml-- <configuration>     <!--数据库配置信息-->     <dataSource>         <property name="driverClass" value="com.mysql.jdbc.Driver"></property>         <property name="jdbcUrl" value="jdbc:mysql://xxx.xxx.xx.xxx:xxxx/xxxx"></property>         <property name="username" value="xxxx"></property>         <property name="password" value="xxxx"></property>     </dataSource>      <!--存放mapper.xml的全路径-->     <mapper resource="userMapper.xml"></mapper>  </configuration>
--IPersistence_test-->userMapper.xml-- <mapper namespace="user">      <!--sql的唯一标识应该是由 namespace.id来组成(statementId)-->     <select id="selectList" resultType="com.yun.pojo.User">         select * from user     </select>      <!--利用反射获取到user对象的参数-->     <select id="selectOne" resultType="com.yun.pojo.User" paramterType="com.yum.pojo.User">         select * from user where id = #{id} and username = #{username}     </select>  </mapper>

--IPersistence-- @Data public class MappedStatement {      //id     private Integer id;      //返回值类型     private String resultType;      //参数值类型     private String paramterType;      //sql语句     private String sql; }
--IPersistence-- @Data public class Configuration {      private DataSource dataSource;      /**      * k:statementId      * v:封装好的MappedStatement对象      */     Map<String,MappedStatement> map = new HashMap<>(); }

按照设计思路编写代码


--解析配置文件返回流 public class Resources {      /**      * 根据配置文件的路径,将配置文件加载成字节输入流,存储在内存中      * @param path      * @return      */     public static InputStream getResourcesAsSteam(String path){         InputStream resourceAsStream = Resources.class.getClassLoader().getResourceAsStream(path);         return resourceAsStream;     }  }
@Data public class Configuration {      private DataSource dataSource;      /**      * k:statementId      * v:封装好的MappedStatement对象      */     Map<String,MappedStatement> map = new HashMap<>();  }
@Data public class MappedStatement {      //id     private String id;      //返回值类型     private String resultType;      //参数值类型     private String paramterType;      //sql语句     private String sql; }
--将解析的流封装到SqlSessionFactoryBuilder中 public class SqlSessionFactoryBuilder {      public SqlSessionFactory build(InputStream is) throws Exception {         //1,使用dom4j解析配置文件,将解析出来的内容封装到Configuration中         XmlConfigBuilder xmlConfigBuilder = new XmlConfigBuilder();         Configuration configuration = xmlConfigBuilder.parseConfig(is);          //2,创建sqlSessionFactory对象,工厂类:生产sqlSession绘画对象         DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration);          return defaultSqlSessionFactory;     } }
--将sqlMapConfig.xml和userMapper.xml流放入configuration中 public class XmlConfigBuilder {      private Configuration configuration;      public XmlConfigBuilder() {         this.configuration = new Configuration();     }      /**      * 该方法就是使用dom4j将配置文件解析,封装Configuration      * @param is      * @return      */     public Configuration parseConfig(InputStream is) throws Exception {          Document document = new SAXReader().read(is);         //获取Configuration根对象<Configuration>         Element rootElement = document.getRootElement();         //获取sqlMapConfig.xml里面的配置信息并且遍历         List<Element> list = rootElement.selectNodes("//property");         Properties properties = new Properties();         for (Element element : list) {             String name = element.attributeValue("name");             String value = element.attributeValue("value");             properties.setProperty(name,value);         }          //创建 c3p0 连接池         ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();         comboPooledDataSource.setDriverClass(properties.getProperty("driverClass"));         comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));         comboPooledDataSource.setUser(properties.getProperty("username"));         comboPooledDataSource.setUser(properties.getProperty("password"));          configuration.setDataSource(comboPooledDataSource);          //mapper.xml解析 步骤:拿到路径-->加载成字节输入流-->dom4j进行解析         List<Element> mapperList = rootElement.selectNodes("//mapper");          for (Element element : mapperList) {             String mapperPath = element.attributeValue("resource");             InputStream resourcesAsSteam = Resources.getResourcesAsSteam(mapperPath);             XmlMApperBuilder xmlMApperBuilder = new XmlMApperBuilder(configuration);             xmlMApperBuilder.prase(resourcesAsSteam);         }          return configuration;     } }
public class XmlMApperBuilder {      private Configuration configuration;      public XmlMApperBuilder(Configuration configuration) {         this.configuration = configuration;     }      public void prase(InputStream is) throws Exception {         Document document = new SAXReader().read(is);         Element rootElement = document.getRootElement();         String namespace = rootElement.attributeValue("namespace");          List<Element> list = rootElement.selectNodes("//select");         for (Element element : list) {             String id = element.attributeValue("id");             String resultType = element.attributeValue("resultType");             String paramterType = element.attributeValue("paramterType");             String sqlText = element.getTextTrim();              MappedStatement mappedStatement = new MappedStatement();             mappedStatement.setId(id);             mappedStatement.setResultType(resultType);             mappedStatement.setParamterType(paramterType);             mappedStatement.setSql(sqlText);              //key值是由 namespace.id来组成             String key = namespace +"."+id;             configuration.getMap().put(key,mappedStatement);         }     } }
--利用工厂模式生产sqlSession public class DefaultSqlSessionFactory implements SqlSessionFactory{      private Configuration configuration;      public DefaultSqlSessionFactory(Configuration configuration) {         this.configuration = configuration;     }      @Override     public SqlSession openSession() {         return new DefaultSqlSession(configuration);     } }
@AllArgsConstructor public class DefaultSqlSession implements SqlSession {      private Configuration configuration;      @Override     public <E> List<E> selectList(String statementId, Object... params) throws Exception {          //将要去完成对 SimpleExecutor 里的query方法的调用         SimpleExecutor simpleExecutor = new SimpleExecutor();         MappedStatement mappedStatement = configuration.getMap().get(statementId);         List<Object> list = simpleExecutor.query(configuration, mappedStatement, params);          return (List<E>) 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("查询结果为空或者返回结果过多");         }     } }
--注册驱动,查询数据信息 并且封装返回 public class SimpleExecutor implements Executor {     @Override                                                                                 //user     public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception{         //1,注册驱动,获取连接         Connection connection = configuration.getDataSource().getConnection();          //2,获取sql    select * from user where id = #{id} and username = #{username}           //转换sql    select * from user where id = ? and username = ?,转换过程中,还需要对#{}里面的值进行存储解析         String sql = mappedStatement.getSql();          BoundSql boundSql = getBoundSql(sql);          //3,获取预处理对象         PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());          //4,设置参数         //获取到参数的全路径         String paramterType = mappedStatement.getParamterType();         Class<?> paramterTypeClass =  getClassType(paramterType);          List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();         for (int i = 0; i < parameterMappingList.size(); i++) {             ParameterMapping parameterMapping = parameterMappingList.get(i);             String content = parameterMapping.getContent();              //反射根据content获取到实体对象中的属性值,再根据属性值获取到当前传过来的参数对象             Field declaredField = paramterTypeClass.getDeclaredField(content);             //暴力访问             declaredField.setAccessible(true);             Object o = declaredField.get(params[0]);              preparedStatement.setObject(i+1,o);         }          //5,执行sql         ResultSet resultSet = preparedStatement.executeQuery();         //获取实体对象         String resultType = mappedStatement.getResultType();         Class<?> resultTypeClass = getClassType(resultType);         //获取实体对象的具体实现         Object instance = resultTypeClass.newInstance();          List<Object> objects = new ArrayList<>();          //6,封装返回结果集         while (resultSet.next()) {             //1,取出元数据             ResultSetMetaData metaData = resultSet.getMetaData();             for (int i = 1; i <= metaData.getColumnCount(); i++) {                 //获取字段名                 String columnName = metaData.getColumnName(i);                 //获取字段值                 Object value = resultSet.getObject(columnName);                 //使用反射或者内省根据数据库表和实体的对应关系,完成封装                 PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);                 Method writeMethod = propertyDescriptor.getWriteMethod();                 writeMethod.invoke(instance,value);             }             objects.add(instance);         }          return (List<E>) objects;     }      /**      * 反射获取实体      * @param paramterType      * @return      * @throws Exception      */     private Class<?> getClassType(String paramterType) throws Exception {         if (!StringUtils.isNullOrEmpty(paramterType)) {             Class<?> aClass = Class.forName(paramterType);             return aClass;         }         return null;     }      /**      * 完成对#{}的解析工作:1,将#{}使用?进行代替;2,解析出#{}里面的值进行存储      * @param sql      * @return      */     private BoundSql getBoundSql(String sql) {         //标记处理类:配置标记解析器来完成对占位符的解析处理工作         ParameterMappingTokenHandler tokenHandler = new ParameterMappingTokenHandler();         //标记解析器,对占位符的转换         GenericTokenParser tokenParser = new GenericTokenParser("#{", "}", tokenHandler);         //解析出来的sql         String parseSql = tokenParser.parse(sql);         //#{}里面的解析出来的参数名称         List<ParameterMapping> parameterMappings = tokenHandler.getParameterMappings();          BoundSql boundSql = new BoundSql(parseSql,parameterMappings);          return boundSql;      } }

开始测试

public class IPersistenceTest {      @Test     public void test() throws Exception {         //获取配置文件流         InputStream resourcesAsSteam = Resources.getResourcesAsSteam("sqlMapConfig.xml");         SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourcesAsSteam);         SqlSession sqlSession = sqlSessionFactory.openSession();          //调用         User user = new User();         user.setId(1);         user.setUsername("张三");         User user2 = sqlSession.selectOne("user.selectOne", user);         System.out.println(user2);     } }

结果:

自定义持久层框架


我们可以自定义DAO层来处理与数据库交互


public interface IUserDao {      /**      * 查询所有      * @return      */     public List<User> findAll() throws Exception;      /**      * 根据条件进行查询      * @return      */     public User findByCondition(User user) throws Exception; }
public class IUserDaoImpl implements IUserDao {     @Override     public List<User> findAll() throws Exception {         InputStream inputStream = Resources.getResourcesAsSteam("sqlMapConfig.xml");         SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuild().build(inputStream);         SqlSession sqlSession = sqlSessionFactory.openSession();          User user = new User();         user.setId(1);         user.setUsername("Tom");         List<User> users = sqlSession.selectList("user.selectList");         users.forEach(item ->{             System.out.println(item);         });         return users;     }      @Override     public User findByCondition(User user) throws Exception {         InputStream inputStream = Resources.getResourcesAsSteam("sqlMapConfig.xml");         SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuild().build(inputStream);         SqlSession sqlSession = sqlSessionFactory.openSession();          user.setId(1);         user.setUsername("Tom");         //调用         User user2 = sqlSession.selectOne("user.selectOne", user);         System.out.println(user2);         return user2;     }  }

不过又出现以下的问题:

自定义持久层框架

既然咱们想出可以使用代理模式生成Dao层接口的代理实现,那咱们就开始动手测试一下,代码如下:


public interface SqlSession {      /**      * 查询所有      * @param statemnetId      * @param parmes      * @param <E>      * @return      */     public <E> List<E> selectList(String statemnetId,Object...parmes) throws Exception;      /**      * 根据条件查询      * @param statemnetId      * @param parmes      * @param <T>      * @return      */     public <T> T selectOne(String statemnetId,Object...parmes) throws Exception;      /**      * 为Dao接口生成代理实现类      * @param mapperClass      * @param <T>      * @return      */     public <T> T getMapper(Class<?> mapperClass); }
    @Override     public <T> T getMapper(Class<?> mapperClass) {         //使用JDK动态代理来为Dao接口生成代理对象,并返回         Object instance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {             @Override             public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {                 /*                  * 本质上底层还是去执行jdbc代码,                  * 其实可以根据不同的情况来调用selectList或者selectOne,                  * 但是,invole方法是没办法获取到映射配置文件中namespace和id的值,                  * 不过我们可以借助method对象来获取当前执行的方法名和当前方法所在文件的全限定名,                  * 所以:namespace.id == 接口全限定名.方法名                  */                 //准备参数1,statementId:sql语句的唯一标识(namespace.id),parmes                 //方法名                 String methodName = method.getName();                 //接口全限定名                 String className = method.getDeclaringClass().getName();                  String statementId = className + "." + methodName;                  //准备参数2, parms : args                 //首先获取被调用方法的返回值类型,                 Type genericReturnType = method.getGenericReturnType();                 //判断是否进行了 泛型类型参数化-->意思就是当饭返回值有泛型,则返回list无泛型,则返回实体对象                 if (genericReturnType instanceof ParameterizedType) {                     List<Object> objects = selectList(statementId, args);                     return objects;                 }                  return selectOne(statementId,args);             }         });         return (T) instance;     }

此时,xml文件就得做出相对应的修改:

<mapper namespace="com.yun.dao.IUserDao">      <!--sql的唯一标识:namespace.id来组成 : statementId-->     <select id="findAll" resultType="com.yun.pojo.User" >         select * from user     </select>       <!--         User user = new User()         user.setId(1);         user.setUsername("Tom")     -->     <select id="findByCondition" resultType="com.yun.pojo.User" paramterType="com.yun.pojo.User">         select * from user where id = #{id} and username = #{username}     </select>   </mapper> -- 与IUserDao接口的全限定名和方法名绑定;

接下来咱们进行测试

	@Test     public void test() throws Exception {         InputStream inputStream = Resources.getResourcesAsSteam("sqlMapConfig.xml");         SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuild().build(inputStream);         SqlSession sqlSession = sqlSessionFactory.openSession();          User user = new User();         user.setId(1);         user.setUsername("Tom");          //返回代理对象         IUserDao userDao = sqlSession.getMapper(IUserDao.class);         User user1 = userDao.findByCondition(user);         System.out.println(user1);      }

 


测试结果如下:

自定义持久层框架


以上就是自定义持久层框架,其实本质上就是Mybatis的雏形;

发表评论

相关文章