JDBC
JDBC
1.获取数据库连接
1.获取数据库连接方式一
1.Driver接口的实现类
补充:Uniform Resource Locator,统一资源定位器
2.获取连接
3.代码参考:
package com.xurong.connection;
import org.junit.Test;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.SQLException;
import java.util.Properties;
/**
* @auther xu
* @date 2022/4/20 - 10:35
*/
public class ConnectionTest {
@Test
public void testConnection1() throws SQLException {
Driver drive = new com.mysql.jdbc.Driver();//1.获取mysql所实现的驱动类(多态:引用类型为原始的接口)
String url = "jdbc:mysql://localhost:3306/test";//获取对应目的主机所要访问的数据库
//封装数据库连接必要的信息(用户名和密码)
Properties info = new Properties();
info.setProperty("user","root");
info.setProperty("password","draf19");
//2.获取连接对象
Connection connect = drive.connect(url, info);//多态:com.mysql.jdbc.Driver重写了的Driver接口中的connect()方法
System.out.println(connect);//调用实现类中的重写的toString()方法
}
}
2.获取数据库连接方式二
new com.mysql.jdbc.Driver();--第3方的API,为了使程序具有更好的可移植性(也就是说,换成Oracle数据库时,不需要通过改源代码也能够获取连接),我们尽量不要出现第3方的API.
@Test
/**
* new com.mysql.jdbc.Driver();--第3方的API,为了使程序具有更好的可移植性(也就是说,换成Oracle数据库时,
* 不需要通过改源代码也能够获取连接),我们尽量不要出现第3方的API.
*/
public void testConnection2() throws Exception {
//1.获取mysql所实现的驱动类,采用反射机制
Class clazz = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver) clazz.newInstance();
//2.指明所要连接的数据库
String url = "jdbc:mysql://localhost:3306/test";
//3.提供连接所需要的用户名和密码
Properties info = new Properties();
info.setProperty("user","root");
info.setProperty("password","draf19");
//4.获取连接
Connection connect = driver.connect(url, info);
System.out.println(connect);
}
3.获取数据库连接方式三---使用DriverManager替换Driver
4.获取数据库连接方式四
类的加载:
我们都知道 Java 源文件通过编译器 javac 命令能够编译生成相应的 class 文件,即二进制字节码文件。Java 虚拟机将描述类或接口的 class 文件(准确地说,应该是类的二进制字节流)加载到内存,对数据进行校验、转换解析和初始化,最终形成能够被虚拟机直接使用的 Java 类型,真正能够执行字节码的操作才刚刚开始。这个过程就是虚拟机的类加载机制。
静态代码块:随着类的加载而执行。
@Test
/**
* mysql的Driver实现类自动完成了Driver的注册功能
*/
public void testConnection4() throws Exception {
//1.获取另外三个连接的基本信息
String url = "jdbc:mysql://localhost:3306/test";
String user = "root";
String password = "draf19";
//2.获取mysql所实现的驱动类,采用反射机制
//Class clazz = Class.forName("com.mysql.jdbc.Driver");
//Driver driver = (Driver) clazz.newInstance();
//3.注册驱动
// DriverManager.registerDriver(driver);
//加载驱动,并完成了注册
Class.forName("com.mysql.jdbc.Driver");
//4.获取连接
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println(connection);
}
输出结果:
5.获取数据库连接方式五
/**
* 获取连接的最终版本,将基本信息写入配置文件中
* @throws Exception
*/
@Test
public void testConnection5() throws Exception {
//1.读取配置文件中的基本信息
InputStream is = ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties properties = new Properties();
properties.load(is);
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String url = properties.getProperty("url");
String driverClass = properties.getProperty("driverClass");
//2.加载驱动
Class.forName(driverClass);
//3.获取连接
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println(connection);
}
总代码参考:
package com.xurong1.connection;
import org.junit.Test;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
/**
* @auther xu
* @date 2022/4/20 - 10:35
*/
public class ConnectionTest {
@Test
public void testConnection1() throws SQLException {
Driver drive = new com.mysql.jdbc.Driver();//1.获取mysql所实现的驱动类(多态:引用类型为原始的接口)
String url = "jdbc:mysql://localhost:3306/test";//获取对应目的主机所要访问的数据库
//封装数据库连接必要的信息(用户名和密码)
Properties info = new Properties();
info.setProperty("user","root");
info.setProperty("password","draf19");
//2.获取连接对象
Connection connect = drive.connect(url, info);//多态:com.mysql.jdbc.Driver重写了的Driver接口中的connect()方法
System.out.println(connect);//调用实现类中的重写的toString()方法
}
@Test
/**
* new com.mysql.jdbc.Driver();--第3方的API,为了使程序具有更好的可移植性(也就是说,换成Oracle数据库时,
* 不需要通过改源代码也能够获取连接),我们尽量不要出现第3方的API.
*/
public void testConnection2() throws Exception {
//1.获取mysql所实现的驱动类,采用反射机制
Class clazz = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver) clazz.newInstance();
//2.指明所要连接的数据库
String url = "jdbc:mysql://localhost:3306/test";
//3.提供连接所需要的用户名和密码
Properties info = new Properties();
info.setProperty("user","root");
info.setProperty("password","draf19");
//4.获取连接
Connection connect = driver.connect(url, info);
System.out.println(connect);
}
@Test
/**
* 使用DriverManager替换Driver
*/
public void testConnection3() throws Exception {
//1.获取mysql所实现的驱动类,采用反射机制
Class clazz = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver) clazz.newInstance();
//2.获取另外三个连接的基本信息
String url = "jdbc:mysql://localhost:3306/test";
String user = "root";
String password = "draf19";
//3.注册驱动
DriverManager.registerDriver(driver);
//4.获取连接
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println(connection);
}
@Test
/**
* mysql的Driver实现类自动完成了Driver的注册功能
*/
public void testConnection4() throws Exception {
//1.获取另外三个连接的基本信息
String url = "jdbc:mysql://localhost:3306/test";
String user = "root";
String password = "draf19";
//2.获取mysql所实现的驱动类,采用反射机制
//Class clazz = Class.forName("com.mysql.jdbc.Driver");
//Driver driver = (Driver) clazz.newInstance();
//3.注册驱动
// DriverManager.registerDriver(driver);
//加载驱动,并完成了注册
// Class.forName("com.mysql.jdbc.Driver");
//4.获取连接
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println(connection);
}
/**
* 获取连接的最终版本,将基本信息写入配置文件中
* @throws Exception
*/
@Test
public void testConnection5() throws Exception {
//1.读取配置文件中的基本信息
InputStream is = ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties properties = new Properties();
properties.load(is);
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String url = properties.getProperty("url");
String driverClass = properties.getProperty("driverClass");
//2.加载驱动
Class.forName(driverClass);
//3.获取连接
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println(connection);
}
}
2.使用PreparedStatemenet-进行增删改
大概访问数据库的思路:
将所使用厂商的数据库驱动加载进来,通过获取的驱动得到数据库连接对象,通过数据库连接对象获取Statement对象,通过Statement对象操作sql语句,对于增,删,改是直接对数据库进行的操作,而对于查就需要返回结果集。
2.1 操作和访问数据库
-
数据库连接被用于向数据库服务器发送命令和 SQL 语句,并接受数据库服务器返回的结果。其实一个数据库连接就是一个Socket连接。
-
在 java.sql 包中有 3 个接口分别定义了对数据库的调用的不同方式:
- Statement:用于执行静态 SQL 语句并返回它所生成结果的对象。
- PrepatedStatement:SQL 语句被预编译并存储在此对象中,可以使用此对象多次高效地执行该语句。
- CallableStatement:用于执行 SQL 存储过程
2-2 使用Statement操作数据表的弊端
1.弊端:
1.SQL注入问题:
2-3 PrepareSatement简介
2.3.1 PreparedStatement介绍
-
可以通过调用 Connection 对象的 preparedStatement(String sql) 方法获取 PreparedStatement 对象
-
PreparedStatement 接口是 Statement 的子接口,它表示一条预编译过的 SQL 语句
-
PreparedStatement 对象所代表的 SQL 语句中的参数用问号(?)来表示,调用 PreparedStatement 对象的 setXxx() 方法来设置这些参数. setXxx() 方法有两个参数,第一个参数是要设置的 SQL 语句中的参数的索引(从 1 开始),第二个是设置的 SQL 语句中的参数的值
2.3.2 PreparedStatement vs Statement
-
代码的可读性和可维护性。
-
PreparedStatement 能最大可能提高性能:
- DBServer会对预编译语句提供性能优化。因为预编译语句有可能被重复调用,所以语句在被DBServer的编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中就会得到执行。
- 在statement语句中,即使是相同操作但因为数据内容不一样,所以整个语句本身不能匹配,没有缓存语句的意义.事实是没有数据库会对普通语句编译后的执行代码缓存。这样每执行一次都要对传入的语句编译一次。
- (语法检查,语义检查,翻译成二进制命令,缓存)
-
PreparedStatement 可以防止 SQL 注入
2.3.3 Java与SQL对应数据类型转换表
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 |
2-4对数据库的增删改操作
2-4-1 向customer表中插入一条数据
/**
* 向customer表中插入一条数据
* @throws Exception
*/
@Test
public void testInsert() throws Exception {
InputStream is = null;
Connection connection = null;
PreparedStatement ps = null;
try {
//1.读取配置文件中的基本信息
is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");//该方式也可以获取类的加载器
Properties properties = new Properties();
properties.load(is);
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String url = properties.getProperty("url");
String driverClass = properties.getProperty("driverClass");
//2.加载驱动
Class.forName(driverClass);
//3.获取连接
connection = DriverManager.getConnection(url, user, password);
//4预编译SQL语句,返回PrepareStatement对象
String sql = "insert customers(name,email,birth)values(?,?,?)";//?是占位符,解决了Statement弊端
ps = connection.prepareStatement(sql);
//5.填充占位符
ps.setString(1,"孙悟空");//注意:数据库表的的下标是从1开始的
ps.setString(2,"sunwukong@gmail.com");
//填充日期
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
Date parseDate = simpleDateFormat.parse("2000-01-01");//import java.util.Date;
ps.setDate(3,new java.sql.Date(parseDate.getTime()));
//6对数据库进行操作
ps.execute();
} catch (Exception e) {
e.printStackTrace();
} finally {
//7.资源的关闭
try {
if (ps != null)
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (connection != null)
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
2-4-2 修改customer表中的一条数据---封装了数据库连接操作和关闭资源的操作
package com.xurong3.util;
import com.xurong1.connection.ConnectionTest;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/**
* @auther xu
* @date 2022/4/23 - 11:20
*/
public class JDBCUtils {
/**
* 获取数据库的连接
* @return
* @throws Exception
*/
public static Connection getConnection() throws Exception {
//1.读取配置文件中的基本信息
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");//该方式也可以获取类的加载器
Properties properties = new Properties();
properties.load(is);
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String url = properties.getProperty("url");
String driverClass = properties.getProperty("driverClass");
//2.加载驱动
Class.forName(driverClass);
//3.获取连接
Connection connection = DriverManager.getConnection(url, user, password);
return connection;
}
/**
* 关闭连接数据库的底层资源
* @param conn
* @param ps
*/
public static void closeResource(Connection conn, Statement ps) {//java.sql public interface Statement
//7.资源的关闭
try {
if (ps != null)
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
try {
if (conn != null)
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/**
* 修改customer表中的一条数据
*/
@Test
public void testUpdate() throws Exception {
Connection conn = null;
PreparedStatement ps = null;
try {
//1.获取数据库的连接
conn = JDBCUtils.getConnection();
//2.预编译SQL语句,返回一个PrepareStatement对象
String sql = "update customers set name = ? where id = ?";
ps = conn.prepareStatement(sql);
//3.填充占位符
ps.setObject(1,"莫扎特");
ps.setObject(2,18);
//4.执行操作
ps.execute();
} catch (Exception e) {
e.printStackTrace();
} finally {
//5.关闭资源
JDBCUtils.closeResource(conn,ps);
}
}
2-4-3 通用的增删改操作
/**
* 通用的增删改操作
* @param sql
* @param args 相当于占位符的不定长度的数组
* @throws Exception
*/
public void update(String sql,Object...args) {
Connection connection = null;
PreparedStatement ps = null;
try {
//1.获取数据库的连接
connection = JDBCUtils.getConnection();
//2.预编译SQL语句,返回一个PrepareStatement对象
ps = connection.prepareStatement(sql);
//3.填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i+1,args[i]);
}
//4.执行操作
ps.execute();
} catch (Exception e) {
e.printStackTrace();
} finally {
//5.资源的关闭
JDBCUtils.closeResource(connection,ps);
}
}
测试结果:
@Test
public void testCommonUpdate() {
// //删除customerb编号为3的顾客
// String sql = "delete from customers where id = ?";
// update(sql,3);
String sql = "update order set order_name = ? where order_id = ?";
update(sql,"CCC",4);
}
2-5 对数据库进行查询操作
结果集中运用next()与迭代器中的hasNext(),next()不同:
结果集中的next():结果集中下一个位置有元素,该方法会返回true,并自动将指针下移。否则,返回false,指针不下移。
迭代器中的hasNext(),next()不同:
hasNext():就只有一个作用,返回ture表示下一个位置有元素,返回false表示下一个位置没有元素。
next():两个作用:将指针下移,并且将将下移位置的元素给返回了。
2-5-1:正针对customers表写了一个查询操作
customer表映射的实体类:
package com.xurong3.bean;
import java.sql.Date;
/**
*
*
* ORM编程思想(object relational mapping)
* 一个数据表对应一个java类
* 表中的一条记录对应java类的一个对象
* 表中的一个字段对应java中的一个属性
* @auther xu
* @date 2022/4/24 - 15:16
*/
public class Customer {
private int id;
private String name;
private String email;
private Date birth;
public Customer() {
}
public Customer(int id, String name, String email, Date date) {
this.id = id;
this.name = name;
this.email = email;
this.birth = date;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Date getBirth() {
return birth;
}
public void setBirth(Date birth) {
this.birth = birth;
}
@Override
public String toString() {
return "Customer{" +
"id=" + id +
", name='" + name + '\'' +
", email='" + email + '\'' +
", birth=" + birth +
'}';
}
}
对顾客表格的查询操作:
package com.xurong3.PrepareStatement.crud;
import com.xurong3.bean.Customer;
import com.xurong3.util.JDBCUtils;
import org.junit.Test;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
/**
*
* 对顾客表格的查询操作
* @auther xu
* @date 2022/4/24 - 14:36
*/
public class CustomerForQuery {
@Test
public void testQuery(){
Connection conn = null;
PreparedStatement ps = null;
ResultSet resultSet = null;
try {
//1.获取连接
conn = JDBCUtils.getConnection();
//2.预编译SQL语句,返回一个PrepareStatement对象,并填充占位符
String sql = "select id,name,email,birth,photo from customers where id = ?";
ps = conn.prepareStatement(sql);
ps.setObject(1,1);
//3.执行操作并返回一个结果集对象
resultSet = ps.executeQuery();
//4.将结果集中的每一个元素进行封装
if (resultSet.next()) {
//获取当前字段的各个数据
int id = resultSet.getInt(1);
String name = resultSet.getString(2);
String email = resultSet.getString(3);
Date birth = resultSet.getDate(4);
//显示数据
//方式1:
// System.out.println("id = "+id+",name="+name+",email="+email+",birth="+birth);
//方式2
// Object[] data = new Object[]{id,name,email,birth};
//方式3:将数据封装为一个对象
Customer customer = new Customer(id, name, email, birth);
System.out.println(customer);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//5.关闭资源对象
JDBCUtils.closeResource(conn,ps,resultSet);
}
}
}
返回结果:
2-5-2:针对customer写一个通用查询操作:
此处着重利用反射完成数据属性封装的任务:
package com.xurong3.PrepareStatement.crud;
import com.xurong3.bean.Customer;
import com.xurong3.util.JDBCUtils;
import org.junit.Test;
import java.lang.reflect.Field;
import java.sql.*;
/**
*
* 对customers表格的查询操作
* @auther xu
* @date 2022/4/24 - 14:36
*/
public class CustomerForQuery {
@Test
public void testQueryForCustomers(){
String sql1 = "select id,name,email,birth from customers where id = ?";
Customer customer1 = queryForCustomers(sql1, 1);
System.out.println(customer1);
String sql2 = "select name,email from customers where id = ?";
Customer customer2 = queryForCustomers(sql2, 10);
System.out.println(customer2);
}
/**
* 对customers的通用查询操作
* @param sql sql语句
* @param args 占位符所需要填充的数据
* @return 实体对象--customer
*/
public Customer queryForCustomers(String sql,Object...args){
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
//1.获取连接对象
conn = JDBCUtils.getConnection();
//2.预编译SQL语句,返回一个PrepareStatement对象
ps = conn.prepareStatement(sql);
//3.填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i+1,args[i]);
}
//4.执行操作并返回一个结果集对象
rs = ps.executeQuery();
//5.获取结果集中的元数据(类似于元注解)--用于修饰结果集:ResultSetMetaData
ResultSetMetaData metaData = rs.getMetaData();
//6.通过ResultSetMetaData获取结果集中的列数
int columnCount = metaData.getColumnCount();
//7.将数据封装到结果集当中
if (rs.next()) {
Customer customer = new Customer();
for (int i = 0; i < columnCount; i++) {
//获取(i+1)所对应的值,例如,第一行第2列获取name=汪峰
Object columnVale = rs.getObject(i + 1);
/*
将拿到的数据封装到对象当中,需要把属性按照拿到的数据附上值
造对象两种方式:
1.通过含参构造器的方式完成对属性的赋值;
2.使用空参构造器造一个对象,通过该对象调用set方法,完成对属性的赋值;
此处选择第二种,因为参数个数的不确定,对应的含参构造器不一定有,eg:我这只需要两个参数的构造器去赋值,但是实体类中并没有含两个
参数的构造器
*/
//获取每一个列的列名
String columnName = metaData.getColumnName(i + 1);
//给customer对象指定的columnName属性,赋值为columValue,通过反射
Field declaredField = customer.getClass().getDeclaredField(columnName);
declaredField.setAccessible(true);//有可能是私有变量,需要设置访问权限
declaredField.set(customer,columnVale);//完成对customer对象中的declaredField属性的赋值操作
}
return customer;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//8.关闭资源
JDBCUtils.closeResource(conn,ps,rs);
}
return null;//结果集中没有数据返回null
}
/**
* 对customer基本的查询操作
*/
@Test
public void testCustomers(){
Connection conn = null;
PreparedStatement ps = null;
ResultSet resultSet = null;
try {
//1.获取连接
conn = JDBCUtils.getConnection();
//2.预编译SQL语句,返回一个PrepareStatement对象,并填充占位符
String sql = "select id,name,email,birth,photo from customers where id = ?";
ps = conn.prepareStatement(sql);
ps.setObject(1,1);
//3.执行操作并返回一个结果集对象
resultSet = ps.executeQuery();
//4.将结果集中的每一个元素进行封装
if (resultSet.next()) {
//获取当前字段的各个数据
int id = resultSet.getInt(1);
String name = resultSet.getString(2);
String email = resultSet.getString(3);
Date birth = resultSet.getDate(4);
//显示数据
//方式1:
// System.out.println("id = "+id+",name="+name+",email="+email+",birth="+birth);
//方式2
// Object[] data = new Object[]{id,name,email,birth};
//方式3:将数据封装为一个对象
Customer customer = new Customer(id, name, email, birth);
System.out.println(customer);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//5.关闭资源对象
JDBCUtils.closeResource(conn,ps,resultSet);
}
}
}
结果:
2-5-2:针对order写一个通用查询操作:
异常1:
由于表中的列名于实体类中的属性名不一致所导致。
解决:
使用getColumnLabel 替换 getColumnName方法,并且使用实体类中的表名作为表的别名:
getColumnLabel :起了别名就使用别名,没有起别名就与getColumnName方法一样。
Gets the designated(指定的) column's suggested title for use in printouts and displays.
The suggested title is usually specified by the SQL AS clause. If a SQL AS is not specified,
the value returned from getColumnLabel will be the same as the value returned by the getColumnName method.
package com.xurong3.PrepareStatement.crud;
import com.xurong3.bean.Order;
import com.xurong3.util.JDBCUtils;
import org.junit.Test;
import java.lang.reflect.Field;
import java.sql.*;
/**
* @auther xu
* @date 2022/4/25 - 7:59
*/
public class OrderForQuery {
@Test
public void TestOrderForQuery() {
// String sql = "select order_id,order_name,order_date from `order` where order_id = ?";
String sql = "select order_id orderId,order_name orderName,order_date orderDate from `order` where order_id = ?";
Order order = orderForQuery(sql, 1);
System.out.println(order);
}
/**
* 对order表的通用查询操作
* @param sql
* @param args
* @return
* @throws Exception
*/
public Order orderForQuery(String sql,Object...args) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = JDBCUtils.getConnection();
ps = conn.prepareStatement(sql);
//填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i+1,args[i]);
}
//执行操作和获取结果集
rs = ps.executeQuery();
//获取结果集的元数据
ResultSetMetaData metaData = rs.getMetaData();
//根据元数据获取该表的列的数目
int columnCount = metaData.getColumnCount();
//封装数据
if (rs.next()) {
//造一个Order对象,用以封装数据
Order order = new Order();
for (int i = 0; i < columnCount; i++) {
//获取对应行的对应列的某一个数据
Object columnValue = rs.getObject(i + 1);
//获取对应行的对应列的名字
// String columnName = metaData.getColumnName(i + 1);
//Gets the designated(指定的) column's suggested title for use in printouts and displays.
// The suggested title is usually specified by the SQL AS clause. If a SQL AS is not specified,
// the value returned from getColumnLabel will be the same as the value returned by the getColumnName method.
String columnLabel = metaData.getColumnLabel(i + 1);
//通过反射将对应属性赋值
// Field declaredField = order.getClass().getDeclaredField(columnName);
Field declaredField = order.getClass().getDeclaredField(columnLabel);
declaredField.setAccessible(true);//权限问题
declaredField.set(order,columnValue);
}
return order;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭资源
JDBCUtils.closeResource(conn,ps,rs);
}
return null;
}
@Test
public void testQuery1(){
Connection connection = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
//1.获取连接
connection = JDBCUtils.getConnection();
//2.预编译sql语句
String sql = "select order_id,order_name,order_date from `order` where order_id = ?";
ps = connection.prepareStatement(sql);
//3.填充占位符
ps.setObject(1,1);//填充占位符
//4.执行操作并返回一个结果集对象
rs = ps.executeQuery();
//5.封装数据
if (rs.next()) {
//获取第一行的每一列数据
int id = (int)rs.getObject(1);
String name = (String)rs.getObject(2);
Date date = (Date)rs.getObject(3);
//创建一个order对象,对数据进行封装
Order order = new Order(id, name, date);
System.out.println(order);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//6.关闭资源
JDBCUtils.closeResource(connection,ps,rs);
}
}
}
结果:
2-5-3图解查询的操作流程:
2-6:使用PrepareStatement实现不同表的查询操作---传入实体类的字节码文件对象,利用该对象动态实例化一个类
2-6-1:查询的是某一个表中的一条数据
代码参考:
package com.xurong3.PrepareStatement.crud;
import com.xurong3.bean.Customer;
import com.xurong3.bean.Order;
import com.xurong3.util.JDBCUtils;
import org.junit.Test;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
/**
*
*
*
* 使用PrepareStatement实现针对不同表的一个查询操作
*
* @auther xu
* @date 2022/4/25 - 10:37
*/
public class PrepareStatementForQuery {
@Test
public void testgetInstance() {
String sql = "select id,name,email,birth from customers where id = ?";
Customer customer = getInstance(Customer.class, sql, 1);
System.out.println(customer);
String sql1 = "select order_id orderId,order_name orderName,order_date orderDate from `order` where order_id = ?";
Order order = getInstance(Order.class, sql1, 1);
System.out.println(order);
}
/**
* 针对不同表的一个通用查询操作,返回表的一条数据
* @param clazz
* @param sql
* @param args
* @param <T>
* @return
*/
public <T> T getInstance(Class<T> clazz,String sql,Object...args) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
//1.获取连接对象
conn = JDBCUtils.getConnection();
//2.预编译SQL语句,返回一个PrepareStatement对象
ps = conn.prepareStatement(sql);
//3.填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i+1,args[i]);
}
//4.执行操作并返回一个结果集对象
rs = ps.executeQuery();
//5.获取结果集中的元数据(类似于元注解)--用于修饰结果集:ResultSetMetaData
ResultSetMetaData metaData = rs.getMetaData();
//6.通过ResultSetMetaData获取结果集中的列数
int columnCount = metaData.getColumnCount();
//7.将数据封装到结果集当中
if (rs.next()) {
T t = clazz.newInstance();//通过字节码文件对象,动态实例化一个类
for (int i = 0; i < columnCount; i++) {
//获取(i+1)所对应的值,例如,第一行第2列获取name=汪峰
Object columnVale = rs.getObject(i + 1);
/*
将拿到的数据封装到对象当中,需要把属性按照拿到的数据附上值
造对象两种方式:
1.通过含参构造器的方式完成对属性的赋值;
2.使用空参构造器造一个对象,通过该对象调用set方法,完成对属性的赋值;
此处选择第二种,因为参数个数的不确定,对应的含参构造器不一定有,eg:我这只需要两个参数的构造器去赋值,但是实体类中并没有含两个
参数的构造器
*/
//获取每一个列的列名
// String columnName = metaData.getColumnName(i + 1);
String columnLabel = metaData.getColumnLabel(i + 1);
//给customer对象指定的columnName属性,赋值为columValue,通过反射
Field declaredField = clazz.getDeclaredField(columnLabel);
declaredField.setAccessible(true);//有可能是私有变量,需要设置访问权限
declaredField.set(t,columnVale);//完成对customer对象中的declaredField属性的赋值操作
}
return t;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//8.关闭资源
JDBCUtils.closeResource(conn,ps,rs);
}
return null;//结果集中没有数据返回null
}
}
输出结果:
2-6-2 查询的是某一个表中的多条数据
@Test
public void testGetForList() {
String sql = "select id,name,email,birth from customers where id < ?";
List<Customer> list = getForListInstance(Customer.class,sql,12);
list.forEach(System.out::println);
String sql1 = "select order_id orderId,order_name orderName,order_date orderDate from `order` where order_id < ?";
List<Order> list1 = getForListInstance(Order.class,sql1,4);
list1.forEach(System.out::println);
}
/**
* 查询的是某一个表中的多条数据
* @param clazz
* @param sql
* @param args
* @param <T>
* @return
*/
public <T> List<T> getForListInstance(Class<T> clazz, String sql, Object...args) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
//1.获取连接对象
conn = JDBCUtils.getConnection();
//2.预编译SQL语句,返回一个PrepareStatement对象
ps = conn.prepareStatement(sql);
//3.填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i+1,args[i]);
}
//4.执行操作并返回一个结果集对象
rs = ps.executeQuery();
//5.获取结果集中的元数据(类似于元注解)--用于修饰结果集:ResultSetMetaData
ResultSetMetaData metaData = rs.getMetaData();
//6.通过ResultSetMetaData获取结果集中的列数
int columnCount = metaData.getColumnCount();
//7.创建一个集合对象
ArrayList<T> list = new ArrayList<>();
//8.将数据封装到结果集当中
while (rs.next()) {
T t = clazz.newInstance();//通过字节码文件对象,动态实例化一个类
for (int i = 0; i < columnCount; i++) {
//获取(i+1)所对应的值,例如,第一行第2列获取name=汪峰
Object columnVale = rs.getObject(i + 1);
/*
将拿到的数据封装到对象当中,需要把属性按照拿到的数据附上值
造对象两种方式:
1.通过含参构造器的方式完成对属性的赋值;
2.使用空参构造器造一个对象,通过该对象调用set方法,完成对属性的赋值;
此处选择第二种,因为参数个数的不确定,对应的含参构造器不一定有,eg:我这只需要两个参数的构造器去赋值,但是实体类中并没有含两个
参数的构造器
*/
//获取每一个列的列名
// String columnName = metaData.getColumnName(i + 1);
String columnLabel = metaData.getColumnLabel(i + 1);
//给customer对象指定的columnName属性,赋值为columValue,通过反射
Field declaredField = clazz.getDeclaredField(columnLabel);
declaredField.setAccessible(true);//有可能是私有变量,需要设置访问权限
declaredField.set(t,columnVale);//完成对customer对象中的declaredField属性的赋值操作
}
list.add(t);
}
return list;
} catch (Exception e) {
e.printStackTrace();
} finally {
//9.关闭资源
JDBCUtils.closeResource(conn,ps,rs);
}
return null;//结果集中没有数据返回null
}
结果:
总的代码参考:
package com.xurong3.PrepareStatement.crud;
import com.xurong3.bean.Customer;
import com.xurong3.bean.Order;
import com.xurong3.util.JDBCUtils;
import org.junit.Test;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.List;
/**
*
*
*
* 使用PrepareStatement实现针对不同表的一个查询操作
*
* @auther xu
* @date 2022/4/25 - 10:37
*/
public class PrepareStatementForQuery {
@Test
public void testGetForList() {
String sql = "select id,name,email,birth from customers where id < ?";
List<Customer> list = getForListInstance(Customer.class,sql,12);
list.forEach(System.out::println);
String sql1 = "select order_id orderId,order_name orderName,order_date orderDate from `order` where order_id < ?";
List<Order> list1 = getForListInstance(Order.class,sql1,4);
list1.forEach(System.out::println);
}
public <T> List<T> getForListInstance(Class<T> clazz, String sql, Object...args) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
//1.获取连接对象
conn = JDBCUtils.getConnection();
//2.预编译SQL语句,返回一个PrepareStatement对象
ps = conn.prepareStatement(sql);
//3.填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i+1,args[i]);
}
//4.执行操作并返回一个结果集对象
rs = ps.executeQuery();
//5.获取结果集中的元数据(类似于元注解)--用于修饰结果集:ResultSetMetaData
ResultSetMetaData metaData = rs.getMetaData();
//6.通过ResultSetMetaData获取结果集中的列数
int columnCount = metaData.getColumnCount();
//7.创建一个集合对象
ArrayList<T> list = new ArrayList<>();
//8.将数据封装到结果集当中
while (rs.next()) {
T t = clazz.newInstance();//通过字节码文件对象,动态实例化一个类
for (int i = 0; i < columnCount; i++) {
//获取(i+1)所对应的值,例如,第一行第2列获取name=汪峰
Object columnVale = rs.getObject(i + 1);
/*
将拿到的数据封装到对象当中,需要把属性按照拿到的数据附上值
造对象两种方式:
1.通过含参构造器的方式完成对属性的赋值;
2.使用空参构造器造一个对象,通过该对象调用set方法,完成对属性的赋值;
此处选择第二种,因为参数个数的不确定,对应的含参构造器不一定有,eg:我这只需要两个参数的构造器去赋值,但是实体类中并没有含两个
参数的构造器
*/
//获取每一个列的列名
// String columnName = metaData.getColumnName(i + 1);
String columnLabel = metaData.getColumnLabel(i + 1);
//给customer对象指定的columnName属性,赋值为columValue,通过反射
Field declaredField = clazz.getDeclaredField(columnLabel);
declaredField.setAccessible(true);//有可能是私有变量,需要设置访问权限
declaredField.set(t,columnVale);//完成对customer对象中的declaredField属性的赋值操作
}
list.add(t);
}
return list;
} catch (Exception e) {
e.printStackTrace();
} finally {
//9.关闭资源
JDBCUtils.closeResource(conn,ps,rs);
}
return null;//结果集中没有数据返回null
}
@Test
public void testgetInstance() {
String sql = "select id,name,email,birth from customers where id = ?";
Customer customer = getInstance(Customer.class, sql, 1);
System.out.println(customer);
String sql1 = "select order_id orderId,order_name orderName,order_date orderDate from `order` where order_id = ?";
Order order = getInstance(Order.class, sql1, 1);
System.out.println(order);
}
public <T> T getInstance(Class<T> clazz,String sql,Object...args) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
//1.获取连接对象
conn = JDBCUtils.getConnection();
//2.预编译SQL语句,返回一个PrepareStatement对象
ps = conn.prepareStatement(sql);
//3.填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i+1,args[i]);
}
//4.执行操作并返回一个结果集对象
rs = ps.executeQuery();
//5.获取结果集中的元数据(类似于元注解)--用于修饰结果集:ResultSetMetaData
ResultSetMetaData metaData = rs.getMetaData();
//6.通过ResultSetMetaData获取结果集中的列数
int columnCount = metaData.getColumnCount();
//7.将数据封装到结果集当中
if (rs.next()) {
T t = clazz.newInstance();//通过字节码文件对象,动态实例化一个类
for (int i = 0; i < columnCount; i++) {
//获取(i+1)所对应的值,例如,第一行第2列获取name=汪峰
Object columnVale = rs.getObject(i + 1);
/*
将拿到的数据封装到对象当中,需要把属性按照拿到的数据附上值
造对象两种方式:
1.通过含参构造器的方式完成对属性的赋值;
2.使用空参构造器造一个对象,通过该对象调用set方法,完成对属性的赋值;
此处选择第二种,因为参数个数的不确定,对应的含参构造器不一定有,eg:我这只需要两个参数的构造器去赋值,但是实体类中并没有含两个
参数的构造器
*/
//获取每一个列的列名
// String columnName = metaData.getColumnName(i + 1);
String columnLabel = metaData.getColumnLabel(i + 1);
//给customer对象指定的columnName属性,赋值为columValue,通过反射
Field declaredField = clazz.getDeclaredField(columnLabel);
declaredField.setAccessible(true);//有可能是私有变量,需要设置访问权限
declaredField.set(t,columnVale);//完成对customer对象中的declaredField属性的赋值操作
}
return t;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//8.关闭资源
JDBCUtils.closeResource(conn,ps,rs);
}
return null;//结果集中没有数据返回null
}
}
2-7:使用PrepareStatement解决了SQl注入问题以及其他优点(不知道我的代码为什么运行不出来)
2-8 小结:
疑惑:
1.在2-4-2中关闭底层资源时,为什么没有关闭流对象?
2.SQL语句中使用关键字所导致至的异常
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'order set order_name = 'CCC' where order_id = 4' at line 1
解决:
将该关键字使用反单引号---``即可。
3.在2-7 中自己的代码运行不出来(没有解决)
3.Blob数据类型
3-1 从表中读取Blob数据
/**
* 向数据表customers中插入Blob类型的字段
*/
@Test
public void testInsert() throws Exception {
//获取连接
Connection connection = JDBCUtils.getConnection();
//预编译sql语句
String sql = "insert into customers(name,email,birth,photo) value(?,?,?,?)";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//填充占位符
preparedStatement.setObject(1,"lixintin");
preparedStatement.setObject(2,"li@qq.com");
preparedStatement.setObject(3,"2001-01-01");
//采用流的方式将照片文件以流的方式插入到数据库当中
FileInputStream fis = new FileInputStream(new File("lib/butifulGirl.jpg"));
preparedStatement.setBlob(4,fis);
//执行对数据库的操作
preparedStatement.execute();
//关闭资源
JDBCUtils.closeResource(connection,preparedStatement);
}
结果:(已经插入成功(从下面的读取数据也可验证),但是sqlyog没法显示)
3-2 从表中读取Blob数据
package com.xurong5.biob;
import com.xurong3.bean.Customer;
import com.xurong3.util.JDBCUtils;
import org.junit.Test;
import java.io.*;
import java.sql.*;
/**
*
* 测试使用PrepareStatement操作Blob类型的数据
* @auther xu
* @date 2022/4/25 - 16:06
*/
public class BlobTest {
/**
* 向数据表customers中插入Blob类型的字段
*/
@Test
public void testInsert() throws Exception {
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
//获取连接
connection = JDBCUtils.getConnection();
//预编译sql语句
String sql = "insert into customers(name,email,birth,photo) value(?,?,?,?)";
preparedStatement = connection.prepareStatement(sql);
//填充占位符
preparedStatement.setObject(1,"lixintin");
preparedStatement.setObject(2,"li@qq.com");
preparedStatement.setObject(3,"2001-01-01");
//采用流的方式将照片文件以流的方式插入到数据库当中
FileInputStream fis = new FileInputStream(new File("lib/butifulGirl.jpg"));
preparedStatement.setBlob(4,fis);
//执行对数据库的操作
preparedStatement.execute();
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭资源
JDBCUtils.closeResource(connection,preparedStatement);
}
}
/**
* 查询表中的Blob数据(照片)
* @throws Exception
*/
@Test
public void testQuery() {
Connection conn = null;//连接
PreparedStatement ps = null;//预编译sql语句
ResultSet resultSet = null;
InputStream binaryStream = null;
FileOutputStream fileOutputStream = null;
try {
conn = JDBCUtils.getConnection();
String sql = "select id,name,email,birth,photo from customers where id = ?";
ps = conn.prepareStatement(sql);
ps.setObject(1,23);//填充占位符
resultSet = ps.executeQuery();
if (resultSet.next()) {
//获取改行属性值的方法1:
// int id = resultSet.getInt(1);
// String name = resultSet.getString(2);
// String email = resultSet.getString(3);
// Date birth = resultSet.getDate(4);
//获取改行属性值的方法2:
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
String email = resultSet.getString("email");
Date birth = resultSet.getDate("birth");
Customer customer = new Customer(id,name,email,birth);
System.out.println(customer);
//将Blob类型的字段下载下来并保存到本地
Blob photo = resultSet.getBlob("photo");
binaryStream = photo.getBinaryStream();
fileOutputStream = new FileOutputStream(new File("lib/butifulGirlCopy.jpg"));
byte[] buffer = new byte[1024];
int len = 0;
while ((len = binaryStream.read(buffer)) != -1) {
fileOutputStream.write(buffer,0,len);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//关流
try {
if (binaryStream != null)
binaryStream.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (fileOutputStream != null)
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
JDBCUtils.closeResource(conn,ps,resultSet);
}
}
}
输出结果:
3-3 插入Blob字段特殊情况的说明---插入文件过大解决方案
解决:设置max_allowed_packet=16M
结果:
4.批量插入数据
1.验证批量插入数据时PrepareStatement性能优于Statement
2.批量数据的插入
2-1: 方式1:进行数据批处理
2-2: 方式2:进行数据批处理
/**
* 方式2:进行数据批处理
* 批量插入数据时PrepareStatement性能优与Statement
* @throws Exception
*/
@Test
public void testInsert1(){
Connection conn = null;
PreparedStatement ps = null;
try {
long start = System.currentTimeMillis();
//获取连接
conn = JDBCUtils.getConnection();
//预编译sql语句,返回PrepareStatement对象
String sql = "insert into gooods(name) values(?)";
ps = conn.prepareStatement(sql);
//填充占位符
for (int i = 1; i <= 20000; i++) {
ps.setObject(1,"name_"+i);
//执行操作
ps.execute();
}
long end = System.currentTimeMillis();
System.out.println("所花费的时间:"+(end-start));
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(conn,ps);
}
}
结果:执行插入20000行时的结果
2-3: 方式3:进行数据批处理
- 方式3:进行数据批处理
- 修改1: 使用 addBatch() / executeBatch() / clearBatch()----类似于流中的缓冲数组
- 修改2:mysql服务器默认是关闭批处理的,我们需要通过一个参数,让mysql开启批处理的支持。
-
?rewriteBatchedStatements=true 写在配置文件的url后面
- 修改3:使用更新的mysql 驱动:mysql-connector-java-5.1.37-bin.jar
/**
*
* 方式3:进行数据批处理
* 修改1: 使用 addBatch() / executeBatch() / clearBatch()
* 修改2:mysql服务器默认是关闭批处理的,我们需要通过一个参数,让mysql开启批处理的支持。
* ?rewriteBatchedStatements=true 写在配置文件的url后面
* 修改3:使用更新的mysql 驱动:mysql-connector-java-5.1.37-bin.jar
*/
@Test
public void testInsert2(){
Connection conn = null;
PreparedStatement ps = null;
try {
long start = System.currentTimeMillis();
//获取连接
conn = JDBCUtils.getConnection();
//预编译sql语句,返回PrepareStatement对象
String sql = "insert into gooods(name) values(?)";
ps = conn.prepareStatement(sql);
//填充占位符
for (int i = 1; i <= 20000; i++) {
ps.setObject(1,"name_"+i);
//1.攒,sql
ps.addBatch();
if (i % 500 == 0) {//每500次执行一次插入操作
//2.执行batch(批)
ps.executeBatch();
//清空batch
ps.clearBatch();
}
}
long end = System.currentTimeMillis();
System.out.println("所花费的时间:"+(end-start));//所花费的时间:33472
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(conn,ps);
}
}
结果:执行插入20000行的结果
2-4: 方式4:进行数据批处理
/**
* 通过限制数据的事务提交减少时间花费
*/
@Test
public void testInsert4(){
Connection conn = null;
PreparedStatement ps = null;
try {
long start = System.currentTimeMillis();
//获取连接
conn = JDBCUtils.getConnection();
//将事务的自动提交提交关闭,因为一次事务提交都需要花费时间,不如,将所有数据插入完成之后再进行事务提交
conn.setAutoCommit(false);
//预编译sql语句,返回PrepareStatement对象
String sql = "insert into gooods(name) values(?)";
ps = conn.prepareStatement(sql);
//填充占位符
for (int i = 1; i <= 1000000; i++) {
ps.setObject(1,"name_"+i);
//1.攒,sql
ps.addBatch();
if (i % 500 == 0) {//每500次执行一次插入操作
//2.执行batch(批)
ps.executeBatch();
//清空batch
ps.clearBatch();
}
}
//提交数据
conn.commit();
long end = System.currentTimeMillis();
System.out.println("所花费的时间:"+(end-start));//所花费的时间:33472
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(conn,ps);
}
}
结果:执行插入1000000行的结果
5. 数据库事务
关于异常抛出与捕捉异常:
5-1:利用AA给BB转账来引出事务
1.正常转账:
转账结果:
2.不正常转账(用户名不正确或卡号不正确)----该如何处理
代码参考:
//------------------------------未考虑数据库事务的转账操作-------------------------------
/**
* 针对数据表uer_table来说
* AA用户给BB用户转账100
*
* update user_table set balance = balance - 100 where user = "AA";
* update user_table set balance = balance + 100 where user = "BB";
*/
@Test
public void testUpdate() {
String sql1 = "update user_table set balance = balance - 100 where user = ? ";
update(sql1,"AA");
//模拟网络异常(eg:卡号写错了)
System.out.println(10 / 0);
String sql2 = "update user_table set balance = balance + 100 where user = ? ";
update(sql2,"BB");
System.out.println("转账成功");
}
/**
* 通用的增删改操作
* @param sql
* @param args 相当于占位符的不定长度的数组
* @throws Exception
*/
public int update(String sql,Object...args) {
Connection connection = null;
PreparedStatement ps = null;
try {
//1.获取数据库的连接
connection = JDBCUtils.getConnection();
//2.预编译SQL语句,返回一个PrepareStatement对象
ps = connection.prepareStatement(sql);
//3.填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i+1,args[i]);
}
//4.执行操作
//Returns:返回操作成功的几条记录数量(eg:删除了几条数据的数量,更新了几条数据的数量,插入了几条数据的数量)
//either (1) the row count for SQL Data Manipulation Language (DML) statements or (2) 0 for SQL statements
//that return nothing
return ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
//5.资源的关闭
JDBCUtils.closeResource(connection,ps);
}
return 0;
}
5-2. 什么是事务
补充:在处理一个事务的时候,不要关闭连接,也就是不要执行资源关闭的操作。
总结:要想达到事务的回滚,必须保证数据没有提交
5-3:考虑事务以后的代码实现
1.正常转账
@Test
public void testUpdateWithTx(){
Connection connection = null;
try {
connection = JDBCUtils.getConnection();
System.out.println(connection.getAutoCommit());//默认是自动提交
//1.取消数据的自动提交
connection.setAutoCommit(false);
String sql1 = "update user_table set balance = balance - 100 where user = ? ";
update(connection,sql1,"AA");
//模拟网络异常(eg:卡号写错了)
// System.out.println(10 / 0);
String sql2 = "update user_table set balance = balance + 100 where user = ? ";
update(connection,sql2,"BB");
System.out.println("转账成功");
//2.提交数据
connection.commit();
} catch (Exception e) {
e.printStackTrace();
//3.解决中途出现异常,需要对数据进行回滚
try {
connection.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
} finally {
//修改其为自动提交
//主要正对数据库连接池的使用
try {
connection.setAutoCommit(true);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
JDBCUtils.closeResource(connection,null);
}
}
public int update(Connection conn,String sql,Object...args) {
PreparedStatement ps = null;
try {
//1.预编译SQL语句,返回一个PrepareStatement对象
ps = conn.prepareStatement(sql);
//2.填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i+1,args[i]);
}
//3.执行操作
//Returns:返回操作成功的几条记录数量(eg:删除了几条数据的数量,更新了几条数据的数量,插入了几条数据的数量)
//either (1) the row count for SQL Data Manipulation Language (DML) statements or (2) 0 for SQL statements
//that return nothing
return ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
//4.资源的关闭
JDBCUtils.closeResource(null,ps);
}
return 0;
}
结果:
2.不正常转账--进行了事务处理就会进行数据回滚(也就是说数据库表中的数据不会变化)
@Test
public void testUpdateWithTx(){
Connection connection = null;
try {
connection = JDBCUtils.getConnection();
System.out.println(connection.getAutoCommit());//默认是自动提交
//1.取消数据的自动提交
connection.setAutoCommit(false);
String sql1 = "update user_table set balance = balance - 100 where user = ? ";
update(connection,sql1,"AA");
//模拟网络异常(eg:卡号写错了)
System.out.println(10 / 0);
String sql2 = "update user_table set balance = balance + 100 where user = ? ";
update(connection,sql2,"BB");
System.out.println("转账成功");
//2.提交数据
connection.commit();
} catch (Exception e) {
e.printStackTrace();
//3.解决中途出现异常,需要对数据进行回滚
try {
connection.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
} finally {
//修改其为自动提交
//主要正对数据库连接池的使用
try {
connection.setAutoCommit(true);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
JDBCUtils.closeResource(connection,null);
}
}
public int update(Connection conn,String sql,Object...args) {
PreparedStatement ps = null;
try {
//1.预编译SQL语句,返回一个PrepareStatement对象
ps = conn.prepareStatement(sql);
//2.填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i+1,args[i]);
}
//3.执行操作
//Returns:返回操作成功的几条记录数量(eg:删除了几条数据的数量,更新了几条数据的数量,插入了几条数据的数量)
//either (1) the row count for SQL Data Manipulation Language (DML) statements or (2) 0 for SQL statements
//that return nothing
return ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
//4.资源的关闭
JDBCUtils.closeResource(null,ps);
}
return 0;
}
3.小结一波
1.从连接对象入手--预防数据自动提交
2.通过设置参数--预防增删改等操作的自动提交功能
3.通过以上两部起到预防数据自动提交,而此操作针对出现异常时才起到作用,也就是说,出现异常时,数据就不会自动提交。数据没有
提交成功,还需要对数据进行回滚,使数据保持原样。
5.4 事务的ACID属性
-
原子性(Atomicity)
原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。 -
一致性(Consistency)
事务必须使数据库从一个一致性状态变换到另外一个一致性状态。 -
隔离性(Isolation)
事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。 -
持久性(Durability)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。
5.4.1 数据库的并发问题
-
对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制, 就会导致各种并发问题:
- 脏读: 对于两个事务 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段。之后, 若 T2 回滚, T1读取的内容就是临时且无效的。
- 不可重复读: 对于两个事务T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段。之后, T1再次读取同一个字段, 值就不同了。
- 幻读: 对于两个事务T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行。之后, 如果 T1 再次读取同一个表, 就会多出几行。
-
数据库事务的隔离性: 数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题。
-
一个事务与其他事务隔离的程度称为隔离级别。数据库规定了多种事务隔离级别, 不同隔离级别对应不同的干扰程度, 隔离级别越高, 数据一致性就越好, 但并发性越弱。
5.4.2 四种隔离级别
-
数据库提供的4种事务隔离级别:
-
Oracle 支持的 2 种事务隔离级别:READ COMMITED, SERIALIZABLE。 Oracle 默认的事务隔离级别为: READ COMMITED 。
-
Mysql 支持 4 种事务隔离级别。Mysql 默认的事务隔离级别为: REPEATABLE READ。
5-4-3:命令行验证mysql的隔离级别
1.创建两个mysql用户代表两个事务:
2.对用户2授权可以操作的数据库:
3.防止数据自动提交,并查看mysql事务默认的隔离级别:
4.可重复读:
疑惑:如果Tom提交了数据,root仍然访问的是原来的重复数据,但是在下文的java代码实现中,如果更新事务提交了数据,查询事务查询到的是更新的数据,因为查询事务并没有关闭,应该查询到的数据是重复的数据。
5.将隔离级别设置为 READ-COMMITTED--读已提交的数据
root事务中设置隔离级别:
Microsoft Windows [版本 10.0.19044.1645]
(c) Microsoft Corporation。保留所有权利。
C:\Users\Administrator>mysql -u root -p
Enter password: ******
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 15
Server version: 5.5.15 MySQL Community Server (GPL)
Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> set transaction isolation level read committed;
Query OK, 0 rows affected (0.00 sec)
mysql> Ctrl-C -- exit!
Bye
C:\Users\Administrator>mysql -u root -p
Enter password: ******
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 16
Server version: 5.5.15 MySQL Community Server (GPL)
Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| READ-COMMITTED |
+----------------+
1 row in set (0.00 sec)
mysql>
Tom事务中查看隔离级别:
Microsoft Windows [版本 10.0.19044.1645]
(c) Microsoft Corporation。保留所有权利。
C:\Users\Administrator>mysql -u Tom -pabc123
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 17
Server version: 5.5.15 MySQL Community Server (GPL)
Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| READ-COMMITTED |
+----------------+
1 row in set (0.00 sec)
mysql>
能够读取可以提交的数据(Oracle采用这种隔离级别)-----没有解决可重复读问题
6.READ UNCOMMITTED--见视频(40节,后面部分)
5-4-4 在没有设置隔离级别的数据库当中,需要用java代码设置隔离级别()
代码参考:
package com.xu.transaction;
import com.xu.util.JDBCUtils;
import org.junit.Test;
import java.lang.reflect.Field;
import java.sql.*;
/**
* @auther xu
* @date 2022/4/27 - 9:12
*/
public class TransationTest {
//---------------------------下面是考虑事务的数据库转账操作-----------------------------
@Test
public void testUpdateWithTx(){
Connection connection = null;
try {
connection = JDBCUtils.getConnection();
System.out.println(connection.getAutoCommit());//默认是自动提交
//1.取消数据的自动提交
connection.setAutoCommit(false);
String sql1 = "update user_table set balance = balance - 100 where user = ? ";
update(connection,sql1,"AA");
//模拟网络异常(eg:卡号写错了)
System.out.println(10 / 0);
String sql2 = "update user_table set balance = balance + 100 where user = ? ";
update(connection,sql2,"BB");
System.out.println("转账成功");
//2.提交数据
connection.commit();
} catch (Exception e) {
e.printStackTrace();
//3.解决中途出现异常,需要对数据进行回滚
try {
connection.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
} finally {
//修改其为自动提交
//主要正对数据库连接池的使用
try {
connection.setAutoCommit(true);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
JDBCUtils.closeResource(connection,null);
}
}
public int update(Connection conn,String sql,Object...args) {
PreparedStatement ps = null;
try {
//1.预编译SQL语句,返回一个PrepareStatement对象
ps = conn.prepareStatement(sql);
//2.填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i+1,args[i]);
}
//3.执行操作
//Returns:返回操作成功的几条记录数量(eg:删除了几条数据的数量,更新了几条数据的数量,插入了几条数据的数量)
//either (1) the row count for SQL Data Manipulation Language (DML) statements or (2) 0 for SQL statements
//that return nothing
return ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
//4.资源的关闭
JDBCUtils.closeResource(null,ps);
}
return 0;
}
//---------------------考虑隔离级别的版本----------------------------------
@Test
public void testTransactionSelect() throws Exception {
Connection connection = JDBCUtils.getConnection();
//打印当前的隔离级别
System.out.println(connection.getTransactionIsolation());
//设置级别为--读以提交
// connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
connection.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);//mysql默认的隔离级别
//取消数据的自动提交
connection.setAutoCommit(false);
String sql = "select user,password,balance from user_table where user = ?";
User cc = getInstance(connection, User.class, sql, "CC");
System.out.println(cc);
}
@Test
public void testTransactionUpdate() throws Exception {
Connection connection = JDBCUtils.getConnection();
//取消数据的自动提交
connection.setAutoCommit(false);
String sql = "update user_table set balance = ? where user = ?";
update(connection,sql,7000,"CC");
Thread.sleep(10000);//15s
System.out.println("修改结束");
}
/**
* 获取某一个表的一条数据,查询数据库表中的某一个对象
* @param clazz
* @param sql
* @param args
* @param <T>
* @return
*/
public <T> T getInstance(Connection conn,Class<T> clazz,String sql,Object...args) {
PreparedStatement ps = null;
ResultSet rs = null;
try {
//1.预编译SQL语句,返回一个PrepareStatement对象
ps = conn.prepareStatement(sql);
//2.填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i+1,args[i]);
}
//3.执行操作并返回一个结果集对象
rs = ps.executeQuery();
//4.获取结果集中的元数据(类似于元注解)--用于修饰结果集:ResultSetMetaData
ResultSetMetaData metaData = rs.getMetaData();
//5.通过ResultSetMetaData获取结果集中的列数
int columnCount = metaData.getColumnCount();
//6.将数据封装到结果集当中
if (rs.next()) {
T t = clazz.newInstance();//通过字节码文件对象,动态实例化一个类
for (int i = 0; i < columnCount; i++) {
//获取(i+1)所对应的值,例如,第一行第2列获取name=汪峰
Object columnVale = rs.getObject(i + 1);
/*
将拿到的数据封装到对象当中,需要把属性按照拿到的数据附上值
造对象两种方式:
1.通过含参构造器的方式完成对属性的赋值;
2.使用空参构造器造一个对象,通过该对象调用set方法,完成对属性的赋值;
此处选择第二种,因为参数个数的不确定,对应的含参构造器不一定有,eg:我这只需要两个参数的构造器去赋值,但是实体类中并没有含两个
参数的构造器
*/
//获取每一个列的列名
// String columnName = metaData.getColumnName(i + 1);
String columnLabel = metaData.getColumnLabel(i + 1);
//给customer对象指定的columnName属性,赋值为columValue,通过反射
Field declaredField = clazz.getDeclaredField(columnLabel);
declaredField.setAccessible(true);//有可能是私有变量,需要设置访问权限
declaredField.set(t,columnVale);//完成对customer对象中的declaredField属性的赋值操作
}
return t;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//7.关闭资源
JDBCUtils.closeResource(null,ps,rs);//连接对象是从外面通过参数传进来的,关闭的时候不要在此方法中关闭
}
return null;//结果集中没有数据返回null
}
}
结果探索:
未解决的问题:
1.我想将mysql的隔离级别改为但是没有成功:
connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);//读已提交
6.DAO及相关实现类
6-1. 提供操作数据表的BaseDAO
package com.xu.dao;
import com.xu.util.JDBCUtils;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
/**
*
* 封装了数据库表的通用的操作--增删改查
* @auther xu
* @date 2022/4/27 - 10:41
*/
public abstract class BaseDAO {//将此类抽象化是为了避免实例化,改抽象类只提供对数据库的增删改查
/**
* 通用的增删改操作----version 2.0(考虑上事务)
* @param conn
* @param sql
* @param args
* @return
*/
public int update(Connection conn, String sql, Object...args) {
PreparedStatement ps = null;
try {
//1.预编译SQL语句,返回一个PrepareStatement对象
ps = conn.prepareStatement(sql);
//2.填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i+1,args[i]);
}
//3.执行操作
//Returns:返回操作成功的几条记录数量(eg:删除了几条数据的数量,更新了几条数据的数量,插入了几条数据的数量)
//either (1) the row count for SQL Data Manipulation Language (DML) statements or (2) 0 for SQL statements
//that return nothing
return ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
//4.资源的关闭
JDBCUtils.closeResource(null,ps);
}
return 0;
}
/**
*
* 查询--返回一个对象--version 2.0(考虑上事务)
* 获取某一个表的一条数据,查询数据库表中的某一个对象
* @param clazz
* @param sql
* @param args
* @param <T>
* @return
*/
public <T> T getInstance(Connection conn,Class<T> clazz,String sql,Object...args) {
PreparedStatement ps = null;
ResultSet rs = null;
try {
//1.预编译SQL语句,返回一个PrepareStatement对象
ps = conn.prepareStatement(sql);
//2.填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i+1,args[i]);
}
//3.执行操作并返回一个结果集对象
rs = ps.executeQuery();
//4.获取结果集中的元数据(类似于元注解)--用于修饰结果集:ResultSetMetaData
ResultSetMetaData metaData = rs.getMetaData();
//5.通过ResultSetMetaData获取结果集中的列数
int columnCount = metaData.getColumnCount();
//6.将数据封装到结果集当中
if (rs.next()) {
T t = clazz.newInstance();//通过字节码文件对象,动态实例化一个类
for (int i = 0; i < columnCount; i++) {
//获取(i+1)所对应的值,例如,第一行第2列获取name=汪峰
Object columnVale = rs.getObject(i + 1);
/*
将拿到的数据封装到对象当中,需要把属性按照拿到的数据附上值
造对象两种方式:
1.通过含参构造器的方式完成对属性的赋值;
2.使用空参构造器造一个对象,通过该对象调用set方法,完成对属性的赋值;
此处选择第二种,因为参数个数的不确定,对应的含参构造器不一定有,eg:我这只需要两个参数的构造器去赋值,但是实体类中并没有含两个
参数的构造器
*/
//获取每一个列的列名
// String columnName = metaData.getColumnName(i + 1);
String columnLabel = metaData.getColumnLabel(i + 1);
//给customer对象指定的columnName属性,赋值为columValue,通过反射
Field declaredField = clazz.getDeclaredField(columnLabel);
declaredField.setAccessible(true);//有可能是私有变量,需要设置访问权限
declaredField.set(t,columnVale);//完成对customer对象中的declaredField属性的赋值操作
}
return t;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//7.关闭资源
JDBCUtils.closeResource(null,ps,rs);//连接对象是从外面通过参数传进来的,关闭的时候不要在此方法中关闭
}
return null;//结果集中没有数据返回null
}
/**
* 查询--返回表中的多个对象---需要考虑事务,将连接对象以参数的方式传递--version 2.0(考虑上事务)
* 查询的是某一个表中的多条数据
* @param clazz
* @param sql
* @param args
* @param <T>
* @return
*/
public <T> List<T> getForListInstance(Connection conn,Class<T> clazz, String sql, Object...args) {
PreparedStatement ps = null;
ResultSet rs = null;
try {
//2.预编译SQL语句,返回一个PrepareStatement对象
ps = conn.prepareStatement(sql);
//3.填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i+1,args[i]);
}
//4.执行操作并返回一个结果集对象
rs = ps.executeQuery();
//5.获取结果集中的元数据(类似于元注解)--用于修饰结果集:ResultSetMetaData
ResultSetMetaData metaData = rs.getMetaData();
//6.通过ResultSetMetaData获取结果集中的列数
int columnCount = metaData.getColumnCount();
//7.创建一个集合对象
ArrayList<T> list = new ArrayList<>();
//8.将数据封装到结果集当中
while (rs.next()) {
T t = clazz.newInstance();//通过字节码文件对象,动态实例化一个类
for (int i = 0; i < columnCount; i++) {
//获取(i+1)所对应的值,例如,第一行第2列获取name=汪峰
Object columnVale = rs.getObject(i + 1);
/*
将拿到的数据封装到对象当中,需要把属性按照拿到的数据附上值
造对象两种方式:
1.通过含参构造器的方式完成对属性的赋值;
2.使用空参构造器造一个对象,通过该对象调用set方法,完成对属性的赋值;
此处选择第二种,因为参数个数的不确定,对应的含参构造器不一定有,eg:我这只需要两个参数的构造器去赋值,但是实体类中并没有含两个
参数的构造器
*/
//获取每一个列的列名
// String columnName = metaData.getColumnName(i + 1);
String columnLabel = metaData.getColumnLabel(i + 1);
//给customer对象指定的columnName属性,赋值为columValue,通过反射
Field declaredField = clazz.getDeclaredField(columnLabel);
declaredField.setAccessible(true);//有可能是私有变量,需要设置访问权限
declaredField.set(t,columnVale);//完成对customer对象中的declaredField属性的赋值操作
}
list.add(t);
}
return list;
} catch (Exception e) {
e.printStackTrace();
} finally {
//9.关闭资源
JDBCUtils.closeResource(null,ps,rs);
}
return null;//结果集中没有数据返回null
}
/**
* SELECT COUNT(*) FROM user_table;
* 此方法对应如上sql语句的特殊查询(结果集只有一列数据)
* @param conn
* @param sql
* @param args
* @param <F>
* @return
* @throws SQLException
*/
public <F> F getValue(Connection conn,String sql,Object...args) throws SQLException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
//1.预编译sql语句,并返回PrepareStatement对象
ps = conn.prepareStatement(sql);
//2.填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i+1,args[i]);
}
//执行操作,返回结果集
rs = ps.executeQuery();
if (rs.next()) {
return (F)rs.getObject(1);//特殊:只有一列数据
}
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
JDBCUtils.closeResource(null,ps,rs);
}
return null;
}
}
补充:对特殊sql语句定义方法的补充说明
6-2:CustomerDAO以及CustomerDAOImpl实现
CustomerDAO:
package com.xu2.dao;
import com.xu2.bean.Customer;
import java.sql.Connection;
import java.sql.Date;
import java.util.List;
/**
* 接口体现具体的规范,具体实现类实现接口就可以了
* 此接口用于规范对customers表的常用操作
* @auther xu
* @date 2022/4/27 - 11:26
*/
public interface CustomerDAO {
/**
* @Description 将Customer对象添加到数据库中
* @Author xu
* @param conn :
* @param customer :
* @return
* @throws
* @Date
*/
void insert(Connection conn, Customer customer);
/**
* @Description 针对指定的id,删除表中的一条记录
* @Author xu
* @param conn :
* @param id :
* @return
* @throws
* @Date
*/
void deleteById(Connection conn, int id);
/**
* @Description 针对内存中的customer对象,去修改数据表中指定的记录
* @Author xu
* @param conn :
* @param customer :
* @return
* @throws
* @Date
*/
void update(Connection conn,Customer customer);
/**
* @Description 针对指定的id查询得到对应的Customer对象
* @Author xu
* @param conn :
* @param id :
* @return
* @throws
* @Date
*/
Customer getCustomerById(Connection conn,int id);
/**
* @Description 查询到表中的所有记录构成的集合
* @Author xu
* @param conn :
* @return
* @throws
* @Date
*/
List<Customer> getAll(Connection conn);
/**
* @Description 返回数据表中的数据的条目数(注意:数据表中的数据条目可能特别多,所以需要用到long类型作为返回值)
* @Author xu
* @param conn :
* @return
* @throws
* @Date
*/
long getCount(Connection conn);
/**
* @Description 返回数据表中的最大生日
* @Author xu
* @param conn :
* @return
* @throws
* @Date
*/
Date getMaxBirth(Connection conn);
}
CustomerDAOImpl:
package com.xu2.dao;
import com.xu2.bean.Customer;
import java.sql.Connection;
import java.sql.Date;
import java.util.List;
/**
* @auther xu
* @Description
* @date 2022/4/28 - 20:06
* @Modified By:
*/
public class CustomerDAOImpl extends BaseDAO implements CustomerDAO {
@Override
public void insert(Connection conn, Customer customer) {
String sql = "insert into customers(name,email,birth)values(?,?,?)";
update(conn,sql,customer.getName(),customer.getEmail(),customer.getBirth());
}
@Override
public void deleteById(Connection conn, int id) {
String sql = "delete from customers where id = ?";
update(conn,sql,id);
}
@Override
public void update(Connection conn, Customer customer) {
String sql = "update customers set name = ?,email = ?,birth = ? where id = ?";
update(conn,sql,customer.getName(),customer.getEmail(),customer.getBirth(),customer.getId());
}
@Override
public Customer getCustomerById(Connection conn, int id) {
String sql = "select id,name,email,birth from customers where id = ?";
Customer customer = getInstance(conn, Customer.class, sql, id);
return customer;
}
@Override
public List<Customer> getAll(Connection conn) {
String sql = "select id,name,email,birth from customers";
List<Customer> customers = getForListInstance(conn, Customer.class, sql);
return customers;
}
@Override
public long getCount(Connection conn) {
String sql = "select count(*) from customers";
return getValue(conn,sql);
}
@Override
public Date getMaxBirth(Connection conn) {
String sql = "select max(birth) from customers";
return getValue(conn,sql);
}
}
6-3. CustomerDAOImpl测试类
import com.xu1.util.JDBCUtils;
import com.xu2.bean.Customer;
import com.xu2.dao.CustomerDAOImpl;
import junit.framework.TestCase;
import org.junit.Assert;
import org.junit.Test;
import java.sql.Connection;
import java.sql.Date;
import java.util.List;
/**
* @auther xu
* @Description
* @date 2022/4/29 - 16:55
* @Modified By:
*/
public class CustomerDAOImplTest extends Assert {
private CustomerDAOImpl dao = new CustomerDAOImpl();
@Test
public void testInsert() {
Connection conn = null;
try {
conn = JDBCUtils.getConnection();
Customer customer = new Customer(1,"婷欣","abc@bca.com", new Date(122789609L));
dao.insert(conn,customer);
System.out.println("添加成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(conn,null);
}
}
@Test
public void testDeleteById() {
Connection conn = null;
try {
conn = JDBCUtils.getConnection();
dao.deleteById(conn,23);
System.out.println("删除成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(conn,null);
}
}
@Test
public void testUpdate() {
Connection conn = null;
try {
conn = JDBCUtils.getConnection();
Customer customer = new Customer(13, "张学友", "zhangxueyou@123.com", new Date(1234213L));
dao.update(conn,customer);
System.out.println("修改成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(conn,null);
}
}
@Test
public void testGetCustomerById() {
Connection conn = null;
try {
conn = JDBCUtils.getConnection();
Customer customer = dao.getCustomerById(conn, 29);
System.out.println(customer);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(conn,null);
}
}
@Test
public void testGetAll() {
Connection conn = null;
try {
conn = JDBCUtils.getConnection();
List<Customer> customers = dao.getAll(conn);
customers.forEach(System.out::println);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(conn,null);
}
}
@Test
public void testGetCount() {
Connection conn = null;
try {
conn = JDBCUtils.getConnection();
long count = dao.getCount(conn);
System.out.println("customers表中数据条数为:"+count);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(conn,null);
}
}
@Test
public void testGetMaxBirth() {
Connection conn = null;
try {
conn = JDBCUtils.getConnection();
Date maxBirth = dao.getMaxBirth(conn);
System.out.println("customers表中的最大生日为:"+maxBirth);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(conn,null);
}
}
}
6-4. 优化后的DAO及其实现类的测试
1.对BaseDAO进行优化:
2.修改之处:
3.相关java类
1.BaseDAO类:
package com.xu3.dao;
import com.xu1.util.JDBCUtils;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
/**
*
* 数据访问对象模式(Data Access Object Pattern)或 DAO 模式。
* 是指位于业务逻辑和持久化数据之间实现对持久化数据的访问。
* 封装了数据库表的通用的操作--增删改查
* @auther xu
* @date 2022/4/27 - 10:41
*/
public abstract class BaseDAO<T> {//将此类抽象化是为了避免实例化,改抽象类只提供对数据库的增删改查
private Class<T> clazz = null;
{
//获取当前BaseDAO的子类继承的父类中的泛型
Type genericSuperclass = this.getClass().getGenericSuperclass();//获取带泛型的父类
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;//获取带参数的泛型
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();//对于该类泛型参数只有一个Customer,所以该数组只有一个参数Customer
clazz = (Class<T>) actualTypeArguments[0];//获取泛型的第一个参数,也就是得到了T实体(eg:Customer)
}
/**
* 通用的增删改操作----version 2.0(考虑上事务)
* @param conn
* @param sql
* @param args
* @return
*/
public int update(Connection conn, String sql, Object...args) {
PreparedStatement ps = null;
try {
//1.预编译SQL语句,返回一个PrepareStatement对象
ps = conn.prepareStatement(sql);
//2.填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i+1,args[i]);
}
//3.执行操作
//Returns:返回操作成功的几条记录数量(eg:删除了几条数据的数量,更新了几条数据的数量,插入了几条数据的数量)
//either (1) the row count for SQL Data Manipulation Language (DML) statements or (2) 0 for SQL statements
//that return nothing
return ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
} finally {
//4.资源的关闭
JDBCUtils.closeResource(null,ps);
}
return 0;
}
/**
*
* 查询--返回一个对象--version 2.0(考虑上事务)
* 获取某一个表的一条数据,查询数据库表中的某一个对象
* @param clazz
* @param sql
* @param args
* @param <T>
* @return
*/
public T getInstance(Connection conn, String sql, Object...args) {
PreparedStatement ps = null;
ResultSet rs = null;
try {
//1.预编译SQL语句,返回一个PrepareStatement对象
ps = conn.prepareStatement(sql);
//2.填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i+1,args[i]);
}
//3.执行操作并返回一个结果集对象
rs = ps.executeQuery();
//4.获取结果集中的元数据(类似于元注解)--用于修饰结果集:ResultSetMetaData
ResultSetMetaData metaData = rs.getMetaData();
//5.通过ResultSetMetaData获取结果集中的列数
int columnCount = metaData.getColumnCount();
//6.将数据封装到结果集当中
if (rs.next()) {
T t = clazz.newInstance();//通过字节码文件对象,动态实例化一个类
for (int i = 0; i < columnCount; i++) {
//获取(i+1)所对应的值,例如,第一行第2列获取name=汪峰
Object columnVale = rs.getObject(i + 1);
/*
将拿到的数据封装到对象当中,需要把属性按照拿到的数据附上值
造对象两种方式:
1.通过含参构造器的方式完成对属性的赋值;
2.使用空参构造器造一个对象,通过该对象调用set方法,完成对属性的赋值;
此处选择第二种,因为参数个数的不确定,对应的含参构造器不一定有,eg:我这只需要两个参数的构造器去赋值,但是实体类中并没有含两个
参数的构造器
*/
//获取每一个列的列名
// String columnName = metaData.getColumnName(i + 1);
String columnLabel = metaData.getColumnLabel(i + 1);
//给customer对象指定的columnName属性,赋值为columValue,通过反射
Field declaredField = clazz.getDeclaredField(columnLabel);
declaredField.setAccessible(true);//有可能是私有变量,需要设置访问权限
declaredField.set(t,columnVale);//完成对customer对象中的declaredField属性的赋值操作
}
return t;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//7.关闭资源
JDBCUtils.closeResource(null,ps,rs);//连接对象是从外面通过参数传进来的,关闭的时候不要在此方法中关闭
}
return null;//结果集中没有数据返回null
}
/**
* 查询--返回表中的多个对象---需要考虑事务,将连接对象以参数的方式传递--version 2.0(考虑上事务)
* 查询的是某一个表中的多条数据
* @param clazz
* @param sql
* @param args
* @param <T>
* @return
*/
public List<T> getForListInstance(Connection conn, String sql, Object...args) {
PreparedStatement ps = null;
ResultSet rs = null;
try {
//2.预编译SQL语句,返回一个PrepareStatement对象
ps = conn.prepareStatement(sql);
//3.填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i+1,args[i]);
}
//4.执行操作并返回一个结果集对象
rs = ps.executeQuery();
//5.获取结果集中的元数据(类似于元注解)--用于修饰结果集:ResultSetMetaData
ResultSetMetaData metaData = rs.getMetaData();
//6.通过ResultSetMetaData获取结果集中的列数
int columnCount = metaData.getColumnCount();
//7.创建一个集合对象
ArrayList<T> list = new ArrayList<>();
//8.将数据封装到结果集当中
while (rs.next()) {
T t = clazz.newInstance();//通过字节码文件对象,动态实例化一个类
for (int i = 0; i < columnCount; i++) {
//获取(i+1)所对应的值,例如,第一行第2列获取name=汪峰
Object columnVale = rs.getObject(i + 1);
/*
将拿到的数据封装到对象当中,需要把属性按照拿到的数据附上值
造对象两种方式:
1.通过含参构造器的方式完成对属性的赋值;
2.使用空参构造器造一个对象,通过该对象调用set方法,完成对属性的赋值;
此处选择第二种,因为参数个数的不确定,对应的含参构造器不一定有,eg:我这只需要两个参数的构造器去赋值,但是实体类中并没有含两个
参数的构造器
*/
//获取每一个列的列名
// String columnName = metaData.getColumnName(i + 1);
String columnLabel = metaData.getColumnLabel(i + 1);
//给customer对象指定的columnName属性,赋值为columValue,通过反射
Field declaredField = clazz.getDeclaredField(columnLabel);
declaredField.setAccessible(true);//有可能是私有变量,需要设置访问权限
declaredField.set(t,columnVale);//完成对customer对象中的declaredField属性的赋值操作
}
list.add(t);
}
return list;
} catch (Exception e) {
e.printStackTrace();
} finally {
//9.关闭资源
JDBCUtils.closeResource(null,ps,rs);
}
return null;//结果集中没有数据返回null
}
/**
* SELECT COUNT(*) FROM user_table;
* 此方法对应如上sql语句的特殊查询(结果集只有一列数据)
* @param conn
* @param sql
* @param args
* @param <F>
* @return
* @throws SQLException
*/
public <F> F getValue(Connection conn,String sql,Object...args) {
PreparedStatement ps = null;
ResultSet rs = null;
try {
//1.预编译sql语句,并返回PrepareStatement对象
ps = conn.prepareStatement(sql);
//2.填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i+1,args[i]);
}
//执行操作,返回结果集
rs = ps.executeQuery();
if (rs.next()) {
return (F)rs.getObject(1);//特殊:只有一列数据
}
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
JDBCUtils.closeResource(null,ps,rs);
}
return null;
}
}
2.CustomerDAO接口:
package com.xu3.dao;
import com.xu2.bean.Customer;
import java.sql.Connection;
import java.sql.Date;
import java.util.List;
/**
* 接口体现具体的规范,具体实现类实现接口就可以了
* 此接口用于规范对customers表的常用操作
* @auther xu
* @date 2022/4/27 - 11:26
*/
public interface CustomerDAO {
/**
* @Description 将Customer对象添加到数据库中
* @Author xu
* @param conn :
* @param customer :
* @return
* @throws
* @Date
*/
void insert(Connection conn, Customer customer);
/**
* @Description 针对指定的id,删除表中的一条记录
* @Author xu
* @param conn :
* @param id :
* @return
* @throws
* @Date
*/
void deleteById(Connection conn, int id);
/**
* @Description 针对内存中的customer对象,去修改数据表中指定的记录
* @Author xu
* @param conn :
* @param customer :
* @return
* @throws
* @Date
*/
void update(Connection conn,Customer customer);
/**
* @Description 针对指定的id查询得到对应的Customer对象
* @Author xu
* @param conn :
* @param id :
* @return
* @throws
* @Date
*/
Customer getCustomerById(Connection conn,int id);
/**
* @Description 查询到表中的所有记录构成的集合
* @Author xu
* @param conn :
* @return
* @throws
* @Date
*/
List<Customer> getAll(Connection conn);
/**
* @Description 返回数据表中的数据的条目数(注意:数据表中的数据条目可能特别多,所以需要用到long类型作为返回值)
* @Author xu
* @param conn :
* @return
* @throws
* @Date
*/
long getCount(Connection conn);
/**
* @Description 返回数据表中的最大生日
* @Author xu
* @param conn :
* @return
* @throws
* @Date
*/
Date getMaxBirth(Connection conn);
}
3.CustomerDAOImpl实现类:
package com.xu3.dao;
import com.xu2.bean.Customer;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.sql.Date;
import java.util.List;
/**
* @auther xu
* @Description
* @date 2022/4/28 - 20:06
* @Modified By:
*/
public class CustomerDAOImpl extends BaseDAO<Customer> implements CustomerDAO {
// {
// Type genericSuperclass = this.getClass().getGenericSuperclass();//获取带泛型的父类
// ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;//获取带参数的泛型
// Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();//对于该类泛型参数只有一个Customer,所以该数组只有一个参数Customer
// clazz = actualTypeArguments[0];//获取泛型的第一个参数,也就是得到了Customer
// }
// 注释的代码就是为了获取Customer,但是每创建一个实体DAOImple对象都需要进行该操作,未免麻烦,直接放到父类BaseDAO<Customer>继承即可,注意this句柄的使用
@Override
public void insert(Connection conn, Customer customer) {
String sql = "insert into customers(name,email,birth)values(?,?,?)";
update(conn,sql,customer.getName(),customer.getEmail(),customer.getBirth());
}
@Override
public void deleteById(Connection conn, int id) {
String sql = "delete from customers where id = ?";
update(conn,sql,id);
}
@Override
public void update(Connection conn, Customer customer) {
String sql = "update customers set name = ?,email = ?,birth = ? where id = ?";
update(conn,sql,customer.getName(),customer.getEmail(),customer.getBirth(),customer.getId());
}
@Override
public Customer getCustomerById(Connection conn, int id) {
String sql = "select id,name,email,birth from customers where id = ?";
Customer customer = getInstance(conn, sql, id);
return customer;
}
@Override
public List<Customer> getAll(Connection conn) {
String sql = "select id,name,email,birth from customers";
List<Customer> customers = getForListInstance(conn, sql);
return customers;
}
@Override
public long getCount(Connection conn) {
String sql = "select count(*) from customers";
return getValue(conn,sql);
}
@Override
public Date getMaxBirth(Connection conn) {
String sql = "select max(birth) from customers";
return getValue(conn,sql);
}
}
4.CustomerDAOImpl测试类
package com.xu3.dao.junit;
import com.xu1.util.JDBCUtils;
import com.xu2.bean.Customer;
import com.xu3.dao.CustomerDAOImpl;
import org.junit.Assert;
import org.junit.Test;
import java.sql.Connection;
import java.sql.Date;
import java.util.List;
/**
* @auther xu
* @Description
* @date 2022/4/29 - 16:55
* @Modified By:
*/
public class CustomerDAOImplTest extends Assert {
private CustomerDAOImpl dao = new CustomerDAOImpl();
@Test
public void testInsert() {
Connection conn = null;
try {
conn = JDBCUtils.getConnection();
Customer customer = new Customer(1,"婷欣","abc@bca.com", new Date(122789609L));
dao.insert(conn,customer);
System.out.println("添加成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(conn,null);
}
}
@Test
public void testDeleteById() {
Connection conn = null;
try {
conn = JDBCUtils.getConnection();
dao.deleteById(conn,23);
System.out.println("删除成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(conn,null);
}
}
@Test
public void testUpdate() {
Connection conn = null;
try {
conn = JDBCUtils.getConnection();
Customer customer = new Customer(13, "张学友", "zhangxueyou@123.com", new Date(1234213L));
dao.update(conn,customer);
System.out.println("修改成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(conn,null);
}
}
@Test
public void testGetCustomerById() {
Connection conn = null;
try {
conn = JDBCUtils.getConnection();
Customer customer = dao.getCustomerById(conn, 10);
System.out.println(customer);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(conn,null);
}
}
@Test
public void testGetAll() {
Connection conn = null;
try {
conn = JDBCUtils.getConnection();
List<Customer> customers = dao.getAll(conn);
customers.forEach(System.out::println);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(conn,null);
}
}
@Test
public void testGetCount() {
Connection conn = null;
try {
conn = JDBCUtils.getConnection();
long count = dao.getCount(conn);
System.out.println("customers表中数据条数为:"+count);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(conn,null);
}
}
@Test
public void testGetMaxBirth() {
Connection conn = null;
try {
conn = JDBCUtils.getConnection();
Date maxBirth = dao.getMaxBirth(conn);
System.out.println("customers表中的最大生日为:"+maxBirth);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(conn,null);
}
}
}
7.数据库连接池
7-1 数据库连接池技术概述
7-2 数据库连接池两种实现方式
7-2-1 根据需要设置数据库连接池的基本配置属性
7-2-2 从连接池中获取连接对象方式--c3p0
方式1--c3p0:
/**
* @Description 从连接池中获取连接对象方式1
* @Author xu
* @return
* @throws
* @Date 2022/5/1 16:23
*/
@Test
public void testGetConnection() throws Exception {
ComboPooledDataSource cpds = new ComboPooledDataSource();//厂商的实现类接口
cpds.setDriverClass( "com.mysql.jdbc.Driver" );//数据库驱动
cpds.setJdbcUrl( "jdbc:mysql://localhost:3306/test" );
cpds.setUser("root");
cpds.setPassword("draf19");
//设置初始时数据库连接池中的连接对象
cpds.setInitialPoolSize(10);//设置连接对象为10个
Connection connection = cpds.getConnection();
System.out.println(connection);
}
结论:
方式2--c3p0:
c3p0-connection.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<c3p0-config>
<!-- 提供获取连接的4个基本信息-->
<named-config name="c3p0Conn">
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/test</property>
<property name="user">root</property>
<property name="password">draf19</property>
<!-- 进行数据库管理的基本信息 -->
<!-- 当数据库连接池中的连接数不够时,c3p0每一次性向数据库服务器申请的连接数-->
<property name="acquireIncrement">5</property>
<!-- c3p0数据库连接池中初始化时的连接数-->
<property name="initialPoolSize">10</property>
<!-- c3p0数据库连接池维护的最少连接数-->
<property name="minPoolSize">10</property>
<!-- c3p0数据库连接池维护的最多连接数-->
<property name="maxPoolSize">1000</property>
<!-- c3p0数据库连接池最多维护的Statements的个数-->
<property name="maxStatements">0</property>
<!-- 每一个连接中可以最多使用的Statement的个数-->
<property name="maxStatementsPerConnection">5</property>
</named-config>
</c3p0-config>
/**
* @Description 使用配置文件
* @Author xu
* @return
* @throws
* @Date 2022/5/1 16:57
*/
@Test
public void testGetConnection2() throws Exception {
ComboPooledDataSource cpds = new ComboPooledDataSource("c3p0Conn");//自动读取配置文件信息初始化
Connection connection = cpds.getConnection();
System.out.println(connection);
}
7-3-2:
使用c3p0数据库连接池获取连接测试:
改进:
7-2-3--使用JBCP连接池
1.导入的jar包:
2.相关类及其方法:
3.dbcp连接池常用基本配置属性:
dbcp连接池常用基本配置属性
1.initialSize :连接池启动时创建的初始化连接数量(默认值为0)
2.maxActive :连接池中可同时连接的最大的连接数(默认值为8,调整为20,高峰单机器在20并发左右,自己根据应用场景定)
3.maxIdle:连接池中最大的空闲的连接数,超过的空闲连接将被释放,如果设置为负数表示不限制(默认为8个,maxIdle不能设置太小,因为假如在高负载的情况下,连接的打开时间比关闭的时间快,会引起连接池中idle的个数 上升超过maxIdle,而造成频繁的连接销毁和创建,类似于jvm参数中的Xmx设置)
4.minIdle:连接池中最小的空闲的连接数,低于这个数量会被创建新的连接(默认为0,调整为5,该参数越接近maxIdle,性能越好,因为连接的创建和销毁,都是需要消耗资源的;但是不能太大,因为在机器很空闲的时候,也会创建低于minidle个数的连接,类似于jvm参数中的Xmn设置)
5.maxWait :最大等待时间,当没有可用连接时,连接池等待连接释放的最大时间,超过该时间限制会抛出异常,如果设置-1表示无限等待(默认为无限,调整为60000ms,避免因线程池不够用,而导致请求被无限制挂起)
6.poolPreparedStatements:开启池的prepared(默认是false,未调整,经过测试,开启后的性能没有关闭的好。)
7.maxOpenPreparedStatements:开启池的prepared 后的同时最大连接数(默认无限制,同上,未配置)
8.minEvictableIdleTimeMillis :连接池中连接,在时间段内一直空闲, 被逐出连接池的时间
9.removeAbandonedTimeout :超过时间限制,回收没有用(废弃)的连接(默认为 300秒,调整为180)
10.removeAbandoned :超过removeAbandonedTimeout时间后,是否进 行没用连接(废弃)的回收(默认为false,调整为true)
4.代码实现获取连接对象及其属性设置:
方式1:
/**
* @Description 测试DBCP数据库连接技术
* @Author xu
* @return
* @throws
* @Date 2022/5/2 17:04
*/
@Test
public void testGetConnection() throws SQLException {
//创建DBCP数据库连接池
BasicDataSource source = new BasicDataSource();
//设置获取连接的基本信息
source.setDriverClassName("com.mysql.jdbc.Driver");
source.setUrl("jdbc:mysql://localhost:3306/test");
source.setUsername("root");
source.setPassword("draf19");
//设置其他设计数据库连接池管理的相关属性
source.setInitialSize(10);
source.setMaxActive(10);
Connection connection = source.getConnection();
System.out.println(connection);
}
输出结果:
方式2:使用加载配置文件的 方式
dbcp.properties
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test
username=root
password=draf19
initialSize=10
/**
* @Description 使用配置文件获取连接对象
* @Author xu
* @return
* @throws
* @Date 2022/5/2 17:11
*/
@Test
public void testGetConnection2() throws Exception {
Properties properties = new Properties();
//导入文件流方式1:
// FileInputStream is = new FileInputStream(new File("src/dbcp.properties"));
//导入文件流方式2:使用类的系统加载器
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("dbcp.properties");
// InputStream is = ClassLoader.getSystemResourceAsStream("dbcp.properties");
properties.load(is);
DataSource dataSource = BasicDataSourceFactory.createDataSource(properties);
Connection connection = dataSource.getConnection();
System.out.println(connection);
}
结果:
改进:
private static DataSource dataSource = null;
static {
try {
Properties properties = new Properties();
//导入文件流方式1:
// FileInputStream is = new FileInputStream(new File("src/dbcp.properties"));
//导入文件流方式2:使用类的系统加载器
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("dbcp.properties");
// InputStream is = ClassLoader.getSystemResourceAsStream("dbcp.properties");
properties.load(is);
dataSource = BasicDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* @Description 使用dbcp数据库连接池技术获取数据库连接对象
* @Author xu
* @return
* @throws
* @Date 2022/5/2 17:28
*/
public static Connection getConnection2() throws Exception {
Connection connection = dataSource.getConnection();
return connection;
}
测试:
7-2-4--使用Druid
1.入门
- 详细配置参数:
配置 | 缺省 | 说明 |
---|---|---|
name | 配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。 如果没有配置,将会生成一个名字,格式是:”DataSource-” + System.identityHashCode(this) | |
url | 连接数据库的url,不同数据库不一样。例如:mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto | |
username | 连接数据库的用户名 | |
password | 连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。详细看这里:https://github.com/alibaba/druid/wiki/使用ConfigFilter | |
driverClassName | 根据url自动识别 这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName(建议配置下) | |
initialSize | 0 | 初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时 |
maxActive | 8 | 最大连接池数量 |
maxIdle | 8 | 已经不再使用,配置了也没效果 |
minIdle | 最小连接池数量 | |
maxWait | 获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。 | |
poolPreparedStatements | false | 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。 |
maxOpenPreparedStatements | -1 | 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100 |
validationQuery | 用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。 | |
testOnBorrow | true | 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 |
testOnReturn | false | 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能 |
testWhileIdle | false | 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 |
timeBetweenEvictionRunsMillis | 有两个含义: 1)Destroy线程会检测连接的间隔时间2)testWhileIdle的判断依据,详细看testWhileIdle属性的说明 | |
numTestsPerEvictionRun | 不再使用,一个DruidDataSource只支持一个EvictionRun | |
minEvictableIdleTimeMillis | ||
connectionInitSqls | 物理连接初始化的时候执行的sql | |
exceptionSorter | 根据dbType自动识别 当数据库抛出一些不可恢复的异常时,抛弃连接 | |
filters | 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall | |
proxyFilters | 类型是List,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系 |
2.使用Druid获取数据库连接对象:
3.代码实现
/**
* @Description 使用Druid数据库连接池
* @Author xu
* @param null :
* @return
* @throws
* @Date 2022/5/2 19:53
*/
private static DataSource dataSourceForDruid = null;
static {
try {
//创建配置文件对象
Properties properties = new Properties();
//读取配置文件,以流的形式载入到配置文件对象中
InputStream is = ClassLoader.getSystemResourceAsStream("druid.properties");
properties.load(is);
//直接使用配置文件获取连接对象
dataSourceForDruid = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection getConnection3() throws SQLException {
Connection connection = dataSourceForDruid.getConnection();
return connection;
}
测试结果:
8.Apache-DBUtils实现CRUD操作
1.增删改操作的实现:
package com.xu5.dbutil;
import com.xu4.util.JDBCUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.junit.Test;
import java.sql.Connection;
import java.sql.SQLException;
/**
* @auther xu
* @Description
* @date 2022/5/2 - 20:02
* @Modified By:
*/
public class QueryRunnerTest {
/**
* @Description 利用dbutils实现数据的插入操作(删除与更改就是sql语句不同)
* @Author xu
* @return
* @throws
* @Date 2022/5/2 20:50
*/
@Test
public void testInsert(){
Connection conn = null;
try {
QueryRunner queryRunner = new QueryRunner();
//获取连接对象
conn = JDBCUtils.getConnection3();//com.xu4.util.JDBCUtils
//执行操作
String sql = "insert into customers(name,email,birth)values(?,?,?)";
int insertCount = queryRunner.update(conn, sql, "黄家驹", "huangjiaju@123.com", "1993-06-24");
System.out.println("插入操作成功了"+insertCount+"条数据");
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
JDBCUtils.closeResource(conn,null);//com.xu4.util.JDBCUtils
}
}
}
结果:
2.查询操作
1.查询一条记录:
2.查询多条记录
1.BeanListHandler:是ResultSetHandler接口的实现类,用于封装表中的多条记录构成的集合
/**
* BeanListHandler:是ResultSetHandler接口的实现类,用于封装表中的多条记录构成的集合
* @Description 查询多条记录
* @Author xu
* @return
* @throws
* @Date 2022/5/2 21:16
*/
@Test
public void testQuery2() throws Exception {
QueryRunner queryRunner = new QueryRunner();
Connection connection = JDBCUtils.getConnection();
String sql = "select id,name,email,birth from customers where id < ?";
BeanListHandler<Customer> listHandler = new BeanListHandler<>(Customer.class);//结果集处理器
List<Customer> query = queryRunner.query(connection, sql, listHandler, 30);
query.forEach(System.out::println);
}
结果:
2.BeanListHandler:是ResultSetHandler接口的实现类,用于封装表中的多条记录构成的集合
/**
* BeanListHandler:是ResultSetHandler接口的实现类,用于封装表中的多条记录构成的集合
* @Description 查询多条记录
* @Author xu
* @return
* @throws
* @Date 2022/5/2 21:16
*/
@Test
public void testQuery2() throws Exception {
Connection connection = null;
try {
QueryRunner queryRunner = new QueryRunner();
connection = JDBCUtils.getConnection();
String sql = "select id,name,email,birth from customers where id < ?";
BeanListHandler<Customer> listHandler = new BeanListHandler<>(Customer.class);//结果集处理器
List<Customer> query = queryRunner.query(connection, sql, listHandler, 30);
query.forEach(System.out::println);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(connection,null);
}
}
3.BeanHander:是ResultSetHandler接口的实现类,对应表中的一条记录,将字段及相应字段的值作为map中的key和value
/**
* BeanHander:是ResultSetHandler接口的实现类,对应表中的一条记录
* 将字段及相应字段的值作为map中的key和value
* @Description
* @Author xu
* @return
* @throws
* @Date 2022/5/2 21:25
*
*/
@Test
public void testQuery3() throws Exception {
Connection connection = null;
try {
QueryRunner queryRunner = new QueryRunner();
connection = JDBCUtils.getConnection();
String sql = "select id,name,email,birth from customers where id = ?";
MapHandler handler = new MapHandler();//结果集处理器
Map<String, Object> customer = queryRunner.query(connection, sql, handler, 32);
System.out.println(customer);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(connection,null);
}
}
4.BeanHander:是ResultSetHandler接口的实现类,对应表中的多条记录
/**
* BeanHander:是ResultSetHandler接口的实现类,对应表中的多条记录
* 将字段及相应字段的值作为map中的key和value
* @Description
* @Author xu
* @return
* @throws
* @Date 2022/5/2 21:25
*
*/
@Test
public void testQuery4() throws Exception {
Connection connection = null;
try {
QueryRunner queryRunner = new QueryRunner();
connection = JDBCUtils.getConnection();
String sql = "select id,name,email,birth from customers where id < ?";
MapListHandler handler = new MapListHandler();//结果集处理器
List<Map<String, Object>> customerList = queryRunner.query(connection, sql, handler, 32);
customerList.forEach(System.out::println);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(connection,null);
}
}
结果:
5.特殊查询--ScalarHandler用于查询特殊值
1.总条数
@Test
public void testQuery5() throws Exception {
Connection connection = null;
try {
QueryRunner queryRunner = new QueryRunner();
connection = JDBCUtils.getConnection();
String sql = "select count(*) from customers";
ScalarHandler handler = new ScalarHandler();
Long count = (Long)queryRunner.query(connection, sql, handler);
System.out.println("customers数据条数为:"+count);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(connection,null);
}
}
2.最大生日
/**
*
* @Description 特殊查询---查询最大的生日为
* @Author xu
* @return
* @throws
* @Date 2022/5/2 22:06
*
*/
@Test
public void testQuery6() throws Exception {
Connection connection = null;
try {
QueryRunner queryRunner = new QueryRunner();
connection = JDBCUtils.getConnection();
String sql = "select max(birth) from customers";
ScalarHandler handler = new ScalarHandler();
Date maxBirth = (Date)queryRunner.query(connection, sql, handler);
System.out.println("最大的生日为:"+maxBirth);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(connection,null);
}
}
结果:
6.自己实现一个匿名实现类,通过重写handler方法达到自己对结果集处理的目的
/**
*
* @Description 自己实现一个匿名实现类,通过重写handler方法达到自己对结果集处理的目的
* @Author xu
* @return
* @throws
* @Date 2022/5/2 22:13
*
*/
@Test
public void testQuery7() throws Exception {
Connection connection = null;
try {
QueryRunner queryRunner = new QueryRunner();
connection = JDBCUtils.getConnection();
String sql = "select id,name,email,birth from customers where id = ?";
ResultSetHandler<Customer> handler = new ResultSetHandler<Customer>() {
@Override
public Customer handle(ResultSet resultSet) throws SQLException {
System.out.println("重写了handle方法");
return null;
}
};//结果集处理器
Customer customer = queryRunner.query(connection, sql, handler, 32);
System.out.println(customer);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtils.closeResource(connection,null);
}
}
3.使用dbutils.jar中提供的DBUtils工具类,实现资源的关闭
/**
*
* @Description 使用dbutils.jar中提供的DBUtils工具类,实现资源的关闭
* @Author xu
* @return
* @throws
* @Date 2022/5/2 22:28
*
*/
public static void closeResource1(Connection conn, Statement ps, ResultSet rs) {//java.sql public interface Statement
//8.资源的关闭
// try {
// DbUtils.close(rs);
// } catch (SQLException throwables) {
// throwables.printStackTrace();
// }
// try {
// DbUtils.close(ps);
// } catch (SQLException throwables) {
// throwables.printStackTrace();
// }
// try {
// DbUtils.close(conn);
// } catch (SQLException throwables) {
// throwables.printStackTrace();
// }
DbUtils.closeQuietly(rs);
DbUtils.closeQuietly(ps);
DbUtils.closeQuietly(conn);
}