在上一文中,我们对JDBC进行了详细的分析,介绍了使用JDBC操作数据库的步骤,上篇文章更多的是一个理论分析,今天我们就动手来搞代码,两相印证下,加深我们对JDBC的理解。
废话不多说了,撸起袖子,码起来。
1.Statement与PreparedStatement的关系与区别
一个尴尬又不是礼貌的微笑🙂。。在开始码代码之前,还是要更简单的讲解下Statement与PreparedStatement,这两个接口都是JDBC中提供的重要接口,也是我们用来执行增删改查的主力军(CallableStatement更多的用来执行存储过程)。
首先我们来讲下两者的联系,PreparedStatement接口继承(extends
)了Satement接口,也就是说数据库驱动帮我们实例化的Satement对象和PreparedStatement对象就像是父类和子类之间的关系,PreparedStatement就像一个超集,其中实现了更多的方法,可以提供更强劲的功能。
两者之间最主要的区别就是PreparedStatement相比于Satement多了一个SQL预编译的功能,并且SQL中还可以使用?
作为占位符,只需在SQL执行前设置具体的参数即可。而这个功能则使PreparedStatement能非常方便处理批量数据的变更,不仅可以更好的获得执行SQL,可以节省每次执行时SQL编译的时间,可以让此次数据操作更快的完成,并且PreparedStatement对象创建时就已经对SQL进行了预编译,因此还可有效的防止SQL注入攻击。
2.一个好的JDBC操作规范
一个好的后端代码应该是分层的,上层的代码去调用下层封装的方法,增加代码的复用率,并且方便管理,更少出错。
这里我们把上文中JDBC编程步骤拿来,我们按照分层的思想,来对其步骤进行分解。
- 加载并注册数据驱动
- 通过
DriverManager
获取数据库的连接Connection - 通过Connection对象获取
Statment
对象 - 使用获取的
Statment
对象执行SQL语句 - 处理返回结果
- 关闭连接,释放资源
这里,我们将步骤1、2封装成为获取连接的方法,步骤6封装成关闭连接、释放资源的方法,并将这两个方法封装成一个Util类(工具类);在dao层中,封装增、删、改、查的方法,并在每个方法中执行步骤3、4、5;在sevice层实现各个功能(方法),每个方法中调用dao的方法;在Controller(Servlet)层中调用sevice中的方法。各层的调用关系如下如所示:
这里将获取连接和关闭连接的方法封装在JDBCUtil中下面我们给出JDBCUtil.java的代码:
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
public class JDBCUtil {
/**
* 获取连接
*
* @return
* @throws ClassNotFoundException
* @throws SQLException
*/
public static Connection getConnection() throws ClassNotFoundException, SQLException {
//步骤一,注册驱动
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/java_web?useSSL=false&characterEncoding=utf-8";
String userName = "root";
String password = "123456";
//步骤二,创建连接
Connection connection = DriverManager.getConnection(url, userName, password);
return connection;
}
/**
* 步骤六:关闭连接,释放资源
*
* @param statement
* @param connection
*/
public static void release(ResultSet resultSet, Statement statement, Connection connection) {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
resultSet = null;
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
statement = null;
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
connection = null;
}
}
}
3.增删改查的实现
下面我们就以用户(User)对象来实现dao和简单的实现Service。
User的创建SQL如下:
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(32) DEFAULT NULL,
`password` varchar(64) DEFAULT NULL,
`email` varchar(32) DEFAULT NULL,
`birthday` date DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
我们创建与之对应的Java类User.java代码如下(省略了getter、setter和toString方法):
import java.util.Date;
/**
* 用户类
*
* @author lizishu
*
*/
public class User {
private int id;
private String userName;
private String password;
private String email;
private Date birthday;
//getXxx getter方法
//setXxx setter方法
//toString
}
为了充分的展示如何使用Statement和PreparedStatement中的方法实现对users的增删改查的,UserDao中实现了如下方法,方法截图如下:
UserDao的代码如下:
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import com.zzxy.web.domain.User;
import com.zzxy.web.utils.JDBCUtil;
public class UserDao {
/**
* 使用Statement来进行用户查询
* 根据入参的id查询对应的用户信息
*
* @param id
* @return
* @throws ClassNotFoundException
* @throws SQLException
*/
public User findUserById(int id) throws ClassNotFoundException, SQLException {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
// 获取数据库连接
connection = JDBCUtil.getConnection();
// 创建Statement对象
statement = connection.createStatement();
String sql = "SELECT * FROM users WHERE id = " + id;
// 获取查询结果集
resultSet = statement.executeQuery(sql);
User user = new User();
while (resultSet.next()) {
user.setId(resultSet.getInt("id"));
user.setUserName(resultSet.getString("name"));
user.setPassword(resultSet.getString("password"));
user.setEmail(resultSet.getString("email"));
user.setBirthday(resultSet.getDate("birthday"));
}
return user;
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw e;
} catch (SQLException e) {
e.printStackTrace();
throw e;
} finally {
JDBCUtil.release(resultSet, statement, connection);
}
}
/**
* 使用Statement查找表中的所有员工
* 注:一般此功能需要增加分页
*
* @return
* @throws ClassNotFoundException
* @throws SQLException
*/
public List<User> findAllUsers() throws ClassNotFoundException, SQLException {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
// 获取数据库连接
connection = JDBCUtil.getConnection();
// 创建Statement对象 加上其中的两个参数可以让ResultSet中的游标可以自由移动
statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_READ_ONLY);
String sql = "SELECT * FROM users";
// 获取查询结果集
resultSet = statement.executeQuery(sql);
// 这些方法都可使用,具体功能可参考上篇博文
// resultSet.previous();
// resultSet.relative(1);
// resultSet.absolute(1);
// resultSet.beforeFirst();
// resultSet.afterLast();
// resultSet.first();
// resultSet.last();
List<User> userList = new ArrayList<User>();
// 迭代获取所有的用户
while (resultSet.next()) {
// 注意此处User必须重新new一个,否则list中所有的user都是最后一个user
User user = new User();
user.setId(resultSet.getInt("id"));
user.setUserName(resultSet.getString("name"));
user.setPassword(resultSet.getString("password"));
user.setEmail(resultSet.getString("email"));
user.setBirthday(resultSet.getDate("birthday"));
// 添加到列表中
userList.add(user);
}
// System.out.println("获取的用户信息为:" + user);
return userList;
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw e;
} catch (SQLException e) {
e.printStackTrace();
throw e;
} finally {
JDBCUtil.release(resultSet, statement, connection);
}
}
/**
* 使用Statement新增一个用户
*
* @param userList
* @return
* @throws ClassNotFoundException
* @throws SQLException
*/
public boolean insertUser(User user) throws ClassNotFoundException, SQLException {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
// 获取数据库连接
connection = JDBCUtil.getConnection();
// 创建PreparedStatement对象
statement = connection.createStatement();
// 拼接SQL
// 注意下面java.tile.Date 转换为 java.sql.Date
String sql = "INSERT INTO users (name, password, email, birthday) VALUES ('" + user.getUserName() + "', '"
+ user.getPassword() + "', '" + user.getEmail() + "', '"
+ new java.sql.Date(user.getBirthday().getTime()) + "');";
// 执行SQL,并获取影响的行数
int result = statement.executeUpdate(sql);
// 如果影响的行数大于0,表示此用户插入成功
if (result > 0) {
return true;
}
// 返回插入成功的数量
return false;
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw e;
} catch (SQLException e) {
e.printStackTrace();
throw e;
} finally {
JDBCUtil.release(resultSet, statement, connection);
}
}
/**
* 使用PreparedStatement预编译sql
* 一次新增多个用户
*
* @param userList
* @return
* @throws ClassNotFoundException
* @throws SQLException
*/
public int insertUser(List<User> userList) throws ClassNotFoundException, SQLException {
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
// 获取数据库连接
connection = JDBCUtil.getConnection();
// 构建Sql,参数使用占位符?代替
String sql = "INSERT INTO users (name, password, email, birthday) VALUES (?, ?, ?, ?);";
// 创建PreparedStatement对象
statement = connection.prepareStatement(sql);
// 总的插入结果
int resultCount = 0;
// 循环设置SQL中的参数
for (User tmpUser : userList) {
// 设置sql中占位符对应的值,index从1开始
statement.setString(1, tmpUser.getUserName());
statement.setString(2, tmpUser.getPassword());
statement.setString(3, tmpUser.getEmail());
statement.setDate(4, new java.sql.Date(tmpUser.getBirthday().getTime()));
// 执行SQL,并获取影响的行数
int result = statement.executeUpdate();
// 如果影响的行数大于0,表示此用户插入成功
if (result > 0) {
resultCount++;
}
}
return resultCount;
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw e;
} catch (SQLException e) {
e.printStackTrace();
throw e;
} finally {
JDBCUtil.release(resultSet, statement, connection);
}
}
/**
* 使用PreparedStatement更新用户
* 只更新User中的非空字段
* 拼接SQL中参数个数不确定时的情况
*
* @param user
* @return
* @throws ClassNotFoundException
* @throws SQLException
*/
public boolean updateUser(User user) throws ClassNotFoundException, SQLException {
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
// 获取数据库连接
connection = JDBCUtil.getConnection();
// 构建Sql,参数使用占位符代替
// 手动拼接SQL
String sql = "update users set ";
List<Object> paramList = new ArrayList<Object>();
//如果某个属性为null,则跳过,不更新此字段
if (user.getUserName() != null) {
sql += "name = ?,";
paramList.add(user.getUserName());
}
if (user.getPassword() != null) {
sql += "password = ?,";
paramList.add(user.getPassword());
}
if (user.getEmail() != null) {
sql += "email = ?,";
paramList.add(user.getEmail());
}
if (user.getBirthday() != null) {
sql += "birthday = ?,";
paramList.add(user.getBirthday());
}
// 去除多余的逗号
sql = sql.substring(0, sql.length() - 1);
sql += " where id = ?";
// 添加id到参数列表中
paramList.add(user.getId());
// 创建PreparedStatement对象
statement = connection.prepareStatement(sql);
// 设置sql中占位符对应的值
for (int i = 0; i < paramList.size(); i++) {
statement.setObject(i + 1, paramList.get(i));
}
int result = statement.executeUpdate();
// 如果影响的行数大于0,表示此用户更新成功
if (result > 0) {
return true;
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw e;
} catch (SQLException e) {
e.printStackTrace();
throw e;
} finally {
JDBCUtil.release(resultSet, statement, connection);
}
return false;
}
/**
* 使用PreparedStatement删除用户
* SQL中无占位符
*
* @param id
* @return
* @throws ClassNotFoundException
* @throws SQLException
*/
public boolean deleteUser(int id) throws ClassNotFoundException, SQLException {
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try {
// 获取数据库连接
connection = JDBCUtil.getConnection();
// 构建Sql,手动拼接SQL
String sql = "delete from users where id = " + id;
// 创建PreparedStatement对象
statement = connection.prepareStatement(sql);
int result = statement.executeUpdate();
// 如果影响的行数大于0,表示此用户更新成功
if (result > 0) {
return true;
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
throw e;
} catch (SQLException e) {
e.printStackTrace();
throw e;
} finally {
JDBCUtil.release(resultSet, statement, connection);
}
return false;
}
}
上面,我们分别实现了findUserById(int)
、findAllUsers()
、insertUser(User)
、insertUser(List<User>)
、updateUser(User)
、deleteUser(int)
这六个方法,使用Satement、PreparedStatement实现对user的增删改查,并且演示了SQL拼接、具体占位符数量未知如何写SQL、批量插入、ResultSet中游标的使用等。
下面我们简单的实现一个UserService类(具体的Service方法还需要根据业务来写,此处的Service主要是为了测试),其中实现的方法如下:
其代码如下,这里使用了单例模式,使用懒汉式创建UserService。
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import com.zzxy.web.dao.UserDao;
import com.zzxy.web.domain.User;
/**
* User的Service类 使用单例模式,保证service在整个项目中唯一
*
* @author lizishu
*
*/
public class UserService {
private static UserService instance;
private static UserDao userDao = null;
// 私有的构造函数,在UserService类外部无法调用
private UserService() {
}
// 单例 懒汉式
public static UserService getInstance() {
// 第一次调用时初始化UserService、UserDao
if (instance == null) {
instance = new UserService();
userDao = new UserDao();
}
return instance;
}
/**
* 根据id查询用户测试
*
* @param id
* @throws SQLException
* @throws ClassNotFoundException
*/
public void testSelectUserById(int id) throws ClassNotFoundException, SQLException {
System.out.println("查用用户,入参为:" + id);
User user = userDao.findUserById(id);
// ...
System.out.println("用户查询成功,查询到的用户为:" + user);
}
/**
* 查询所有用户测试
*
* @throws ClassNotFoundException
* @throws SQLException
*/
public List<User> testFindAllUsers() throws ClassNotFoundException, SQLException {
System.out.println("查用所有用户");
List<User> userList = userDao.findAllUsers();
// ...
System.out.println("FindAllUsers执行成功,查询到的用户数量为:" + userList.size());
return userList;
}
/**
* 测试使用Statement一次插入一个用户
*
* @throws SQLException
* @throws ClassNotFoundException
*/
public void testInsertUser(User user) throws ClassNotFoundException, SQLException {
System.out.println("新增用户");
boolean result = userDao.insertUser(user);
// ...
System.out.println("新增用户执行结果为" + result);
}
/**
* 测试使用PreparedStatement一次插入多个用户
*
* @throws SQLException
* @throws ClassNotFoundException
*/
public void testInsertUser(List<User> userList) throws ClassNotFoundException, SQLException {
System.out.println("批量插入多个用户,开始时间为:" + System.currentTimeMillis());
long beginTimes = System.currentTimeMillis();
// 如果传入的list为null或其中无数据,方法结束
if (userList == null || userList.isEmpty()) {
return;
}
int result = userDao.insertUser(userList);
// ...
System.out.println("批量插入执行成功,结束时间为:" + System.currentTimeMillis() + ", 插入结果为" + result);
long millis = System.currentTimeMillis() - beginTimes;
System.out.println("PreparedStatement新增用户成功,耗时:" + millis + "豪秒");
}
/**
* 测试更新用户
*
* @param user
* @throws SQLException
* @throws ClassNotFoundException
*/
public void testUpdateUser(User user) throws ClassNotFoundException, SQLException {
System.out.println("更新指定用户信息");
boolean result = userDao.updateUser(user);
// ...
System.out.println("用户更新执行结果为:" + result);
}
/**
* 测试删除用户
*
* @param user
* @throws SQLException
* @throws ClassNotFoundException
*/
public void testDeleteUser(int id) throws ClassNotFoundException, SQLException {
System.out.println("删除指定用户信息:id=" + id);
boolean result = userDao.deleteUser(id);
// ...
System.out.println("删除用户执行结果为:" + result);
}
}
4.测试
关于User类的操作,dao层、Service层都已实现,一般来说,就可以在Servlet中进行Service的加载,并调用其中封装好的方法,完成业务逻辑并将处理结果返回给客户端即可。
这里为了方便测试,我们就使用最简单的main函数来进行调用(这里推荐使用单元测试–Junit),下面代码的测试顺序为:用户的插入–>查询所有用户–>更新某个用户–>查询更新用户–>删除某个用户。
public static void main(String[] args) {
UserService userService = UserService.getInstance();
// 批量构造10个user,并存入userList中
List<User> userList = new ArrayList<User>();
String initName = "test-";
for (int i = 1; i <= 10; i++) {
User user = new User();
user.setUserName(initName + i);
user.setPassword("123456");
user.setEmail("xxxxxxx");
user.setBirthday(new Date());
userList.add(user);
}
// 一般来讲,异常抛到Controller层就需要处理了,否则发生异常时会将异常的堆栈信息抛给用户
try {
// 使用过Statement对象插入单用户--该对象是从userList中获取的队首的用户
userService.testInsertUser(userList.get(0));
// 使用过PreparedStatement对象插入用户列表
userService.testInsertUser(userList);
// 查询所有用户,即上面新插入的十一条用户数据
userList = userService.testFindAllUsers();
for (User tmp : userList) {
System.out.println("查询到的用户:" + tmp);
}
// 更新userList中index为5数据
User user = userList.get(5);
System.out.println("更新数据的id为:" + user.getId());
user.setPassword("11111");
// 注意""与null 在UserDao中updateUser中产生的不同效果
user.setEmail("");
user.setBirthday(null);
userService.testUpdateUser(user);
// 查询上一条更新后的数据
userService.testSelectUserById(user.getId());
// 删除userList中index为7数据
userService.testDeleteUser(userList.get(7).getId());
} catch (ClassNotFoundException | SQLException e) {
// 记录异常信息,一般使用error级别
System.out.println("方法执行异常,异常信息为:" + e.getMessage());
e.printStackTrace();
}
}
上面测试代码运行后,控制台输出信息如下,从下图可以看到,单次插入了一个用户,批量插入了10个用户,因此查询处的所有用户工11个,对id为6的员工进行更新,查询后可以发现,password顺利修改了,而email和birthday也是一个被更新为空字符串,一个无变化,最后删除了id为8的用户。
为了展示上面代码对数据库的操作,我们来看下数据库中users表的截图,可以看到,id为6的用户已经被更新了,而id为8的用户也已被删除。
4.总结
本文简单的介绍了Statement与PreparedStatement对象,简述了两者的关系与区别;介绍了软件开发中的分层思想,较好的代码分层,不仅可以增加代码复用性,也因为这种分层的实现让代码的鲁棒性也更好;最后简单的师范了下如何分层开发代码,并演示了使用Statement与PreparedStatement完成数据库的增删改查。
在使用JDBC操作数据库时,Statement与PreparedStatement两个相比,推荐大家使用PreparedStatement,因为其更方便、更高效、更安全。
Statement效果一般,PreparedStatement效果不错,但是若要和ORM框架比起来,我还是选Mybatis。。。
参考阅读:
又到了分隔线以下,本文到此就结束了,本文内容全部都是由博主自己进行整理并结合自身的理解进行总结,如果有什么错误,还请批评指正。
Java web这一专栏会是一个系列博客,喜欢的话可以持续关注,如果本文对你有所帮助,还请还请点赞、评论加关注。
有任何疑问,可以评论区留言。