一、JDBC!你会用么?
(一)什么是JDBC
在讲解mybatis之前,先了解下JDBC
Java数据库连接,(Java Database Connectivity,简称JDBC)是
java语言
中用来规范客户端程序如何来访问数据库的
应用程序接口
,提供了诸如查询和更新数据库中数据的方法。(摘自百度百科
)
JDBC是Java语言访问数据库的规范,Java操作数据库离不开JDBC。类似hibernate和mybatis也都是在JDBC的基础上进行了进一步封装,将复杂和重复的工作抽取出来,从而简化JDBC代码开发,使开发者更加专注于业务逻辑的开发,可以说JDBC是一切Java 持久层框架的基石。
(二)JDBC的使用
现在想一想在不使用spring、mybatis等其他第三方提供的框架时,如何实现一个简单的查询功能?
作者在此给出参考:
/**
* 以mysql为例,读者可以自行建库,编写语句,此代码只做参考。
*/
public class JdbcDemo {
public static void main(String[] args) throws Exception {
//1.数据库配置信息,当然这些信息在实际开发中肯定要写在配置文件中的,此处仅做演示使用
String url = "jdbc:mysql://127.0.0.1:3306/mybatis_study";
String driverClass = "com.mysql.jdbc.Driver";
String username = "root";
String password = "123456";
//2.获取连接,首先根据驱动类加载驱动
Class.forName(driverClass);
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = DriverManager.getConnection(url, username, password);
//3.执行查询
String sql = "SELECT * FROM t_user";
preparedStatement = connection.prepareStatement(sql);
resultSet = preparedStatement.executeQuery();
//4.遍历结果集,并封装对象
List users = new ArrayList<>();
while (resultSet.next()) {
int id = resultSet.getInt("user_id");
String userName = resultSet.getString("user_name");
int userAge = resultSet.getInt("user_age");
Date createDate = resultSet.getDate("create_date");
User user = new User();
user.setUserId(id);
user.setUserName(userName);
user.setUserAge(userAge);
user.setCreateDate(createDate);
users.add(user);
}
System.out.println(users);
//5.释放资源
} finally {
try{
if (resultSet != null) {
resultSet.close();
}
if (preparedStatement != null) {
preparedStatement.close();
}
if (connection != null) {
connection.close();
}
}catch(Exception e){
e.printStackTrace();
}
}
}
如上就是一个简单的查询数据功能的实现,你是否能完整的写出来,是否能知道每步的操作?
java连接数据库并执行操作的基本步骤:
加载JDBC驱动程序
获得数据库连接
获得执行sql的对象
执行sql并获得结果集
遍历结果集,封装成java对象
关闭JDBC连接,释放资源
这里只是简单的JDBC使用,例如事务处理,存储过程调用等操作,也完全是基于JDBC实现的,可以说JDBC基本包含了数据库的所有操作,不懂JDBC的话很难真正了解mybatis。
(三)直接使用JDBC存在的问题
如(二)中的代码,一个简单的查询功能,使用JDBC来实现时,我们需要写好多的代码。毫不夸张的说,这些代码有时候会比业务代码都要多,而且很多代码都是重复的,例如加载驱动、获得连接、释放连接等。
当然我们可以写一个工具类,例如这样:
public class JdbcHelper {
//这些信息应该从配置文件中读取,此处直接写死,读者可以自行实现
private static final String DRIVER_CLASS_NAME;
private static final String URL;
private static final String USERNAME;
private static final String PASSWORD;
static {
DRIVER_CLASS_NAME = "com.mysql.jdbc.Driver";
URL = "jdbc:mysql://127.0.0.1:3306/mybatis_study";
USERNAME = "root";
PASSWORD = "123456";
try {
Class.forName(DRIVER_CLASS_NAME);
} catch (ClassNotFoundException e) {
// 这里需要日志输出,用控制台输出代替,下同
System.out.println("驱动加载失败:" + e.getMessage());
}
}
/**
* 获得数据库连接
*
* @return 数据库连接对象
*/
public static Connection getConnection() {
Connection connection = null;
try {
connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);
} catch (SQLException e) {
System.out.println("数据库连接获取失败:" + e.getMessage());
}
return connection;
}
/**
* 关闭数据库连接
*
* @param connection 数据库连接对象
*/
public static void closeConnection(Connection connection) {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
System.out.println("数据库连接关闭失败:" + e.getMessage());
}
}
}
/**
* 执行查询,并返回列表集合
*
* @param entityeClass 泛型类型
* @param sql 要执行查询的sql语句
* @param params 查询参数
* @return 查询结果
*/
public static List queryEntityList(Class entityeClass, String sql, Object... params) {
List list = new ArrayList<>();
Connection connection = getConnection();
try {
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//此处没考虑动态SQL
for (int i = 0; i < params.length; i++) {
preparedStatement.setObject(i + 1, params[i]);
}
ResultSet resultSet = preparedStatement.executeQuery();
//.....利用反射等技术封装entityClass对象,并添加到list
} catch (SQLException e) {
System.out.println(e.getMessage());
} finally {
closeConnection(connection);
}
return list;
}
....其他方法不再累述,可自行实现
}
这样我们可以解决大量JDBC操作的代码,但这就完美了么?
首先,sql语句与java代码还是耦合在一起的;
其次,结果集的封装比较复杂的 (这个读者可以试着写一下
)
再次,动态sql
编写的逻辑复杂(这个读者可以试着写一下
)
其他 ...
动态SQL
在编译时无法确定,只有等到程序运行起来,在执行的过程中才能确定,这种SQL叫做动态SQL。
例如这样一个场景,有个用户界面(用户表t_user),界面上有一系列查询条件例如“用户名查询(user_name)”、“所属分组(user_group)”、“性别查询(user_sex)”等,当然这些查询条件都是非必填选项。此时就会产生动态SQL的问题:
首先给出没有输入查询条件时的sql:SELECT * FROM t_user LIMIT ?,?
当输入用户名称查询时的sql :SELECT * FROM t_user WHERE user_name = ? LIMIT ?,?;
当输入用户名及性别时的sql :SELECT * FROM t_user WHERE user_name = ? AND user_sex = ? LIMIT ?,?
通过上述三个sql,用户输入的查询条件是随机的,导致要执行的sql是无法在编写代码阶段确定的,这就需要在执行查询时根据查询条件拼装相应sql,这就需要写一些逻辑代码来实现sql拼装;由于是sql是随机拼装的,导致查询参数也是随机的,也需要逻辑代码去设置...是不是很麻烦,就一个简单的查询业务,却在JDBC上花费了很多时间。
读者可以尝试实现下这个简单的场景,体会下用原生JDBC面临的问题。这样才能更好的理解我们在开发中使用mybatis的意义,以及mybatis为我们做了什么,还有mybatis各种配置的意义何在。正所谓知其然,更要知其所以然,多思考,多实践,多质疑,是一个程序员进步的关键。
综上,使用原生的jdbc来做数据访问存在着诸多不便,而且很多模板化的代码完全可以提取出来。而mybatis、hibernate等一系列持久层框架的诞生,正是为解决这些问题。接下来我们通过剖析mybatis的运行机制和部分源码,来学习下mybatis是如何工作的以及底层是如何封装JDBC的。