JDBC
学习目标
- 理解JDBC相关概念(面试问到的时候,你能通过自己的语言描述说明什么是JDBC)
- 能够熟练使用JDBC相关的类和接口
一、初识
1.概念
Java DataBase Connectivity。Java 数据库连接, Java语言操作数据库
2.本质
其实是官方(sun公司)定义的一套操作所有关系型数据库的规则,即接口。各个数据库厂商去实现这套接口,提供数据库驱动jar包。我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类。
3.步骤
//1.导包(对应数据库的数据库驱动包) —— mysql-connector-java-5.1.37-bin.jar
//1. 复制jar包到项目新建的libs目录中(libs和src同级)
//2. 选中jar包右键build path - add to path
//2. 注册驱动
Class.forName("com.mysql.jdbc.Driver");
//3.通过驱动获取到数据库连接对象Connection,可以简写jdbc:mysql:///db3
//注意Connetction是导import java.sql.Connection;
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db3", "root", "root");
//4. 定义sql语句
String sql = "update account set balance=10000 where id = 1";
//5. 获取到执行sql的对象 Statement对象,注意要导import java.sql.Statement;
Statement stmt = conn.createStatement();
//6.执行sql,获取返回结果
int count = stmt.executeUpdate(sql);
//7. 处理结果
System.out.println("结果为:"+count);
//8. 释放资源
stmt.close();
conn.close();
4.对象详解
Statement:执行sql的对象:
1.boolean execute(String sql)
可以执行任意的sql语句 了解即可
返回值: 如果第一个结果为 ResultSet 对象,则返回 true;如果其为更新计数或者不存在任何结果,则返回 false
2.int executeUpdate(String sql)
执行给定 DML(INSERT、UPDATE 或 DELETE) 语句或 DDL(create,alter,drop) 语句
返回值:int代表受影响的行数,可以通过该返回值来判断DML语句是否执行成功(返回值>0则成功,反之则失败)如果执行的是DDL语句,成功则返回0.(很少使用)
3.ResultSet executeQuery(String sql)
执行DQL(select)语句
返回值:ResultSet:结果集对象
ResultSet结果集对象
boolean next():游标会向下移动一行 返回值:如果新的当前行有效,则返回 true;如果不存在下一行,则返回 false
getXxx(参数):获取对应的数据
Xxx:代表的是数据类型 eg:getInt() getString()
参数:int:代表列的编号值 从1开始的 getString(1):获取当前光标所在行的第一列的值 int getInt(int columnIndex)
String:代表列的名称 getDouble(“balance”) int getInt(String columnLabel)
ResultSet res = stmt.executeQuery(sql);
while(res.next()) { //判断游标是否位于最后一行
int id = res.getInt(1); //int类型的参数,代表第一列
String name = res.getString("name"); //获取列名叫name的那一列,类型是String(varchar)类型
Double balance = res.getDouble("balance");
System.out.println(id+"--->"+name+"--->"+balance);
}
进行封装:查询emp表中的所有数据,并将其封装成一个对象,然后装载到集合中,最后返回集合
res = stmt.executeQuery(sql);
list = new ArrayList<>();
Emp emp = null;
//遍历结果集,将获取到的字段值封装到对象中,对象装载到集合中
while(res.next()) {
int id = res.getInt("id");
String name = res.getString("name");
String gender = res.getString("gender");
Double salary = res.getDouble("salary");
Date date = res.getDate("join_date");
int deptId = res.getInt("dept_id");
emp = new Emp();
emp.setId(id);
emp.setName(name);
emp.setGender(gender);
emp.setSalary(salary);
emp.setJoinDate(date);
emp.setDeptId(deptId);
list.add(emp);
}
二、自定义JDBC工具类
1.抽取JDBC工具类
抽取JDBC工具类:JDBCUtils
- 抽取驱动的注册
- 抽取连接对象
- 将释放资源代码抽取出来
public class JDBCUtils {
private static String driver ;
private static String url;
private static String user;
private static String pwd;
/**
* 通过文件的读取,获取到文件中的值,只要读取一次即可. 使用静态代码块实现
*/
static {
try {
//读取文件资源,获取其中的值
//新建jdbc.properties 文件
// 1.创建一个Properties集合类
Properties pro = new Properties();
//2.获取加载文件 获取src路径下的文件--->ClassLoader类加载器
ClassLoader classLoader = JDBCUtils.class.getClassLoader();
URL res = classLoader.getResource("jdbc.properties"); //获取到src目录下的指定文件
String path = res.getPath();
pro.load(new FileReader(path));
//3.获取数据,赋值
driver = pro.getProperty("driver");
url = pro.getProperty("url");
user = pro.getProperty("user");
pwd = pro.getProperty("pwd");
Class.forName(driver);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* 获取conn连接对象
* @throws SQLException
*/
public static Connection getConnection() throws SQLException {
// return DriverManager.getConnection("jdbc:mysql:///db3", "root", "123456");
return DriverManager.getConnection(url,user,pwd);
}
/**
* 释放资源(两个参数) 通过方法重载实现功能的全面性
*/
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();
}
}
}
}
//jdbc.properties:
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/db3
user=root
password=root
2.使用JDBC工具类
/**
* 使用JDBCUtils工具类
*/
public static List<Emp> findAll2() {
Connection conn = null;
Statement stmt =null;
ResultSet res = null;
List<Emp> list = null;
try {
String sql = "select * from emp";
conn = JDBCUtils.getConnection(); //调用工具类连接对象方法,同时会注册驱动
stmt = conn.createStatement();
res = stmt.executeQuery(sql);
list = new ArrayList<>();
Emp emp = null;
//遍历结果集,将获取到的字段值封装到对象中,对象装载到集合中
while(res.next()) {
int id = res.getInt("id");
String name = res.getString("name");
String gender = res.getString("gender");
Double salary = res.getDouble("salary");
Date date = res.getDate("join_date");
int deptId = res.getInt("dept_id");
emp = new Emp();
emp.setId(id);
emp.setName(name);
emp.setGender(gender);
emp.setSalary(salary);
emp.setJoinDate(date);
emp.setDeptId(deptId);
list.add(emp);
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
JDBCUtils.close(res, stmt, conn); //调用工具类关闭资源方法
}
return list;
}
三、事务
一个包含多个步骤的业务,如果被事务管理,则这多个步骤要么同时成功,要么同时失败
操作:
-
开启事务:
void setAutoCommit(boolean autoCommit):调用该方法时将参数设置为false,即开启事务。执行sql语句前开启事务。
-
提交事务
void commit():当所有的sql语句执行完后去提交事务。
-
回滚事务
void rollback():一般情况回滚操作会在try catch中的catch块中执行。
[案例见](# 事务与PreparedStatement)
四、PreparedStatement
表示预编译的 SQL 语句的对象
PreparedStatement是Statement的子类
为什么要使用PreparedStatement
Statement的问题:
SQL注入问题: 在拼接sql的时候,有一些特殊的sql的关键字参与字符串的拼接,会引起安全问题
输入一个随意的用户名,输入密码:a’ or ‘a’='a 出现登录成功的操作
sql语句:SELECT * FROM USER WHERE USER= ‘12sadadad’ AND pwd=‘a’ OR ‘a’=‘a’ 不管用户名密码正确与否,where都为true
PreparedStatement将一些sql中的参数使用占位符(?)替代,后续再为占位符赋值
事务与PreparedStatement
Connection conn = null;
PreparedStatement pstmt1 = null;
PreparedStatement pstmt2 = null;
try {
//2.3.注册驱动,通过驱动获取到数据库连接对象Connection
conn = JDBCUtils.getConnection();
//执行sql前开启事务,调用该方法时将参数设置为false,即开启事务,需要执行sql语句前开启事务
conn.setAutoCommit(false);
//4.定义sql语句 account 表的转账操作
String sql1 = "update account set balance = balance - ? where id = ?";
String sql2 = "update account set balance = balance + ? where id = ?";
//5.获取到执行sql的PreparedStatement对象 与conn.createStatement()区别: 无参与有参(因为要预编译)
pstmt1 = conn.prepareStatement(sql1); //将sql1语句预编译
pstmt2 = conn.prepareStatement(sql2); //将sql2语句预编译
//6.为?去赋值
pstmt1.setDouble(1, 500); //sql1的第1个?=500
pstmt1.setInt(2, 1); //sql1的第二个?=1
pstmt2.setDouble(1, 500);
pstmt2.setInt(2, 2);
//7.执行sql,获取返回结果,此时不需要传递任何sql语句(与Statement区别)
pstmt1.executeUpdate();
int i = 9/0; //出错
pstmt2.executeUpdate();
conn.commit(); //提交事务
} catch (SQLException e) {
if (conn != null) {
try {
conn.rollback(); //进行回滚事务
} catch (SQLException e1) {
e1.printStackTrace();
}
}
e.printStackTrace();
}finally {
JDBCUtils.close(pstmt1, conn);
JDBCUtils.close(pstmt2, conn);
}
//报错
java.lang.ArithmeticException
五、JDBC连接池
1.概念
其实是一个容器(集合),存放数据库连接的一个容器。
当系统进行初始化的时候,容器会被创建,容器内会自动申请一些连接对象,当用户来访问数据库的时候,从该容器中获取到连接对象,用户使用完数据库后,将连接对象归还给容器.
2.优势
- 节约资源,数据库连接对象可以重复利用
- 用户访问高效
3.使用
- 标准接口:DataSource javax.sql包下的
- 方法:
- Connection getConnection():获取连接
- Connection.close() 如果Connection对象是从连接池中获取的,Connection.close()方法不会关闭连接,而是会将该连接归还给数据库连接池.
- 方法:
- 一般我们不需要去实现DataSource接口,会有专门的数据库厂商帮你实现
- C3P0:数据库连接池技术
- Druid:Druid(德鲁伊)是阿里巴巴开发的号称为监控而生的数据库连接池,Druid是目前最好的数据库连接池。
//1.创建数据库连接池对象
DataSource ds = new ComboPooledDataSource();
//2.通过连接池对象获取连接对象
Connection conn = null;
PreparedStatement pstmt1 = null;
try {
conn = ds.getConnection(); //不再需要自定义工具类中的获取连接对象方法
//执行sql前开启事务
conn.setAutoCommit(false); //开启
//account 表的转账操作
String sql1 = "update account set balance = balance - ? where id = ?";
pstmt1 = conn.prepareStatement(sql1);
pstmt1.setDouble(1, 500);
pstmt1.setInt(2, 1);
pstmt1.executeUpdate(); //执行sql
conn.commit(); //提交事务
} catch (SQLException e) {
e.printStackTrace();
}finally {
conn.close();
}
DataSource实现类
1)C3P0
C3P0:数据库连接池技术
步骤:
1.导包(注意build path) 首先要导入数据库驱动包 mysql-connector-java-5.1.37-bin.jar
1.c3p0-0.9.5.2.jar
2.mchange-commons-java-0.2.12.jar
2.定义配置文件:
名称: c3p0.properties 或者 c3p0-config.xml -->自动导入的
c3p0-config.xml
<c3p0-config>
<!-- 使用默认的配置读取连接池对象 -->
<default-config>
<!-- 连接参数 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/db3</property>
<property name="user">root</property>
<property name="password">root</property>
<!-- 连接池参数 -->
<property name="initialPoolSize">5</property> <!-- 默认的连接池的连接数 5 -->
<property name="maxPoolSize">10</property> <!-- 最大的连接池的连接数 如果超出最大连接数 会报错 -->
<property name="checkoutTimeout">3000</property> <!--如果超出最大连接数 3s后 会报错 -->
</default-config>
<named-config name="otherc3p0">
<!-- 连接参数 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/db3</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>
c3p0.properties
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql:///db3
username=root
password=123456
# 默认的连接池的连接数 5
initialSize=5
# 最大的连接池的连接数 如果超出最大连接数10 会报错
maxActive=10
# 如果超出最大连接数 3s后 会报错
maxWait=3000
路径:直接放在src目录即可
3.创建核心对象 CombolPooledDataSource
4.获取连接 getConnection()
/**
* 使用默认的连接池配置获取连接
*/
public static void testc3p01() throws SQLException{
//1.获取ds对象
DataSource ds = new ComboPooledDataSource();
//2.获取连接
for (int i = 1; i <= 11; i++) {
Connection conn = ds.getConnection(); //不用传参
System.out.println(i+":"+conn);
if (i==5) {
conn.close();//归还连接到连接池中
}
}
}
/**
* 使用带name的连接配置创建连接
*/
public static void testc3p02() throws SQLException{
//1.获取ds对象 使用含有一个configName 的参数
DataSource ds = new ComboPooledDataSource("otherc3p0");
//2.获取连接
for (int i = 1; i <= 9; i++) {
Connection conn = ds.getConnection(); //不用传参
System.out.println(i+":"+conn);
}
}
2)Druid(德鲁伊)
数据库连接池技术
-
使用步骤:
-
导包:druid-1.0.9.jar 首先要导入数据库驱动包 mysql-connector-java-5.1.37-bin.jar
-
定义配置文件
是properties形式的
可以叫任意的名称,可以放在任意的位置—>手动导入
-
加载配置文件
-
获取数据库连接池对象
-
获取连接
//1. 导包:druid-1.0.9.jar 首先要导入数据库驱动包 //2. 定义配置文件: // * 是properties形式的 // * 可以叫任意的名称,可以放在任意的位置--->手动导入 //3. 加载配置文件 Properties pro = new Properties(); pro.load(DruidDemo01.class.getClassLoader().getResourceAsStream("druid.properties")); //4. 获取数据库连接池对象 工厂类获取对象 DataSource ds = DruidDataSourceFactory.createDataSource(pro); //5. 获取连接 Connection conn = ds.getConnection(); System.out.println(conn);
-
-
定义工具类
-
定义一个类
-
提供静态代码块加载Druid的配置文件,初始化连接池对象
-
提供以下方法
-
获取连接对象的方法:通过Druid数据库连接池获取连接对象
-
释放资源
-
获取连接池的方法
-
-
[与之前自定义抽取工具类比较](# 1.抽取JDBC工具类)
public class JDBCDruidUtils {
//定义成员变量DataSource
private static DataSource ds ;
static{
//加载配置文件并初始化连接池对象
Properties pro = new Properties();
try {
pro.load(JDBCDruidUtils.class.getClassLoader().getResourceAsStream("druid.properties"));
ds = DruidDataSourceFactory.createDataSource(pro);
} catch (Exception e1) {
e1.printStackTrace();
}
}
/**
* 获取连接对象
* @throws SQLException
*/
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 {
stmt.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;
}
}
//使用工具类
Connection conn = null;
PreparedStatement pstmt = null;
// 完成添加操作 account表中添加一条记录
try {
conn = JDBCDruidUtils.getConnection();
String sql = "insert into account values(null,?,?)";
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, "wangwu");
pstmt.setDouble(2, 5000);
int count = pstmt.executeUpdate();
System.out.println(count);
} catch (SQLException e) {
e.printStackTrace();
}finally {
JDBCDruidUtils.close(pstmt, conn);
}
# druid.properties
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql:///db3
username=root
password=root
initialSize=5
maxActive=10
maxWait=3000
六、Spring JDBC
Spring框架对JDBC的简单封装 提供了一个JDBCTemplate对象简化JDBC的开发
实现步骤:
-
导包
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
-
创建jdbcTemplate对象 依赖数据源DataSource
JdbcTemplate template = new jdbcTemplate(ds);
3.操作
private JdbcTemplate template = new JdbcTemplate(JDBCDruidUtils.getDataSource());
//1. 修改id为1的数据的工资为17200
@Test
public void test01() {
//String sql = "update emp set salary = 17200 where id = 1";
String sql = "update emp set salary = ? where id = ?";
//int count = template.update(sql);
//参数一:sql语句 参数二:代表一个长度可变的参数,重点来为sql中的占位符赋值
int count = template.update(sql,17200,1);
System.out.println(count);
}
//2. 为emp表添加一条记录
@Test
public void test02() {
String sql = "insert into emp(id,name,salary) values(?,?,?)";
int count = template.update(sql, 6,"红孩儿","300");
System.out.println(count);
}
//3. 将需求2添加的记录删除掉
@Test
public void test03() {
String sql = "delete from emp where id = ?";
int count = template.update(sql,6);
System.out.println(count);
}
//4. 查询id为1的记录,将其封装到Map集合中 queryForMap():该方法查询的结果集的长度只能是1.
@Test
public void test04() {
//String sql = "select * from emp where id = ?";
String sql = "select * from emp where id = ? or id = ?"; //不能这样使用
//Map<String, Object> map = template.queryForMap(sql, 1);
Map<String, Object> map = template.queryForMap(sql, 1,2); //错误的
System.out.println(map);
//{id=1, NAME=孙悟空, gender=男, salary=7200.0, join_date=2013-02-24, dept_id=1}
}
//5. 查询所有的记录,将其封装到list集合中 queryForList(sql)
@Test
public void test05() {
String sql = "select * from emp";
List<Map<String, Object>> list = template.queryForList(sql);
for (Map<String, Object> map : list) {
System.out.println(map);
}
}
//6.查询所有的记录,将其封装为Emp对象的list集合中
@Test
public void test06() {
String sql = "select * from emp";
List<Emp> list = template.query(sql, new RowMapper<Emp>() {
@Override
public Emp mapRow(ResultSet re, int i) throws SQLException {
Emp emp = new Emp();
emp.setId(re.getInt("id"));
emp.setName(re.getString("name"));
emp.setSalary(re.getDouble("salary"));
emp.setGender(re.getString("gender"));
emp.setJoinDate(re.getDate("join_date"));
emp.setDeptId(re.getInt("dept_id"));
return emp;
}});
for (Emp emp : list) {
System.out.println(emp);
}
}
//6.查询所有的记录,将其封装为Emp对象的list集合中 BeanPropertyRowMapper
@Test
public void test06_02() {
String sql = "select * from emp";
List<Emp> list = template.query(sql,new BeanPropertyRowMapper<Emp>(Emp.class));
for (Emp emp : list) {
System.out.println(emp);
}
}
//7.查询总记录数量
@Test
public void test07() {
String sql = "select count(id) from emp";
long total = template.queryForObject(sql, Long.class);
System.out.println(total);
}