JDBC学习
注:本文资料是对尚硅谷JDBC视频的详细学习笔记。
一:什么是JDBC:
JDBC(Java Database Connectivity)是Java编程语言的一种API,用于连接和操作数据库。它允许Java应用程序与各种关系型数据库进行通信,以便进行数据的读取、写入和更新操作。通过JDBC,开发人员可以使用标准的SQL语句来访问数据库,并且可以实现跨平台的数据库访问。JDBC提供了一组接口和类,开发人员可以使用这些接口和类来编写数据库操作的代码。
二:两种实现方法:
Statement和PreparedStatement都是用来执行SQL语句的接口,但它们之间有一些重要的区别。
Statement是一种静态的SQL语句,它在执行之前已经被编译好了,每次执行SQL语句时都会重新编译,因此可能会导致性能上的损失。而PreparedStatement是一种预编译的SQL语句,它在执行之前已经被编译好了,可以多次执行同一个SQL语句而不需要重新编译,从而提高了性能。
Statement不支持参数化查询,即在执行SQL语句时不能动态地传入参数,而PreparedStatement支持参数化查询,可以在执行SQL语句时动态地传入参数,这样可以防止SQL注入攻击。
PreparedStatement通常比Statement更安全,因为它可以预编译SQL语句并使用参数化查询,从而减少了SQL注入攻击的风险。
总的来说,PreparedStatement比Statement更高效、更安全,因此在实际开发中更推荐使用PreparedStatement来执行SQL语句。
我们先来看看它们共有的代码部分与属性:
也就是说,它们都要有以下的代码:
//DriverManager.registerDriver(new Driver());//两次驱动,只触发代码块
//new Driver();不推荐,在Oracle中可能无法成功使用
Class.forName("com.mysql.cj.jdbc.Driver");//调用类的加载器
Connection conn = DriverManager.getConnection("jdbc:mysql:///BasicInfoDB","root","zheshimima");
//........
ResultSet resultSet = stmt.executeQuery();//Statement还要传入参数sql
//资源的释放
......
在Java中,使用Class.forName()方法加载驱动是因为数据库驱动程序通常在静态代码块中注册自己,而Class.forName()方法会触发静态代码块的执行,从而注册数据库驱动程序。通过加载驱动,可以建立与数据库的连接,并执行相应的数据库操作。因此,使用Class.forName()方法加载驱动是连接数据库的必要步骤。
我们再来看看Statement的实现交互:
public class StatementQueryPart {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql:///BasicInfoDB","root","zheshimima");
Statement stmt = conn.createStatement();
String sql = "SELECT * FROM Users";
ResultSet resultSet = stmt.executeQuery(sql);
while(resultSet.next()){
int id = resultSet.getInt(1);
String name = resultSet.getString("name");
System.out.println("id = "+id+' '+"name = "+name);
}
resultSet.close();
stmt.close();
conn.close();
}
它使用resultSet.next()
来检查是否还有更多的结果集记录。resultSet.next()
方法会移动指针到结果集的下一行,并返回一个布尔值,如果当前位置之后还有更多的记录,则返回true
,否则返回false
。
注意,无论是executeUpdate还是executeQuery,Statement与Preparedstatement都可以调用该静态方法,不过Preparedstatement不要传入参数,而对于选择,我们有:
我们再来看看Preparedstatement:
public static void main(String[] args) throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql:///BasicInfoDB", "root", "zheshimima");
String sql = "SELECT * FROM Users WHERE id = ? AND name = ? ;";
PreparedStatement preparedStatement = conn.prepareStatement(sql);
preparedStatement.setObject(1, "101");
preparedStatement.setObject(2, "Alice");
ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
int id = resultSet.getInt(1);
String name = resultSet.getString("name");
System.out.println("id = " + id + ' ' + "name = " + name);
}
resultSet.close();
preparedStatement.close();
conn.close();
}
这是流程介绍:
我们在实际中,更多的使用的是这种方法,除了上面的查询,我们还可以有增加,删除,更新等操作:
@Test
public void add_data() throws SQLException, ClassNotFoundException {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql:///BasicInfoDB", "root", "zheshimima");
String sql = "INSERT INTO Users(id,name,age) VALUES(?,?,?);";
PreparedStatement preparedStatement = conn.prepareStatement(sql);
preparedStatement.setObject(1, 103);
preparedStatement.setObject(2, "Shelly");
preparedStatement.setObject(3, 56);
int rows = preparedStatement.executeUpdate();
if (rows > 0) {
System.out.println("OK");
} else {
System.out.println("NO");
}
preparedStatement.close();
conn.close();
}
@Test
public void update_data() throws SQLException, ClassNotFoundException {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql:///BasicInfoDB", "root", "zheshimima");
String sql = "UPDATE Users SET name=?, age=? WHERE id=?;";
PreparedStatement preparedStatement = conn.prepareStatement(sql);
preparedStatement.setObject(1, "Shelly");
preparedStatement.setObject(2, 57);
preparedStatement.setObject(3, 103);
int rows = preparedStatement.executeUpdate();
if (rows > 0) {
System.out.println("OK");
} else {
System.out.println("NO");
}
preparedStatement.close();
conn.close();
}
@Test
public void delete_data() throws SQLException, ClassNotFoundException {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql:///BasicInfoDB", "root", "zheshimima");
String sql = "DELETE FROM Users WHERE id=?;";
PreparedStatement preparedStatement = conn.prepareStatement(sql);
preparedStatement.setObject(1, 103);
int rows = preparedStatement.executeUpdate();
if (rows > 0) {
System.out.println("OK");
} else {
System.out.println("NO");
}
preparedStatement.close();
conn.close();
}
当然,我们可以把结果封装到集合框架中,并且这种方法,可以不用手动,泛用性更高:
@Test
public void show_all_data() throws SQLException, ClassNotFoundException {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql:///BasicInfoDB", "root", "zheshimima");
String sql = "SELECT * FROM Users;";
PreparedStatement preparedStatement = conn.prepareStatement(sql);
List<Map> resultList = new ArrayList<>();
ResultSet rs = preparedStatement.executeQuery();
ResultSetMetaData metaData = rs.getMetaData();
int columnCount = metaData.getColumnCount();
while (rs.next()) {
Map<String, Object> row = new HashMap<>();
for (int i = 1; i <= columnCount; i++) {
String columnName = metaData.getColumnLabel(i);//获取列的别名
Object columnValue = rs.getObject(i);
row.put(columnName, columnValue);
}
resultList.add(row);
}
System.out.println(resultList);
rs.close();
preparedStatement.close();
conn.close();
}
这段代码是一个Java测试方法,它连接到名为"BasicInfoDB"的MySQL数据库,并从"Users"表中选择所有数据。代码中首先加载MySQL驱动程序,然后建立与数据库的连接。接着执行一个SELECT查询,将结果存储在一个ResultSet对象中。然后,通过ResultSetMetaData获取结果集的元数据,包括列数和列名等信息。在一个循环中,遍历结果集的每一行,并将每一行的数据存储在一个Map对象中,然后将该Map对象添加到一个List中。最后,输出这个List,即包含所有数据的结果集。最后,关闭ResultSet、PreparedStatement和Connection对象,释放资源。
在这段代码中,有几个重要的方法和对象需要重点关注:
DriverManager.getConnection(url, username, password):这是Java中用于建立数据库连接的方法。它接受三个参数,分别是数据库的URL、用户名和密码。在这里,我们使用这个方法连接到名为"BasicInfoDB"的MySQL数据库。
PreparedStatement:这是用于执行SQL语句的对象。在这段代码中,我们使用PreparedStatement对象来执行SELECT查询语句,可以使用PreparedStatement的setXXX()方法设置参数,防止SQL注入攻击。
ResultSet:这是用于存储查询结果的对象。在这段代码中,我们通过执行executeQuery()方法获取查询结果集,然后通过ResultSetMetaData获取结果集的元数据,包括列数和列名等信息。
ResultSetMetaData:这是用于获取ResultSet元数据的对象。在这段代码中,我们使用ResultSetMetaData对象获取结果集的元数据,如列数和列名等信息。
Map和List:这是Java中用于存储数据的集合类。在这段代码中,我们使用Map来存储每一行的数据,使用List来存储所有行的数据,最终输出整个结果集。
关闭资源:在代码的最后,我们通过调用close()方法关闭ResultSet、PreparedStatement和Connection对象,释放资源,避免资源泄漏和性能问题。
这些方法和对象是这段代码中的重点,通过它们可以实现与数据库的连接、查询数据、获取元数据和存储数据等功能。
注意,metaData.getColumnLabel
方法是显示列的别名,而metaData.getColumnName是不显示列别名,使用在某些时候,并不符合我们开发的需要。
三:主键回显与主键值的获取:
@Test
public void Obtain() throws SQLException, ClassNotFoundException {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql:///BasicInfoDB", "root", "zheshimima");
String sql = "INSERT INTO Users(id,name,age) VALUES(?,?,?);";
PreparedStatement preparedStatement = conn.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
preparedStatement.setObject(1, 105);
preparedStatement.setObject(2, "Jack");
preparedStatement.setObject(3, 57);
int rows = preparedStatement.executeUpdate();
if (rows > 0) {
System.out.println("OK");
ResultSet generatedKeys = preparedStatement.getGeneratedKeys();
generatedKeys.next();
int id = generatedKeys.getInt(1);
System.out.println(id);
} else {
System.out.println("NO");
}
preparedStatement.close();
conn.close();
}
主键回显是在插入数据库记录后,获取该记录的主键值。在这段代码中,主键回显的操作如下:
执行插入操作:
int rows = preparedStatement.executeUpdate();
这行代码执行了插入操作,将新的用户信息插入到Users
表中。检查插入是否成功:
if (rows > 0) {...}
这行代码检查插入操作是否成功。如果executeUpdate()
方法返回的值大于 0,说明插入操作成功。获取生成的键:
ResultSet generatedKeys = preparedStatement.getGeneratedKeys();
这行代码获取了插入操作生成的键。在这里,生成的键就是新插入记录的主键。读取主键值:
generatedKeys.next(); int id = generatedKeys.getInt(1);
这两行代码读取了生成的主键值。next()
方法将结果集的光标移动到下一行,getInt(1)
方法获取第一列的值,也就是主键的值。打印主键值:
System.out.println(id);
这行代码打印了主键值。conn.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
,注意,常量参数的传入。
这就是主键回显的操作。在实际应用中,主键回显常常用于插入记录后,需要使用新记录的主键进行其他操作的场景。例如,插入一个订单记录后,可能需要使用订单的主键作为外键,插入订单项记录。这时,就需要在插入订单记录后,立即获取订单的主键。主键回显就可以满足这种需求。
四:批量添加:
public void batchInsert(List<User> users) throws SQLException, ClassNotFoundException {
Class.forName("com.mysql.cj.jdbc.Driver");
// 在连接字符串中启用rewriteBatchedStatements,以允许批量插入
Connection conn = DriverManager.getConnection("jdbc:mysql:///BasicInfoDB?rewriteBatchedStatements=true", "root", "password");
String sql = "INSERT INTO Users(id,name,age) VALUE(?,?,?)";
PreparedStatement preparedStatement = conn.prepareStatement(sql);
for (User user : users) {
preparedStatement.setInt(1, user.getId());
preparedStatement.setString(2, user.getName());
preparedStatement.setInt(3, user.getAge());
preparedStatement.addBatch();
}
int[] rows = preparedStatement.executeBatch();
for (int i = 0; i < rows.length; i++) {
if (rows[i] > 0) {
System.out.println("第 " + (i + 1) + " 条记录插入成功");
} else {
System.out.println("第 " + (i + 1) + " 条记录插入失败");
}
}
preparedStatement.close();
conn.close();
}
在这个示例中,我们首先创建了一个 PreparedStatement
,然后对于 users
列表中的每个用户,我们都将用户的信息添加到批处理中。然后,我们执行批处理,得到一个表示每条记录是否插入成功的数组。最后,我们遍历这个数组,打印出每条记录是否插入成功。
五:事务类设计:
这里是一个简单的示例,使用 JDBC 模拟转账操作,体现了事务的特性。我们设计了两个类,一个是业务类 TransferService
,另一个是加减钱类 AccountDao
。
// 加减钱类
public class AccountDao {
public void decreaseMoney(Connection conn, int id, double money) throws SQLException {
String sql = "UPDATE Account SET balance = balance - ? WHERE id = ?";
PreparedStatement preparedStatement = conn.prepareStatement(sql);
preparedStatement.setDouble(1, money);
preparedStatement.setInt(2, id);
preparedStatement.executeUpdate();
}
public void increaseMoney(Connection conn, int id, double money) throws SQLException {
String sql = "UPDATE Account SET balance = balance + ? WHERE id = ?";
PreparedStatement preparedStatement = conn.prepareStatement(sql);
preparedStatement.setDouble(1, money);
preparedStatement.setInt(2, id);
preparedStatement.executeUpdate();
}
}
// 业务类
public class TransferService {
private AccountDao accountDao = new AccountDao();
public void transfer(int fromId, int toId, double money) throws SQLException, ClassNotFoundException {
Connection conn = null;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql:///BankDB", "root", "password");
// 开启事务
conn.setAutoCommit(false);
// 执行转账操作
accountDao.decreaseMoney(conn, fromId, money);
accountDao.increaseMoney(conn, toId, money);
// 提交事务
conn.commit();
} catch (Exception e) {
if (conn != null) {
// 回滚事务
conn.rollback();
}
throw e;
} finally {
if (conn != null) {
conn.close();
}
}
}
}
在这个示例中,TransferService
类是业务类,它负责处理转账业务。AccountDao
类是加减钱类,它负责更新账户余额。在 transfer
方法中,我们首先开启了一个事务,然后执行转账操作,最后提交事务。如果在执行转账操作过程中发生了异常,我们会回滚事务,以保证数据的一致性。
DAO(Data Access Object)是一种设计模式,用于将应用程序的业务逻辑与数据访问逻辑分离。在使用DAO模式时,数据访问逻辑被封装在一个独立的类中,这个类通常包含对数据库的CRUD(创建、读取、更新、删除)操作。通过使用DAO模式,可以使应用程序的不同部分相互独立,提高了代码的可维护性和可扩展性。DAO模式通常用于将数据库操作与业务逻辑分离,使得应用程序更易于管理和测试。
注意:关键是把分开的Connection合为一个,统一的关闭和操作,避免了负数转帐的异常,我们要关闭自动提交,这样,遇到异常时,才可以及时回滚。
六:连接池:
连接池是一种数据库连接管理技术,用于提高数据库访问性能和资源利用率。连接池会在应用程序启动时创建一定数量的数据库连接,并将这些连接保存在一个池中。当应用程序需要访问数据库时,它会从连接池中获取一个空闲的连接,使用完毕后再将连接放回池中,而不是每次都重新建立连接。这样可以减少数据库连接的建立和关闭次数,提高数据库访问效率,同时也能减轻数据库的负担,提高系统的稳定性和性能。
我们先来看看参数的设置:
硬编码:
Druid的硬编码是指在代码中直接指定Druid数据源的连接信息和配置参数,而不是通过外部配置文件或者动态配置来管理。硬编码通常是在代码中直接创建Druid数据源对象,并设置连接URL、用户名、密码等信息,然后通过该数据源对象来获取数据库连接。
下面是一个简单的Druid硬编码示例:
import com.alibaba.druid.pool.DruidDataSource;
public class DruidExample {
public static void main(String[] args) {
// 创建Druid数据源
DruidDataSource dataSource = new DruidDataSource();
// 设置连接URL
dataSource.setUrl("jdbc:mysql://localhost:3306/mydatabase");
// 设置用户名和密码
dataSource.setUsername("root");
dataSource.setPassword("password");
// 设置其他配置参数
dataSource.setInitialSize(5);
dataSource.setMaxActive(20);
// 获取数据库连接
try {
Connection conn = dataSource.getConnection();
// do something with the connection
conn.close();
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 关闭数据源
conn.close();
}
}
}
在上面的示例中,我们直接在代码中创建了一个Druid数据源对象,并设置了连接URL、用户名、密码等信息,然后通过该数据源对象获取数据库连接。这种硬编码的方式虽然简单直接,但是不够灵活,如果需要修改连接信息或者配置参数,就需要修改代码并重新编译。因此,通常建议使用外部配置文件或者动态配置来管理Druid数据源的连接信息和配置参数。
软编码:
软编码是指将应用程序中的配置信息、连接信息、参数等硬编码到外部配置文件中,而不是直接写在代码中。这样可以提高代码的灵活性和可维护性,因为配置信息可以在不修改代码的情况下进行调整。
下面是一个简单的软编码示例,使用外部配置文件来配置Druid数据源:
在resources目录下创建一个名为application.properties的配置文件,添加如下内容:
jdbc.url=jdbc:mysql://localhost:3306/mydatabase
jdbc.username=root
jdbc.password=password
注意,后缀必须是properties。
然后在代码中读取配置文件中的信息来创建Druid数据源:
import com.alibaba.druid.pool.DruidDataSource;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class DruidExample {
public static void main(String[] args) {
Properties properties = new Properties();
try (InputStream input = DruidExample.class.getClassLoader().getResourceAsStream("application.properties")) {
properties.load(input);
} catch (IOException e) {
e.printStackTrace();
}
//工程模式会在方法内部自动配置好参数并且返回一个实例
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
// 获取数据库连接
try {
Connection conn = dataSource.getConnection();
// do something with the connection
conn.close();
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 关闭数据源
conn.close();
}
}
}
在这个示例中,我们通过读取外部配置文件application.properties来获取数据库连接信息,而不是直接在代码中硬编码。这样可以方便地修改连接信息而不需要修改代码。软编码可以提高代码的可维护性和灵活性,推荐在实际项目中使用。
七:工具类的封装:
(1):连接池的封装
public class DBConnectionUtil {
private static DataSource dataSource;
static {
Properties properties = new Properties();
try {
InputStream in = DBConnectionUtil.class.getClassLoader().getResourceAsStream("db.properties");
properties.load(in);
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
private DBConnectionUtil() {
}
public Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
public void closeConnection(Connection conn) throws SQLException {
if (conn != null && !conn.isClosed()) {
conn.close();
}
}
}
在这个示例中,DBConnectionUtil
是一个单例类,它只有一个实例。我们在静态初始化块中创建了这个实例,并初始化了 DataSource
。然后,我们提供了 getConnection()
和 closeConnection()
方法来获取和释放数据库连接。
(2):在考虑事务和连接池下,怎么让一个线程的不同方法获取同一个连接:
注意,在上面的事务中,我们不能直接在两个类中获取(1)的实例,因为那是同一个连接池,而不是一个连接,一个连接池是许多连接的集合。我们现在就要引入ThreadLocal。
举例:
这样,就不用频繁传入Connection,更加方便。
使用 ThreadLocal
来管理数据库连接。这样可以确保每个线程都有自己的数据库连接,避免了多线程环境下的并发问题。以下是一个修改(1)后的示例:
public class DBConnectionUtil {
private static DataSource dataSource;
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<>();
static {
Properties properties = new Properties();
try {
InputStream in = DBConnectionUtil.class.getClassLoader().getResourceAsStream("db.properties");
properties.load(in);
dataSource = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
public static Connection getConnection() throws SQLException {
Connection conn = connectionHolder.get();
if (conn == null) {
conn = dataSource.getConnection();
connectionHolder.set(conn);
}
return conn;
}
public static void closeConnection() throws SQLException {
Connection conn = connectionHolder.get();
if (conn != null) {
conn.close();
connectionHolder.remove();
}
}
}
在这个示例中,我们使用了 ThreadLocal
来存储每个线程的数据库连接。当调用 getConnection()
方法时,我们首先从 ThreadLocal
中获取连接,如果获取不到(也就是当前线程还没有连接),我们就创建一个新的连接,并存入 ThreadLocal
。当调用 closeConnection()
方法时,我们关闭 ThreadLocal
中的连接,并从 ThreadLocal
中移除。这样,我们就可以确保每个线程都有自己的数据库连接,避免了多线程环境下的并发问题。
注意,如果是在事务中,结束时,我们要在closeConnection
中关闭事务,即打开自动提交,即conn.setAutoCommit(true);
,与此同时,事务也要改变:
// 加减钱类
public class AccountDao {
public void decreaseMoney(int id, double money) throws SQLException {
Connection conn = null;
try {
conn = DBConnectionUtil.getConnection();
String sql = "UPDATE Account SET balance = balance - ? WHERE id = ?";
PreparedStatement preparedStatement = conn.prepareStatement(sql);
preparedStatement.setDouble(1, money);
preparedStatement.setInt(2, id);
preparedStatement.executeUpdate();
} finally {
DBConnectionUtil.closeConnection(conn);
}
}
public void increaseMoney(int id, double money) throws SQLException {
Connection conn = null;
try {
conn = DBConnectionUtil.getConnection();
String sql = "UPDATE Account SET balance = balance + ? WHERE id = ?";
PreparedStatement preparedStatement = conn.prepareStatement(sql);
preparedStatement.setDouble(1, money);
preparedStatement.setInt(2, id);
preparedStatement.executeUpdate();
} finally {
DBConnectionUtil.closeConnection(conn);
}
}
}
// 业务类
public class TransferService {
private AccountDao accountDao = new AccountDao();
public void transfer(int fromId, int toId, double money) throws SQLException {
Connection conn = null;
try {
// 开启事务
conn = DBConnectionUtil.getConnection();
conn.setAutoCommit(false);
// 执行转账操作
accountDao.decreaseMoney(fromId, money);
accountDao.increaseMoney(toId, money);
// 提交事务
conn.commit();
} catch (Exception e) {
if (conn != null) {
// 回滚事务
conn.rollback();
}
throw e;
} finally {
DBConnectionUtil.closeConnection(conn);
}
}
}
在这个示例中,我们修改了 AccountDao
类的 decreaseMoney
和 increaseMoney
方法,使它们在方法内部获取和释放数据库连接。这样,我们就不需要在方法参数中传递 Connection
对象了。同时,我们也修改了 TransferService
类的 transfer
方法,使其在方法内部获取和释放数据库连接。
(3):BaseDao的封装:
DAO(Data Access Object)是一种设计模式,用于将应用程序的业务逻辑与数据访问逻辑分离。在使用DAO模式时,数据访问逻辑被封装在一个独立的类中,这个类通常包含对数据库的CRUD(创建、读取、更新、删除)操作。通过使用DAO模式,可以使应用程序的不同部分相互独立,提高了代码的可维护性和可扩展性。DAO模式通常用于将数据库操作与业务逻辑分离,使得应用程序更易于管理和测试。
注意,我们不难发现,DQL与非DQL之间调用方法的区别,所以我们分情况讨论:
3.1.非DQL封装:
这里是一个使用 ThreadLocal
的 BaseDao
类的示例,以及一个继承自 BaseDao
的 AccountDao
类的示例:
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public abstract class BaseDao {
public int update(String sql, Object... params) throws SQLException {
try {
Connection conn = DBConnectionUtil.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
PreparedStatement preparedStatement = conn.prepareStatement(sql);
for (int i = 0; i < params.length; i++) {
preparedStatement.setObject(i + 1, params[i]);
}
preparedStatement.close();
//判断是不是为事务!
if(conn.getAutoCommit()){
DBConnectionUtil.closeConnection();
}
return preparedStatement.executeUpdate();
}
}
public class AccountDao extends BaseDao {
public void decreaseMoney(int id, double money) throws SQLException {
String sql = "UPDATE Account SET balance = balance - ? WHERE id = ?";
update(sql, money, id);
}
public void increaseMoney(int id, double money) throws SQLException {
String sql = "UPDATE Account SET balance = balance + ? WHERE id = ?";
update(sql, money, id);
}
}
在这个示例中,BaseDao
类是一个抽象类,它封装了非查询(非 DQL)语句的执行。update
方法接受一个 SQL 语句和一组参数,然后执行 SQL 语句并返回影响的行数。
AccountDao
类继承自 BaseDao
类,它提供了 decreaseMoney
和 increaseMoney
方法,这两个方法分别用于减少和增加账户的余额。
注意:
preparedStatement.close();
//判断是不是为事务!
if(conn.getAutoCommit()){
DBConnectionUtil.closeConnection();
}
return preparedStatement.executeUpdate();
要注意是否要修改业务类的代码和如何判断是否为业务。
3.2.DQL封装:
这里是一个使用反射和泛型集合封装 DQL(数据查询语言)的 BaseDao
类的示例:
public abstract class BaseDao {
public <T> List<T> query(String sql, Class<T> clazz, Object... params) throws SQLException {
try {
Connection conn = DBConnectionUtil.getConnection();
} catch (SQLException e) {
e.printStackTrace();
}
PreparedStatement preparedStatement = conn.prepareStatement(sql);
for (int i = 0; i < params.length; i++) {
preparedStatement.setObject(i + 1, params[i]);
}
ResultSet resultSet = preparedStatement.executeQuery();
ResultSetMetaData metaData = resultSet.getMetaData();
List<T> list = new ArrayList<>();
while (resultSet.next()) {
T instance = null;
try {
instance = clazz.newInstance();
for (int i = 1; i <= metaData.getColumnCount(); i++) {
String columnName = metaData.getColumnLabel(i);
Object value = resultSet.getObject(i);
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);
field.set(instance, value);//把instance的filed字段设置为value
}
} catch (Exception e) {
e.printStackTrace();
}
list.add(instance);
}
}
resultSet.close();
preparedStatement.close();
if(conn.getAutoCommit()){
DBConnectionUtil.closeConnection();
}
return list;
}
在这个示例中,BaseDao
类是一个抽象类,它封装了 DQL 语句的执行。query
方法接受一个 SQL 语句、一个类对象和一组参数,然后执行 SQL 语句并返回查询结果的列表。查询结果的类型是泛型 T
,这样我们就可以返回任何类型的对象列表。
在这个BaseDao
类的query
方法中,instance
是一个泛型类型的对象,它的类型是T
。T
是一个占位符,代表任何类型,这是Java泛型的一部分。
在方法的执行过程中,当从数据库查询结果集ResultSet
中取出每一行数据时,instance
被用来创建一个新的对象,该对象的类型由clazz
参数指定(clazz
是一个Class<T>
对象,代表要实例化的类的类型)。
具体来说,当遍历ResultSet
时,对于每一行数据:
使用
clazz.newInstance()
创建一个新的T
类型的实例,并将这个新实例赋值给instance
。这一步假设T
的类有一个无参数的构造函数。然后,代码遍历
ResultSet
的每一列,并使用clazz.getDeclaredField(columnName)
获取T
类型对象中对应的字段。使用
field.setAccessible(true)
使得可以访问私有字段,接着使用field.set(instance, value)
将resultSet
中当前列的值设置到instance
对象的对应字段中。field.set(instance, value);
:这行代码将instance
对象的field
字段设置为value
。如果字段是static
的,instance
可以是null
。最后,将填充完数据的
instance
对象添加到list
列表中。