前言
在日常的开发工作中我们项目的数据不可避免的需要去持久化,也就是存储到数据库中。而我们在持久化数据时大部分是使用MyBatis、Hibernate、JPA等ORM框架来完成的。那么我们为什么不用更为原生的JDBC来实现呢,这些框架的出现又是为了解决什么问题呢?
ORM框架其实都是基于JDBC的封装增强的实现:
MyBatis把JDBC的数据库连接信息和SQL相关信息分为两个配置部分,我们使用MyBatis能够很灵活的定义SQL以及相关数据库对象来实现基础的CRUD操作,并且对自定义SQL支持较好,自动化程度没有那么高,相对比较灵活。如果需要使用单表CRUD操作无XMl这种功能,也能通过自定义插件或者引入第三方插件(TkMapper、MyBatisPlus)来完成,特别适合一些需求改动比较频繁、传统三层架构开发模式的项目。
JPA和Hibernate都相对来说封装的更为完整,我们只需要定义好数据库和实体对象的映射关系,自动化程度较高,不需要自己编写SQL就能实现大部分的持久化操作。但是缺点就是在处理一些特殊业务需要自定义SQL执行时不够灵活。这两个框架更适合于一些使用DDD(领域驱动)思想来设计开发的中大型项目。
JDBC代码分析
下面先贴出一个基本的JDBC查询数据库的代码,用来分析使用JDBC执行SQL操作中存在的问题。
Connection connection=null;
PreparedStatement preparedStatement=null;
ResultSet resultSet=null;
try {
// 加载数据库连接驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 通过驱动管理类获取数据库连接
connection= DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/simple-orm?characterEncoding=utf-8","root","yxh123..");
// 定义要执行的SQL语句,?表示传参占位符
String sql="select * from sm_user where user_name=?";
// 获取预处理statement
preparedStatement=connection.prepareStatement(sql);
// 设置sql 参数 第一个为参数序号从1开始数(SQL语句内第几个?号),第二个参数为穿进去的值
preparedStatement.setString(1,"tom");
// 向数据库发出查询请求,执行SQL语句并接收返回值结果集
resultSet=preparedStatement.executeQuery();
// 遍历并解析结果集
while (resultSet.next()){
String id=resultSet.getString("id");
String userName=resultSet.getString("user_name");
// 封装SmUser
SmUser smUser=new SmUser();
smUser.setId(id);
smUser.setUserName(userName);
// 输出查询结果
System.out.println(smUser.toString());
}
} catch (Exception e) {
e.printStackTrace();
}finally {
// 关闭数据库连接
}
上面这些代码,就是最基本的JDBC连接数据库并执行SQL的一整套流程了,从这些代码中我们可以看出以下问题:
-
数据库驱动加载、数据库连接信息、SQL语句信息、结果集解析字段配置。都存在硬编码问题
这些信息一旦发生改变,都需要改动源代码进行重新编译再重新部署,这种重新编译部署的流程频繁执行会使我们的生产环境极其的不稳定,而且流程复杂。
// 加载数据库连接驱动 Class.forName("com.mysql.cj.jdbc.Driver"); // 通过驱动管理类获取数据库连接 connection= DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/simple-orm?characterEncoding=utf-8","root","yxh123.."); // 定义要执行的SQL语句,?表示传参占位符 String sql="select * from sm_user where user_name=?"; // 设置sql 参数 第一个为参数序号从1开始数(SQL语句内第几个?号),第二个参数为穿进去的值 preparedStatement.setString(1,"tom"); // 遍历并解析结果集 String id=resultSet.getString("id"); String userName=resultSet.getString("user_name");
-
大量重复、无意义的代码
在结果集解析,调用相应对象的set方法进行结果集的封装时,代码量多而且重复率高。
// 封装SmUser SmUser smUser=new SmUser(); smUser.setId(id); smUser.setUserName(userName);
-
数据库频繁创建关闭连接,消耗性能
如果该方法要被调用多次,这个数据库连接会多次的去连接数据库服务端和释放连接,而每次连接都需要TCP三次握手等步骤,影响代码执行效率而且大量消耗数据库性能
// 加载数据库连接驱动 Class.forName("com.mysql.cj.jdbc.Driver"); // 通过驱动管理类获取数据库连接 connection= DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/simple-orm?characterEncoding=utf-8","root","yxh123.."); finally { // 关闭数据库连接 }
解决方案
- 使用配置文件来封装数据库驱动加载、数据库连接信息、SQL语句信息、结果集解析字段等配置,来解决硬编码问题
- 使用Java的反射、自省特性来接收解析封装结果集
- 创建连接池来解决频繁连接关闭数据库连接的问题