JDBC概述
-
JDBC(Java Database Connectivity)是一个独立于特定数据库管理系统、通用的SQL数据库存取和操作的公共接口(一组API),定义了用来访问数据库的标准Java类库,(java.sql,javax.sql)使用这些类库可以以一种标准的方法、方便地访问数据库资源。
-
JDBC为访问不同的数据库提供了一种统一的途径,为开发者屏蔽了一些细节问题。
-
JDBC的目标是使Java程序员使用JDBC可以连接任何提供了JDBC驱动程序的数据库系统,这样就使得程序员无需对特定的数据库系统的特点有过多的了解,从而大大简化和加快了开发过程。
-
如果没有JDBC,那么Java程序访问数据库时是这样的
-
有了JDBC可以达到这样的效果
- 总结
JDBC制定了一组规范的接口,各大数据库厂商都提供的自家数据库JDBC驱动
JDBC驱动:就是JDBC接口的实现类
所以,JAVA程序员在操作数据库时,只要加载对应数据库驱动后
调用JDBC定义的接口方法,就可以实现对各个数据的操作
不需要去了解数据库厂家的具体实现和细节
JDBC编写步骤
加载驱动到项目
连接数据库
方式1
@Test
public void test1() throws SQLException {
//1. 调用mysql的驱动实例化Driver
Driver driver = new com.mysql.cj.jdbc.Driver();
//2. 配置账号密码
Properties info = new Properties();
info.setProperty("user","root");
info.setProperty("password","1234");
/** 3. 数据库连接地址
* jdbc:mysql : 连接协议
* localhost : IP地址
* 3306:mysql端口号
* test :数据库的名字
*/
String url = "jdbc:mysql://localhost:3306/test";
//4. 打开连接
Connection connect = driver.connect(url, info);
System.out.println(connect);
}
方式2
方式2是对方式1的一个迭代,方式1中出现了第三方的api
方式2则是利用反射将去实例化类,从而动态去实例化第三方驱动
这样可以更加灵活的切换连接的数据库,例如从mysql到orcale的切换
public void test2() throws Exception {
//1. 通过反射获取驱动实现类
Class clazz = Class.forName("com.mysql.cj.jdbc.Driver");
Driver driver = (Driver) clazz.newInstance();
//2. 配置账号密码
Properties info = new Properties();
info.setProperty("user","root");
info.setProperty("password","1234");
/** 3. 数据库连接地址
* jdbc:mysql : 连接协议
* localhost : IP地址
* 3306:mysql端口号
* test :数据库的名字
*/
String url = "jdbc:mysql://localhost:3306/test";
//4. 打开连接
Connection connect = driver.connect(url, info);
System.out.println(connect);
}
方式3
使用DriverManager代替Driver进行注册和连接
@Test
public void test3() throws Exception {
//1. 通过反射获取驱动实现类
Class clazz = Class.forName("com.mysql.cj.jdbc.Driver");
Driver driver = (Driver) clazz.newInstance();
//2. 注册Driver驱动
DriverManager.registerDriver(driver);
//3. 配置账号密码
Properties info = new Properties();
info.setProperty("user","root");
info.setProperty("password","1234");
/** 4. 数据库连接地址
* jdbc:mysql : 连接协议
* localhost : IP地址
* 3306:mysql端口号
* test :数据库的名字
*/
String url = "jdbc:mysql://localhost:3306/test";
//5. 连接数据库
Connection con = DriverManager.getConnection(url, info);
System.out.println(con);
}
方式4
省略显示的驱动实例化和注册的过程,只需要加载驱动类文件
静态代码块自动实例化和注册
@Test
public void test4() throws Exception {
//1. 加载驱动类文件
Class.forName("com.mysql.cj.jdbc.Driver");
//2. 配置账号密码、数据库地址
Properties info = new Properties();
info.setProperty("user","root");
info.setProperty("password","1234");
String url = "jdbc:mysql://localhost:3306/test";
//3. 连接数据库
Connection con = DriverManager.getConnection(url, info);
System.out.println(con);
}
为什么可以省略驱动注册的步骤呢?
//mysql的驱动源码,
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
**static** {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
- 反射mysql的驱动类时,会加载mysql的driver实现类
- 实现类中有一段静态代码块,随着实现类的加载而加载,自动实例化驱动,并且注册了DriverManager
- 所以我们可以省略,驱动类实例化、DriverManager注册的过程
方式5
实际开发中最常用的方式
通过配置文件读取数据库的连接信息
@Test
public void test5() throws Exception {
//1. 通过类加载器 获取 数据库文件中的配置信息
ClassLoader loader = JDBCTest1.class.getClassLoader();
InputStream is = loader.getResourceAsStream("jdbcTest.properties");
Properties info = new Properties();
info.load(is);
String user = info.getProperty("user");
String password = info.getProperty("password");
String url = info.getProperty("url");
String driver = info.getProperty("driver");
//2. 通过反射加载驱动类
Class.forName(driver);
//3. 连接数据库
Connection con = DriverManager.getConnection(url, user, password);
System.out.println(con);
}
CURD
- 数据库连接被用于向数据库服务器发送命令和 SQL 语句,并接受数据库服务器返回的结果。其实一个数据库连接就是一个Socket连接。
- 在 java.sql 包中有 3 个接口分别定义了对数据库的调用的不同方式:
- Statement:用于执行静态 SQL 语句并返回它所生成结果的对象。
- PrepatedStatement:SQL 语句被预编译并存储在此对象中,可以使用此对象多次高效地执行该语句。
- CallableStatement:用于执行 SQL 存储过程
ORM编程思想
object relationship mapping 对象关系映射
一个数据表对应一个java类
表中一条记录对应java类的一个对象
表中一个字段对应java类的一个属性
数据库与SQL类型转化
Statement
- 通过调用 Connection 对象的 createStatement() 方法创建该对象。该对象用于执行静态的 SQL 语句,并且返回执行结果。
- Statement 接口中定义了下列方法用于执行 SQL 语句:
int excuteUpdate(String sql):执行更新操作INSERT、UPDATE、DELETE
ResultSet executeQuery(String sql):执行查询操作SELECT
- 但是使用Statement操作数据表存在弊端:
问题一:存在拼串操作,繁琐
String sql = "SELECT user,password FROM user_table WHERE USER = '" + userName + "' AND PASSWORD = '" + password + "'";
问题二:存在SQL注入问题
#user:1' OR
#password:='1' OR '1' = '1
SELECT USER,PASSWORD FROM user_table WHERE USER = '1' OR ' AND PASSWORD = '='1' OR '1' = '1'
对于 Java 而言,要防范 SQL 注入,只要用 PreparedStatement(从Statement扩展而来) 取代 Statement 就可以了。
- Statement代码演示
public class StatementTest {
// 使用Statement的弊端:需要拼写sql语句,并且存在SQL注入的问题
@Test
public void testLogin() {
Scanner scan = new Scanner(System.in);
System.out.print("用户名:");
String userName = scan.nextLine();
System.out.print("密 码:");
String password = scan.nextLine();
// SELECT user,password FROM user_table WHERE USER = '1' or ' AND PASSWORD = '='1' or '1' = '1';
String sql = "SELECT user,password FROM user_table WHERE USER = '" + userName + "' AND PASSWORD = '" + password
+ "'";
User user = get(sql, User.class);
if (user != null) {
System.out.println("登陆成功!");
} else {
System.out.println("用户名或密码错误!");
}
}
// 使用Statement实现对数据表的查询操作
public <T> T get(String sql, Class<T> clazz) {
T t = null;
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
// 1.加载配置文件
InputStream is = StatementTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties pros = new Properties();
pros.load(is);
// 2.读取配置信息
String user = pros.getProperty("user");
String password = pros.getProperty("password");
String url = pros.getProperty("url");
String driverClass = pros.getProperty("driverClass");
// 3.加载驱动
Class.forName(driverClass);
// 4.获取连接
conn = DriverManager.getConnection(url, user, password);
st = conn.createStatement();
rs = st.executeQuery(sql);
// 获取结果集的元数据
ResultSetMetaData rsmd = rs.getMetaData();
// 获取结果集的列数
int columnCount = rsmd.getColumnCount();
if (rs.next()) {
t = clazz.newInstance();
for (int i = 0; i < columnCount; i++) {
// //1. 获取列的名称
// String columnName = rsmd.getColumnName(i+1);
// 1. 获取列的别名
String columnName = rsmd.getColumnLabel(i + 1);
// 2. 根据列名获取对应数据表中的数据
Object columnVal = rs.getObject(columnName);
// 3. 将数据表中得到的数据,封装进对象
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);
field.set(t, columnVal);
}
return t;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭资源
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (st != null) {
try {
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
return null;
}
}
PreparedStatement
数据库的连接与关闭
- 每次都要进行数据库的连接与关闭
- 可以将开启关闭操作封装到工具类JDBCUtils
public class JDBCUtils {
public static Connection getConnection() throws Exception{
//通过类加载器 获取 数据库文件中的配置信息
ClassLoader loader = ClassLoader.getSystemClassLoader();
InputStream is = loader.getResourceAsStream("jdbc.properties");
Properties info = new Properties();
info.load(is);
String user = info.getProperty("user");
String password = info.getProperty("password");
String url = info.getProperty("url");
String driver = info.getProperty("driver");
//通过反射加载驱动类
Class.forName(driver);
//连接数据库
Connection con = DriverManager.getConnection(url, user, password);
return con;
}
public static void closeResource(Connection con, Statement ps) {
try {
if (ps != null)
ps.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
try {
if (con != null)
con.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
增/删/改
@Test
public void insertTest(){
Connection conn = null;
PreparedStatement ps = null;
try {
//开启连接
conn = JDBCUtils.getConnection();
//1.SQL预编译、获取PrepareStatement
String sql = "INSERT INTO customers(NAME,email,birth) VALUES(?,?,?);";
ps = conn.prepareStatement(sql);
//2.填充占位符
ps.setString(1,"哪吒");
ps.setString(2,"nezha@163.com");
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
java.util.Date date = sdf.parse("1000-01-01");
ps.setDate(3,new Date(date.getTime()));
//3. 执行sql
ps.*execute*();
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭连接
JDBCUtils.closeResource(conn,ps);
}
}
删除操作
@Test
public void deleteTest(){
Connection conn = null;
PreparedStatement ps = null;
try {
//开启连接
conn = JDBCUtils.getConnection();
//1.SQL预编译、获取PrepareStatement
String sql = "delete from customers where id = ?";
ps = conn.prepareStatement(sql);
//2.填充占位符
ps.setObject(1,18);
//3. 执行sql
ps.execute();
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭连接
JDBCUtils.closeResource(conn,ps);
}
}
修改操作
@Test
public void updateTest(){
Connection conn = null;
PreparedStatement ps = null;
try {
//开启连接
conn = JDBCUtils.getConnection();
//1.SQL预编译、获取PrepareStatement
String sql = "update customers set name = ? where id = ?;";
ps = conn.prepareStatement(sql);
//2.填充占位符
ps.setObject(1,"莫扎特11");
ps.setObject(2,18);
//3. 执行sql
ps.execute();
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭连接
JDBCUtils.closeResource(conn,ps);
}
}
增/删/改:通用
通过上方增删改的例子,可以看出,变化的只有SQL和填充的占位符
所以可以将增删改操作写成一个通用的方法,每次只要传入SQL和可变参数即可
@Test
public void commonUpdate(String sql, Object... args){
Connection conn = null;
PreparedStatement ps = null;
try {
//数据库建立连接
conn = JDBCUtils.getConnection();
//预编译SQL
ps = conn.prepareStatement(sql);
//填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i+1,args[i]);
}
//执行SQL
ps.execute();
} catch (Exception e) {
e.printStackTrace();
}
finally {
//关闭连接
JDBCUtils.closeResource(conn,ps);
}
}
查询
@Test
public void queryTest() throws Exception {
//连接数据库
Connection conn = JDBCUtils.getConnection();
//预编译SQL
String sql = "SELECT id,`NAME`,email,birth FROM customers where id = ?;";
PreparedStatement ps = conn.prepareStatement(sql);
//填充占位符
ps.setObject(1,1);
//执行,获取结果集
ResultSet res = ps.executeQuery();
if(res.next()){ //判断结果集下一条是否有数据,有数据返回Ture,并指针下移,如果返回false,指针不会下移
int id = res.getInt(1);
String name = res.getString(2);
String email = res.getString(3);
Date birth = res.getDate(4);
//打印方式一
System.out.println("id:" + id + " name:" + name + " email:" + email+ " birth:" + birth);
//打印方式二
Object[] data = new Object[]{id,name,email,birth};
System.out.println(Arrays.toString(data));
//打印方式三
Customer customer = new Customer(id, name, email, birth);
System.out.println(customer);
}
//关闭资源
JDBCUtils.closeResource(conn,ps,res);
}
-
查询与增删改的区别,查询返回结果集ResultSet
-
使用next方法检查结果集中是否存在数据
-
有数据则指针下移看,指向数据,无数据则指针不下移
ResultSet.next方法类似iterate.hasNext 与 iterate.next的功能综合版本,但是不会返回数据
-
是通过索引获取当前行的数据
-
推荐使用方式三的方式存储数据:存储到对象之中
查询:通用
针对单表进行查询
//Customer 表进行查询
public Customer CommonqueryTest(String sql,Object... args) throws Exception {
//连接数据库
Connection conn = JDBCUtils.getConnection();
//预编译SQL
PreparedStatement ps = conn.prepareStatement(sql);
//填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i+1,args[i]);
}
//返回结果集
ResultSet res = ps.executeQuery();
//获取结果集的元数据
ResultSetMetaData rsmd = res.getMetaData();
int columnCount = rsmd.getColumnCount();
//判断结果集中是否存在对象,将指针指向数据行
if(res.next())
{
Customer customer = new Customer();
for (int i = 0; i < columnCount; i++) {
//获取列名、值
Object columnValue = res.getObject(i + 1);
String columnName = rsmd.getColumnName(i + 1); //使用getColumnLabel代替
//反射类字段值
Field field = Customer.class.getDeclaredField(columnName);
//访问私有字段
field.setAccessible(true);
//设置值
field.set(customer,columnValue);
}
return customer;
}
return null;
}
针对通用表进行查询
public <T> T CommonqueryTest2(Class<T> clazz, String sql,Object... args) throws Exception {
//连接数据库
Connection conn = JDBCUtils.getConnection();
//预编译
PreparedStatement ps = conn.prepareStatement(sql);
//填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i+1,args[i]);
}
//查询获取结果集
ResultSet res = ps.executeQuery();
//获取结果集的元数据
ResultSetMetaData rsmd = res.getMetaData();
int columnCount = rsmd.getColumnCount();
//获取结果集
if(res.next())
{
T t = clazz.newInstance();
for (int i = 0; i < columnCount; i++) {
//获取列名、值
Object columnValue = res.getObject(i + 1);
String columnName = rsmd.getColumnName(i + 1);
//反射类字段值
Field field = Customer.class.getDeclaredField(columnName);
//访问私有字段
field.setAccessible(true);
//设置值
field.set(t,columnValue);
}
return t;
}
return null;
}
返回查询的集合
public <T> List<T> CommonqueryTest3(Class<T> clazz, String sql, Object... args) throws Exception {
//连接数据库
Connection conn = JDBCUtils.getConnection();
//预编译
PreparedStatement ps = conn.prepareStatement(sql);
//填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i+1,args[i]);
}
//查询获取结果集
ResultSet res = ps.executeQuery();
//获取结果集的元数据
ResultSetMetaData rsmd = res.getMetaData();
int columnCount = rsmd.getColumnCount();
ArrayList<T> list = new ArrayList<>();
//获取结果集
while(res.next())
{
T t = clazz.newInstance();
for (int i = 0; i < columnCount; i++) {
//获取列名、值
Object columnValue = res.getObject(i + 1);
String columnName = rsmd.getColumnName(i + 1);
//反射类字段值
Field field = Customer.class.getDeclaredField(columnName);
//访问私有字段
field.setAccessible(true);
//设置值
field.set(t,columnValue);
}
list.add(t);
}
return list;
}
※ 通常不使用Throws ,而是Try Catch处理异常
总结
数据库字段名要与Java类中属性名一致
- 根据ORM编程思想,数据库一行数据对应一个Java对象
- 数据库一个字段对应Java类的一个属性
- 所以当数据库字段与Java属性名不一致会报错
数据库字段名要与Java类中属性名不一致:则必须在SQL语句中给字段起别名
思维导图
PreparedStatement对比Statement
- 拼串和SQL注入的问题
# 1.Statement中,直接输入SQL语句进行执行,就会出现SQL注入问题
SELECT USER,PASSWORD FROM user_table WHERE USER = '1' OR ' AND PASSWORD = '='1' OR '1' = '1'
# 2.在PreparStatement中则是先进行SQL的预编译, 使用占位符,不用拼串,后续填充占位符,也不会改变SQL原有的逻辑,解决SQL注入
String sql = "SELECT * FROM admin WHERE username = ? AND PASSWORD = ?;";
PreparedStatement ps = conn.prepareStatement(sql);
- Statement无法操作Blob类型的数据
- repaerStatement便于批量操作,
Statement批量操作:执行一次SQL语句,需要校验一次SQL
repaerStatement批量操作:无论执行多少次SQL,都只校验一次,效率更高
- PreparedStatement 能最大可能提高性能:
DBServer会对预编译语句提供性能优化。因为预编译语句有可能被重复调用,所以语句在被DBServer的编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中就会得到执行。
在statement语句中,即使是相同操作但因为数据内容不一样,所以整个语句本身不能匹配,没有缓存语句的意义.事实是没有数据库会对普通语句编译后的执行代码缓存。这样每执行一次都要对传入的语句编译一次。
(语法检查,语义检查,翻译成二进制命令,缓存)
操作Blob
- MySQL中,BLOB是一个二进制大型对象,是一个可以存储大量数据的容器,它能容纳不同大小的数据。
- 插入BLOB类型的数据必须使用PreparedStatement,因为BLOB类型的数据无法使用字符串拼接写的。
- MySQL的四种BLOB类型(除了在存储的最大信息量上不同外,他们是等同的)
-
实际使用中根据需要存入的数据大小定义不同的BLOB类型。
-
需要注意的是:如果存储的文件过大,数据库的性能会下降。
-
如果在指定了相关的Blob类型以后,还报错:xxx too large,那么在mysql的安装目录下,找my.ini文件加上如下的配置参数:
max_allowed_packet=16M。同时注意:修改了my.ini文件之后,需要重新启动mysql服务。
插入
@Test
public void test1() ~~throws Exception~~ { //正式应该使用Try catch进行操作
Connection conn = JDBCUtils.getConnection();
String sql = "INSERT INTO customers(NAME,email,photo) VALUES(?,?,?);";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setObject(1,"Tom");
ps.setObject(2,"Tom@163.com");
InputStream is = new FileInputStream(new File("a1.jpeg"));
ps.setBlob(3,is);
ps.execute();
is.close();
JDBCUtils.closeResource(conn,ps);
}
查询
@Test
public void test1() ~~throws Exception~~ { //正式应该使用Try catch进行操作
Connection conn = JDBCUtils.getConnection();
String sql = "SELECT NAME,email,photo FROM customers WHERE id = ?;";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setObject(1,27);
ResultSet rs = ps.executeQuery();
if(rs.next()){
String name = rs.getString("Name");
String email = rs.getString("email");
System.out.println("Name: " + name + "email: " + email);
//将Blob类型的数据保存到本地
Blob photo = rs.getBlob("photo");
InputStream is = photo.getBinaryStream();
FileOutputStream fis = new FileOutputStream(new File("pic.jpg"));
byte[] buffer = new byte[1024];
int len = 0;
while((len = is.read(buffer)) != -1){
fis.write(buffer,0,len);
}
fis.close();
is.close();
}
JDBCUtils.closeResource(conn,ps);
}
批量操作
方式一
使用Statement进行批量添加
@Test
public void test3() throws Exception{
Connection conn = JDBCUtils.getConnection();
Statement st = conn.createStatement();
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
String sql = "INSERT INTO goods(NAME) VALUES('Jack_"+i+"')";
st.execute(sql);
}
long end = System.currentTimeMillis();
System.out.println(end-start);
JDBCUtils.closeResource(conn,st);
}
------------------
142333
方式二
使用PrepaerStatement进行批量添加
@Test
public void test4() throws Exception{
Connection conn = JDBCUtils.getConnection();
String sql = "INSERT INTO goods(NAME) VALUES(?);";
PreparedStatement ps = conn.prepareStatement(sql);
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
ps.setObject(1,"jack_"+i);
ps.execute();
}
long end = System.currentTimeMillis();
System.out.println(end-start);
JDBCUtils.closeResource(conn,ps);
}
--------------------
142134
方式三
方式一和方式二,每一条SQL都会去执行一次,效率比较慢
方式三通过将需要执行的SQL缓存下来,批量执行,提升效率
- 使用 addBatch() / executeBatch() / clearBatch()
- mysql服务器默认是关闭批处理的,我们需要通过一个参数,让mysql开启批处理的支持。
?rewriteBatchedStatements=true 写在配置文件的url后面
@Test
public void test5() throws Exception{
Connection conn = JDBCUtils.getConnection();
String sql = "INSERT INTO goods(NAME) VALUES(?)";
PreparedStatement ps = conn.prepareStatement(sql);
long start = System.currentTimeMillis();
for (int i = 1; i <= 100000; i++) {
ps.setObject(1,"jack_"+i);
//缓存SQL
ps.addBatch();
if(i%500 == 0){
//将缓存的SQL批量执行
ps.executeBatch();
//清理缓存
ps.clearBatch();
}
}
long end = System.currentTimeMillis();
System.out.println(end-start);
JDBCUtils.closeResource(conn,ps);
}
--------------------
1374
方式二插入10W数据:142134毫秒
方式三插入10W条数据:1377毫秒
方式三插入100W条数据:11602毫秒
方式四
在方式三的基础上再次进行迭代
将自动提交事务关闭,全部SQL执行完毕之后再提交
@Test
public void test6() throws Exception{
Connection conn = JDBCUtils.getConnection();
//关闭事务自动提交
conn.setAutoCommit(false);
String sql = "INSERT INTO goods(NAME) VALUES(?)";
PreparedStatement ps = conn.prepareStatement(sql);
long start = System.currentTimeMillis();
for (int i = 1; i <= 1000000; i++) {
ps.setObject(1,"jack_"+i);
ps.addBatch();
if(i%500 == 0){
ps.executeBatch();
ps.clearBatch();
}
}
//提交事务
conn.commit();
long end = System.currentTimeMillis();
System.out.println(end-start);
JDBCUtils.closeResource(conn,ps);
}
---------------------
//插入100w数据
8730
JDBC事务
- 数据一旦提交,就不可回滚
- 哪些操作会导致数据的自动提交
- DDL操作一旦执行,就会自行提交 【set_autocommit = false 对DDL操作无效】
- DML默认情况下一旦执行,就会自动提交
- 默认再关闭连接时,会自动的提交数据
模拟事务
- 调用 Connection 对象的 setAutoCommit(false); 以取消自动提交事务
- 在所有的 SQL 语句都成功执行后,调用 commit(); 方法提交事务
- 在出现异常时,调用 rollback(); 方法回滚事务
- 若此时 Connection 没有被关闭,还可能被重复使用,则需要恢复其自动提交状态 setAutoCommit(true)。
@Test
public void test3(){
Connection conn = null;
try {
//1. 获取连接
conn = JDBCUtills.getConnection();
//2. 开启事务
conn.setAutoCommit(false);
String sql1 = "UPDATE user_table SET balance = 1100 WHERE USER = ?";
//3. 进行数据库操作
updateWithTx(conn,sql1,"AA");
//模拟网络异常
System.out.println(10/0);
String sql2 = "UPDATE user_table SET balance = 900 WHERE USER = ?";
updateWithTx(conn,sql2,"BB");
//4.提交事务
conn.commit();
} catch (Exception e) {
e.printStackTrace();
try {
//5.异常回滚
conn.rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
finally {
try {
//6.恢复DML自动提交,主要针对数据库连接池
conn.setAutoCommit(true);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
//7.关闭资源
JDBCUtills.closeResource(conn,null);
}
}
//通用更新操作
public void updateWithTx(Connection conn,String sql, Object... args){
PreparedStatement ps = null;
try {
ps = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
ps.setObject(i+1,args[i]);
}
ps.execute();
} catch (Exception e) {
e.printStackTrace();
}
finally {
JDBCUtills.closeResource(null,ps);
}
}
小知识
- Oracle 支持的 2 种事务隔离级别:READ COMMITED, SERIALIZABLE,默认的事务隔离级别为: READ COMMITED
- Mysql 支持 4 种事务隔离级别:Mysql 默认的事务隔离级别为: REPEATABLE READ
代码演示隔离级别问题
展示脏读/不可重复读/幻读 在JDBC中用代码如何重现,详细可以查看MYSQL笔记中事务并发补充章节
脏读
/**
* 重现脏读
* @return
*/
@Test
public void T1(){
Connection con1 = null;
List<user_table> res = null;
try {
con1 = JDBCUtills.getConnection();
//设置隔离级别为read uncommitted
con1.setTransactionIsolation(1);
//开启事务
con1.setAutoCommit(false);
String sql1 = "Select * from user_table";
//读取数据
res = getInstance(con1, user_table.class, sql1);
res.forEach(System.out::println);
//T2修改表后再读取数据
res = getInstance(con1, user_table.class, sql1);
res.forEach(System.out::println);
//T2回滚后再读取数据
res = getInstance(con1, user_table.class, sql1);
res.forEach(System.out::println);
con1.commit();
} catch (Exception e) {
e.printStackTrace();
}
JDBCUtills.closeResource(con1,null);
}
@Test
public void T2(){
Connection con1 = null;
List<user_table> res = null;
try {
con1 = JDBCUtills.getConnection();
con1.setTransactionIsolation(1);
con1.setAutoCommit(false);
String sql1 = "update user_table set balance = 6666 where user = 'AA'";
getUpdate(con1,sql1);
con1.rollback();
} catch (Exception e) {
e.printStackTrace();
}
JDBCUtills.closeResource(con1,null);
}
不可重复读
@Test
public void T1(){
Connection con1 = null;
List<user_table> res = null;
try {
con1 = JDBCUtills.getConnection();
//设置隔离级别为read committed
con1.setTransactionIsolation(2);
//开启事务
con1.setAutoCommit(false);
String sql1 = "Select * from user_table";
//第一次读取数据
res = getInstance(con1, user_table.class, sql1);
res.forEach(System.out::println);
//T2提交后 T1第二次读取数据
res = getInstance(con1, user_table.class, sql1);
res.forEach(System.out::println);
con1.commit();
} catch (Exception e) {
e.printStackTrace();
}
JDBCUtills.closeResource(con1,null);
}
@Test
public void T2(){
Connection con1 = null;
List<user_table> res = null;
try {
con1 = JDBCUtills.getConnection();
con1.setTransactionIsolation(2);
con1.setAutoCommit(false);
String sql1 = "update user_table set balance = 2333 where user = 'AA'";
getUpdate(con1,sql1);
con1.commit();
} catch (Exception e) {
e.printStackTrace();
}
JDBCUtills.closeResource(con1,null);
}
幻读
/**
* 模拟幻读
* @return
*/
@Test
public void T1(){
Connection con1 = null;
List<user_table> res = null;
try {
con1 = JDBCUtills.getConnection();
//设置隔离级别为repeatable read
con1.setTransactionIsolation(4);
//开启事务
con1.setAutoCommit(false);
String sql1 = "Select * from user_table";
//读取数据查看数据
res = getInstance(con1, user_table.class, sql1);
res.forEach(System.out::println);
//等待T2插入数据后,进行更新操作
String sql2 = "update user_table set password = 'ccc'";
//输出受影响行数
int count = getUpdate(con1, sql2);
System.out.println(count);
con1.commit();
} catch (Exception e) {
e.printStackTrace();
}
JDBCUtills.closeResource(con1,null);
}
@Test
public void T2(){
Connection con1 = null;
List<user_table> res = null;
try {
con1 = JDBCUtills.getConnection();
con1.setTransactionIsolation(4);
con1.setAutoCommit(false);
String sql1 = "insert into user_table values('FF','aaa',3000)";
getUpdate(con1,sql1);
con1.commit();
} catch (Exception e) {
e.printStackTrace();
}
JDBCUtills.closeResource(con1,null);
}
Dao相关实现类
- DAO:Data Access Object访问数据信息的类和接口,包括了对数据的CRUD,而不包含任何业务相关的信息。有时也称作:BaseDAO
- 作用:为了实现功能的模块化,更有利于代码的维护和升级
BaseDao的实现
public abstract class BaseDao {
//事务通用查询
public <T> List<T> getInstance(Connection conn, Class<T> clazz, String sql, Object... args) {
List<T> list = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
rs = ps.executeQuery();
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
list = new ArrayList<>();
while (rs.next()) {
T t = clazz.newInstance();
for (int i = 0; i < columnCount; i++) {
String columnLabel = rsmd.getColumnLabel(i + 1);
Object value = rs.getObject(i + 1);
//反射类所有字段,并且赋值
Field field = clazz.getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(t, value);
}
list.add(t);
}
} catch (Exception throwables) {
throwables.printStackTrace();
} finally {
JDBCUtills.closeResource(null, ps, rs);
}
return list;
}
//事务通用更新
public <T> int getUpdate(Connection conn, String sql, Object... args) {
List<T> list = null;
PreparedStatement ps = null;
int row = -1;
try {
ps = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
row = ps.executeUpdate();
} catch (Exception throwables) {
throwables.printStackTrace();
} finally {
JDBCUtills.closeResource(null, ps);
return row;
}
}
public <T> T getValue(Connection conn, String sql, Object... args) {
Object val = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = conn.prepareStatement(sql);
val = null;
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
rs = ps.executeQuery();
if (rs.next()) {
val = rs.getObject(1);
}
} catch (SQLException throwables) {
throwables.printStackTrace();
} finally {
JDBCUtills.closeResource(null, ps, rs);
}
return (T) val;
}
}
Dao接口
public interface CustomerDao {
/**
* 将cust对象添加到数据库之中
* @param conn
* @param cust
*/
void insert(Connection conn, Customer cust);
/**
* 根据id号删除数据库中customer数据
* @param conn
* @param id
*/
void deleteById(Connection conn, int id);
/**
* 根据id号修改数据库中customer数据
* @param conn
* @param cust
* @param id
*/
void updateById(Connection conn,Customer cust, int id);
/**
* 根据id号查詢数据库中customer数据
* @param conn
* @param id
*/
Customer getCustomerById(Connection conn, int id);
/**
* 查询数据库customer表中所有的数据
* @param conn
*/
List<Customer> getAll(Connection conn);
/**
* 查询数据库customer表中所有的行数
* @param conn
*/
long getCount(Connection conn);
/**
* 查询数据库customer表中年龄最大的生日
* @param conn
*/
Date getMaxBirth(Connection conn);
}
Dao接口实现类
public class CustomerDaoImpl extends BaseDao implements CustomerDao{
@Override
public void insert(Connection conn, Customer cust) {
String sql = "insert into customers(name,email,birth) values(?,?,?)";
getUpdate(conn,sql,cust.getName(),cust.getEmial(),cust.getBirth());
}
@Override
public void deleteById(Connection conn, int id) {
String sql = "delete from customers where id = ?";
getUpdate(conn,sql,id);
}
@Override
public void updateById(Connection conn, Customer cust, int id) {
String sql = "update customers set name = ?, email = ?, birth = ? where id = ?";
getUpdate(conn,sql,cust.getName(),cust.getEmial(),cust.getBirth(),id);
}
@Override
public Customer getCustomerById(Connection conn, int id) {
String sql = "select name, email, birth from customers where id = ?";
List<Customer> list = getInstance(conn, Customer.class, sql, id);
return list.size() > 0 ? list.get(0) :null;
}
@Override
public List<Customer> getAll(Connection conn) {
String sql = "select name, email, birth from customers";
List<Customer> list = getInstance(conn, Customer.class, sql);
return list;
}
@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);
}
}
实现类的单元测试
Ctril + Shift + T 创建实现类的单元测试类
public class CustomerDaoImplTest {
CustomerDaoImpl custImpl = new CustomerDaoImpl();
@Test
public void insert() {
Connection conn = null;
try {
conn = JDBCUtills.getConnection();
Customer customer = new Customer("jack","jack@163.com",new Date(543534543534L));
custImpl.insert(conn,customer);
System.out.println("添加成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtills.closeResource(conn,null);
}
}
@Test
public void deleteById() {
Connection conn = null;
try {
conn = JDBCUtills.getConnection();
custImpl.deleteById(conn,19);
System.out.println("删除成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtills.closeResource(conn,null);
}
}
@Test
public void updateById() {
Connection conn = null;
try {
conn = JDBCUtills.getConnection();
Customer customer = new Customer("jack.ma","jack@163.com",new Date(543534543534L));
custImpl.updateById(conn,customer,20);
System.out.println("更新成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtills.closeResource(conn,null);
}
}
@Test
public void getCustomerById() {
Connection conn = null;
try {
conn = JDBCUtills.getConnection();
Customer cust = custImpl.getCustomerById(conn, 20);
System.out.println(cust);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtills.closeResource(conn,null);
}
}
@Test
public void getAll() {
Connection conn = null;
try {
conn = JDBCUtills.getConnection();
List<Customer> list = custImpl.getAll(conn);
list.forEach(System.out::println);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtills.closeResource(conn,null);
}
}
@Test
public void getCount() {
Connection conn = null;
try {
conn = JDBCUtills.getConnection();
long count = custImpl.getCount(conn);
System.out.println(count);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtills.closeResource(conn,null);
}
}
@Test
public void getMaxBirth() {
Connection conn = null;
try {
conn = JDBCUtills.getConnection();
Date birdth = custImpl.getMaxBirth(conn);
System.out.println(birdth);
} catch (Exception e) {
e.printStackTrace();
} finally {
JDBCUtills.closeResource(conn,null);
}
}
}
优化方案
优化前:BaseDao类中,需要传入具体的类型的Class对象
//未优化:需要传入clazz
public <T> List<T> getInstance(Connection conn, Class<T> clazz, String sql, Object... args) {
优化后:BaseDao类中,不需要传入Class对象
- BaseDao层中使用泛型类的方式,进行类型进行标记
- 添加类型属性
- 实例化反射获取泛型类型
- 替代方法调用,类型传入
//添加泛型类
public abstract class BaseDao<T> {
//添加泛型类型变量
private Class<T> clazz = null;
//反射获取泛型类型
public BaseDao(){
Class clazz = this.getClass();
//getGenericSuperclass 获取当前带泛型的父类
Type genericSuperclass = clazz.getGenericSuperclass();
System.out.println(genericSuperclass);
//getActualTypeArguments 获取当前带泛型父类的泛型
ParameterizedType paraType = (ParameterizedType) genericSuperclass;
Type[] arguments = paraType.getActualTypeArguments();
//获取泛型类型
this.clazz = (Class) arguments[0];
}
}
- 使用clazz属性进行实例化
T t = clazz.newInstance();
总结:使用属性Class进行实例化,不再方法调用时进行类型传递
总结
BaseDao:实现对表的通用操作:通用增删改查
Dao接口: 定于对于某个表的的抽象方法
Dao接口实现类:实现接口中的抽象方法
数据库连接池
JDBC数据库连接池的必要性
- 在使用开发基于数据库的web程序时,传统的模式基本是按以下步骤:
- 在主程序(如servlet、beans)中建立数据库连接
- 进行sql操作
- 断开数据库连接
- 这种模式开发,存在的问题:
- 普通的JDBC数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection 加载到内存中,再验证用户名和密码(得花费0.05s~1s的时间)。需要数据库连接的时候,就向数据库要求一个,执行完成后再断开连接。这样的方式将会消耗大量的资源和时间。数据库的连接资源并没有得到很好的重复利用。若同时有几百人甚至几千人在线,频繁的进行数据库连接操作将占用很多的系统资源,严重的甚至会造成服务器的崩溃。
- 对于每一次数据库连接,使用完后都得断开。否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将导致重启数据库。(回忆:何为Java的内存泄漏?)
- 这种开发不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去,如连接过多,也可能导致内存泄漏,服务器崩溃。
数据库连接池技术
- 为解决传统开发中的数据库连接问题,可以采用数据库连接池技术。
- 数据库连接池的基本思想:就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。
- 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。
- 数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数来设定的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。
- 工作原理
数据库连接池技术的优点
1. 资源重用
由于数据库连接得以重用,避免了频繁创建,释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增加了系统运行环境的平稳性。
2. 更快的系统反应速度
数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于连接池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而减少了系统的响应时间
3. 新的资源分配手段
对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接池的配置,实现某一应用最大可用数据库连接数的限制,避免某一应用独占所有的数据库资源
4. 统一的连接管理,避免数据库连接泄漏
在较为完善的数据库连接池实现中,可根据预先的占用超时设定,强制回收被占用连接,从而避免了常规数据库连接操作中可能出现的资源泄露
多种开源的数据库连接池
- JDBC 的数据库连接池使用 javax.sql.DataSource 来表示,DataSource 只是一个接口,该接口通常由服务器(Weblogic, WebSphere, Tomcat)提供实现,也有一些开源组织提供实现:
- DBCP 是Apache提供的数据库连接池。tomcat 服务器自带dbcp数据库连接池。速度相对c3p0较快,但因自身存在BUG,Hibernate3已不再提供支持。
- C3P0 是一个开源组织提供的一个数据库连接池,速度相对较慢,稳定性还可以。hibernate官方推荐使用
- Proxool 是sourceforge下的一个开源项目数据库连接池,有监控连接池状态的功能,稳定性较c3p0差一点
- BoneCP 是一个开源组织提供的数据库连接池,速度快
- Druid 是阿里提供的数据库连接池,据说是集DBCP 、C3P0 、Proxool 优点于一身的数据库连接池,但是速度不确定是否有BoneCP快
- DataSource 通常被称为数据源,它包含连接池和连接池管理两个部分,习惯上也经常把 DataSource 称为连接池
- DataSource用来取代DriverManager来获取Connection,获取速度快,同时可以大幅度提高数据库访问速度。
- 特别注意:
- 数据源和数据库连接不同,数据源无需创建多个,它是产生数据库连接的工厂,因此整个应用只需要一个数据源即可。
- 当数据库访问结束后,程序还是像以前一样关闭数据库连接:conn.close(); 但conn.close()并没有关闭数据库的物理连接,它仅仅把数据库连接释放,归还给了数据库连接池。
c3p0数据库连接池
连接方式1:不推荐
- 加载启动,导入c3p0的jar包
- c3p0连接数据库
@Test
public void test1() throws Exception {
ComboPooledDataSource cpds = new ComboPooledDataSource();
cpds.setDriverClass( "com.mysql.cj.jdbc.Driver" ); //loads the jdbc driver
cpds.setJdbcUrl( "jdbc:mysql://localhost:3306/test" );
cpds.setUser("root");
cpds.setPassword("123");
//设置数据库连接池
//设置初始化数据库连接池的连接数
cpds.setInitialPoolSize(10);
Connection conn = cpds.getConnection();
System.out.println(conn);
//销毁c3p0连接池,一般不销毁
//DataSources.destroy( cpds );
}
连接方式2:推荐 使用xml配置文件连接
c3p0-config.xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<c3p0-config>
<!-- This app is massive! -->
<named-config name="helloC3po">
<property name="user">root</property>
<property name="password">123</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/test</property>
<property name="driverClass">com.mysql.cj.jdbc.Driver</property>
<!-- 涉及到数据库连接池的管理的相关属性的设置 -->
<!-- 若数据库中连接数不足时, 一次向数据库服务器申请多少个连接 -->
<property name="acquireIncrement">5</property>
<!-- 初始化数据库连接池时连接的数量 -->
<property name="initialPoolSize">5</property>
<!-- 数据库连接池中的最小的数据库连接数 -->
<property name="minPoolSize">5</property>
<!-- 数据库连接池中的最大的数据库连接数 -->
<property name="maxPoolSize">10</property>
<!-- C3P0 数据库连接池可以维护的 Statement 的个数 -->
<property name="maxStatements">20</property>
<!-- 每个连接同时可以使用的 Statement 对象的个数 -->
<property name="maxStatementsPerConnection">5</property>
</named-config>
</c3p0-config>
//数据库连接池-饿汉式
private static ComboPooledDataSource cpds = new ComboPooledDataSource("helloC3po");
//返回连接
public static Connection getConnection() throws SQLException {
Connection conn = cpds.getConnection();
return conn;
}
dbcp数据库连接池
- 加载驱动,导入dbcp的jar包
- dbcp依赖pool,需要导入pool的jar包
方式一:硬编码,不推荐
@Test
public void test1() throws SQLException {
//创建dbcp的数据库连接池
BasicDataSource source = new BasicDataSource();
source.setDriverClassName("com.mysql.cj.jdbc.Driver");
source.setUsername("root");
source.setPassword("123");
source.setUrl("jdbc:mysql://localhost:3306/test");
//设置数据库连接池
source.setInitialSize(10);
source.setMaxActive(10);
Connection conn = source.getConnection();
System.out.println(conn);
}
方式二:配置文件,推荐
@Test
public void test2() throws Exception {
//读取properties配置文件
InputStream is = ClassLoader.getSystemResourceAsStream("dbcp2.properties");
Properties info = new Properties();
info.load(is);
//创建dbcp的数据库连接池
DataSource source = BasicDataSourceFactory.createDataSource(info);
Connection conn = source.getConnection();
System.out.println(conn);
}
⭐druid数据库连接池
- 加载驱动,导入druid的jar包
- 方式一:不推荐,硬编码
@Test
public void getConnection() throws Exception {
DruidDataSource source = new DruidDataSource();
//硬编码加载
source.setUsername("root");
source.setPassword("123");
source.setUrl("jdbc:mysql://localhost:3306/test");
source.setDriverClassName("com.mysql.cj.jdbc.Driver");
//多态调用连接方法
DataSource s1 = source;
Connection conn = s1.getConnection();
System.out.println(conn);
}
- 方式二:推荐,配置文件
@Test
public void getConnection() throws Exception {
Properties info = new Properties();
//类加载器
InputStream is = ClassLoader.getSystemResourceAsStream("jdbc.properties");
info.load(is);
DataSource source = DruidDataSourceFactory.createDataSource(info);
Connection conn = source.getConnection();
System.out.println(conn);
}
总结
- 相比c3p0、dbcp数据库连接池,更加推荐druid
- 获取数据库连接池的方法时,推荐使用单例模式(饱汉式/饿汉式)
Apache-DBUtils
使用PreparedStatement对CURD进行封装,底层一致,对比自己写的健壮性更好一点
ResultSetHandler接口及实现类
- 该接口用于处理 java.sql.ResultSet,将数据按要求转换为另一种形式。
- ResultSetHandler 接口提供了一个单独的方法:Object handle (java.sql.ResultSet .rs)。
- 接口的主要实现类:
- ArrayHandler:把结果集中的第一行数据转成对象数组。
- ArrayListHandler:把结果集中的每一行数据都转成一个数组,再存放到List中。
- BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中。
- BeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。
- ColumnListHandler:将结果集中某一列的数据存放到List中。
- KeyedHandler(name):将结果集中的每一行数据都封装到一个Map里,再把这些map再存到一个map里,其key为指定的key。
- MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。
- MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List
- ScalarHandler:查询单个值对象
更新
@Test
public void addTest() throws Exception {
//Druid连接池
Connection conn = source.getConnection();
//DBUtills
QueryRunner runner = new QueryRunner();
String sql = "insert into user_table values(?,?,?)";
int res = runner.update(conn, sql, "jack1", "123", 2000);
System.out.println("insert successed");
}
查询
查询单条记录
/*
* 测试查询:查询一条记录
*
* 使用ResultSetHandler的实现类:BeanHandler
*/
@Test
public void querySingleTest() throws Exception {
//Druid连接池
Connection conn = source.getConnection();
//DBUtills
QueryRunner runner = new QueryRunner();
//查询单条结果
String sql = "SELECT * FROM customers WHERE id = ?";
BeanHandler<Customer> handler = new BeanHandler(Customer.class);
Customer customer = runner.query(conn, sql, handler, 1);
System.out.println(customer);
//释放连接到资源池
JDBCUtills.closeResource(conn,null);
}
查询多条记录
/*
* 测试查询:查询多条记录构成的集合
*
* 使用ResultSetHandler的实现类:BeanListHandler
*/
@Test
public void queryMultipleTest() throws Exception {
//Druid连接池
Connection conn = source.getConnection();
//DBUtills
QueryRunner runner = new QueryRunner();
//查询多条结果集
String sql2 = "SELECT * FROM customers";
BeanListHandler<Customer> listHandler = new BeanListHandler(Customer.class);
List<Customer> list = runner.query(conn, sql2, listHandler);
list.forEach(System.out::println);
//释放连接到资源池
JDBCUtills.closeResource(conn,null);
}
查询单个值
/*
* 测试查询:查询单个值
*
* 使用ResultSetHandler的实现类:ScalarHandler
*/
@Test
public void queryValueTest() throws Exception {
//Druid连接池
Connection conn = source.getConnection();
//DBUtills
QueryRunner runner = new QueryRunner();
//查询单条结果
String sql = "SELECT Count(*) FROM customers";
ScalarHandler handler = new ScalarHandler();
long o = (long) runner.query(conn, sql, handler);
System.out.println(o);
//释放连接到资源池
JDBCUtills.closeResource(conn,null);
}
自定义Handler
@Test
//自定义resultHandler
public void test1() throws SQLException {
//Druid连接池
Connection conn = source.getConnection();
//DBUtills
QueryRunner runner = new QueryRunner();
//查询单条结果
String sql = "SELECT * FROM customers where id = 1";
ResultSetHandler<Customer> selfDefineHandler = new ResultSetHandler<Customer>() {
@Override
public Customer handle(ResultSet resultSet) throws SQLException {
ResultSetMetaData rsmd = resultSet.getMetaData();
int columnCount = rsmd.getColumnCount();
if(resultSet.next()){
Customer cust = new Customer();
for (int i = 0; i < columnCount; i++) {
String columnLabel = rsmd.getColumnLabel(i + 1);
Object value = resultSet.getObject(i + 1);
try {
Field field = Customer.class.getDeclaredField(columnLabel);
field.setAccessible(true);
field.set(cust,value);
catch (Exception e) {
e.printStackTrace();
}
finally {
if(resultSet!=null)
resultSet.close();
}
}
return cust;
}
return null;
}
};
Customer query = runner.query(conn, sql, selfDefineHandler);
System.out.println(query);
//释放连接到资源池
JDBCUtills.closeResource(conn,null);
}
资源关闭
public void test1(Connection con, Statement st, ResultSet rs){
//方式1
try {
DbUtils.close(con);
DbUtils.close(st);
DbUtils.close(rs);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
//方式2
DbUtils.closeQuietly(con);
DbUtils.closeQuietly(st);
DbUtils.closeQuietly(rs);
}
⭐对于JDBC核心技术的理解与总结
经过系统的学习JDBC核心技术,以下为个人总结的JDBC核心知识点,总体概括。
概念理解
- 公共接口:JDBC就是SUM公司定义的一系列接口和标准,通用的SQL数据库存取和操作接口
- 驱动文件:如果想要融入Java的开发环境,那么各大数据库厂商(Mysql,Oracle,Sqlserver等)就需要实现公共接口,不同数据库厂商对接口的实现类,生成了各自的Jar包文件也被称为驱动文件。
环境准备
- 加载驱动:将数据库厂商提供的驱动文件,加载到项目之中,而从能够调用公共接口的实现方法
- 获取连接:五种数据库连接方式,都可以对数据库进行连接,不断升级,提升了连接的效率,更加便捷。
- JDBCUtils:可以将数据库的连接与关闭单独封装到一个工具类之中,方便调用
CURD
- Statement:Sum公司提供的执行SQL语句的类,缺点是容易注入,拼串,效率低,无法操作Blob数据,无法批量执行SQL
- PrepareStatement:替代Statement的类,解决以上容易出现的各种缺陷,并且效率更高,特别是批量执行方面
事务
- 调用 Connection 对象的 setAutoCommit(false); 以取消自动提交事务
- 在所有的 SQL 语句都成功执行后,调用 commit(); 方法提交事务
- 在出现异常时,调用 rollback(); 方法回滚事务
- 若此时 Connection 没有被关闭,还可能被重复使用,则需要恢复其自动提交状态 setAutoCommit(true)。
#1关闭自动提交事务
SET autocommit = 0;
#2开始事务
START TRANSACTION;
#3 编写一组事务的语句【select、insert、update、delete】
语句1
语句2
...
语句3
#4结束事务
commit; #提交
rollback; #回滚
Dao
对数据库的CURD,不包含具体的业务信息,优点是实现了功能的模块化,有利于代码维护和升级
- Dao层:对数据库通用的基础操作
- 表接口:定义某张表具有哪些数据库的操作,指定标准和规范
- 表接口实现类:对表接口进行功能的实现,调用Dao层的通用方法进行实现
- Bean层:方便进行数据库的存储,根据ORM映射思想,将数据库中表的结构,定义为Bean文件类
数据库连接池
每次数据库请求连接,关闭连接都很耗费时间,所以出现了数据库连接池技术,一次性开辟指定连接,每次请求都从连接池中获取,释放连接,实际上是释放到数据库连接池,而不是将连接关闭,实现对数据库连接的管理以及提升运行效率。
Apache-DBUtils
使用PreparedStatement对CURD进行封装,底层一致,对比自己写的健壮性更好一点
由于对于数据的增删改查操作非常频繁,所以官方将这些比较通用的方法封装成了一个工具类,供我们使用,底层方法其实和例子中的是一致的,健壮性更好些而已。