Java进阶之路-自定义持久层框架
Posted 程序员yqy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java进阶之路-自定义持久层框架相关的知识,希望对你有一定的参考价值。
1 持久层以及JDBC问题分析
1.1 什么是持久层
数据访问层又称为DAL或Dao层,有时候也称为是持久层,其功能主要是负责数据库的访问,简单的说法就是实现对数据表的Select(查询),Insert(插入),Update(更新),Delete(删除)等操作。
早期实现持久层使用JDBC操作即可完成数据层的操作,接触过JDBC的同学应该不陌生,那么既然JDBC已经能够完成数据库的操作,为什么还会出现各种持久层框架呢?有新框架出现说明JDBC本身还是存在一些问题。
1.2分析JDBC操作问题
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");
// 定义sql语句?表示占位符
String sql = "select * from user where username = ?";
// 获取预处理
statement preparedStatement = connection.prepareStatement(sql);
// 设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值
preparedStatement.setString(1, "tom");
// 向数据库发出sql执行查询,查询出结果集
resultSet = preparedStatement.executeQuery();
// 遍历查询结果集
while(resultSet.next())
int id = resultSet.getInt("id");
String username = resultSet.getString("username");
// 封装
User 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开发存在的问题如下:
- 数据库连接创建、释放频繁造成系统资源浪费,从而影响系统性能。
- 数据库配置信息存在硬编码问题。
- Sql语句在代码中硬编码,造成代码不易维护,实际应用中sql变化的可能较大,sql变动需要改变java代码。
- 使用preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不一定,可能多也可能少,修改sql还要修改代码,系统不易维护。
- 对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成实体对象解析比较方便
1.3 问题解决思路
- 使用数据库连接池初始化连接资源
- 将sql语句、数据配置信息抽取到xml配置文件中
- 使用反射、内省等底层技术,自动将实体与表进行属性与字段的自动映射
2 自定义持久层框架思路分析
有了以上1.3的问题解决思路,那接下来我们要自定义一个持久层框架,分析一下,使用者 真正关心的其实只有数据库配置信息和对应的sql语句。为了解决硬编码的问题,可以考虑让使用提供两个核心配置文件
使用端:
- sqlMapConfig.xml : 存放数据源信息,引入mapper.xml
- Mapper.xml : sql语句的配置文件信息
框架端:
-
读取配置文件
读取完成以后以流的形式存在,我们不能将读取到的配置信息以流的形式存放在内存中,不好操作,可以创建javaBean来存储
(1)Configuration : 存放数据库基本信息、Map<唯一标识,MappedStatement> ,这里Map存放所有sql语句对象的集合
(2)MappedStatement:sql语句、statement类型、输入参数java类型、输出参数java类型 -
使用dom4j解析配置文件,将解析出来的内容封装到Configuration和MappedStatement中
-
创建一个使用类,定义增删查改的方法并加以实现,封装结果集。
3 自定义持久层框架实现
3.1 使用端
刚才我们说了使用只关心数据库信息,sql语句以及保存数据的实体
数据库配置文件
<configuration>
<datasource>
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql:///zyd_mybatis"/>
<property name="username" value="root"/>
<property name="password" value="mimashi3124"/>
</datasource>
<!-- 存放mapper.xml全路径-->
<mapper resource="UserMapper.xml"></mapper>
</configuration>
sql语句配置文件
<mapper namespace="com.yqy.dao.IUserDao">
<!-- sql唯一标识 namespace.id组成:statementId-->
<select id="findAll" resultType="com.yqy.pojo.User">
select * from user
</select>
<select id="findByCondition" resultType="com.yqy.pojo.User" parameterType="com.yqy.pojo.User">
select * from user where id = #id and username = #username
</select>
</mapper>
定义实体
package com.yqy.pojo;
/**
* Created by yangqy on 2020/11/2.
*/
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 + '\\'' +
'';
3.2 框架端
框架端需要解析使用端的xml文件,以及数据库连接等,新建maven项目后引入如下依赖
<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>
要解析数据,得先有保存数据的实体bean,则定义Configuration和MappedStatement两个实体,分别存放数据库配置信息、sql语句。
/**
* Created by yangqy on 2020/11/2.
*/
public class Configuration
private DataSource dataSource;
/**
* key为statementId,value为封装好的mappedStatement对象
*/
Map<String,MappedStatement> mappedStatementMap = new HashMap<>();
public DataSource getDataSource()
return dataSource;
public Configuration setDataSource(DataSource dataSource)
this.dataSource = dataSource;
return this;
public Map<String, MappedStatement> getMappedStatementMap()
return mappedStatementMap;
public Configuration setMappedStatementMap(Map<String, MappedStatement> mappedStatementMap)
this.mappedStatementMap = mappedStatementMap;
return this;
/**
* Created by yangqy on 2020/11/2.
*/
public class MappedStatement
/**
* id标识
*/
private String id;
/**
* 返回值类型
*/
private String resultType;
/**
* 参数值类型
*/
private String parameterType;
/**
* 参数值类型
*/
private String sql;
public String getId()
return id;
public MappedStatement setId(String id)
this.id = id;
return this;
public String getResultType()
return resultType;
public MappedStatement setResultType(String resultType)
this.resultType = resultType;
return this;
public String getParameterType()
return parameterType;
public MappedStatement setParameterType(String parameterType)
this.parameterType = parameterType;
return this;
public String getSql()
return sql;
public MappedStatement setSql(String sql)
this.sql = sql;
return this;
要读取文件还得以流的形式操作,定义Resources类
package com.yqy.io;
import java.io.InputStream;
/**
* Created by yangqy on 2020/11/2.
*/
public class Resources
/**
* 根据配置文件的路径,将配置文件加载成字节输入流,存储在内存中
*
* @param path
* @return
*/
public static InputStream getResourceAsStream(String path)
InputStream resourceAsStream = Resources.class.getClassLoader().getResourceAsStream(path);
return resourceAsStream;
定义xml解析类,进行解析操作,这里XmlMapperBuilder用来解析sql信息,XmlConfigBuilder解析配置信息
/**
* Created by yangqy on 2020/11/3.
*/
public class XmlConfigBuilder
private Configuration configuration;
public XmlConfigBuilder()
this.configuration = new Configuration();
/**
* 该方法就是使用dom4j将配置文件解析为Configuration对象
*
* @param inputStream
* @return
*/
public Configuration parseConfiguration(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);
//mapper.xml解析:拿到路径-->字节输入流-->dom4进行解析
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;
/**
* Created by yangqy on 2020/11/3.
*/
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 parameterType = element.attributeValue("parameterType");
String sqlText = element.getTextTrim();
MappedStatement mappedStatement = new MappedStatement();
mappedStatement.setId(id).setResultType(resultType)
.setParameterType(parameterType)
.setSql(sqlText);
String key = namespace + "." + id;
configuration.getMappedStatementMap().put(key,mappedStatement);
创建工厂构建类生成工厂类,以便获取最终的SqlSession类
public class SqlSessionFactoryBuilder
public static SqlSessionFactory build(InputStream inputStream) throws DocumentException, PropertyVetoException
//1.使用dom4j解析配置文件,将解析出来的内容封装到Configuration中
XmlConfigBuilder xmlConfigBuilder = new XmlConfigBuilder();
Configuration configuration = xmlConfigBuilder.parseConfiguration(inputStream);
//2.创建sqlSessionFactory:工厂类:生成sqlSession会话对象
DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration);
return defaultSqlSessionFactory;
创建sqlSessionFactory:工厂类:生成sqlSession会话对象
public interface SqlSessionFactory
SqlSession openSession();
public class DefaultSqlSessionFactory implements SqlSessionFactory
private Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration)
this.configuration = configuration;
@Override
public SqlSession openSession()
return new DefaultSqlSession(this.configuration);
定义SqlSession的操作方法,这里只定义查询
public interface SqlSession
/**
* 查询所有
*/
<E> List<E> selectList(String statementId,Object... params) throws Exception;
<E> E selectOne(String statementId,Object... params) throws Exception;
statementId代表sql唯一id,params为参数,返回类型不确定,使用到了泛型
SqlSession实现类
public class DefaultSqlSession implements SqlSession
private Configuration configuration;
public DefaultSqlSession(Configuration configuration)
this.configuration = configuration;
@Override
public <E以上是关于Java进阶之路-自定义持久层框架的主要内容,如果未能解决你的问题,请参考以下文章