JDBC
快速上手
常用的数据库存取技术有 JDBC、JDO Java Data Object 和其他第三方 O/R 工具。其中 JDBC 是 Java 访问数据库的基石,JDO、Hibernate、MyBatis 等只是进一步的封装了 JDBC。
Java Database Connectivity 是一个独立于特定数据库的通用数据库存取和操作的公共接口。通过定义访问数据库的标准类库,并使用 java.sql、javax.sql 这些类库,可以以一种标准的方法访问数据库资源。
面向应用的 Java API 抽象接口,供开发人员连接数据库并执行 SQL。面向数据库的 Java Driver API,供开发商开发数据库驱动程序用。
JDBC 是 SUN 公司提供一套用于数据库操作的接口,开发者只需要面向这套接口编程即可。不同的数据库厂商,需要针对这套接口,提供不同实现。不同的实现的集合,即为不同数据库的驱动。
ODBC Open Database Connectivity 开放式数据库连接是微软在 Windows 平台下推出的。使用者在程序中只需要调用 ODBC API,由 ODBC 驱动程序将调用转换成为对特定的数据库的调用请求。
获取数据库连接
连接要素
- Driver 接口实现类
java.sql.Driver 接口是所有 JDBC 驱动程序需要实现的接口。此接口提供给数据库厂商使用,不同数据库厂商提供不同的实现。在程序中不需要直接去访问实现 Driver 接口的类,而是由驱动程序管理器类 java.sql.DriverManager 去调用这些 Driver 实现。
将 connector-mysql-java 的 jar 包拷贝到工程的目录中的新建 lib 文件夹。勿忘在驱动 jar 包上点击右键选择 Build Path => Add to Build Path。
若是 Dynamic Web Project 动态项目的话,则将驱动 jar 包放入 WebContent 或 WebRoot 目录中 WEB-INF 的 lib 内。
加载驱动需调用 Class 类的静态方法 forName()
,并向其传递待加载 JDBC 驱动的类名。
// 加载驱动
Class.forName(“com.mysql.cj.jdbc.Driver”);
DriverManager 是驱动程序管理器类,负责管理驱动程序。通常不用显式调用此类的 registerDriver()
方法来注册驱动程序类的实例,因 Driver 接口的驱动程序类都包含了注册自身实例的静态代码块,而在这个静态代码块中,会调用 DriverManager.registerDriver()
。
// 注册驱动 => 通常不需显示调用
DriverManager.registerDriver(com.mysql.cj.jdbc.Driver)
- 配置信息
JDBCURL 用于标识一个被注册的驱动程序,驱动程序管理器通过这个 URL 选择正确的驱动程序,从而建立到数据库的连接。
/* 几种常用数据库的 JDBC URL */
// 1.MySQL
// jdbc协议:标识数据库驱动程序的子协议://子名称 => 标识数据库的方法
jdbc:mysql://localhost:3306/<DatabaseName>
// jdbc:mysql://主机名称:mysql服务端口号/数据库名称?参数=值&参数=值
// 如果JDBC程序与服务器端的字符集不一致会导致乱码,可以通过参数指定服务器端的字符集.
jdbc:mysql://localhost:3306/<DatabaseName>?useUnicode=true&characterEncoding=utf8
jdbc:mysql://localhost:3306/<DatabaseName>?user=<user>&password=<password>
// 2.Oracle
// jdbc:oracle:thin:@主机名称:oracle服务端口号:数据库名称
jdbc:oracle:thin:@localhost:1521:<DatabaseName>
// 3.SQLServer
// jdbc:sqlserver://主机名称:sqlserver服务端口号:DatabaseName=数据库名称
jdbc:sqlserver://localhost:1433:DatabaseName=<DatabaseName>
连接方式
// jdbc.properties
url=jdbc:mysql://localhost:3306/jdbc_learn
user=<user>
password=<password>
driverClass=com.mysql.cj.jdbc.Driver
// ConnectionTest.java
import java.io.InputStream;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
import org.junit.Test;
public class ConnectionTest {
// 方式一
@Test
public void testConnection1() throws SQLException {
// 获取 Driver 的实现类对象
// Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'.
// Driver driver = new com.mysql.jdbc.Driver(); => 被废弃
Driver driver = new com.mysql.cj.jdbc.Driver();
// jdbc:mysql => 协议
String url = "jdbc:mysql://localhost:3306/jdbc_learn";
// 用户名与密码封装在properties中
Properties info = new Properties();
info.setProperty("user", <user>);
info.setProperty("password", <password>);
Connection conn = driver.connect(url, info);
System.out.println(conn);
}
// 方式二 => 提高可移植性 => 不要出现第三方 API => 通用性增加
@Test
public void testConnection2() throws Exception {
// 获取 Driver 的实现类对象 => 反射
Class clazz = Class.forName("com.mysql.cj.jdbc.Driver");
Driver driver = (Driver) clazz.newInstance();
// 提供待连接的数据库
String url = "jdbc:mysql://localhost:3306/jdbc_learn";
Properties info = new Properties();
info.setProperty("user", <user>);
info.setProperty("password", <password>);
Connection conn = driver.connect(url, info);
System.out.println(conn);
}
// 方式三 => 使用 DriverManager 替换 Driver
@Test
public void testConnection3() throws Exception {
// 1.获取Driver的实现类对象
Class clazz = Class.forName("com.mysql.cj.jdbc.Driver");
Driver driver = (Driver) clazz.newInstance();
// 2.提供连接的基本信息
String url = "jdbc:mysql://localhost:3306/jdbc_learn";
String user = <user>;
String password = <password>;
// 注册驱动
DriverManager.registerDriver(driver);
// 获取连接
Connection conn = DriverManager.getConnection(url, user, password);
System.out.println(conn);
}
// 方式四 => 可以只加载驱动,无需显示的注册驱动
@Test
public void testConnection4() throws Exception {
// 1.提供连接的基本信息
String url = "jdbc:mysql://localhost:3306/jdbc_learn";
String user = <user>;
String password = <password>;
// 2.加载Driver
// mysql 的 Driver 类加载进内存自动进行 => 静态代码块随着类的加载而执行
// 这一步 mysql 也可以省,但是oracle就不行了 => Jar包入路径自动加载了
Class.forName("com.mysql.cj.jdbc.Driver");
// 获取连接
Connection conn = DriverManager.getConnection(url, user, password);
System.out.println(conn);
}
// 方式五 => 配置信息不以硬编码方式写入代码中 => 最终版 => 配置文件方式引入
// 数据与代码分离 => 实现解耦; 修改配置避免重新打包
@Test
public void testConnection5() throws Exception {
// 1.读取配置文件
InputStream is = ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties props= new Properties();
props.load(is);
String user = props.getProperty("user");
String password = props.getProperty("password");
String url = props.getProperty("url");
String driverClass = props.getProperty("driverClass");
// 2.加载Driver
Class.forName(driverClass);
// 获取连接
Connection conn = DriverManager.getConnection(url, user, password);
System.out.println(conn);
}
}
CRUD 操作
数据库连接被用于向数据库服务器发送命令和语句,并接受返回的结果。其实一个数据库连接就是一个 Socket 连接。java.sql 中定义了以不同方式调用数据库的接口。Statement 执行静态 sql 语句并返回结果对象。PreparedStatement 将被预编译的 sql 语句存储在此对象中,并能使用此对象多次高效地执行该语句。CallableStatement 用于执行 SQL 存储过程。
Statement 操作存在弊端,其一是存在繁琐的拼串操作,其二是存在 SQL 注入的风险。
SELECT user, password FROM user_table WHERE user='a' OR 1 = ' AND password = ' OR '1' = '1';
PreparedStatement
PreparedStatement 接口是 Statement 的子接口,该接口实现的对象表示一条预编译过的 SQL 语句。调用 Connection 对象的 prepareStatement(String sql)
方法能获取 PreparedStatement 对象。通常存在于 PreparedStatement 对象里的 SQL 语句参数起初会以问号 ? 进行表示,需调用 PreparedStatement 对象的 setXxx()
方法来具体指定设置这些参数。此方法有两个参数,SQL 语句中的参数索引(从 1 开始)和参数的具体值。
DBServer 会对预编译语句提供性能优化,即被 DBServer 的编译器编译后的执行代码会被缓存下来,下次调用时只要是相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中就会直接执行。在 statement 语句中,即使是相同操作,但因数据内容不一样,所以整个语句本身不能匹配,就没有缓存语句这一说。事实是没有数据库会对普通语句编译后的执行代码进行缓存,这样每执行一次都要对传入的语句编译一次。
Java类型 | SQL类型 |
---|---|
boolean | BIT |
byte | TINYINT |
short | SMALLINT |
int | INTEGER |
long | BIGINT |
String | CHAR,VARCHAR,LONGVARCHAR |
byte array | BINARY , VAR BINARY |
java.sql.Date | DATE |
java.sql.Time | TIME |
java.sql.Timestamp | TIMESTAMP |
Interface ResultSet
查询需调用 PreparedStatement 的 executeQuery()
方法,其返回结果是一个 ResultSet 对象。该对象以表格的形式封装了执行数据库操作的结果集,或者说是一张有限定的数据表。注意此时存在一个指针指在数据表的第一条记录前,可以通过 ResultSet 对象的 next()
方法移动到下一行。该方法检测下一行是否有效,若有效则返回 true,且指针下移。
...
当指针指向一行时, 可以调用 get[?](index|label) 获取所需数据 => 主要是拿"值"
int getInt(int columnIndex)
Retrieves the value of the designated column in the current row of this ResultSet object as an int in the Java programming language.
int getInt(String columnLabel)
Retrieves the value of the designated column in the current row of this ResultSet object as an int in the Java programming language.
...
ResultSetMetaData
ResultSetMetaData 可用于获取关于 ResultSet 对象中列的类型和属性信息的对象。
ResultSetMetaData meta = rs.getMetaData(); => 获取 meta.
getColumnName(int column) => 获取指定列的名称.
getColumnLabel(int column) => 获取指定列的别名. !!!
getColumnCount() => 返回当前 ResultSet 对象中的列数. !!!
getColumnTypeName(int column) => 检索指定列的数据库特定的类型名称.
getColumnDisplaySize(int column) => 指示指定列的最大标准宽度,以字符为单位.
isNullable(int column) => 指示指定列中的值是否可以为 null.
isAutoIncrement(int column) => 指示是否自动为指定列进行编号,注意这些列仍然只读.
ORM object relational mapping
一个数据表对应一个 java 类,表中的一条记录对应 java 类的一个对象,表中的一个字段对应 java 类的一个属性。
实现增删改查
PreparedStatement 预编译对象传入 sql 并执行方法后可得到 ResultSet 结果集,从结果集能获取到制作反射对象的"值",而结果集的 ResultSetMetaData 元数据对象能获取到结果集的列数与对应列的属性。最后根据传入运行时类指定的对象使用反射具体的属性,指定具体的值。
/*
* 通用的增删改操作
*/
public void GeneralUpdate(String sql,Object ...args){
Connection connection = null;
PreparedStatement prepareStatement = null;
try {
connection = JDBCUtils.getConnection();
prepareStatement = connection.prepareStatement(sql);
for(int i = 0;i < args.length;i++) {
prepareStatement.setObject(i+1, args[i]);
}
// 信使完成sql转而执行
prepareStatement.execute();
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(connection, prepareStatement);
}
}
// ---
/*
* 针对不同表的通用查询,返回表中一条记录.
*/
public <T> T getInstance(Class<T> clazz, String sql,Object ...args) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet executeQuery = null;
try {
connection = JDBCUtils.getConnection();
preparedStatement = connection.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
preparedStatement.setObject(i+1, args[i]);
}
executeQuery = preparedStatement.executeQuery();
// 获取结果集的原数据 => 元注解
ResultSetMetaData metaData = executeQuery.getMetaData();
// 通过结果集元数据获取结果集中的列数
int columnCount = metaData.getColumnCount();
if(executeQuery.next()) {
// 查到结果再造结果集
T t = clazz.newInstance();
// Customer customer = new Customer();
// 处理一行数据中的每一个列
for (int i = 0; i < columnCount; i++) {
Object columnValue = executeQuery.getObject(i+1);
// 获取每个列的列名
String columnLabel = metaData.getColumnLabel(i+1);
// 通过反射给cust对象指定的columnName赋值为columnValue
Field field = clazz.getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(t, columnValue);
}
return t;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(connection, preparedStatement, executeQuery);
}
return null;
}
/**
*
* @Description 操作数据库的工具类 => JDBCUtils
* @author zsxzy
* @version 1.0.0
*
*/
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
public class JDBCUtils {
/**
*
* @Description 获取数据库连接
* @author zsxzy
* @return
* @throws Exception
*/
public static Connection getConnection() throws Exception {
// 1.读取配置文件
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
Properties props= new Properties();
props.load(is);
String user = props.getProperty("user");
String password = props.getProperty("password");
String url = props.getProperty("url");
String driverClass = props.getProperty("driverClass");
// 2.加载Driver
Class.forName(driverClass);
// 获取连接
Connection conn = DriverManager.getConnection(url, user, password);
return conn;
}
/**
*
* @Description 关闭资源操作
* @author zsxzy
* @param conn
* @param ps
*/
public static void closeResource(Connection conn,PreparedStatement ps) {
try {
if(ps != null)
ps.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
if(conn != null)
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void closeResource(Connection conn,PreparedStatement ps,ResultSet resultSet) {
try {
if(ps != null)
ps.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
if(conn != null)
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
if(resultSet != null)
resultSet.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
操作 BLOB 类型字段
插入 BLOB 类型的数据必须使用 PreparedStatement,因为此类型的数据无法使用字符串拼接。在指定类型后仍然报错 xxx too large,那么需要在配置文件加上 max_allowed_packet=16M
。
类型 | 大小(单位:字节) |
---|---|
TinyBlob | Max_225 |
Blob | Max_65K |
MediumBlob | Max_16M |
LongBlob | Max_4G |
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Blob;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.junit.Test;
import com.mysql.cj.exceptions.RSAException;
import com.zszyjdbc.bean.Customer;
import com.zszyjdbc.util.JDBCUtils;
public class BlobTest {
// 向数据表插入blob类型字段
@Test
public void testInsert() throws Exception {
Connection connection = JDBCUtils.getConnection();
String sql = "INSERT INTO customers(name,email,birth,photo) VALUES (?,?,?,?)";
PreparedStatement prepareStatement = connection.prepareStatement(sql);
prepareStatement.setObject(1, "gz");
prepareStatement.setObject(2, "gz@mail.com");
prepareStatement.setObject(3, "1997-01-01");
FileInputStream is = new FileInputStream(new File("gz.jpg"));
prepareStatement.setBlob(4, is);
prepareStatement.execute();
JDBCUtils.closeResource(connection, prepareStatement);
}
// 查询数据表blob类型字段
@Test
public void testQuery(){
Connection connection = null;
PreparedStatement prepareStatement = null;
InputStream is = null;
FileOutputStream fos = null;
ResultSet resultSet = null;
try {
connection = JDBCUtils.getConnection();
String sql = "SELECT id,name,email,birth,photo FROM customers WHERE id = ?";
prepareStatement = connection.prepareStatement(sql);
prepareStatement.setObject(1, 21);
resultSet = prepareStatement.executeQuery();
if(resultSet.next()) {
int id = resultSet.getInt(1);
String name = resultSet.getString(2);
String email = resultSet.getString(3);
Date birth = resultSet.getDate(4);
Customer customer = new Customer(id,name,email,birth);
System.out.println(customer);
Blob photo = resultSet.getBlob("photo");
is = photo.getBinaryStream();
fos = new FileOutputStream("doggz.jpg");
byte[] buffer = new byte[1024];
int len;
while((len = is.read(buffer))!= -1) {
fos.write(buffer,0,len);
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
if(is != null)
is.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
if(fos != null)
fos.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
JDBCUtils.closeResource(connection, prepareStatement,resultSet);
}
}
}
批量插入
Java 的批量更新机制是当需要成批的插入或更新记录时,允许多条语句一次性提交给数据库进行批量处理。此方法通常情况下比单独提交处理更有效率。
// 使用批量执行的情况 => 1.多条SQL语句的批量处理;2.一个SQL语句的批量传参
JDBC的批量处理语句方法:
addBatch(String) => 添加需要批量处理的SQL语句或是参数;
executeBatch() => 执行批量处理语句;
clearBatch() => 清空缓存的数据
# 提供goods表
CREATE TABLE goods(id INT PRIMARY KEY AUTO_INCREMENT,NAME VARCHAR(20));
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.Statement;
import org.junit.Test;
import com.zszyjdbc.util.JDBCUtils;
public class BatchTest {
@Test
public void testInsert() throws Exception{
long start = System.currentTimeMillis();
Connection conn = JDBCUtils.getConnection();
// 1.设置为不自动提交数据
conn.setAutoCommit(false);
String sql = "insert into goods(name)values(?)";
PreparedStatement ps = conn.prepareStatement(sql);
for(int i = 1;i <= 1000;i++){
ps.setString(1, "name_" + i);
// 1.攒sql
ps.addBatch();
if(i % 500 == 0){
// 2.执行
ps.executeBatch();
// 3.清空
ps.clearBatch();
}
}
// 2.提交数据
conn.commit();
long end = System.currentTimeMillis();
System.out.println("花费的时间为 => " + (end - start));
JDBCUtils.closeResource(conn, ps);
}
}
事务
数据库事务
数据库事务即一组逻辑操作单元,使数据从一种状态变换到另一种状态。事务处理保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。在事务中执行多个操作时,要么所有的事务都被提交,所做修改永久保存;要么放弃所作的所有修改,整个事务回滚到最初状态。
JDBC 事务处理
当一个连接对象被创建时,默认情况下是自动提交事务。此外在关闭数据库连接时,数据也会自动的提交。如果多个操作都是使用的是各自单独的连接,则无法保证事务。即同一个事务的多个操作必须在同一个连接下。
JDBC 可调用 Connection 对象的 setAutoCommit(false);
取消自动提交事务。在语句都成功执行后,可调用 commit();
提交事务。出现异常时应调用 rollback();
回滚事务。此时若数据库连接没有关闭,则应恢复自动提交状态 setAutoCommit(true)
。尤其是使用数据库连接池时,应在 close()
方法前恢复其自动提交状态。
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.junit.Test;
import com.zszyjdbc.util.JDBCUtils;
public class TransactionTest {
// 通用增删改操作---version1.0
public void update(String sql,Object ...args){
Connection connection = null;
PreparedStatement prepareStatement = null;
try {
connection = JDBCUtils.getConnection();
prepareStatement = connection.prepareStatement(sql);
for(int i = 0;i < args.length;i++) {
prepareStatement.setObject(i+1, args[i]);
}
prepareStatement.execute();
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(connection, prepareStatement);
}
}
@Test
public void testUpdate() {
/*
* user_table AA给BB转账100
*/
String sql1 = "UPDATE user_table SET balance = balance - 100 WHERE user = ?";
String sql2 = "UPDATE user_table SET balance = balance + 100 WHERE user = ?";
update(sql1, "AA");
// 模拟网络异常 => System.out.println(10/0);
update(sql2, "BB");
System.out.println("okk");
}
@Test
public void testUpdatenew(){
Connection conn = null;
try {
conn = JDBCUtils.getConnection();
// 默认情况会提交
System.out.println(conn.getAutoCommit());
// 取消数据的自动提交
conn.setAutoCommit(false);
String sql1 = "UPDATE user_table SET balance = balance - 100 WHERE user = ?";
String sql2 = "UPDATE user_table SET balance = balance + 100 WHERE user = ?";
updateNew(conn,sql1, "AA");
// 模拟网络异常 => System.out.println(10/0);
System.out.println(10/0);
updateNew(conn,sql2, "BB");
System.out.println("okk");
// 手动提交
conn.commit();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
try {
// 出现异常就回滚
conn.rollback();
} catch (SQLException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
} finally {
JDBCUtils.closeResource(conn, null);
}
}
// 考虑事务的增删改
public int updateNew(Connection conn, String sql,Object ...args){
PreparedStatement prepareStatement = null;
try {
prepareStatement = ((Connection) conn).prepareStatement(sql);
for(int i = 0;i < args.length;i++) {
prepareStatement.setObject(i+1, args[i]);
}
prepareStatement.execute();
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(null, prepareStatement);
}
return 0;
}
@Test
public void testTransactionSelect() throws Exception {
Connection connection = JDBCUtils.getConnection();
// 获取当前隔离级别 => 1=read uncommit
System.out.println(connection.getTransactionIsolation());
// 设置数据库的隔离级别
connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
// 取消自动提交
connection.setAutoCommit(false);
String sql = "SELECT user,password,balance FROM user_table WHERE user = ?";
User user = getInstance(connection,User.class,sql,"CC");
System.out.println(user);
}
public void testTransactionUpdate() throws Exception {
Connection connection = JDBCUtils.getConnection();
// 取消自动提交
connection.setAutoCommit(false);
String sql = "UPDATE user_table SET balance = ? WHERE user = ?";
updateTransac(connection, sql,5000,"CC");
Thread.sleep(15000);
System.out.println("修改结束");
}
// 通用的查询操作,返回数据表中的一条记录 VERSION2.0 => 考虑事务
public <T> T getInstance(Connection conn, Class<T> clazz, String sql,Object ...args) {
PreparedStatement preparedStatement = null;
ResultSet executeQuery = null;
try {
preparedStatement = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
preparedStatement.setObject(i+1, args[i]);
}
executeQuery = preparedStatement.executeQuery();
// 获取结果集的原数据 => 元注解
ResultSetMetaData metaData = executeQuery.getMetaData();
// 通过结果集元数据获取结果集中的列数
int columnCount = metaData.getColumnCount();
if(executeQuery.next()) {
// 查到结果再造结果集
T t = clazz.newInstance();
// Customer customer = new Customer();
// 处理一行数据中的每一个列
for (int i = 0; i < columnCount; i++) {
Object columnValue = executeQuery.getObject(i+1);
// 获取每个列的列名
String columnLabel = metaData.getColumnLabel(i+1);
// 通过反射给cust对象指定的columnName赋值为columnValue
Field field = clazz.getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(t, columnValue);
}
return t;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(null, preparedStatement, executeQuery);
}
return null;
}
// 通用增删改操作---version2.0 => 插入事务
public int updateTransac(Connection conn,String sql,Object ...args){
PreparedStatement prepareStatement = null;
try {
prepareStatement = conn.prepareStatement(sql);
for(int i = 0;i < args.length;i++) {
prepareStatement.setObject(i+1, args[i]);
}
return prepareStatement.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(null, prepareStatement);
}
return 0;
}
}
事务的 ACID 属性
原子性 Atomicity => 事务是一个不可分割的工作单位,操作要么都发生,要么都不发生。
一致性 Consistency => 事务必须使数据库从一个一致性状态变换到另外一个一致性状态。
隔离性 Isolation => 一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
持久性 Durability => 一个事务一旦被提交,其对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。
并发问题
对于同时运行的多个事务访问数据库中相同的数据时,如果没有采取隔离机制,就会导致各种并发问题。一个事务与其他事务隔离的程度称为隔离级别。数据库规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度, 隔离级别越高, 数据一致性就越好, 但并发性越弱。
脏读 => 对于两个事务 T1、T2,T1 读取了已经被 T2 更新但还没有被提交的字段。之后 T2 回滚,T1 读取的内容就是临时且无效的。
不可重复读 => 对于两个事务T1、T2, T1 读取了一个字段,然后 T2 更新了该字段。之后 T1 再次读取同一个字段,值就不同了。
幻读 => 对于两个事务T1、T2, T1 从一个表中读取了一个字段,然后 T2 在该表中插入了一些新的行。如果 T1 再次读取同一个表,就会多出几行。
隔离级别
Oracle 支持 READ COMMITED、SERIALIZABLE 事务隔离级别。默认的事务隔离级别为 READ COMMITED。Mysql 支持四种种事务隔离级别,默认的事务隔离级别为 REPEATABLE READ。
隔离级别 | 描述 |
---|---|
READ UNCOMMITTED 读未提交数据 | 允许事务读取未被提交的更改。脏读,不可重复读和幻读的问题都会出现。 |
READ COMMTED 读已提交数据 | 只允许事务读取已经被其他事务提交的变更。可以避免脏读,但不可重复读和幻读问题仍然存在。 |
REPEATABLE READ 可重复读 | 确保事务可多次从一个字段中读取相同的值。在这个事务持续期间,禁止其他事物对这个字段进行更新。可以避免脏读和不可重复读,但幻读的问题仍然存在。 |
SERIALIZABLE 串行化 | 确保事务可从一个表中读取相同的行,在这个事务持续期间,禁止其他事务对该表执行插入、更新和删除操作。所有并发问题都可以避免,但性能十分低下。 |
/*
在MySql中设置隔离级别
每启动一个 mysql 程序, 就会获得一个单独的数据库连接. 每个数据库连接都有一个全局变量 @@tx_isolation, 表示当前的事务隔离级别。
*/
# 查看当前的隔离级别
mysql> SELECT @@tx_isolation;
mysql> SELECT @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| REPEATABLE-READ |
+-------------------------+
# 设置当前连接的隔离级别
mysql> set transaction isolation level read committed;
# 设置数据库系统的全局的隔离级别
mysql> set global transaction isolation level read committed;
# 创建 mysql 数据库用户
mysql> create user gz identified by 'abc123';
#授予通过网络方式登录的tom用户,对所有库所有表的全部权限,密码设为abc123.
mysql> grant all privileges on *.* to gz@'%' identified by 'abc123';
# 给tom用户使用本地命令行方式,授予 xxxdb 这个库下的所有表的插删改查的权限。
grant select,insert,delete,update on xxxdb.* to gz@localhost identified by 'abc123';
DAO 及相关实现类
DAO Data Access Object 访问数据信息的类和接口,有时也称作 BaseDAO。包括了对数据的 CRUD,而不包含任何业务相关的信息。实现功能的模块化,有利于代码的维护和升级。
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import com.zszyjdbc.bean.Customer;
import com.zszyjdbc.util.JDBCUtils;
/*
* 封装针对数据表的通用操作
*/
public abstract class BaseDao <T>{
private Class<T> clazz = null;
public BaseDao(){
}
{
/*
* 获取当前BaseDAO的子类继承的父类中的泛型
*/
// getClass() => 获取运行时类
// getGenericSuperclass() => 获得带有泛型的父类
Type genericSuperclass = this.getClass().getGenericSuperclass();
System.out.println(this.getClass()); // class com.zszyjdbc.dao.CustomersDAOImpl
System.out.println(genericSuperclass); // com.zszyjdbc.dao.BaseDao<com.zszyjdbc.bean.Customer>
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); // 获取父类泛型参数
clazz = (Class<T>) actualTypeArguments[0]; // 泛型第一个参数 => Customer
}
// 通用的增删改操作 -- version2 考虑事务
public int updateTransac(Connection conn,String sql,Object ...args){
PreparedStatement prepareStatement = null;
try {
prepareStatement = conn.prepareStatement(sql);
for(int i = 0;i < args.length;i++) {
prepareStatement.setObject(i+1, args[i]);
}
return prepareStatement.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(null, prepareStatement);
}
return 0;
}
public T getInstance(Connection conn, String sql,Object ...args) {
PreparedStatement preparedStatement = null;
ResultSet executeQuery = null;
try {
preparedStatement = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
preparedStatement.setObject(i+1, args[i]);
}
executeQuery = preparedStatement.executeQuery();
// 获取结果集的原数据 => 元注解
ResultSetMetaData metaData = executeQuery.getMetaData();
// 通过结果集元数据获取结果集中的列数
int columnCount = metaData.getColumnCount();
if(executeQuery.next()) {
// 查到结果再造结果集
T t = clazz.newInstance();
// Customer customer = new Customer();
// 处理一行数据中的每一个列
for (int i = 0; i < columnCount; i++) {
Object columnValue = executeQuery.getObject(i+1);
// 获取每个列的列名
String columnLabel = metaData.getColumnLabel(i+1);
// 通过反射给cust对象指定的columnName赋值为columnValue
Field field = clazz.getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(t, columnValue);
}
return t;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(null, preparedStatement, executeQuery);
}
return null;
}
// 通用查询,返回数据表多条记录构成的集合2.0.0
public List<T> getForList(Connection conn, String sql,Object ...args){
PreparedStatement preparedStatement = null;
ResultSet executeQuery = null;
try {
preparedStatement = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
preparedStatement.setObject(i+1, args[i]);
}
executeQuery = preparedStatement.executeQuery();
// 获取结果集的原数据 => 元注解
ResultSetMetaData metaData = executeQuery.getMetaData();
// 通过结果集元数据获取结果集中的列数
int columnCount = metaData.getColumnCount();
// 创建存储结果的集合对象
ArrayList<T> arrayList = new ArrayList<T>();
while(executeQuery.next()) {
// 查到结果再造结果集
T t = clazz.newInstance();
// Customer customer = new Customer();
// 处理一行数据中的每一个列 => 给t对象指定的属性赋值
for (int i = 0; i < columnCount; i++) {
Object columnValue = executeQuery.getObject(i+1);
// 获取每个列的列名
String columnLabel = metaData.getColumnLabel(i+1);
// 通过反射给cust对象指定的columnName赋值为columnValue
Field field = clazz.getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(t, columnValue);
}
arrayList.add(t);
}
return arrayList;
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(null, preparedStatement, executeQuery);
}
return null;
}
// 查询特殊值的通用方法
public <E> E getValue(Connection conn, String sql,Object ...args) {
PreparedStatement prepareStatement = null;
ResultSet rs = null;
try {
prepareStatement = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
prepareStatement.setObject(i+1, args[i]);
}
rs = prepareStatement.executeQuery();
if(rs.next()) {
return(E) rs.getObject(1);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
JDBCUtils.closeResource(null, prepareStatement,rs);
}
return null;
}
}
数据库连接池
传统模式开发存在的问题是,每次向数据库建立连接时都要将连接加载到内存中进行用户验证。需要数据库连接的时候,向数据库要求一个,执行完成后断开连接。这种模式将会消耗大量的资源和时间。若多人频繁的进行数据库连接操作将占用很多的系统资源,严重的甚至会造成服务器的崩溃。对于每一次数据库连接,使用完后都得断开。若程序出现异常而未能关闭,将会导致数据库系统的内存泄漏,最终重启数据库。Java 的内存泄漏即内存中存在有对象不能被回收的情况。这种开发模式也不能控制被创建的连接对象数,系统资源会被毫无顾及的分配,而连接过多,也可能导致内存泄漏,服务器崩溃。
数据库连接池技术即为数据库连接建立一个缓冲池。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从缓冲池中取出一个,使用完毕后再放回去。数据库连接池负责分配、管理和释放数据库连接,允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。
使用数据库连接池技术的优点
-
资源重用,避免了频繁创建,释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增加了系统运行环境的平稳性。
-
数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于连接池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而减少系统的响应时间。
-
作为新的资源分配手段。对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接池的配置,实现某一应用最大可用数据库连接数的限制,避免某一应用独占所有的数据库资源。
-
统一的连接管理,避免数据库连接泄漏。根据预先的占用超时设定,强制回收被占用连接,从而避免了常规数据库连接操作中可能出现的资源泄露。
JDBC 的数据库连接池使用 javax.sql.DataSource 来表示,DataSource 只是一个接口,通常由服务器 Weblogic、WebSphere、Tomcat 提供实现。
包含连接池和连接池管理的 DataSource 称为数据源或连接池,使用其取代 DriverManager 来获取 Connection 可以大幅度提高访问速度。
C3P0 数据库连接池
hibernate 官方推荐的 C3P0 是一个开源组织提供的数据库连接池,速度相对较慢,稳定性适中。
import java.sql.Connection;
import java.sql.SQLException;
import javax.activation.DataSource;
import org.junit.Test;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import com.mchange.v2.c3p0.DataSources;
public class C3P0Test {
// 方式一
@Test
public void testGetConnection() throws Exception {
// 获取C3P0数据库连接池
ComboPooledDataSource cpds = new ComboPooledDataSource();
cpds.setDriverClass( "com.mysql.cj.jdbc.Driver" ); //loads the jdbc driver
cpds.setJdbcUrl("jdbc:mysql://localhost:3306/jdbc_learn");
cpds.setUser("<username>");
cpds.setPassword("<password>");
// 配置信息 => Appendix A: Configuration Properties
// 设置初始时数据库连接池中的连接数
// cpds.setInitialPoolSize(10);
Connection connection = cpds.getConnection();
System.out.println(connection);
// 销毁c3p0数据库连接池
// DataSources.destroy(cpds);
}
// 方式二
// 连接池一个就够,不用每次调用都得新造
ComboPooledDataSource cpds = new ComboPooledDataSource("helloc3p0");
@Test
public void testConnectionGet() throws Exception {
Connection connection = cpds.getConnection();
System.out.println(connection);
}
}
<!-- c3p0-config -->
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<named-config name="helloc3p0">
<!-- 提供获取连接的4个基本信息 -->>
<property name="driverClass">com.mysql.cj.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/jdbc_learn</property>
<property name="user"><username></property>
<property name="password"><yourpassword></property>
<!-- 数据库连接池管理的基本信息 -->
<!-- 当数据库连接池的连接数不够,c3p0一次性向数据库服务器申请的连接数 -->
<property name="acquireIncrement">5</property>
<!-- 初始化的连接池容量 -->
<property name="initialPoolSize">10</property>
<!-- c3p0数据库连接池维护的最少连接数 -->
<property name="minPoolSize">10</property>
<!-- c3p0数据库连接池维护的连接数上限 -->
<property name="maxPoolSize">100</property>
<!-- c3p0连接池维护的最多statement的个数 -->
<property name="maxStatements">50</property>
<!-- 每一个连接中可以最多使用statement的个数 -->
<property name="maxStatementsPerConnection">5</property>
</named-config>
</c3p0-config>
DBCP 数据库连接池
DBCP 是 Apache 软件基金组织下的开源连接池实现,该连接池依赖该组织下的另一个开源系统:Common-pool。如需使用该连接池实现,应在系统中增加如下两个 jar 文件:
Commons-dbcp.jar:连接池的实现
Commons-pool.jar:连接池实现的依赖库
Tomcat 的连接池正是采用该连接池来实现的。该数据库连接池既可以与应用服务器整合使用,也可由应用程序独立使用。
数据源和数据库连接不同,数据源无需创建多个,它是产生数据库连接的工厂,因此整个应用只需要一个数据源即可。
当数据库访问结束后,程序还是像以前一样关闭数据库连接:conn.close(); 但上面的代码并没有关闭数据库的物理连接,它仅仅把数据库连接释放,归还给了数据库连接池。
属性 | 默认值 | 说明 |
---|---|---|
initialSize | 0 | 连接池启动时创建的初始化连接数量 |
maxActive | 8 | 连接池中可同时连接的最大的连接数 |
maxIdle | 8 | 连接池中最大的空闲的连接数,超过的空闲连接将被释放,如果设置为负数表示不限制 |
minIdle | 0 | 连接池中最小的空闲的连接数,低于这个数量会被创建新的连接。该参数越接近maxIdle,性能越好,因为连接的创建和销毁,都是需要消耗资源的;但是不能太大。 |
maxWait | 无限制 | 最大等待时间,当没有可用连接时,连接池等待连接释放的最大时间,超过该时间限制会抛出异常,如果设置-1表示无限等待 |
poolPreparedStatements | false | 开启池的Statement是否prepared |
maxOpenPreparedStatements | 无限制 | 开启池的prepared 后的同时最大连接数 |
minEvictableIdleTimeMillis | 连接池中连接,在时间段内一直空闲, 被逐出连接池的时间 | |
removeAbandonedTimeout | 300 | 超过时间限制,回收没有用(废弃)的连接 |
removeAbandoned | false | 超过removeAbandonedTimeout时间后,是否进 行没用连接(废弃)的回收 |
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;
import org.junit.Test;
public class DBCPTest {
// 方式一 => 硬编码
@Test
// 测试DBCP数据库连接池
public void testGetConnection() throws Exception {
// 创建DBCP数据库连接池
// command+t => BasicDataSource
BasicDataSource source = new BasicDataSource();
// 设置基本信息
source.setDriverClassName("com.mysql.cj.jdbc.Driver");
source.setUrl("jdbc:mysql://localhost:3306/jdbc_learn");
source.setUsername("<username>");
source.setPassword("<password>");
// 设置其他设计数据库连接池管理的相关属性
source.setInitialSize(10);
source.setMaxActive(10);
// ...
Connection connection = source.getConnection();
System.out.println(connection);
}
// 方式二 => 配置文件
@Test
public void testConnectionGet() throws Exception {
Properties pros = new Properties();
// 方式一
// InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("dbcp.properties");
// 方式二 => 识别的是当前工程
FileInputStream is = new FileInputStream(new File("src/dbcp.properties"));
pros.load(is);
DataSource dataSource = BasicDataSourceFactory.createDataSource(pros);
Connection connection = dataSource.getConnection();
System.out.println(connection);
}
}
// dbcp.properties
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbc_learn
username=<username>
password=<password>
initialSize=10
Druid 德鲁伊数据库连接池
Druid是阿里巴巴开源平台上一个数据库连接池实现,它结合了C3P0、DBCP、Proxool等DB池的优点,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况,可以说是针对监控而生的DB连接池,可以说是目前最好的连接池之一。
import java.io.InputStream;
import java.sql.Connection;
import java.util.Properties;
import org.junit.Test;
import com.alibaba.druid.pool.DruidDataSourceFactory;
public class Druidtest {
@Test
public void getConnection() throws Exception {
Properties props = new Properties();
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties");
props.load(is);
javax.sql.DataSource source = DruidDataSourceFactory.createDataSource(props);
Connection connection = source.getConnection();
System.out.println(connection);
}
}
// druid.properties
url=jdbc:mysql://localhost:3306/jdbc_learn
username=<username>
password=<password>
driverClassName=com.mysql.cj.jdbc.Driver
Apache-DBUtils
commons-dbutils 是 Apache 组织提供的一个开源 JDBC 工具类库,通过对 JDBC 的封装,极大简化 JDBC 编码的工作量,同时也不会影响程序的性能。
org.apache.commons.dbutils.QueryRunner
org.apache.commons.dbutils.ResultSetHandler
org.apache.commons.dbutils.DbUtils
DbUtils
DbUtils 类是一个提供关闭资源(连接)、装载驱动与事务处理方法的工具类,其中的所有方法都是静态的。
public static void close(…) throws java.sql.SQLException => 三个重载的关闭方法;检查所提供的参数是不是 NULL, 如果不是就关闭 Connection、Statement 和 ResultSet.
public static void closeQuietly(…) => 三个重载的关闭方法;在 Connection、Statement 和 ResultSet 为 NULL 时能避免关闭,同时隐藏一些在程序中抛出的 SQLEeception.
public static void commitAndClose(Connection conn) throws SQLException => 提交连接的事务,然后关闭连接.
public static void commitAndCloseQuietly(Connection conn) => 用来提交连接,然后关闭连接,并且在关闭连接时不抛出SQL异常.
public static void rollback(Connection conn)throws SQLException => 允许 conn 为 null,因方法内部做出判断.
public static void rollbackAndClose(Connection conn) throws SQLException
public static void rollbackAndCloseQuietly(Connection) => 回滚操作并关闭连接
public static boolean loadDriver(java.lang.String driverClassName) => 装载并注册JDBC驱动程序,成功返回true. 使用该方法,不需要捕捉这个异常ClassNotFoundException。
QueryRunner 类
QueryRunner 提供对 sql 语句操作的 API,与 ResultSetHandler 组合在一起使用可以完成大部分的数据库操作。QueryRunner 类提供了默认的构造器以及需要一个 javax.sql.DataSource 来作参数的构造器。
/* QueryRunner 类的主要方法 */
执行一个更新、插入或删除操作 => public int update(Connection conn, String sql, Object… params) throws SQLException
// rsh => The handler used to create the result object from the ResultSet of auto-generated keys. 返回值 => An object generated by the handler => 自动生成的键值.
支持 INSERT 语句 => public T insert(Connection conn,String sql,ResultSetHandler rsh, Object… params) throws SQLException
批处理 INSERT、UPDATE or DELETE 语句 => public int[] batch(Connection conn,String sql,Object[][] params)throws SQLException
只支持 INSERT 语句的批处理 => public T insertBatch(Connection conn,String sql,ResultSetHandler rsh,Object[][] params)throws SQLException
// 该方法会自行处理 PreparedStatement 和 ResultSet 的创建和关闭.
// 对象数组中的每个元素值被用来作为查询语句的置换参数.
执行一个查询操作 => public Object query(Connection conn, String sql, ResultSetHandler rsh, Object… params) throws SQLException
ResultSetHandler
ResultSetHandler 接口用于处理 java.sql.ResultSet,将结果集按要求封装为另一种形式。
/* ResultSetHandler接口的主要实现类 */
ArrayHandler => 把结果集中的第一行数据转成对象数组.
ArrayListHandler => 把结果集中的每一行数据都转成一个数组再存放到 List 中.
BeanHandler => 将结果集中的第一行数据封装到一个对应的 JavaBean 实例.
BeanListHandler => 将结果集中的每一行数据都封装到一个对应的 JavaBean 实例中,存放到List里.
ColumnListHandler => 将结果集中某一列的数据存放到List中.
KeyedHandler(name) => ResultSetHandler implementation that returns a Map of Maps. ResultSet rows are converted into Maps which are then stored in a Map under the given key.
MapHandler => 将结果集中的第一行数据封装到一个 Map 里,key 是列名,value 是对应的值.
MapListHandler => 将结果集中的每一行数据都封装到一个 Map 里,然后再存放到 List.
ScalarHandler => 查询单个值对象.
DBUtils 实现 CRUD
import java.sql.Connection;
import java.sql.Date;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.ResultSetHandler;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.MapHandler;
import org.apache.commons.dbutils.handlers.MapListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import org.junit.Test;
import com.zsztjdbc.utilsP.JDBCUtilsP;
import com.zszyjdbc.bean.Customer;
public class QueryRunnerTest {
// 插入测试
@Test
public void testInsert() {
Connection connectiond = null;
try {
QueryRunner runner = new QueryRunner();
connectiond = JDBCUtilsP.getConnectiond();
String sql = "INSERT INTO customers (name,email,birth) VALUES (?,?,?)";
int insertCount = runner.update(connectiond, sql, "hz","hz@mail.com","1997-01-01");
System.out.println("添加了"+insertCount+"条数据");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
JDBCUtilsP.closeResource(connectiond,null);
}
}
// 查询测试
// BeanHandler是ResultSethandler接口的实现类,用于封装表中的一条记录
@Test
public void testQuery1() {
Connection connectiond = null;
try {
QueryRunner runner = new QueryRunner();
connectiond = JDBCUtilsP.getConnectiond();
String sql = "SELECT id,name,email,birth FROM customers WHERE id = ?";
BeanHandler<Customer> handler = new BeanHandler<Customer>(Customer.class);
Customer customer = runner.query(connectiond, sql, handler, 10);
System.out.println(customer);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
JDBCUtilsP.closeResource(connectiond, null);
}
}
// BeanlistHandler是ResultSethandler接口的实现类,用于封装表中的多条记录构成的集合
@Test
public void testQuery2() throws Exception {
Connection connectiond = null;
try {
QueryRunner runner = new QueryRunner();
connectiond = JDBCUtilsP.getConnectiond();
String sql = "SELECT id,name,email,birth FROM customers WHERE id < ?";
BeanListHandler<Customer> handler = new BeanListHandler<Customer>(Customer.class);
List<Customer> list = runner.query(connectiond, sql, handler, 10);
System.out.println(list);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
JDBCUtilsP.closeResource(connectiond, null);
}
}
// MapHandler是ResultSethandler接口的实现类,对应表中一条记录,将字段即相应字段的值作为map的key和value
@Test
public void testQuery3() throws Exception {
Connection connectiond = null;
try {
QueryRunner runner = new QueryRunner();
connectiond = JDBCUtilsP.getConnectiond();
String sql = "SELECT id,name,email,birth FROM customers WHERE id = ?";
MapHandler handler = new MapHandler();
Map<String, Object> map = runner.query(connectiond, sql, handler, 10);
System.out.println(map);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtilsP.closeResource(connectiond, null);
}
}
// MapListHandler是ResultSethandler接口的实现类,对应表中多条记录,将字段即相应字段的值作为map的key和value,将这些map添加到对应的list中
@Test
public void testQuery4() throws Exception {
Connection connectiond = null;
try {
QueryRunner runner = new QueryRunner();
connectiond = JDBCUtilsP.getConnectiond();
String sql = "SELECT id,name,email,birth FROM customers WHERE id < ?";
MapListHandler handler = new MapListHandler();
List<Map<String, Object>> maplist = runner.query(connectiond, sql, handler, 10);
maplist.forEach(System.out::println);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtilsP.closeResource(connectiond, null);
}
}
// scalarhandler 使用特殊值
@Test
public void testQuery5() throws Exception {
Connection connectiond = null;
try {
QueryRunner runner = new QueryRunner();
connectiond = JDBCUtilsP.getConnectiond();
String sql = "SELECT COUNT(*) FROM customers";
ScalarHandler handler = new ScalarHandler();
Long count = (Long) runner.query(connectiond, sql, handler);
System.out.println(count);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtilsP.closeResource(connectiond, null);
}
}
@Test
public void testQuery6() throws Exception {
Connection connectiond = null;
try {
QueryRunner runner = new QueryRunner();
connectiond = JDBCUtilsP.getConnectiond();
String sql = "SELECT max(birth) FROM customers";
ScalarHandler handler = new ScalarHandler();
Date maxdate = (Date) runner.query(connectiond, sql, handler);
System.out.println(maxdate);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtilsP.closeResource(connectiond, null);
}
}
// 自定义ResultSetHandler实现类
@Test
public void testQuerybymyself() throws Exception {
Connection connectiond = null;
try {
QueryRunner runner = new QueryRunner();
connectiond = JDBCUtilsP.getConnectiond();
String sql = "SELECT id,name,email,birth FROM customers WHERE id = ?";
// 普通类可以推断 => 匿名实现类在Java7还不能识别 => java10可
ResultSetHandler<Customer> handler = new ResultSetHandler<Customer>(){
@Override
public Customer handle(ResultSet rs) throws SQLException {
if(rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
String email = rs.getString("email");
Date birth = rs.getDate("birth");
Customer customer = new Customer(id,name,email,birth);
return customer;
}
return null;
}
};
Customer customer = runner.query(connectiond, sql, handler,10); // 重写方法的返回值即此行返回值
System.out.println(customer);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtilsP.closeResource(connectiond, null);
}
}
}
BUGS 处理
- druid 连接池与数据库建立链接时无法使用
createDataSource()
方法
概率是导包导入了不正确的包。应是 com.alibaba.druid.pool
,而不是 com.alibaba.druid.support.ibatis
。
- ERROR c.a.d.p.DruidDataSource .. create connection SQLException
[Druid-ConnectionPool-Create-1216454894] ERROR c.a.d.p.DruidDataSource - [run,2840] - create connection SQLException, url: jdbc:mysql://127.0.0.1:3306/iamericano?characterEncoding=utf8&serverTimezone=Asia/Shanghai, errorCode 1045, state 28000
java.sql.SQLException: Access denied for user 'xieziyi'@'localhost' (using password: YES)
SSM 框架通过 Druid 进行数据库连接时出现循环报错。错误代码 errorCode 1045,状态 state 28000。原因是在 Spring 中,${username} 和 ${user} 并不会加载配置文件中的 username|user=xxx,而是加载操作系统用户名。故应在 application.xml 中的数据源配置 Bean 里修改 property 的 name,以及 jdbc.properties 的 name 配置。
<!-- jdbc.properties -->
userok=root
...
<!-- application.xml -->
...
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${driverClassName}"/>
<property name="username" value="${userok}"/>
...
</bean>
附录
// JDBCUtilsP.java
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;
import org.apache.commons.dbutils.DbUtils;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import com.mysql.cj.xdevapi.Statement;
public class JDBCUtilsP {
/**
*
* @Description 使用c3p0数据库连接池
* @author zsxzy
* @return
* @throws Exception
*/
public static Connection getConnection() throws Exception {
ComboPooledDataSource cpds = new ComboPooledDataSource("helloc3p0");
Connection connection = cpds.getConnection();
return connection;
}
/**
*
* @Description 使用DBCP数据库连接池技术获取数据连接
* @author zsxzy
* @return
* @throws Exception
*/
private static DataSource dataSource;
static {
// 静态代码快随着类的加载而加载,只执行一次
// 创建一个 DBCP 数据库连接池
try {
Properties pros = new Properties();
FileInputStream is = new FileInputStream(new File("src/dbcp.properties"));
pros.load(is);
dataSource = BasicDataSourceFactory.createDataSource(pros);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static Connection connectionget() throws Exception {
Connection connection = dataSource.getConnection();
return connection;
}
/**
* Druid
*/
private static DataSource sourcee;
static {
try {
Properties props = new Properties();
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties");
props.load(is);
sourcee = DruidDataSourceFactory.createDataSource(props);
} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection getConnectiond() throws Exception {
Connection connection = sourcee.getConnection();
return connection;
}
public static void closeResource(Connection conn,PreparedStatement ps) {
try {
if(ps != null)
ps.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
if(conn != null)
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// 使用dbutils.jar中提供的工具类实现资源的关闭
public static void closeResourceP(Connection conn,Statement sm,ResultSet rs) {
// try {
// DbUtils.close(conn);
// } catch (SQLException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
// try {
// DbUtils.close((Connection) sm);
// } catch (SQLException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
// try {
// DbUtils.close(rs);
// } catch (SQLException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
DbUtils.closeQuietly(conn);
DbUtils.closeQuietly(rs);
DbUtils.closeQuietly((Connection) sm);
}
}
结束
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议,转载请注明出处!