JDBC之使用Statement与PreparedStatement实现增删改查

上一文中,我们对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编程步骤拿来,我们按照分层的思想,来对其步骤进行分解。

  1. 加载并注册数据驱动
  2. 通过DriverManager获取数据库的连接Connection
  3. 通过Connection对象获取Statment对象
  4. 使用获取的Statment对象执行SQL语句
  5. 处理返回结果
  6. 关闭连接,释放资源

​ 这里,我们将步骤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。。。

参考阅读:

  1. Java Web数据库开发(MySQL)之环境准备
  2. 写给求甚解的你—JDBC详解

​ 又到了分隔线以下,本文到此就结束了,本文内容全部都是由博主自己进行整理并结合自身的理解进行总结,如果有什么错误,还请批评指正。

​ Java web这一专栏会是一个系列博客,喜欢的话可以持续关注,如果本文对你有所帮助,还请还请点赞、评论加关注。

​ 有任何疑问,可以评论区留言。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章