JDBC入门
概念
Java DataBase Connectivity Java 数据库连接, Java语言操作数据库
本质
它是官方(sun公司)定义的一套操作所有关系型数据库的规则,即接口。各个数据库厂商去实现这套接口,提供数据库驱动jar包。
我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类。
快速入门
步骤
- 导入驱动jar包
- 复制mysql-connector-java-5.1.37-bin.jar到项目的lib目录下
- 2.右键–>Add As Library
- 注册驱动
- 获取数据库连接对象 Connection
- 定义sql
- 获取执行sql语句的对象 Statement
- 执行sql,接受返回结果
- 处理结果
- 释放资源
对象功能详解
- DriverManager:驱动管理对象
- Connection:数据库连接对象
- Statement:执行sql的对象
- ResultSet:结果集对象
- PreparedStatement:执行sql的对象
DriverManager
驱动管理对象,主要功能如下:
- 注册驱动
- 获取数据库连接对象
1. 注册驱动
- 告诉程序该使用哪一个数据库驱动(填写jar包中Driver类的路径)
// 注册与给定的驱动程序 DriverManager
static void registerDriver(Driver driver) :
通过查看源码发现:在com.mysql.jdbc.Driver
类中存在静态代码块
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
注意:mysql5之后的驱动jar包可以省略注册驱动的步骤(jar包中有文件java.sql.Driver)。
写代码时使用此方法:
Class.forName("com.mysql.jdbc.Driver");
2. 获取数据库连接对象
方法:
static Connection getConnection(String url, String user, String password)
参数:
-
user:用户名
-
password:密码
-
url:指定连接的路径
jdbc:mysql://ip地址(域名):端口号/数据库名称
例如:
jdbc:mysql://localhost:3306/db3
细节:
- 如果连接的是本机mysql服务器,并且mysql服务默认端口是3306,则url可以简写为:
jdbc:mysql:///数据库名称
Connection
数据库连接对象,主要功能如下:
- 获取执行sql 的对象
- 管理事务
1. 获取执行sql的对象
// 两种
Statement createStatement()
PreparedStatement prepareStatement(String sql)
2. 管理事务
- 开启事务:
调用该方法设置参数为false,即开启事务
setAutoCommit(boolean autoCommit) :
- 提交事务:
commit()
- 回滚事务:
rollback()
Statement
执行sql的对象,用于执行静态SQL语句并返回器生成的结果的对象。
- 可以执行任意的sql
boolean execute(String sql)
- 执行DQL(select)语句
// 返回的是结果集对象
ResultSet executeQuery(String sql)
- 执行DML(insert、update、delete)语句、DDL(create,alter、drop)语句
int executeUpdate(String sql)
返回的是影响的行数,判断DML语句是否执行成功:返回值大于零。DDL:只返回0。
功能演示
- 对account表进行添加一条记录
package cn.luis.demo1.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
/**
* @ClassName JDBCDemo2
* @Description 对表添加一条记录
* @Author L
* @Date 2020.01.17 21:31
* @Version 1.0
* @Remark account表 添加一条记录 insert 语句
**/
public class JDBCDemo2 {
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
try {
// 1.注册驱动(填写jar包中Driver类的路径)
Class.forName("com.mysql.jdbc.Driver");
// 2.定义sql
String sql = "insert into account values(null,'王五',3000)";
// 3.获取Connection对象
conn = DriverManager.getConnection("jdbc:mysql:///db3","root","root");
// 4.获取执行sql的对象,Statement
stmt = conn.createStatement();
// 5.执行sql
int count = stmt.executeUpdate(sql); // 影响的行数
// 6.处理结果
System.out.println("影响的行数:" + count);
if(count > 0) {
System.out.println("添加成功!");
} else {
System.out.println("添加失败!");
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 7.释放资源
// stmt.close();
// 避免空指针异常(上面4.语句执行出错就会发生空指针异常)
if (stmt != null) { // 若为空,代表并未申请资源,也就不用释放了
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
// 避免空指针异常(上面2.语句执行出错就会发生空指针异常)
if (conn != null) { // 若为空,代表并未申请资源,也就不用释放了
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
结果:
影响的行数:1
添加成功!
- 创建一张Student表
package cn.luis.demo1.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
/**
* @Description 执行DDL语句
* @Author L
* @Date 2020.01.17 22:11
* @Version 1.0
* @Remark 创建一张表
**/
public class JDBCDemo5 {
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
try {
// 1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2.获取数据库连接对象
conn = DriverManager.getConnection("jdbc:mysql:///db3","root","root");
// 3.定义sql语句
String sql = "create table student (id int,name varchar(20))";
// 4.获取执行sql对象
stmt = conn.createStatement();
// 5.执行sql(影响的行数)
int count = stmt.executeUpdate(sql);
// 6.处理结果
System.out.println(count); // 0 没有返回值
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
} finally {
// 7.释放资源
/*
若程序还未执行到Stetement就抛出异常,此时stmt为null,
尚未申请资源,也就不用释放了,调用close()方法就会报错!
*/
if(stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
结果:
0
释放资源时需注意
若程序还未执行到Stetement就抛出异常,此时stmt为null,尚未申请资源,也就不用释放了,调用close()方法就会报错!【Connection类似】
if (stmt != null) {
...
}
ResultSet
结果集对象,用于封装查询结果
获取封装数据的两个步骤
-
游标向下移动一行
返回值:布尔值,判断当前行是否是最后一行末尾(是否有数据)。
如果是,则返回false,如果不是则返回true
boolean next():
-
获取数据
Xxx:代表数据类型 如: getInt() 、getString()
-
参数
- int:代表列的编号,从1开始 如: getString(1)
- String:代表列名称。 如: getDouble(“表的列名”)
getXxx(参数):
使用步骤
- 游标向下移动一行
- 判断该行是否有数据
- 获取数据
- 代码演示:
/*
6.处理结果,省略其他部分代码
*/
// 6.1让光标向下移动一行,循环判断游标是否到达最后一行末尾,并判断是否有数据
while(rs.next()) {
// 6.2获取数据
int id = rs.getInt(1);
String name = rs.getString("name");
double balance = rs.getDouble("balance");
System.out.println(id + "---" + name + "---" + balance);
}
// 释放rs资源
案例
定义一个方法,查询emp表的数据将其封装为对象,然后装载集合,打印。
- 定义
Emp
类 - 定义JDBC类
- 定义方法
public List<Emp> findAll(){}
- 注册驱动
- 获取连接
- 定义SQL语句
- 获取执行SQL对象
- 遍历结果集,创建EMP对象,封装数据
- 装载List集合
- 定义方法
代码演示:
- Emp实体类:略
- JDBC类
package cn.luis.demo1.jdbc;
import cn.luis.demo2.domain.Emp;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
/**
* @ClassName JDBCDemo9
* @Description 定义一个方法
* @Author L
* @Date 2020.01.18 10:34
* @Version 1.0
* @Remark 查询emp表中数据将其封装为对象,然后装载集合,返回。
**/
public class JDBCDemo9 {
public static void main(String[] args) {
List<Emp> list = new JDBCDemo9().findALL();
System.out.println(list.size());
for (Emp emp : list) {
System.out.println(emp);
}
}
/**
* @Author Lius
* @Description 查询所有emp对象
* @Date 10:36 2020.01.18
* @Param []
* @Return java.util.List<cn.luis.demo2.domain.Emp>
**/
public List<Emp> findALL() {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
List<Emp> list = null;
try {
// 1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
// 2.获取连接
conn = DriverManager.getConnection("jdbc:mysql:///db2", "root", "root");
// 3.定义sql
String sql = "select * from emp";
// 4.获取执行sql对象
stmt = conn.createStatement();
// 5.执行sql语句
rs = stmt.executeQuery(sql);
// 6.遍历结果集,封装对象,装载集合
// 创建emp对象
Emp emp = null;
list = new ArrayList<>();
while (rs.next()) {
// 6.处理结果("id"):要和数据库中的名称相同!!!
int id = rs.getInt("id");
String ename = rs.getString("ename");
int job_id = rs.getInt("job_id");
int mgr = rs.getInt("mgr");
Date joindate = rs.getDate("joindate");
double salary = rs.getDouble("salary");
double bonus = rs.getDouble("bonus");
int dept_id = rs.getInt("dept_id");
// 创建emp对象
emp= new Emp();
emp.setId(id);
emp.setEname(ename);
emp.setJob_id(job_id);
emp.setMgr(mgr);
emp.setJoindate(joindate);
emp.setSalary(salary);
emp.setBonus(bonus);
emp.setDept_id(dept_id);
// 装载集合
list.add(emp);
}
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
} finally {
if(rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
return list;
}
}·
结果:
15
Emp{id=1001, ename='孙悟空', ... salary=10000.0, bonus=0.0, dept_id=20}
Emp{id=1002, ename='卢俊义', ... salary=16000.0, bonus=3000.0, dept_id=30}
...
PreparedStatement
执行sql的对象,解决sql注入问题
SQL注入问题
在拼接sql时,有一些sql的特殊关键字参与字符串的拼接。会造成安全性问题 【静态sql易发生】
例如
-
输入用户随便,输入密码:a’ or ‘a’ = 'a
此时
sql
语句永远成立,会显示所有数据
select * from user where username = 'fhdsjkf' and password = 'a' or 'a' = 'a'
解决sql注入问题
使用PreparedStatement对象来解决
- 预编译的SQL:参数使用
?
作为占位符
select * from user where username = ? and password = ?;
步骤
- 导入驱动jar包
mysql-connector-java-5.1.37-bin.jar
- 注册驱动
- 获取数据库连接对象
Connection
- 定义
sql
select * from user where username = ? and password = ?;
- 获取执行sql语句的对象
PreparedStatement
PreparedStatement pstmt = conn.prepareStatement(sql);
-
给
?
赋值:方法:setXxx(参数1,参数2)
参数1:
?
的位置编号,从1 开始;参数2:
?
的值。
pstmt.setString(1,username);
pstmt.setString(2,password);
-
执行sql,接收返回结果
不需要传递sql语句,在获取
PreparedStatement
已传入
pstmt.executeQuery();
-
处理结果
-
释放资源
注意
后期都会使用PreparedStatement来完成增删改查的所有操作
- 可以防止SQL注入
- 效率更高
代码实现
package cn.luis.demo1.jdbc;
import cn.luis.demo2.domain.Emp;
import cn.luis.demo3.util.JDBCUtils;
import java.sql.*;
import java.util.List;
import java.util.Scanner;
/**
* @Description 定义一个方法
* @Author L
* @Date 2020.01.18 10:34
* @Version 1.0
* @Remark
**/
public class JDBCDemo12 {
public static void main(String[] args) {
// 1.键盘录入
Scanner sc = new Scanner(System.in);
System.out.print("输入用户名:");
String username = sc.next();
System.out.print("输入密码:");
String password = sc.next();
// 2.调用方法
boolean flag = new JDBCDemo12().login(username, password);
// 3.判断结果
if(flag) {
System.out.println("登陆成功!");
} else {
System.out.println("用户名或密码错误!");
}
}
/**
* @Author Lius
* @Description 练习 登录方法,使用PreparedStatement实现
* @Date 10:36 2020.01.18
* @Param []
* @Return java.util.List<cn.luis.demo2.domain.Emp>
**/
public boolean login(String username, String password) {
if(username == null || password == null) {
return false;
}
// 连接数据库
// 1.获取连接
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
// 1.获取连接
conn = JDBCUtils.getConnection();
// 2.定义sql
String sql = "select * from user where username = ? and password = ?";
// 3.获取执行sql对象
pstmt = conn.prepareStatement(sql);
// 4.给 ? 赋值
pstmt.setString(1,username);
pstmt.setString(2,password);
// 5.执行sql语句
rs = pstmt.executeQuery();
// 6.判断
return rs.next(); // 如果有下一行,则返回true
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils.close(rs,pstmt,conn);
}
return false;
}
}
结果:
输入用户名:zhangsan
输入密码:123
登陆成功!
JDBC工具类
简化书写:注册驱动、获取连接对象、释放资源等等。
需要抽取的代码
- 加载sql配置文件、注册驱动:静态代码块(因为只需加载一次即可)
- 获取连接对象:抽取为一个方法
- 释放资源:抽取为一个方法
需求
- 在获取连接对象时,不想传递参数(麻烦),还得保证工具类的通用性。
conn = DriverManager.getConnection("jdbc:mysql:///db2", "root", "root");
思路
使用配置文件
jdbc.properties
url=
user=
password=
driver=
代码实现
package cn.luis.demo3.util;
import jdk.jfr.Description;
import java.io.FileReader;
import java.io.IOException;
import java.net.URL;
import java.sql.*;
import java.util.Properties;
/**
* @ClassName JDBCUtils
* @Description JDBC工具类
* @Author L
* @Date 2020.01.18 11:45
* @Version 1.0
* @Remark 工具类一般都是静态的,方便调用
**/
public class JDBCUtils {
private static String url;
private static String user;
private static String password;
private static String driver;
/**
* @Author Lius
* @Description 静态代码块
* @Date 12:22 2020.01.18
* @Param
* @Return
* @Tip 只需读取一次即可拿到这些值,
**/
static {
// 读取资源文件,获取值
try {
// 1.创建Properties集合类
Properties pro = new Properties();
// 2.加载文件
// 2.1(动态)获取src路径下的文件的方式:Classloader 类加载器
ClassLoader classLoader = JDBCUtils.class.getClassLoader();
// 资源定位
URL resource = classLoader.getResource("jdbc.properties");
// 绝对路径
String path = resource.getPath();
// System.out.println(path);
// 2.2
pro.load(new FileReader(path));
// 3.获取属性,赋值
url = pro.getProperty("url");
user = pro.getProperty("user");
password = pro.getProperty("password");
driver = pro.getProperty("driver");
// 4.注册驱动
Class.forName(driver);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* @Author Lius
* @Description 获取连接
* @Date 11:47 2020.01.18
* @Param []
* @Return java.sql.Connection 返回连接对象
**/
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url,user,password);
}
/**
* @Author Lius
* @Description 释放资源(增删改)
* @Date 11:51 2020.01.18
* @Param [stmt, conn]
* @Return void
**/
public static void close(Statement stmt, Connection conn) {
if(stmt != null) {
// 注意:stmt、和conn最好不要写在一个try里面关闭,若stmt报错,conn无法关闭!
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/**
* @Author Lius
* @Description 释放资源(查询时)
* @Date 11:51 2020.01.18
* @Param [stmt, conn]
* @Return void
**/
public static void close(ResultSet rs, Statement stmt, Connection conn) {
if(rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
演示JDBC工具类:
package cn.luis.demo1.jdbc;
import cn.luis.demo2.domain.Emp;
import cn.luis.demo3.util.JDBCUtils;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
/**
* @Description 定义一个方法
* @Author L
* @Date 2020.01.18 10:34
* @Version 1.0
* @Remark
**/
public class JDBCDemo10 {
public static void main(String[] args) {
List<Emp> list = new JDBCDemo10().findALL();
System.out.println(list.size());
for (Emp emp : list) {
System.out.println(emp);
}
}
/**
* @Author Lius
* @Description 演示JDBC工具类 数据库是:db2
* @Date 10:36 2020.01.18
* @Param []
* @Return java.util.List<cn.luis.demo2.domain.Emp>
**/
public List<Emp> findALL() {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
List<Emp> list = null;
try {
// 1.注册驱动
// 2.获取连接【使用JDBCUtil工具类】
conn = JDBCUtils.getConnection();
// 3.定义sql
String sql = "select * from emp";
// 4.获取执行sql对象
stmt = conn.createStatement();
// 5.执行sql语句
rs = stmt.executeQuery(sql);
// 6.遍历结果集,封装对象,装载集合
// 创建emp对象
Emp emp = null;
list = new ArrayList<>();
while (rs.next()) {
// 6.处理结果("id"):要和数据库中的名称相同!!!
int id = rs.getInt("id");
String ename = rs.getString("ename");
int job_id = rs.getInt("job_id");
int mgr = rs.getInt("mgr");
Date joindate = rs.getDate("joindate");
double salary = rs.getDouble("salary");
double bonus = rs.getDouble("bonus");
int dept_id = rs.getInt("dept_id");
// 创建emp对象
emp= new Emp();
emp.setId(id);
emp.setEname(ename);
emp.setJpb_id(job_id);
emp.setMgr(mgr);
emp.setJoindate(joindate);
emp.setSalary(salary);
emp.setBonus(bonus);
emp.setDept_id(dept_id);
// 装载集合
list.add(emp);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils.close(rs,stmt,conn);
}
return list;
}
}
结果:
14
Emp{id=1001, ename='孙悟空',... salary=8000.0, bonus=0.0, dept_id=20}
...
Emp{id=1014, ename='关羽',...salary=13000.0, bonus=0.0, dept_id=10}
案例:使用JDBCUtil工具类模拟登陆功能
需求
-
通过键盘录入用户名和密码
-
判断用户是否登录成功
如果这个sql有查询结果,则成功,反之,则失败
select * from user where username = "" and password = "";
- 创建数据库表 user
CREATE TABLE USER(
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(32),
PASSWORD VARCHAR(32)
);
INSERT INTO USER VALUES(NULL,'zhangsan','123');
INSERT INTO USER VALUES(NULL,'lisi','234');
代码实现
package cn.luis.demo1.jdbc;
import cn.luis.demo2.domain.Emp;
import cn.luis.demo3.util.JDBCUtils;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
/**
* @Description 定义一个方法
* @Author L
* @Date 2020.01.18 10:34
* @Version 1.0
* @Remark
**/
public class JDBCDemo11 {
public static void main(String[] args) {
// 1.键盘录入
Scanner sc = new Scanner(System.in);
System.out.print("输入用户名:");
String username = sc.next();
System.out.print("输入密码:");
String password = sc.next();
// 2.调用方法
boolean flag = new JDBCDemo11().login(username, password);
// 3.判断结果
if(flag) {
System.out.println("登陆成功!");
} else {
System.out.println("用户名或密码错误!");
}
}
/**
* @Author Lius
* @Description 练习,登录方法
* @Date 10:36 2020.01.18
* @Param []
* @Return java.util.List<cn.luis.demo2.domain.Emp>
**/
public boolean login(String username, String password) {
if(username == null || password == null) {
return false;
}
// 连接数据库
// 1.获取连接
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
// 1.获取连接
conn = JDBCUtils.getConnection();
// 2.定义sql
String sql = "select * from user where username = '"+ username +"' and password = '"+ password +"'";
// 3.获取执行sql对象
stmt = conn.createStatement();
// 4.执行sql语句
rs = stmt.executeQuery(sql);
// 5.判断
/*if(rs.next()) { // 如果有下一行,则返回true
return true;
} else {
return false;
}*/
return rs.next(); // 如果有下一行,则返回true
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils.close(rs,stmt,conn);
}
return false;
}
}
结果:
输入用户名:zhangsan
输入密码:123
Sat Jan 18 13:35:28 CST 2020 WARN: ...
登陆成功!
事务
概念:一个包含多个步骤的业务操作。如果这个业务操作被事务管理,则这多个步骤要么同时成功,要么同时失败。
操作
- 开启事务
- 提交事务
- 回滚事务
使用Connection对象来管理事务
开启事务:在执行sql前开启事务
- 调用该方法设置参数为false,即开启事务,默认是关闭的。
setAutoCommit(boolean autoCommit);
代码实现:
//开启事务
conn.setAutoCommit(false);
提交事务:在所有sql都执行完提交事务
commit()
代码实现:
//提交事务
conn.commit();
回滚事务:在catch中回滚事务
conn.rollback()
代码实现:
if (conn != null) { // 避免conn空指针异常
try {
conn.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
流程演示
package cn.luis.demo4.Commit;
import cn.luis.demo3.util.JDBCUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Scanner;
/**
* @ClassName JDBCDemo1
* @Description 定义一个方法
* @Author L
* @Date 2020.01.18 10:34
* @Version 1.0
* @Remark 事务操作
**/
public class JDBCDemo1 {
public static void main(String[] args) {
new JDBCDemo1().update();
}
/**
* @Author Lius
* @Description 练习 登录方法,事务操作 数据库是:db3 account
* @Date 10:36 2020.01.18
* @Param []
* @Return java.util.List<cn.luis.demo2.domain.Emp>
**/
public void update() {
Connection conn = null;
PreparedStatement pstmt1 = null;
PreparedStatement pstmt2 = null;
try {
//1.获取连接
conn = JDBCUtils.getConnection();
//开启事务
conn.setAutoCommit(false);
//2.定义sql
//2.1 张三 - 500
String sql1 = "update account set balance = balance - ? where id = ?";
//2.2 李四 + 500
String sql2 = "update account set balance = balance + ? where id = ?";
//3.获取执行sql对象
pstmt1 = conn.prepareStatement(sql1);
pstmt2 = conn.prepareStatement(sql2);
//4. 设置参数
pstmt1.setDouble(1, 500);
pstmt1.setInt(2, 1);
pstmt2.setDouble(1, 500);
pstmt2.setInt(2, 2);
//5.执行sql
pstmt1.executeUpdate();
// 手动制造异常
// int i = 3 / 0;
pstmt2.executeUpdate();
//提交事务
conn.commit();
} catch (SQLException e) {
//事务回滚
try {
if (conn != null) { // 避免conn空指针异常
try {
conn.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
e.printStackTrace();
} finally {
JDBCUtils.close(pstmt1, conn);
JDBCUtils.close(pstmt1, null);
}
}
}
}
数据库连接池
概念
获取数据库连接的操作,是向系统底层申请资源的,是非常耗时的,为了避免资源浪费,我们使用连接池技术。
连接池:其实就是一个容器(集合),存放数据库连接的容器。
当系统初始化好后,容器被创建,容器中会申请一些连接对象,当用户来访问数据库时,从容器中获取连接对象,用户访问完之后,会将连接对象归还给容器。
理解
- 假设有一家饭馆,来了一位客人点菜,你招聘了一个服务员,给客人上菜,客人吃完结账走了之后,你把服务员(连接对象)也给开了。下次再来个客人也是这样。【非常不合理!】
- 改进:客人走后、服务员要回到自己位置(连接池),等待下一个客人来临。
好处
- 节约资源
- 用户访问高效
实现
- 标准接口:
javax.sql.DataSource
连接池接口
一般我们不去实现它,有数据库厂商来实现
- C3P0:数据库连接池技术
- Druid:数据库连接池实现技术,由阿里巴巴提供的
方法
- 获取连接
getConnection()
- 归还连接
如果连接对象Connection是从连接池中获取的,那么调用Connection.close()
方法,则不会再关闭连接了,而是归还连接。
Connection.close()
C3P0
数据库连接池技术
步骤
1.导入jar包
两个:c3p0-0.9.5.2.jar
、mchange-commons-java-0.2.12.jar
,
不要忘记导入数据库驱动jar包
mysql-connector-java-5.1.37-bin
2. 定义配置文件
名称:c3p0.properties
或者c3p0-config.xml
在src目录使用这两个名字会自动获取配置文件
路径:直接将文件放在src目录下即可。
3. 创建数据库连接池对象(核心对象)
数据库连接池对象 ComboPooledDataSource
DataSource ds = new ComboPooledDataSource();
4. 获取数据库连接对象
Connection conn = ds.getConnection();
C3P0配置文件
- 使用默认的配置创建连接池对象
DataSource ds = new ComboPooledDataSource();
- 使用指定名称配置创建连接池对象
DataSource ds = new ComboPooledDataSource("otherc3p0");
文件:
c3p0-config.xml
<c3p0-config>
<!-- 使用默认的配置读取连接池对象 -->
<default-config>
<!-- 连接参数 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/db4</property>
<property name="user">root</property>
<property name="password">root</property>
<!-- 连接池参数 -->
<property name="initialPoolSize">5</property> <!-- 初始化申请的连接数量 -->
<property name="maxPoolSize">10</property> <!-- 最大的连接数量 -->
<property name="checkoutTimeout">3000</property> <!-- 超时时间 -->
</default-config>
<!--使用指定名称配置-->
<named-config name="otherc3p0">
<!-- 连接参数 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/day25</property>
<property name="user">root</property>
<property name="password">root</property>
<!-- 连接池参数 -->
<property name="initialPoolSize">5</property>
<property name="maxPoolSize">8</property>
<property name="checkoutTimeout">1000</property>
</named-config>
</c3p0-config>
代码:
package cn.luis.DataSource.c3p0;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
/**
* @ClassName C3P0Demo1
* @Description c3p0的演示
* @Author L
* @Date 2020.01.18 20:34
* @Version 1.0
* @Remark TODO
**/
public class C3P0Demo1 {
public static void main(String[] args) throws SQLException {
// 1.创建数据库连接池对象
DataSource ds = new ComboPooledDataSource();
// 2.获取连接对象
Connection conn = ds.getConnection();
// 3.打印
System.out.println(conn);
}
}
Druid
数据库连接池实现技术,由阿里巴巴提供
步骤
1. 导入jar包
druid-1.0.9.jar
2. 定义配置文件
- 是
properties
形式的文件 - 可以叫任意名称,可以放在任意目录下
druid.properties
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/db2
username=root
password=root
# 初始化连接数量
initialSize=5
# 最大连接数量
maxActive=10
# 最大等待时间(超时时间)
maxWait=3000
3. 加载配置文件
- 需手动加载配置文件
Properties pro = new Properties();
InputStream is = DruidDemo1.class.getClassLoader()
.getResourceAsStream("druid.properties");
pro.load(is);
4. 获取数据库连接池对象
通过工厂来来获取 DruidDataSourceFactory
DataSource ds = DruidDataSourceFactory.createDataSource(pro);
5. 获取数据库连接对象
- getConnection
Connection conn = ds.getConnection();
代码:
package cn.luis.DataSource.druid;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.util.Properties;
/**
* @ClassName DruidDemo1
* @Description Druid演示
* @Author L
* @Date 2020.01.18 20:52
* @Version 1.0
* @Remark TODO
**/
public class DruidDemo1 {
public static void main(String[] args) throws Exception {
// 1.导jar包
// 2.定义配置文件
// 3.加载配置文件
Properties pro = new Properties();
InputStream is = DruidDemo1.class.getClassLoader().getResourceAsStream("druid.properties");
pro.load(is);
// 4.获取连接池对象
DataSource ds = DruidDataSourceFactory.createDataSource(pro);
// 5.获取连接
Connection conn = ds.getConnection();
System.out.println(conn);
}
}
数据库连接池工具类
- 定义一个类
- 提供静态代码块加载配置文件,初始化连接池对象
- 提供方法
- 获取连接方法:通过数据库连接池获取连接
- 释放资源
- 获取连接池的方法
代码:
package cn.luis.DataSource.util;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/**
* @ClassName JDBCDataSourceUtil
* @Description Druid连接池的工具类
* @Author L
* @Date 2020.01.18 21:20
* @Version 1.0
* @Remark TODO
**/
public class JDBCDataSourceUtil {
// 1.定义成员变量 DataSource
private static DataSource ds;
static {
try {
// 2.加载配置文件
Properties pro = new Properties();
pro.load(JDBCDataSourceUtil.class.getClassLoader().getResourceAsStream("druid.properties"));
// 3.获取DataSource
ds = DruidDataSourceFactory.createDataSource(pro);
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
/*
获取连接
*/
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
/*
释放资源
*/
public static void close(Statement stmt, Connection conn) {
close(null, stmt, conn);
}
/*
释放资源
*/
public static void close(ResultSet rs, Statement stmt, Connection conn) {
if (rs != null) {
try {
rs.close(); //归还连接
} catch (SQLException e) {
e.printStackTrace();
}
}
if (stmt != null) {
try {
stmt.close(); //归还连接
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close(); //归还连接
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/*
获取连接池方法【有的框架只需获得连接池即可,这里暂未用到】
*/
public static DataSource getDataSource() {
return ds;
}
}
练习
使用新的工具类,完成添加操作,给db3中的account表中添加一条记录
package cn.luis.DataSource.druid;
import cn.luis.utils.JDBCUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* @ClassName DruidDemo2
* @Description 使用新的工具类
* @Author L
* @Date 2020.01.18 21:42
* @Version 1.0
* @Remark 完成添加操作,给db3中的account表中添加一条记录
**/
public class DruidDemo2 {
public static void main(String[] args) {
Connection conn = null;
PreparedStatement pstmt = null;
try {
// 1.获取连接
conn = JDBCUtils.getConnection();
// 2.定义sql
String sql = "insert into account values(null,?,?)";
// 3.获取pstmt对象
pstmt = conn.prepareStatement(sql);
// 4.赋值
pstmt.setString(1,"gangtiexia");
pstmt.setDouble(2,3000);
// 5.执行sql
int count = pstmt.executeUpdate();
System.out.println(count);
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 6.释放资源
JDBCUtils.close(pstmt,conn);
}
}
}
结果:
1
Spring JDBC
Spring框架对JDBC的简单封装。提供了一个JDBCTemplate对象,用来简化JDBC的开发。
步骤
1. 导入jar包(5个)
-
commons-logging-1.2.jar
-
spring-beans-5.0.0.RELEASE.jar
-
spring-core-5.0.0.RELEASE.jar
-
spring-jdbc-5.0.0.RELEASE.jar
-
spring-tx-5.0.0.RELEASE.jar
2. 创建JdbcTemplate对象
- 需传入数据源DataSource
JdbcTemplate template = new JdbcTemplate(ds);
例如:
// 1.获取JdbcTemplate对象(放到成员变量位置上)
private JdbcTemplate template = new JdbcTemplate(JDBCUtils.getDataSource());
入门代码:
- 不需要在申请连接了 还会自动把资源归还到连接池
package cn.luis.jdbctemplate;
import cn.luis.util.JDBCDataSourceUtil;
import org.springframework.jdbc.core.JdbcTemplate;
/**
* @ClassName JdbcTemplate
* @Description JdbcTemplate入门 JDBC模板
* @Author L
* @Date 2020.01.18 22:51
* @Version 1.0
* @Remark JdbcTemplate 不需要在申请连接了 还会自动把资源归还到连接池
**/
public class JdbcTemplateDemo1 {
public static void main(String[] args) {
// 1.导入jar包
// 2.创建JdbcTemplate对象
JdbcTemplate template = new JdbcTemplate(JDBCDataSourceUtil.getDataSource());
// 3.调用方法
String sql = "update account set balance = 5000 where id = ?";
int count = template.update(sql, 4);
System.out.println(count);
}
}
3. 调用JdbcTemplate的方法来完成CRUD的操作
update()
:执行DML语句。增、删、改语句,返回值大于零成功queryForMap()
:查询结果,并将结果集封装为map
集合,只能查询一条记录queryForList()
:查询结果,并将结果集封装为list集合,可查询多条记录query()
:查询结果,并将结果封装为JavaBean
对象。装载到List集合中queryForObject
:查询结果,并将结果封装为对象,一般用于聚合函数的查询
增删改
- JdbcTemplateDemo2
public class JdbcTemplateDemo2 {
//Junit单元测试,可以让方法独立运行
// 1.获取JdbcTemplate对象(放到成员变量位置上)
private JdbcTemplate template = new JdbcTemplate(JDBCUtils.getDataSource());
// 下面的方法都放在这里
}
- 修改1号数据的 salary 为 10000
@Test
public void test1() {
// 2.定义sql
String sql = "update emp set salary = 10000 where id = 1001";
// 3.执行sql
int count = template.update(sql);
System.out.println(count);
}
结果:
1
- 添加一条记录
@Test
public void test2() {
// 2.定义sql (双引号里面只能嵌套单引号!!!)
String sql = "insert into emp(id,ename,dept_id) values(?,?,?)";
// 3.执行sql
int count = template.update(sql, 1015, "钢铁侠", 10);
System.out.println(count);
}
结果:
1
- 删除刚才添加的记录
@Test
public void test3(){
String sql = "delete from emp where id = ?";
int count = template.update(sql, 1015);
System.out.println(count);
}
结果:
1
查询
第一种:将结果集封装为map
集合 【查询一条记录】
-
queryForMap()
:查询结果,并将结果集封装为map
集合-
将列名作为key,将值作为value 将这条记录封装为一个map集合
-
注意:这个方法查询的结果集长度只能是1 (只能查询一条记录)
-
例如:查询id为1001的记录,将其封装为Map集合
@Test
public void test4(){
String sql = "select * from emp where id = ?";
Map<String, Object> map = template.queryForMap(sql, 1001);
System.out.println(map);
}
结果:
{id=1001, ename=孙悟空, job_id=4, mgr=1004,bonus=null, dept_id=20}
第二种:将结果集封装为list集合 【可查询多条记录】
-
queryForList()
:查询结果,并将结果集封装为list集合将每一条记录封装为一个Map集合,再将Map集合装载到List集合中
例如: 查询所有记录,将其封装为List
@Test
public void test5(){
String sql = "select * from emp";
List<Map<String, Object>> list = template.queryForList(sql);
for (Map<String, Object> stringObjectMap : list) {
System.out.println(stringObjectMap);
}
}
结果:
{id=1001, ename=孙悟空, job_id=4, salary=10000.00, bonus=null, dept_id=20}
{id=1002, ename=卢俊义, job_id=3, salary=16000.00, bonus=3000.00, dept_id=30}
{id=1003, ename=林冲, job_id=3, salary=12500.00, bonus=5000.00, dept_id=30}
第三种:将结果封装为JavaBean
对象
query()
:查询结果,并将结果封装为JavaBean
对象,装载到List集合中
参数:RowMapper
一般我们使用BeanPropertyRowMapper
实现类。可以完成数据到JavaBean的自动封装
类的属性要与数据库表字段名一致,才会封装成功。
new BeanPropertyRowMapper<Emp>(Emp.class)); // <类型>(类型.class)
推荐写法:
- 将其封装为Emp(JavaBean)对象的List集合
@Test
public void test6_2(){
String sql = "select * from emp";
List<Emp> list = template.query(sql,
new BeanPropertyRowMapper<Emp>(Emp.class));
for (Emp emp : list) {
System.out.println(emp);
}
}
普通写法:【了解】
@Test
public void test6(){
String sql = "select * from emp";
// query方法会返回一个List集合
List<Emp> list = template.query(sql, new RowMapper<Emp>() {
// mapRow方法每调用一次就会封装一个Emp对象返回
@Override
public Emp mapRow(ResultSet rs, int i) throws SQLException {
Emp emp = new Emp();
int id = rs.getInt("id");
String ename = rs.getString("ename");
int job_id = rs.getInt("job_id");
int mgr = rs.getInt("mgr");
Date joindate = rs.getDate("joindate");
double salary = rs.getDouble("salary");
double bonus = rs.getDouble("bonus");
int dept_id = rs.getInt("dept_id");
emp.setId(id);
emp.setEname(ename);
emp.setJob_id(job_id);
emp.setMgr(mgr);
emp.setJoindate(joindate);
emp.setSalary(salary);
emp.setBonus(bonus);
emp.setDept_id(dept_id);
return emp;
}
});for (Emp emp : list) {
System.out.println(emp);
}
}
结果:
Emp{id=1001, ename='孙悟空', salary=10000.0, bonus=null, dept_id=20}
Emp{id=1002, ename='卢俊义', salary=16000.0, bonus=3000.0, dept_id=30}
Emp{id=1003, ename='林冲', salary=12500.0, bonus=5000.0, dept_id=30}
第四种:将结果封装为对象
queryForObject
:查询结果,并将结果封装为对象
- 一般用于聚合函数的查询
例如:查询总记录数
@Test
public void test7(){
String sql = "select count(id) from emp";
// queryForObject() 一般用于聚合函数的查询
Long total = template.queryForObject(sql, Long.class); // 传递封装的返回值类型
System.out.println(total);
}
结果:
14