JDBC
简介
JDBC 就是使用 Java 语言操作关系型数据库的一套API。全称:Java DataBase Connectivity,Java 数据库连接。
JDBC 为各关系型数据库提供了一套标准接口,而各关系型数据库为了可以使用这些标准接口,提供了相对应的实现类。这些实现类,称为驱动。
JDBC本质:
- 官方(sun公司)定义的一套操作所有关系型数据库的规则,即接口。
- 各个数据库厂商去实现这套接口,提供数据库驱动 jar 包。
- 我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动 jar 包中的实现类。
基础操作:
package edu.hnu.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.Statement;
public class JDBCDemo {
public static void main(String[] args) throws Exception {
//注册驱动
//Class.forName("com.mysql.cj.jdbc.Driver");
//获取连接
String url = "jdbc:mysql://127.0.0.1:3306/test";
String username = "root";
String password = "MySQL:040809"; //此处填写密码
Connection conn = DriverManager.getConnection(url, username, password);
//定义sql语句
String sql = "update account set money = 0 where id = 2";
//获取执行sql的对象 Statement
Statement stmt = conn.createStatement();
//执行sql,返回的是影响的行数
int count = stmt.executeUpdate(sql);
//处理结果
System.out.println(count);
//释放资源
//先创建的Connection后创建的Statement,所以先释放Statement后释放Connection
stmt.close();
conn.close();
}
}
DriverManager
驱动管理类,为工具类。作用有二:
- 注册驱动。
- 获取数据库连接。
关于 DriverManager 当中的 getConnection 方法:
static Connection getConnection(String url, String user, String password);
其中,url 的语法为:jdbc:mysql://ip地址:端口号/数据库名称?参数键值对1&参数键值对2...
。
如果连接的是本机的数据库,并且 mysql 服务默认端口是3306,则 url 可以简写为:jdbc:mysql///数据库名称?参数键值对1&参数键值对2...
。
其中,参数键值对可以设置为useSSL=false
,用于禁用安全连接方式,解决警告提示。
Connection
数据库连接对象,作用有二:
- 获取执行 SQL 的对象。
- 管理事务。
其中,获取 SQL 对象有三:
- 普通执行 SQL 对象:
Statement creatStatement()
。 - 预编译 SQL 的执行 SQL 对象,防止 SQL 注入:
PreparedStatement prepareStatement(sql)
。 - 执行存储过程的对象:
CallableStatement prepareCall(sql)
。
JDBC 的事务管理有三:
- 开启事务 :
setAutoCommit(boolean autoCommit);
,其中,true为自动提交事务,false为手动提交事务,即为开启事务。 - 提交事务:
commit();
- 回滚事务:
rollback();
package edu.hnu.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
public class JDBCDemo_Connection {
public static void main(String[] args) throws Exception {
//获取连接
String url = "jdbc:mysql://127.0.0.1:3306/test";
String username = "root";
String password = "MySQL:040809";
Connection conn = DriverManager.getConnection(url, username, password);
//定义sql语句
String sql1 = "update account set money = 3000 where id = 2";
String sql2 = "update account set money = 3000 where id = 1";
//获取执行sql的对象 Statement
Statement stmt = conn.createStatement();
//开启事务
try {
//开启事务
conn.setAutoCommit(false);
//执行sql1
int count1 = stmt.executeUpdate(sql1);
//处理结果
System.out.println(count1);
//int i = 3/0; 异常!会抛出错误,然后回滚事务
//执行sql2
int count2 = stmt.executeUpdate(sql2);
//处理结果
System.out.println(count2);
//提交事务
conn.commit();
} catch (Exception e) {
//回滚事务
conn.rollback();
throw new RuntimeException(e);
}
//释放资源
//先创建的Connection后创建的Statement,所以先释放Statement后释放Connection
stmt.close();
conn.close();
}
}
Statement
只有一个功能:执行 SQL 语句。
执行DML、DDL语句时:int executeUpdate(sql)
。执行DML时,返回的是影响的行数,DDL执行成功后,可能返回0。
执行DQL语句时:ResultSet executeQuery(sql)
,返回值为结果集对象。
ResultSet
结果集对象,可以获取查询结果。
boolean next()
:将光标从当前位置向前移动一行,boolean 用于判断当前行是否为有效行。
dataType getDatetype(参数)
:参数可以填 int,用于查询列的编号,从1开始;也可以填 String,用于获取列的名称。
package edu.hnu.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class JDBCDemo_ResultSet {
public static void main(String[] args) throws Exception {
//获取连接
String url = "jdbc:mysql://127.0.0.1:3306/test";
String username = "root";
String password = "MySQL:040809";
Connection conn = DriverManager.getConnection(url, username, password);
//定义sql语句
String sql = "select * from account";
//获取执行sql的对象 Statement
Statement stmt = conn.createStatement();
//获取结果集对象
ResultSet rs = stmt.executeQuery(sql);
//打印表
while(rs.next()) { //光标向下移动一行,并判断当前行是否有数据
//获取数据
int id = rs.getInt(1);
String name = rs.getString(2);
int money = rs.getInt(3);
System.out.println(id + " " + name + " " + money);
}
//释放资源
//先创建的Connection后创建的Statement,所以先释放Statement后释放Connection
stmt.close();
conn.close();
}
}
打印表的代码也可以这么写:
//打印表
while(rs.next()) { //光标向下移动一行,并判断当前行是否有数据
//获取数据
int id = rs.getInt("id");
String name = rs.getString("name");
int money = rs.getInt("money");
System.out.println(id + " " + name + " " + money);
}
PreparedStatement
预编译 SQL 语句并执行,预防 SQL 注入问题。
SQL 注入:SQL 注入是通过操作输入来修改事先定义好的 SQL 语句,用以达到执行代码对服务器进行攻击的方法。
现有如下的登录逻辑:
package edu.hnu.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
public class JDBCDemo_UserLogin {
public static void main(String[] args) throws Exception {
//获取连接
String url = "jdbc:mysql://127.0.0.1:3306/test";
String username = "root";
String password = "MySQL:040809";
Connection conn = DriverManager.getConnection(url, username, password);
//接受用户输入的名称和密码
String name = "张三";
String pwd = "123";
String sql = "select * from account where name = '" + name + "' && password = '" + pwd + "'";
//获取stmt对象
Statement stmt = conn.createStatement();
//执行sql
ResultSet rs = stmt.executeQuery(sql);
//判断是否成功
if(rs.next()) System.out.println("登录成功");
else System.out.println("登录失败");
//释放资源
//先创建的Connection后创建的Statement,所以先释放Statement后释放Connection
stmt.close();
conn.close();
}
}
SQL注入 :只需要输入密码 String pwd = "' or '1' = '1";
,则 sql 的后半段就会变成 password = '' or '1' = '1'
,or 后面语句永真,就可以在任意用户名下进行登录。
使用 PreparedStatement 防止 SQL 注入:
使用 ?作为占位符,占用 sql 语句中需要拼接的地方。
//参数值使用?占位符替代 String sql = "select * from account where name = ? && password = ?"; //通过Connection对象获取,并传入对应的sql语句 PreparedStatement pstmt = conn.prepareStatement(sql);
设置参数值。
//利用set函数给参数赋值 setDatetype(参数位置, 参数值)
执行 SQL。
//执行SQL语句 executeUpdate(); executeQuery();
使用 PreparedStatement 防止 SQL 注入:
package edu.hnu.jdbc;
import com.mysql.cj.xdevapi.PreparableStatement;
import java.sql.*;
public class JDBCDemo_UserLogin {
public static void main(String[] args) throws Exception {
//获取连接
String url = "jdbc:mysql://127.0.0.1:3306/test";
String username = "root";
String password = "MySQL:040809";
Connection conn = DriverManager.getConnection(url, username, password);
//接受用户输入的名称和密码
String name = "张三";
String pwd = "' or '1' = '1";
//利用?占位参数
String sql = "select * from account where name = ? && password = ?";
//获取pstmt对象
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1,name);
pstmt.setString(2,pwd);
//执行sql
ResultSet rs = pstmt.executeQuery();
if(rs.next()) System.out.println("登录成功");
else System.out.println("登录失败");
//释放资源
//先创建的Connection后创建的Statement,所以先释放Statement后释放Connection
pstmt.close();
conn.close();
}
}
原理
PreparedStatement 好处:
- 预编译 SQL,性能更高。
- 防止 SQL 注入:将敏感字符进行转义。
预编译:一般来说,Java 代码当中的 sql 语句在编写完毕之后会发送给 mysql 服务器。然后,mysql 服务器再去检查语法,编译 SQL,将 SQL 语句转化为可执行的函数或者直接执行 SQL语句。而当我们使用 PreparedStatement,在 new pstmt 对象的时候就已经将 SQL 语句传入对象当中了,此时就已经启动了检查语法的工作,此之谓预编译。这样子,当语句传入 mysql 服务器之后,只需要执行即可。
使用预编译的时候,需要在 url 的参数当中加入:useServerPrepStmts=true
。
数据库连接池
简介
- 数据库连接池是一个容器,负责分配,管理数据库连接(Connection)。
- 它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个。
- 释放空闲时间超过最大空闲时间的数据库连接来避免因为没有释放数据库连接而引起的数据库连接遗漏。
- 好处是可以进行资源重用,并且提升系统的响应速度,还可以避免数据库连接遗漏(强制断开没有操作的Connection,将其供给给正在等待的用户)。
相当于好多人(顾客)来用一个数据库(餐厅),我们在用户访问数据库之前,创建一个连接池(前台),在连接池当中提前存入Connection(服务员)。这样,当一个用户要使用数据库的时候,Connection 就连接上去,用户不需要使用的时候,Connection 断开连接,但不会消失,而是去等待下一个需要服务的用户,就不需要重新去申请 Connection 了(服务员去为下一名顾客服务,而不是餐厅直接开除服务员然后再去招新员工)。
常见的数据库连接池:
- DBCP
- C3P0
- Druid
Druid
Druid 连接池是阿里巴巴开源的数据库连接池项目,其功能强大,性能优秀,是 Java 语言最好的数据库连接池之一。下载地址。
标准接口:DataSource(即以后利用这个接口来获取 Connection)。
配置文件druid.properties
示例如下:
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/test
username=root
password=MySQL:040809
# 初始化连接数量
initialSize=5
# 最大连接数
maxActive=10
# 最大等待时间
maxWait=3000
Connection 的获取示例:
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.util.Properties;
public class Main {
public static void main(String[] args) throws Exception {
//加载配置文件
Properties prop = new Properties();
prop.load(new FileInputStream("resources/druid.properties"));
//获取连接池对象
DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);
//获取数据库连接Connection
Connection connection = dataSource.getConnection();
//Connection连接成功之后,就可以使用其进行数据库的操作了...
}
}
工具类封装
基本工具类封装
使用 JDBC 时,我们可以封装一个工具类,内部包含连接池对象,同时对外提供连接的方法和回收连接的方法。工具类的方法推荐写成静态的,外部调用会更加方便。
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
public class JDBCUtil {
private static DataSource dataSource = null; //连接池对象
static {
//加载配置文件
Properties prop = new Properties();
//通过类加载器获取文件输入流
InputStream resourceAsStream = JDBCUtil.class
.getClassLoader()
.getResourceAsStream("druid.properties");
try {
prop.load(resourceAsStream); //配置文件加载
} catch (IOException e) {
throw new RuntimeException(e);
}
try {//生成dataSource
dataSource = DruidDataSourceFactory.createDataSource(prop);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/*对外提供连接的方法*/
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
/*对外回收连接的方法*/
public static void freeConnection(Connection connection) throws SQLException {
connection.close();
}
}
import java.sql.Connection;
public class Main {
public static void main(String[] args) throws Exception {
//获取连接
Connection connection = JDBCUtil.getConnection();
//数据库增删改查动作
//连接回收
JDBCUtil.freeConnection(connection);
}
}
ThreadLocal
在 Web 开发中,常用 ThreadLocal
来存储数据库连接对象或者 HttpSession 会话对象。这样做的目的是保证在同一线程内多次获取数据库的连接是同一个连接。
同一个线程保证使用同一个 Connection 对象的原因:
- 事务管理:数据库事务必须在同一个连接中被处理以保持事务的 ACID 属性。如果一个业务流程中的操作分布在不同的
Connection
对象中,那么这些操作就不能组成一个事务。 - 性能考虑:频繁地创建和销毁数据库连接是一个非常昂贵的过程,因为每个连接的建立都涉及到网络通信和数据库资源的分配。通过复用同一个
Connection
对象,可以显著减少这种开销。 - 连接池的有效利用:在多数情况下,系统会使用连接池技术来管理数据库连接。通过复用同一个线程的
Connection
对象,系统可以减少对连接池的请求次数,提高连接池的利用率和效率。 - 线程安全:数据库连接不是线程安全的资源,不同线程并发访问同一个
Connection
可能会导致意想不到的问题。使用ThreadLocal
保证每个线程都有自己的Connection
实例可以避免这类问题。 - 资源跟踪和管理:通过在同一个线程保持对相同
Connection
的引用,可以更容易地对资源进行监控、统计和管理,例如自动关闭长时间未使用的连接,以避免资源泄漏。
普通情况下,为了让 Connection 能够在 service、dao 层传递(保证一致性),我们需要逐层传递参数:
public class Test {
public static void main(String[] args) {
Thread thread = Thread.currentThread();
System.out.println(thread);
//创建Connection对象
Connection connection = new Connection();
UserService service = new UserService();
//Connection对象传递给service层
service.save(connection);
}
}
public class UserService {
private final UserDao userDao = new UserDao();
public void save(Connection connection) {
Thread thread = Thread.currentThread();
System.out.println(thread);
//Connection对象传递给Dao层
userDao.insert(connection);
}
}
public class UserDao {
public void insert(Connection connection) {
Thread thread = Thread.currentThread();
System.out.println(thread);
System.out.println("insert...");
}
}
//并且,通过打印语句,我们可以看出三者是线程一致的
/*
Thread[#1,main,5,main]
Thread[#1,main,5,main]
Thread[#1,main,5,main]
insert...
*/
为了让同一个线程能够获取同一个链接,我们需要创建一个类,这个类的功能是将线程与链接进行映射,使得同一个线程能够对应上同一个链接。很显然,我们需要使用 Map 集合来完成这个类的编写:
public class MyThreadLocal<T> {
//所有需要和当前线程绑定的数据放到这个容器当中
private Map<Thread, Object> map = new HashMap<>();
//向ThreadLocal中绑定数据
public void set(T obj) {
map.put(Thread.currentThread(), obj);
}
//从ThreadLocal中获取数据
public T get() {
return (T) map.get(Thread.currentThread());
}
//从ThreadLocal中移除数据
public void remove() {
map.remove(Thread.currentThread());
}
}
那么,有了这个MyThreadLocal
类之后,我们便可以在创建链接的时候,把当前线程和对应链接进行绑定。这里我们使用一个工具类来完成我们的需求:
public class DBUtil {
private DBUtil() {}
private static MyThreadLocal<Connection> myThreadLocal= new MyThreadLocal<>();
public static Connection getConnection() {
Connection connection = myThreadLocal.get();
//第一次调用getConnection,Connection一定是空的
//我们需要new一次,然后和当前线程绑定
if (connection == null) {
myThreadLocal.set(new Connection());
}
return myThreadLocal.get();
}
}
最后,我们的代码就可以使用我们的工具类来实现同一个线程对应同一个链接了,就不需要我们再去逐层传递参数了:
public class Test {
public static void main(String[] args) {
//创建Connection对象
Connection connection = DBUtil.getConnection();
UserService service = new UserService();
service.save();
}
}
public class UserDao {
public void insert() {
Connection connection = DBUtil.getConnection();
System.out.println("insert...");
}
}
优化工具类
利用 ThreadLocal
优化上述工具类。ThreadLocal
可以为同一个线程存储共享变量。在 Java 中,每一个线程对象都有一个 ThreadLocalMap<ThreadLocal, Object>
,其 key 就是一个 ThreadLocal
,而 Ojbect 就是该线程的共享变量。
优势:在做事务操作的时候,service 和 dao 属于同一个线程,就不用传递参数了。大家都可以获得相同的连接。
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
public class JDBCUtil {
private static DataSource dataSource = null;
private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
static {
//加载配置文件
Properties prop = new Properties();
//通过类加载器获取文件输入流
InputStream resourceAsStream = JDBCUtil.class
.getClassLoader()
.getResourceAsStream("druid.properties");
try {
prop.load(resourceAsStream); //配置文件加载
} catch (IOException e) {
throw new RuntimeException(e);
}
try {//生成dataSource
dataSource = DruidDataSourceFactory.createDataSource(prop);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/*获取连接*/
public static Connection getConnection() throws SQLException {
//先判断本地是否存在连接
Connection connection = threadLocal.get();
//如果没有,则获取一个
if(null == connection) {
connection = dataSource.getConnection();
//将新的连接加入本地线程中
threadLocal.set(connection);
}
return connection;
}
/*释放连接*/
public static void freeConnection() throws SQLException {
//先判断本地是否存在连接
Connection connection = threadLocal.get();
if(null != connection) {
//清空线程本地变量
threadLocal.remove();
connection.setAutoCommit(true); //事务状态回归
//回收连接池
connection.close();
}
}
}
BaseDao 的封装
项目中,DAO 层内部存储的是对于数据库操作的类。数据库的基本增删改查操作在 DAO 层中的每一个类几乎都得使用。故我们不妨把这些基本的操作抽取出来,封装在基本的类中,这个基本的类我们称之为 BaseDao 类。
在使用过程中,可以继承 BaseDao 类以获得基础的增删改查方法。
import java.lang.reflect.Field;
import java.sql.*;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.List;
public abstract class BaseDao {
/*基本的查询方法,返回的是最终的影响行数*/
public int baseUpdate(String sql, Object...args) throws SQLException {
//获取连接
Connection connection = JDBCUtil.getConnection();
//获取语句对象
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//填入参数
for(int i = 0; i < args.length; ++i) {
preparedStatement.setObject(i + 1, args[i]);
}
//执行更改
int rows = preparedStatement.executeUpdate();
//释放资源
preparedStatement.close();
//是否回收连接需要考虑是不是事务
if (connection.getAutoCommit()) { //true代表没有开启事务,则正常回收连接
JDBCUtil.freeConnection();
}
return rows;
}
/*基本查询方法,返回多个对象的集合*/
public <T> List<T> baseQuery(Class<T> clazz,
String sql, Object...args) throws Exception {
List<T> list = new ArrayList<>();
//获取连接
Connection connection = JDBCUtil.getConnection();
//获取语句对象
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//填入参数
for(int i = 0; i < args.length; ++i) {
preparedStatement.setObject(i + 1, args[i]);
}
//执行查询
ResultSet resultSet = preparedStatement.executeQuery();
//获得元数据信息
ResultSetMetaData metaData = resultSet.getMetaData();
//获取成员变量个数
int columnCount = metaData.getColumnCount();
//创建对象并给对象赋值
while(resultSet.next()) {
//一行数据对应一个 T 的对象
T t = clazz.getDeclaredConstructor().newInstance();
for(int i = 1; i <= columnCount; ++i) {
//获取成员变量名
String columnName = metaData.getColumnName(i);
//获取成员变量值
Object value = resultSet.getObject(columnName);
//处理datetime类型字段和java.util.Date转换问题
if (value.getClass().equals(LocalTime.class)) {
value = Timestamp.valueOf((LocalDateTime) value);
}
//利用反射给对象赋值
Field declaredField = clazz.getDeclaredField(columnName);
declaredField.setAccessible(true); //这个属性可能是私有的,需要开启访问权限
declaredField.set(t, value);
}
//添加对象
list.add(t);
}
//关闭资源
resultSet.close();
preparedStatement.close();
if(connection.getAutoCommit()) {
JDBCUtil.freeConnection();
}
return list;
}
}
BaseDao 的最终优化
经过优化之后的 BaseDao 如下:
public class BaseDao {
// 公共的查询方法 返回的是单个对象
public <T> T baseQueryObject(Class<T> clazz, String sql, Object ... args) {
T t = null;
Connection connection = JDBCUtil.getConnection();
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
int rows = 0;
try {
// 准备语句对象
preparedStatement = connection.prepareStatement(sql);
// 设置语句上的参数
for (int i = 0; i < args.length; i++) {
preparedStatement.setObject(i + 1, args[i]);
}
// 执行 查询
resultSet = preparedStatement.executeQuery();
if (resultSet.next()) {
t = (T) resultSet.getObject(1);
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
if (null != resultSet) {
try {
resultSet.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if (null != preparedStatement) {
try {
preparedStatement.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
JDBCUtil.releaseConnection();
}
return t;
}
// 公共的查询方法 返回的是对象的集合
public <T> List<T> baseQuery(Class clazz, String sql, Object ... args){
List<T> list =new ArrayList<>();
Connection connection = JDBCUtil.getConnection();
PreparedStatement preparedStatement=null;
ResultSet resultSet =null;
int rows = 0;
try {
// 准备语句对象
preparedStatement = connection.prepareStatement(sql);
// 设置语句上的参数
for (int i = 0; i < args.length; i++) {
preparedStatement.setObject(i+1,args[i]);
}
// 执行 查询
resultSet = preparedStatement.executeQuery();
ResultSetMetaData metaData = resultSet.getMetaData();
int columnCount = metaData.getColumnCount();
// 将结果集通过反射封装成实体类对象
while (resultSet.next()) {
// 使用反射实例化对象
Object obj =clazz.getDeclaredConstructor().newInstance();
for (int i = 1; i <= columnCount; i++) {
String columnName = metaData.getColumnLabel(i);
Object value = resultSet.getObject(columnName);
// 处理datetime类型字段和java.util.Data转换问题
if(value.getClass().equals(LocalDateTime.class)){
value= Timestamp.valueOf((LocalDateTime) value);
}
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);
field.set(obj,value);
}
list.add((T)obj);
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
if (null !=resultSet) {
try {
resultSet.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if (null != preparedStatement) {
try {
preparedStatement.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
JDBCUtil.releaseConnection();
}
return list;
}
// 通用的增删改方法
public int baseUpdate(String sql,Object ... args) {
// 获取连接
Connection connection = JDBCUtil.getConnection();
PreparedStatement preparedStatement=null;
int rows = 0;
try {
// 准备语句对象
preparedStatement = connection.prepareStatement(sql);
// 设置语句上的参数
for (int i = 0; i < args.length; i++) {
preparedStatement.setObject(i+1,args[i]);
}
// 执行 增删改 executeUpdate
rows = preparedStatement.executeUpdate();
// 释放资源(可选)
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
if (null != preparedStatement) {
try {
preparedStatement.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
JDBCUtil.releaseConnection();
}
// 返回的是影响数据库记录数
return rows;
}
}