1-自定义持久层框架 1.1 分析JDBC操作问题 public static void main (String[] args) { Connection connection = null ; PreparedStatement preparedStatement = null ; ResultSet resultSet = null ; try { Class.forName("com.mysql.jdbc.Driver" ); connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis? characterEncoding = utf - 8" , " root" , " root" ); String sql = "select * from user where username = ?" ; preparedStatement = connection.prepareStatement(sql); preparedStatement.setString(1 , "tom" ); resultSet = preparedStatement.executeQuery(); while (resultSet.next()) { int id = resultSet.getInt("id" ); String username = resultSet.getString("username" ); user.setId(id); user.setUsername(username); } System.out.println(user); } } catch ( Exception e) { e.printStackTrace(); } finally { if (resultSet != null ) { try { resultSet.close(); } catch (SQLException e) { e.printStackTrace(); } } if (preparedStatement != null ) { try { preparedStatement.close(); } catch (SQLException e) { e.printStackTrace(); } } if (connection != null ) { try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } }
JDBC问题总结: 原始jdbc开发存在的问题如下:
数据库连接创建、释放频繁造成系统资源浪费,从⽽影响系统性能。 Sql语句在代码中硬编码,造成代码不易维护,实际应⽤中sql变化的可能较⼤,sql变动需要改变java代码。 使⽤preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不⼀定,可能多也可能少,修改sql还要修改代码,系统不易维护。 对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成pojo对象解析⽐较⽅便 1.2 问题解决思路 使⽤数据库连接池初始化连接资源 将数据库连接信息和sql语句抽取到xml配置⽂件中 使⽤反射、内省等底层技术,⾃动将实体与表进⾏属性与字段的⾃动映射 1.3 ⾃定义框架设计 使⽤端: 引入自定义持久层框架的jar包 提供两部分配置信息:
数据库配置信息 sql配置信息:SQL语句、参数类型、返回值类型 提供核⼼配置⽂件:sqlMapConfig.xml
: 存放数据源信息,引⼊mapper.xml(存放mapper.xml的全路径)Mapper.xml
: sql语句的配置⽂件信息
框架端: 本质上是对JDBC代码进行了封装
读取配置⽂件 根据配置文件的路径,将配置文件加载成字节输入流,存储在内存中,创建Resource类,方法:InputStream getResourceAsStream(String path)
读取完成以后以流的形式存在,我们不能将读取到的配置信息以流的形式存放在内存中,不好操作,可以创建javaBean来存储 (1)Configuration : 存放数据库基本信息(sqlMapConfig.xml解析出来的内容)、Map<唯⼀标识,Mapper> 唯⼀标识:namespace + "." + id
(2)MappedStatement :Mapper.xml解析出来的内容,sql语句、statement类型、输⼊参数java类型、输出参数java类型
解析配置⽂件 创建sqlSessionFactoryBuilder类:⽅法:sqlSessionFactory build(InputStream in): 第⼀:使⽤dom4j解析配置⽂件,将解析出来的内容封装到Configuration和MappedStatement中 第⼆:创建SqlSessionFactory的实现类DefaultSqlSessionFactory
对象:生产SqlSession—会话对象(工厂模式)
创建SqlSessionFactory: ⽅法:openSession()
: 获取sqlSession接⼝的实现类实例对象(生产SqlSession)
创建sqlSession接⼝及实现类:主要封装crud⽅法 ⽅法:
selectList(String statementId,Object param)
:查询所有selectOne(String statementId,Object param)
:查询单个 具体实现:封装JDBC完成对数据库表的查询操作创建Executor接口及实现类SimpleExector实现类 query(Configuration, MappedStatement, Object… params):执行的就是JDBC代码
涉及到的设计模式: Builder构建者设计模式、⼯⼚模式、代理模式
1.4 ⾃定义框架实现 在使⽤端项⽬中创建配置配置⽂件
创建 sqlMapConfig.xml
<configuration > <dataSource > <property name ="driverClass" value ="com.mysql.jdbc.Driver" > </property > <property name ="jdbcUrl" value ="jdbc:mysql:///zdy_mybatis" > </property > <property name ="username" value ="root" > </property > <property name ="password" value ="123456" > </property > </dataSource > <mapper resource ="UserMapper.xml" > </mapper > </configuration >
mapper.xml
<mapper namespace ="user" > <select id ="selectOne" paramterType ="com.lagou.pojo.User" resultType ="com.lagou.pojo.User" > select * from user where id = #{id} and username = #{username} </select > <select id ="selectList" resultType ="com.lagou.pojo.User" > select * from user </select > </mapper >
User实体
public class User { private Integer id; private String username; private String password; private String birthday; public Integer getId () { return id; } public void setId (Integer id) { this .id = id; } public String getUsername () { return username; } public void setUsername (String username) { this .username = username; } public String getPassword () { return password; } public void setPassword (String password) { this .password = password; } public String getBirthday () { return birthday; } public void setBirthday (String birthday) { this .birthday = birthday; } @Override public String toString () { return "User{" + "id=" + id + ", username='" + username + '\'' + ", password='" + password + '\'' + ", birthday='" + birthday + '\'' + '}' ; } }
再创建⼀个Maven⼦⼯程并且导⼊需要⽤到的依赖坐标
<properties > <project.build.sourceEncoding > UTF-8</project.build.sourceEncoding > <maven.compiler.encoding > UTF-8</maven.compiler.encoding > <java.version > 1.8</java.version > <maven.compiler.source > 1.8</maven.compiler.source > <maven.compiler.target > 1.8</maven.compiler.target > </properties > <dependencies > <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 > </dependencies >
Configuration
public class Configuration { private DataSource dataSource; Map<String, MappedStatement> mappedStatementMap = new HashMap<>(); public DataSource getDataSource () { return dataSource; } public void setDataSource (DataSource dataSource) { this .dataSource = dataSource; } public Map<String, MappedStatement> getMappedStatementMap () { return mappedStatementMap; } public void setMappedStatementMap (Map<String, MappedStatement> mappedStatementMap) { this .mappedStatementMap = mappedStatementMap; } }
MappedStatement
public class MappedStatement { private String id; private String resultType; private String paramterType; private String sql; public String getId () { return id; } public void setId (String id) { this .id = id; } public String getResultType () { return resultType; } public void setResultType (String resultType) { this .resultType = resultType; } public String getParamterType () { return paramterType; } public void setParamterType (String paramterType) { this .paramterType = paramterType; } public String getSql () { return sql; } public void setSql (String sql) { this .sql = sql; } }
Resources
public class Resources { public static InputStream getResourceAsStream (String path) { InputStream inputStream = Resources.class.getClassLoader().getResourceAsStream(path); return inputStream; } }
SqlSessionFactoryBuilder
public class SqlSessionFactoryBuilder { public SqlSessionFactory build (InputStream in) throws DocumentException, PropertyVetoException { XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder(); Configuration configuration = xmlConfigBuilder.parseConfig(in); DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration); return defaultSqlSessionFactory; } }
XMLConfigerBuilder
public class XMLConfigBuilder { private Configuration configuration; public XMLConfigBuilder () { this .configuration = new Configuration(); } public Configuration parseConfig (InputStream inputStream) throws DocumentException, PropertyVetoException { Document document = new SAXReader().read(inputStream); Element rootElement = document.getRootElement(); 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); } ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource(); comboPooledDataSource.setDriverClass(properties.getProperty("driverClass" )); comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl" )); comboPooledDataSource.setUser(properties.getProperty("username" )); comboPooledDataSource.setPassword(properties.getProperty("password" )); configuration.setDataSource(comboPooledDataSource); List<Element> mapperList = rootElement.selectNodes("//mapper" ); for (Element element : mapperList) { String mapperPath = element.attributeValue("resource" ); InputStream resourceAsStream = Resources.getResourceAsStream(mapperPath); XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration); xmlMapperBuilder.parse(resourceAsStream); } return configuration; } }
XMLMapperBuilder
public class XMLMapperBuilder { private Configuration configuration; public XMLMapperBuilder (Configuration configuration) { this .configuration = configuration; } public void parse (InputStream inputStream) throws DocumentException { Document document = new SAXReader().read(inputStream); 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); String key = namespace + "." + id; configuration.getMappedStatementMap().put(key, mappedStatement); } } }
sqlSessionFactory 接⼝及D efaultSqlSessionFactory 实现类
public interface SqlSessionFactory { public 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); } }
sqlSession 接⼝及 DefaultSqlSession 实现类
public interface SqlSession { public <E> List<E> selectList (String statementId, Object... params) throws Exception ; public <T> T selectOne (String statementId, Object... params) throws Exception ; public <T> T getMapper (Class<?> mapperClass) ; } public class DefaultSqlSession implements SqlSession { private Configuration configuration; public DefaultSqlSession (Configuration configuration) { this .configuration = configuration; } @Override public <E> List<E> selectList (String statementId, Object... params) throws Exception { SimpleExecutor simpleExecutor = new SimpleExecutor(); MappedStatement mappedStatement = configuration.getMappedStatementMap().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("查询结果为空或者返回结果过多" ); } } @Override public <T> T getMapper (Class<?> mapperClass) { Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() { @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); String className = method.getDeclaringClass().getName(); String statementId = className + "." + methodName; Type returnType = method.getGenericReturnType(); if (returnType instanceof ParameterizedType) { List<Object> objectList = selectList(statementId, args); return objectList; } return selectOne(statementId, args); } }); return (T) proxyInstance; } }
Executor
public interface Executor { public <E> List<E> query (Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception ; }
SimpleExecutor
public class SimpleExecutor implements Executor { @Override public <E> List<E> query (Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception { Connection connection = configuration.getDataSource().getConnection(); String sql = mappedStatement.getSql(); BoundSql boundSql = getBoundSql(sql); PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText()); String paramterType = mappedStatement.getParamterType(); Class<?> parameterTypeClass = getClassType(paramterType); List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList(); for (int i = 0 ; i < parameterMappingList.size(); i++) { ParameterMapping parameterMapping = parameterMappingList.get(i); String content = parameterMapping.getContent(); Field declaredField = parameterTypeClass.getDeclaredField(content); declaredField.setAccessible(true ); Object o = declaredField.get(params[0 ]); preparedStatement.setObject(i + 1 , o); } ResultSet resultSet = preparedStatement.executeQuery(); String resultType = mappedStatement.getResultType(); Class<?> resultTypeClass = getClassType(resultType); List<Object> objectList = new ArrayList<>(); while (resultSet.next()) { Object instance = resultTypeClass.newInstance(); ResultSetMetaData metaData = resultSet.getMetaData(); for (int i = 1 ; i < metaData.getColumnCount(); i++) { String columnName = metaData.getColumnName(i); Object columnValue = resultSet.getObject(columnName); PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass); Method writeMethod = propertyDescriptor.getWriteMethod(); writeMethod.invoke(instance, columnValue); } objectList.add(instance); } return (List<E>) objectList; } private Class<?> getClassType(String className) throws ClassNotFoundException { if (className != null ) { Class<?> clazz = Class.forName(className); return clazz; } return null ; } private BoundSql getBoundSql (String sql) { ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler(); GenericTokenParser genericTokenParser = new GenericTokenParser("#{" , "}" , parameterMappingTokenHandler); String parseSql = genericTokenParser.parse(sql); List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings(); BoundSql boundSql = new BoundSql(parseSql, parameterMappings); return boundSql; } }
BoundSql
public class BoundSql { private String sqlText; private List<ParameterMapping> parameterMappingList = new ArrayList<>(); public BoundSql (String sqlText, List<ParameterMapping> parameterMappingList) { this .sqlText = sqlText; this .parameterMappingList = parameterMappingList; } public String getSqlText () { return sqlText; } public void setSqlText (String sqlText) { this .sqlText = sqlText; } public List<ParameterMapping> getParameterMappingList () { return parameterMappingList; } public void setParameterMappingList (List<ParameterMapping> parameterMappingList) { this .parameterMappingList = parameterMappingList; } }
1.5 ⾃定义框架优化 通过上述我们的⾃定义框架,我们解决了JDBC操作数据库带来的⼀些问题:例如频繁创建释放数据库连接,硬编码,⼿动封装返回结果集等问题,但是现在我们继续来分析刚刚完成的⾃定义框架代码,有没有什么问题?
问题如下:
dao的实现类中存在重复的代码,整个操作的过程模板重复(创建sqlsession,调⽤sqlsession⽅法,关闭 sqlsession) dao的实现类中存在硬编码,调⽤sqlsession的⽅法时,参数statement的id硬编码 解决:使⽤代理模式来创建接⼝的代理对象
@Test public void test2 () throws Exception { InputStream resourceAsSteam = Resources.getResourceAsSteam(path: "sqlMapConfig.xml" ) SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsSteam); SqlSession sqlSession = build.openSession(); User user = new User(); user.setld(l); user.setUsername("tom" ); UserMapper userMapper = sqlSession.getMappper(UserMapper.class); User userl = userMapper.selectOne(user); System・out.println(userl); }
在sqlSession中添加⽅法
public <T> T getMapper (Class<?> mapperClass) ;
实现类
@Override public <T> T getMapper (Class<?> mapperClass) { Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() { @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); String className = method.getDeclaringClass().getName(); String statementId = className + "." + methodName; Type returnType = method.getGenericReturnType(); if (returnType instanceof ParameterizedType) { List<Object> objectList = selectList(statementId, args); return objectList; } return selectOne(statementId, args); } }); return (T) proxyInstance; }