自定义持久层框架
下图是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); }
测试结果如下: