Java精进-手写持久层框架
Posted 程序员yqy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java精进-手写持久层框架相关的知识,希望对你有一定的参考价值。
文章目录
前言
本文适合有一定java基础的同学,通过自定义持久层框架,可以更加清楚常用的mybatis等开源框架的原理。
JDBC操作回顾及问题分析
学习java的同学一定避免不了接触过jdbc,让我们来回顾下初学时期接触的jdbc操作吧
以下代码连接数据库查询用户表信息,用户表字段分别为用户id,用户名username。
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
User user = new User();
try
// 加载数据库驱动
//Class.forName("com.mysql.jdbc.Driver");
Class.forName("com.mysql.cj.jdbc.Driver");
// 通过驱动管理类获取数据库链接
connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8", "root", "mimashi3124");
// 定义sql语句?表示占位符
String sql = "select * from user where username = ?";
// 获取预处理statement
preparedStatement = connection.prepareStatement(sql);
// 设置参数,第⼀个参数为sql语句中参数的序号(从1开始),第⼆个参数为设置的参数值
preparedStatement.setString(1, "盖伦");
// 向数据库发出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变化导致解析代码变化,系统不易维护,如果能将数据 库记录封装成pojo对象解析⽐较⽅便
问题解决思路
- 使⽤数据库连接池初始化连接资源,避免资源浪费
- 将sql语句抽取到xml配置中,这种sql的变动只用关注xml文件,不比去一堆java代码里改写sql
- 参数硬编码问题可以使用反射、内省等技术、自动将实体与表字段进行映射。
自己动手写个持久层框架
接下来,我们来一个个解决上面的问题
数据库连接池我们可以直接使用c3p0提供的ComboPooledDataSource即可
为了解决sql硬编码问题,我们要把sql写到xml文件中,那自然是要定义一个xml文件了。
光有sql肯定不行,毕竟我们要先连接数据库,sql语句才有存在的意义。所以xml中得先定义数据配置信息,然后才是sql语句。
1.定义配置xml文件
我们新建一个sqlMapConfig.xml,定义数据源信息、并且增加两个sql语句,parameterType为sql执行参数,resultType为方法返回实体。
代码如下(数据库不同版本使用驱动类可能不同):
<configuration>
<!--数据库连接信息-->
<property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
<!-- <property name="driverClass" value="com.mysql.jdbc.Driver"/>-->
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="mimashi3124"/>
<select id="selectOne" parameterType="org.example.pojo.User"
resultType="org.example.pojo.User">
select * from user where id = #id and username =#username
</select>
<select id="selectList" resultType="org.example.pojo.User">
select * from user
</select>
</configuration>
现在xml文件数据库信息也有了,sql语句定义也有了,还有什么问题呢?
我们实际中对sql的操作会涉及到不同的表,所以我们改进一下,把每个表的sql语句单独放在一个xml里,这样结构更清晰就容易维护。
优化以后的xml配置现在是这样了
sqlMapConfig.xml
<configuration>
<!--数据库连接信息-->
<property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
<!-- <property name="driverClass" value="com.mysql.jdbc.Driver"/>-->
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="mimashi3124"/>
<!--引⼊sql配置信息-->
<mapper resource="mapper.xml"></mapper>
</configuration>
mapper.xml
<mapper namespace="user">
<select id="selectOne" parameterType="org.example.pojo.User"
resultType="org.example.pojo.User">
select * from user where id = #id and username =#username
</select>
<select id="selectList" resultType="org.example.pojo.User">
select * from user
</select>
</mapper>
顺便定义一下业务实体User
public class User
private int id;
private String username;
public int getId()
return id;
public void setId(int 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 + '\\'' +
'';
2.读取配置文件
读取完成以后以流的形式存在,不好操作,所以我们要做解析拿到信息,创建实体对象来存储。
- Configuration : 存放数据库基本信息、Map<唯⼀标识,Mapper> 唯⼀标识:namespace + “.” + id
- MappedStatement:存放sql语句、输⼊参数类型、输出参数类型
xml解析我们使用dom4j
首先引入maven依赖
代码如下(mysql驱动版本根据实际使用mysql版本调整):
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</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集合: key:statementId value:MappedStatement
private 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;
Sql语句信息实体
public class MappedStatement
//id
private String id;
//sql语句
private String sql;
//输⼊参数
private String parameterType;
//输出参数
private String resultType;
public String getId()
return id;
public MappedStatement setId(String id)
this.id = id;
return this;
public String getSql()
return sql;
public MappedStatement setSql(String sql)
this.sql = sql;
return this;
public String getParameterType()
return parameterType;
public MappedStatement setParameterType(String parameterType)
this.parameterType = parameterType;
return this;
public String getResultType()
return resultType;
public MappedStatement setResultType(String resultType)
this.resultType = resultType;
return this;
顺便定义一个Resources类来读取xml文件流
public class Resources
public static InputStream getResourceAsSteam(String path)
return Resources.class.getClassLoader().getResourceAsStream(path);
接下来就是实际的解析了,因为解析代码比较多,我们考虑封装类单独处理解析
定义XMLConfigBuilder类解析数据库配置信息
public class XMLConfigBuilder
private Configuration configuration;
public XMLConfigBuilder()
this.configuration = new Configuration();
public Configuration parserConfiguration(InputStream inputStream) throws DocumentException, PropertyVetoException, ClassNotFoundException
Document document = new SAXReader().read(inputStream);
Element rootElement = document.getRootElement();
List<Element> propertyElements = rootElement.selectNodes("//property");
Properties properties = new Properties();
for (Element propertyElement : propertyElements)
String name = propertyElement.attributeValue("name");
String value = propertyElement.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);
//将configuration传入XMLMapperBuilder中做sql语句解析。
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(this.configuration);
List<Element> mapperElements = rootElement.selectNodes("//mapper");
for (Element mapperElement : mapperElements)
String mapperPath = mapperElement.attributeValue("resource");
InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(mapperPath);
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,
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 parameterType = element.attributeValue("parameterType"); //输⼊参数
String resultType = element.attributeValue("resultType"); //返回参数
//statementId,后续调用通过statementId,找到对应的sql执行
String key &以上是关于Java精进-手写持久层框架的主要内容,如果未能解决你的问题,请参考以下文章