4 自定义持久层框架_编码
<properties> <!-- Encoding --> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <maven.compiler.encoding>UTF-8</maven.compiler.encoding> <java.version>11</java.version> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> </properties> <!--引入ipersistent的依赖-->
在使用端项目中创建配置配置文件
创建 sqlMapConfig.xml
<configuration> <!--1.配置数据库配置信息--> <dataSource> <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> <property name="url" value="jdbc:mysql:///zdy_mybatis?useSSL=false&characterEncoding=UTF-8&serverTimezone=UTC"></property> <property name="username" value="root"></property> <property name="password" value="root"></property> </dataSource> <!--2.引入映射配置文件--> <mappers> <mapper resource="mapper/UserMapper.xml"></mapper> </mappers> </configuration>
mapper.xml
<mapper namespace="User"> <!--根据条件查询单个--> <select id="selectOne" resultType="com.oldlu.pojo.User" parameterType="com.oldlu.pojo.User"> select * from user where id = #{id} and username = #{username} </select> <!--查询所有--> <select id="selectList" resultType="com.oldlu.pojo.User"> select * from user </select> </mapper>
User实体
public class User { //主键标识 private Integer id; //用户名 private String username; 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; } @Override public String toString() { return "User{" + "id=" + id + ", username='" + username + '\'' + '}'; } }
再创建一个Maven子工程并且导入需要用到的依赖坐标
<properties> <!-- Encoding --> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <maven.compiler.encoding>UTF-8</maven.compiler.encoding> <java.version>11</java.version> <maven.compiler.source>11</maven.compiler.source> <maven.compiler.target>11</maven.compiler.target> </properties> <dependencies> <!-- mysql 依赖--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.6</version> </dependency> <!--junit 依赖--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <!--作用域测试范围--> <scope>test</scope> </dependency> <!--dom4j 依赖--> <dependency> <groupId>dom4j</groupId> <artifactId>dom4j</artifactId> <version>1.6.1</version> </dependency> <!--xpath 依赖--> <dependency> <groupId>jaxen</groupId> <artifactId>jaxen</artifactId> <version>1.1.6</version> </dependency> <!--druid连接池--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.21</version> </dependency> <!-- log日志 --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> </dependencies>
Resources
public class Resources { /** * 根据配置文件的路径,加载成字节输入流,存到内存中 * @param path * @return */ public static InputStream getResourceAsSteam(String path){ InputStream resourceAsStream = Resources.class.getClassLoader().getResourceAsStream(path); return resourceAsStream; }
Configuration
/** * 存放核心配置文件解析的内容 */ public class Configuration { // 数据源对象 private DataSource dataSource; // map : key :statementId value : 封装好的MappedStatement private 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
/** * 存放解析映射配置文件的内容 * <select id="selectOne" resultType="com.oldlu.pojo.User" parameterType="com.oldlu.pojo.User"> * select * from user where id = #{id} and username = #{username} * </select> */ public class MappedStatement { // 1.唯一标识 (statementId namespace.id) private String statementId; // 2.返回结果类型 private String resultType; // 3.参数类型 private String parameterType; // 4.要执行的sql语句 private String sql; // 5.mapper代理:sqlcommandType private String sqlcommandType; public String getSqlcommandType() { return sqlcommandType; } public void setSqlcommandType(String sqlcommandType) { this.sqlcommandType = sqlcommandType; } public String getStatementId() { return statementId; } public void setStatementId(String statementId) { this.statementId = statementId; } public String getResultType() { return resultType; } public void setResultType(String resultType) { this.resultType = resultType; } public String getParameterType() { return parameterType; } public void setParameterType(String parameterType) { this.parameterType = parameterType; } public String getSql() { return sql; } public void setSql(String sql) { this.sql = sql; } }
SqlSessionFactoryBuilder
public class SqlSessionFactoryBuilder { /** * 1.解析配置文件,封装Configuration 2.创建SqlSessionFactory工厂对象 * @return */ public SqlSessionFactory build(InputStream inputStream) throws DocumentException { // 1.解析配置文件,封装Configuration XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder(); Configuration configuration = xmlConfigBuilder.parse(inputStream); SqlSessionFactory defatultSqlSessionFactory = new DefatultSqlSessionFactory(configuration); return defatultSqlSessionFactory; } }
XMLConfigerBuilder
public class XMLConfigBuilder { private Configuration configuration; public XMLConfigBuilder() { configuration = new Configuration(); } /** * 使用dom4j解析xml文件,封装configuration对象 * @param inputStream * @return */ public Configuration parse(InputStream inputStream) throws DocumentException { Document document = new SAXReader().read(inputStream); Element rootElement = document.getRootElement(); // 解析核心配置文件中数据源部分 List<Element> list = rootElement.selectNodes("//property"); // <property name="driverClassName" value="com.mysql.jdbc.Driver"></property> Properties properties = new Properties(); for (Element element : list) { String name = element.attributeValue("name"); String value = element.attributeValue("value"); properties.setProperty(name,value); } // 创建数据源对象(连接池) DruidDataSource druidDataSource = new DruidDataSource(); druidDataSource.setDriverClassName(properties.getProperty("driverClassName")); druidDataSource.setUrl(properties.getProperty("url")); druidDataSource.setUsername(properties.getProperty("username")); druidDataSource.setPassword(properties.getProperty("password")); // 创建好的数据源对象封装进configuration中、 configuration.setDataSource(druidDataSource); // 解析映射配置文件 // 1.获取映射配置文件的路径 2.解析 3.封装好mappedStatement List<Element> mapperList = rootElement.selectNodes("//mapper"); for (Element element : mapperList) { String mapperPath = element.attributeValue("resource"); InputStream resourceAsSteam = Resources.getResourceAsSteam(mapperPath); XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration); xmlMapperBuilder.parse(resourceAsSteam); } return configuration; } }
XMLMapperBuilder
public class XMLMapperBuilder { private Configuration configuration; public XMLMapperBuilder(Configuration configuration) { this.configuration = configuration; } public void parse(InputStream inputStream) throws DocumentException, ClassNotFoundException { Document document = new SAXReader().read(inputStream); Element rootElement = document.getRootElement(); String namespace = rootElement.attributeValue("namespace"); List<Element> select = rootElement.selectNodes("select"); for (Element element : select) { //id的值 String id = element.attributeValue("id"); String paramterType = element.attributeValue("paramterType"); String resultType = element.attributeValue("resultType"); //输入参数class Class<?> paramterTypeClass = getClassType(paramterType); //返回结果class Class<?> resultTypeClass = getClassType(resultType); //statementId String key = namespace + "." + id; //sql语句 String textTrim = element.getTextTrim(); //封装 mappedStatement MappedStatement mappedStatement = new MappedStatement(); mappedStatement.setId(id); mappedStatement.setParamterType(paramterTypeClass); mappedStatement.setResultType(resultTypeClass); mappedStatement.setSql(textTrim); //填充 configuration configuration.getMappedStatementMap().put(key, mappedStatement); private Class<?> getClassType (String paramterType) throws ClassNotFoundException { Class<?> aClass = Class.forName(paramterType); return aClass; } }
sqlSessionFactory 接口及D efaultSqlSessionFactory 实现类
public interface SqlSessionFactory { /** * 生产sqlSession :封装着与数据库交互的方法 * @return */ public SqlSession openSession(); } public class DefatultSqlSessionFactory implements SqlSessionFactory { private Configuration configuration; public DefatultSqlSessionFactory(Configuration configuration) { this.configuration = configuration; } @Override public SqlSession openSession() { // 执行器创建出来 Executor executor = new SimpleExecutor(); DefaultSqlSession defaultSqlSession = new DefaultSqlSession(configuration,executor); return defaultSqlSession; } }
sqlSession 接口及 DefaultSqlSession 实现类
public interface SqlSession { /** * 查询所有的方法 select * from user where username like '%aaa%' and sex = '' * 参数1:唯一标识 * 参数2:入参 */ public <E> List<E> selectList(String statementId,Object parameter) throws Exception; /** * 查询单个的方法 */ public <T> T selectOne(String statementId,Object parameter) throws Exception; } public class DefaultSqlSession implements SqlSession { private Configuration configuration; private Executor executor; public DefaultSqlSession(Configuration configuration, Executor executor) { this.configuration = configuration; this.executor = executor; } @Override // user.selectList 1 tom user public <E> List<E> selectList(String statementId, Object params) throws Exception { MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId); // 将查询操作委派给底层的执行器 List<E> list = executor.query(configuration,mappedStatement,params); return list; } @Override public <T> T selectOne(String statementId, Object params) throws Exception { List<Object> list = this.selectList(statementId, params); if(list.size() == 1){ return (T) list.get(0); }else if(list.size() > 1){ throw new RuntimeException("返回结果过多"); }else { return null; } } }
Executor
public interface Executor { <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object params) throws Exception; }
SimpleExecutor
public class SimpleExecutor implements Executor { /** * 执行JDBC操作 * @param configuration * @param mappedStatement * @param params * @param <E> * @return */ @Override // user product public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object params) throws Exception { // 1. 加载驱动,获取连接 Connection connection = configuration.getDataSource().getConnection(); // 2. 获取prepareStatement预编译对象 /* select * from user where id = #{id} and username = #{username} select * from user where id = ? and username = ? 占位符替换 :#{}替换成? 注意:#{id}里面的id名称要保存 */ String sql = mappedStatement.getSql(); BoundSql boundSql = getBoundSQL(sql); String finaLSql = boundSql.getFinaLSql(); PreparedStatement preparedStatement = connection.prepareStatement(finaLSql); // 3.设置参数 // 问题1: Object param(类型不确定 user/product/map/String) // 问题2:该把对象中的哪一个属性赋值给哪一个占位符呢? List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); if(parameterMappings.size() > 0){ // com.oldlu.pojo.User String parameterType = mappedStatement.getParameterType(); Class<?> parameterTypeClass = Class.forName(parameterType); for (int i = 0; i < parameterMappings.size(); i++) { ParameterMapping parameterMapping = parameterMappings.get(i); // id String content = parameterMapping.getContent(); // 反射 Field declaredField = parameterTypeClass.getDeclaredField(content); // 暴力访问 declaredField.setAccessible(true); Object value = declaredField.get(params); preparedStatement.setObject(i+1 ,value); } } // 4.执行sql,发起查询 ResultSet resultSet = preparedStatement.executeQuery(); String resultType = mappedStatement.getResultType(); Class<?> resultTypeClass = Class.forName(resultType); ArrayList<E> list = new ArrayList<>(); // 5.遍历封装 while (resultSet.next()){ // 元数据信息中包含了字段名 字段的值 ResultSetMetaData metaData = resultSet.getMetaData(); Object obj = resultTypeClass.newInstance(); for (int i = 1; i <= metaData.getColumnCount() ; i++) { // id username String columnName = metaData.getColumnName(i); Object value = resultSet.getObject(columnName); // 属性描述器 PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName,resultTypeClass); Method writeMethod = propertyDescriptor.getWriteMethod(); writeMethod.invoke(obj,value); } list.add((E) obj); } return list; } /** * 1.将sql中#{}替换成? 2.将#{}里面的值保存 * @param sql * @return */ private BoundSql getBoundSQL(String sql) { // 标记处理器:配合标记解析器完成标记的解析工作 ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler(); // 标记解析器 GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler); String finalSql = genericTokenParser.parse(sql); // #{}里面的值的集合 List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings(); BoundSql boundSql = new BoundSql(finalSql, parameterMappings); return boundSql; } }
BoundSql
public class BoundSql { //解析过后的sql语句 private String sqlText; //解析出来的参数 private List<ParameterMapping> parameterMappingList = new ArrayList<ParameterMapping>(); 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; } }
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 interface SqlSession { public <T> T getMappper(Class<?> mapperClass);
实现类
package com.oldlu.sqlSession; import com.oldlu.executor.Executor; import com.oldlu.pojo.Configuration; import com.oldlu.pojo.MappedStatement; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Collection; import java.util.List; public class DefaultSqlSession implements SqlSession { private Configuration configuration; private Executor executor; public DefaultSqlSession(Configuration configuration, Executor executor) { this.configuration = configuration; this.executor = executor; } @Override public <E> List<E> selectList(String statementId, Object param) throws Exception { // 要传递什么参数呢? MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId); List<E> list = executor.query(configuration,mappedStatement,param); return list; } @Override public <T> T selectOne(String statementId, Object param) throws Exception { // 调用selectList方法 List<Object> list = selectList(statementId, param); if(list.size() == 1){ return (T) list.get(0); }else if(list.size() > 1){ throw new RuntimeException("返回结果过多..."); } return null; } /** * 生成代理对象 * @param mapperClass * @param <T> * @return */ @Override public <T> T getMapper(Class<?> mapperClass) { // 使用JDK动态代理生成代理对象 Object proxyInstance = Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() { // 参数1:Object o:代理对象的引用,很少用 // 参数2:Method method :当前被调用的方法对象 // 参数3:Object[] objects:被调用的方法的参数 @Override public Object invoke(Object o, Method method, Object[] objects) throws Throwable { // 具体的逻辑:执行底层的JDBC // 思路:通过调用sqlSession的方法来完成执行 // 问题1:如何获取statementId 根据method获取 Class<?> declaringClass = method.getDeclaringClass(); // 类全路径= namespace的值 String className = declaringClass.getName(); String methodName = method.getName(); String statementId = className + "." + methodName; MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId); // 问题2:该调用增删改查什么方法呢? 优化:sqlCommandType String sqlCommandType = mappedStatement.getSqlCommandType(); switch (sqlCommandType){ case "select": //查询操作 //问题3:调selectOne还是调selectAll呢? Class<?> returnType = method.getReturnType(); boolean assignableFrom = Collection.class.isAssignableFrom(returnType); if(assignableFrom){ if(mappedStatement.getParameterType() !=null) { return selectList(statementId, objects[0]); } return selectList(statementId, null); } return selectOne(statementId,objects[0]); case "update": // 更新操作 break; case "insert": // 更新操作 break; case "delete": // 更新操作 break; } return null; } }); return (T) proxyInstance; } }