JDBC编程六步
1.注册驱动(告诉Java程序,即将连接的是哪个品牌的数据库)
2.获取连接(表示JVM的进程和数据库进程之间的通道打开了,这属于进程之间的通信,使用完后记得关闭通道)。
3.获取数据库操作对象(专门执行sql语句的对象)
Statement statement = connect.createStatement();
4.执行SQL语句(DQL,DML…,增删改)
String sql = "insert into actor values('2','a')"; int rows = statement.executeUpdate(sql);
5.处理查询结果集 (只有当第四步执行的是select语句的时候,才有本步,查)
while (rs.next()){ System.out.println(rs.getString("ename")); }
6.释放资源(使用完资源后一定要关闭资源,Java和数据库之间属于进程间的通信,开启之后一定要记得关闭)
if (rs != null){ try { rs.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } if (stmt != null){ try { stmt.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } if (conn != null){ try { conn.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } }
数据库连接的五种方式(前两步)
一
使用第三方Driver
//方式1 属于静态加载,灵活性差,依赖性强。
@Test
public void connect01() throws SQLException {
Driver driver = new Driver(); //创建driver对象
String url = "jdbc:mysql://localhost:3306/hsp_db02";
//将 用户名和密码放入到Properties 对象
Properties properties = new Properties();
//说明 user 和 password 是规定好,后面的值根据实际情况写
properties.setProperty("user", "root");// 用户
properties.setProperty("password", "hsp"); //密码
Connection connect = driver.connect(url, properties);
System.out.println(connect);
}
二
通过反射机制获取Driver
//方式2
@Test
public void connect02() throws ClassNotFoundException, IllegalAccessException, InstantiationException, SQLException {
//使用反射加载Driver类 , 动态加载,更加的灵活,减少依赖性
Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver)aClass.newInstance();
String url = "jdbc:mysql://localhost:3306/hsp_db02";
//将 用户名和密码放入到Properties 对象
Properties properties = new Properties();
//说明 user 和 password 是规定好,后面的值根据实际情况写
properties.setProperty("user", "root");// 用户
properties.setProperty("password", "hsp"); //密码
Connection connect = driver.connect(url, properties);
System.out.println("方式2=" + connect);
}
三
使用DriverManager来注册,不用写配置文件, 更加方便
//方式3 使用DriverManager 替代 driver 进行统一管理
@Test
public void connect03() throws IllegalAccessException, InstantiationException, ClassNotFoundException, SQLException {
//使用反射加载Driver
Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver) aClass.newInstance();
//创建url 和 user 和 password
String url = "jdbc:mysql://localhost:3306/hsp_db02";
String user = "root";
String password = "hsp";
DriverManager.registerDriver(driver);//注册Driver驱动
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println("第三种方式=" + connection);
}
四
直接可以不用注册驱动
//方式4: 使用Class.forName 自动完成注册驱动,简化代码
//这种方式获取连接是使用的最多,推荐使用
@Test
public void connect04() throws ClassNotFoundException, SQLException {
//使用反射加载了 Driver类
//在加载 Driver类时,完成注册
/*
源码: 1. 静态代码块,在类加载时,会执行一次.
2. DriverManager.registerDriver(new Driver());
3. 因此注册driver的工作已经完成
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
*/
Class.forName("com.mysql.jdbc.Driver");
//创建url 和 user 和 password
String url = "jdbc:mysql://localhost:3306/hsp_db02";
String user = "root";
String password = "hsp";
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println("第4种方式~ " + connection);
}
五
把写死的连接数据库的配置信息,通过properties类来进行获取配置。
//方式5 , 在方式4的基础上改进,增加配置文件,让连接mysql更加灵活
@Test
public void connect05() throws IOException, ClassNotFoundException, SQLException {
//通过Properties对象获取配置文件的信息
Properties properties = new Properties();
properties.load(new FileInputStream("src\\mysql.properties"));
//获取相关的值
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String driver = properties.getProperty("driver");
String url = properties.getProperty("url");
Class.forName(driver);//版本变高,不需要写,但是建议写上
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println("方式5 " + connection);
}
使用
public class JDBC01 {
public static void main(String[] args) throws SQLException {
//前置工作: 在项目下创建一个文件夹比如 libs
// 将 mysql.jar 拷贝到该目录下,点击 add to project ..加入到项目中
//1. 注册驱动
Driver driver = new Driver(); //创建driver对象
//2. 得到连接
// 老师解读
//(1) jdbc:mysql:// 规定好表示协议,通过jdbc的方式连接mysql
//(2) localhost 主机,可以是ip地址
//(3) 3306 表示mysql监听的端口
//(4) hsp_db02 连接到mysql dbms 的哪个数据库
//(5) mysql的连接本质就是前面学过的socket连接
String url = "jdbc:mysql://localhost:3306/hsp_db02";
//将 用户名和密码放入到Properties 对象
Properties properties = new Properties();
//说明 user 和 password 是规定好,后面的值根据实际情况写
properties.setProperty("user", "root");// 用户
properties.setProperty("password", "zouwenhao"); //密码
Connection connect = driver.connect(url, properties);
//3. 执行sql
//String sql = "insert into actor values(null, '刘德华', '男', '1970-11-11', '110')";
//String sql = "update actor set name='周星驰' where id = 1";
String sql = "delete from actor where id = 1";
//statement 用于执行静态SQL语句并返回其生成的结果的对象
Statement statement = connect.createStatement();
// rows返回0,表示执行失败
int rows = statement.executeUpdate(sql); // 如果是 dml语句,返回的就是影响行数,executeUpdate其实就是执行语句
System.out.println(rows > 0 ? "成功" : "失败"); // 受影响的行数大于0,那么执行成功,反之为0,则执行失败
//4. 关闭连接资源
statement.close();
connect.close();
}
}
ResultSet[结果集]
查询数据库,将结果放在一个结果集对象中
ResultSet是一个接口,JDBC42ResultSet才是实现接口的实例对象
其中rowData用于存放查询数据
查询结果的每一行用ArrayList存放在rows中
String sql = "Select * from actor";
ResultSet resultSet =statement.executeQuery(sql);
while(resultSet.next()){
int id = resultSet.getInt("id");
String name =resultSet.getString("name");
String sex = resultSet.getString("sex");
Date date =resultSet.getDate("borndate");
String phone =resultSet.getString("phone");
System.out.println(id + "\t" + name + "\t" + sex + "\t" + date+ "\t" + phone);
}
resultSet.close();
statement.close();
connection.close();
注:查询的列名应当和while循环中的列名一一对应
若返回的查询中有date这一列,但while循环中没有会出现报错
getString()也可以用索引来获得对应的列(index=1为第一列),不管数据类型是什么,取出来的类型都是String,列下标从1开始
可以以特定类型取出,getInt, getDouble
当查询结束后,我们需要将resultSet关闭,查询的结果会丢失。
我们后续若想继续使用该结果集就要重新查询,这很不方便。
在后续DAO的介绍中,我们会为每个表创建一个JavaBean来解决复用这个问题。
SQL注入
用户输入的信息中含有SQL语句的关键字,并且这些关键字参与SQL语句的编译过程,
导致sql语句的原意被扭曲,进而达到SQL注入。
String sql = "select name , pwd from admin where name ='"
+ admin_name + "' and pwd = '" + admin_pwd + "'";
ResultSet resultSet = statement.executeQuery(sql);
PreparedStatement(预处理)
解决sql注入问题,只要用户提供的信息 不参与sql语句的编译过程,问题就解决了
PreparedStatement接口继承了java.sql.PreparedStatement
先对sql语句的框架进行编译,然后再给sql语句传"值"
对比
1.Statement存在sql注入问题,PreparedStatement解决了sql注入问题
2.Statement是编译依次执行一次,PreparedStatement是编译一次,可以执行n次,效率略高
3.PreparedStatement会在编译阶段做类型安全检查
综上所述:大部分时候使用PreparedStatement 有些情况用statement,如需要拼接字符串,输入desc(降序) asc(升序)
//得到PreparedStatement
//1 组织SqL , Sql 语句的 ? 就相当于占位符
//String sql = "insert into admin values(?, ?)";
//String sql = "update admin set pwd = ? where name = ?";
String sql = "select name , pwd from admin where name =? and pwd = ?"; // ?用于占位
//2 preparedStatement 对象实现了 PreparedStatement 接口的实现类的对象
PreparedStatement preparedStatement = connection.prepareStatement(sql);//关联
//3 给 ? 赋值,set类型
preparedStatement.setString(1, admin_name);
preparedStatement.setString(2, admin_pwd);
//4 执行 select 语句使用 executeQuery
// 如果执行的是 dml(update, insert ,delete) executeUpdate()
// 这里执行 executeQuery ,不要在写 sql,因为preparedStatement已经与sql关联
ResultSet resultSet = preparedStatement.executeQuery();
API梳理![](https://i-blog.csdnimg.cn/direct/36a662291641426395031beaf2e50df4.png)
![](https://i-blog.csdnimg.cn/direct/bf147591d30d432fbbfa558b6955d6a6.png)
JDBCUtils
public class JDBCUtils {
//定义相关的属性(4个)
//因为只需要一份,因此用static修饰
private static String user;
private static String password;
private static String url;
private static String driver;
//在static 代码块获得配置文件信息
static{
Properties properties = new Properties();
try {
//加载之前用过的配置文件
properties.load(new FileInputStream("src\\mysql.properties"));
//读取相关属性
user = properties.getProperty("user");
password = properties.getProperty("password");
url = properties.getProperty("url");
driver = properties.getProperty("driver");
} catch (IOException e) {
//在实际开发中,可以这样处理
//1. 将编译异常转成运行异常
//2. 调用这可以选择捕获该异常,也可以选择默认处理(报出编译异常)
throw new RuntimeException(e);
//e.printStackTrace();
}
}
//连接数据库,返回Connection
public static Connection getConnection(){
try {
return DriverManager.getConnection(url,user,password);
}catch (SQLException e){
throw new RuntimeException(e);
}
}
//关闭相关资源
/*
* 1. ResultSet 结果集
* 2. Statement或PreparedStatement
* 3. Connection
*/
public static void close(ResultSet set, Statement statement, Connection connection){
//判断是否为NULL
try {
if(set !=null){
set.close();
}
if(statement!=null){
statement.close();
}
if (connection!=null){
connection.close();
}
} catch (SQLException e) {
//转为运行时异常抛出
throw new RuntimeException(e);
}
}
}
使用
package com.lyxlearn.utils;
import org.junit.jupiter.api.Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class JDBCUtils_Use {
@Test
//dml
public void testDML() throws SQLException {
Connection connection = JDBCUtils.getConnection();
String sql = "update actor1 set name = ? where number = ?";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1,"小红");
preparedStatement.setString(2,"2020200");
int rows = preparedStatement.executeUpdate();
System.out.println(rows > 0 ? "成功":"失败");
JDBCUtils.close(null,preparedStatement,connection);
}
@Test
//查询
public void testQuery() throws SQLException {
Connection connection = JDBCUtils.getConnection();
String sql = "select name , sex from actor1 where number = ?";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1,"2020200");
ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next()){
String name = resultSet.getString(1);
String sex = resultSet.getString(2);
System.out.println(name + "\t" + sex);
}
JDBCUtils.close(resultSet,preparedStatement,connection);
}
}
事务
JDBC中的事务自动提交的,什么是自动提交?
只要执行任意一条 DML语句,则自动提交一次。这是JDBC默认的事务行为。
但是在实际的业务中,通常都是N条DML语句共同联合才能完成,必须
保证这些DML语句在同一个事务中同时成功或者同时失败
解决方案:三行重要的代码
conn.setAutoCommit(false);//手动提交事务
conn.commit();//提交事务
conn.rooback();当发生异常时或者程序错误时,进行回滚。
package com.hspedu.jdbc.transaction_;
import com.hspedu.jdbc.utils.JDBCUtils;
import org.junit.jupiter.api.Test;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* 演示jdbc 中如何使用事务
*/
public class Transaction_ {
//没有使用事务.
@Test
public void noTransaction() {
//操作转账的业务
//1. 得到连接
Connection connection = null;
//2. 组织一个sql
String sql = "update account set balance = balance - 100 where id = 1";
String sql2 = "update account set balance = balance + 100 where id = 2";
PreparedStatement preparedStatement = null;
//3. 创建PreparedStatement 对象
try {
connection = JDBCUtils.getConnection(); // 在默认情况下,connection是默认自动提交
preparedStatement = connection.prepareStatement(sql);
preparedStatement.executeUpdate(); // 执行第1条sql
int i = 1 / 0; //抛出异常
preparedStatement = connection.prepareStatement(sql2);
preparedStatement.executeUpdate(); // 执行第3条sql
} catch (SQLException e) {
e.printStackTrace();
} finally {
//关闭资源
JDBCUtils.close(null, preparedStatement, connection);
}
}
//事务来解决
@Test
public void useTransaction() {
//操作转账的业务
//1. 得到连接
Connection connection = null;
//2. 组织一个sql
String sql = "update account set balance = balance - 100 where id = 1";
String sql2 = "update account set balance = balance + 100 where id = 2";
PreparedStatement preparedStatement = null;
//3. 创建PreparedStatement 对象
try {
connection = JDBCUtils.getConnection(); // 在默认情况下,connection是默认自动提交
//将 connection 设置为不自动提交
connection.setAutoCommit(false); //开启了事务
preparedStatement = connection.prepareStatement(sql);
preparedStatement.executeUpdate(); // 执行第1条sql
int i = 1 / 0; //抛出异常
preparedStatement = connection.prepareStatement(sql2);
preparedStatement.executeUpdate(); // 执行第3条sql
//这里提交事务
connection.commit();
} catch (Exception e) {
//这里我们可以进行回滚,即撤销执行的SQL
//默认回滚到事务开始的状态.
System.out.println("执行发生了异常,撤销执行的sql");
try {
connection.rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
e.printStackTrace();
} finally {
//关闭资源
JDBCUtils.close(null, preparedStatement, connection);
}
}
}
锁
悲观锁和乐观锁的概念
事务1–>读取到版本号1.1
事务2—>读取到版本号1.1
其中事务1先修改了,修改之后看了版本号是1.1 ,于是提交修改的数据,将版本号修改为1.2
其中事务2后修改的,修改之后准备提交的时候,发现版本号是1.2 ,和它最初读的版本号不一致。回滚。
悲观锁:事务必须排队执行。数据锁住了,不允许并发。 (行级锁: select后面添加for update )
乐观锁:支持并发,事务也不需要排队,只不过需要一个版本号。
行级锁 – 悲观锁
select ename, job, sal from emp where job= ' MANAGER ' for update;
批处理
批处理之前
批处理之后
连接池
c3p0
user=root
password=123456
url=jdbc:mysql://localhost:3306/db01
driver=com.mysql.jdbc.Driver
public static void test() throws IOException, PropertyVetoException, SQLException {
//1、创建数据源对象
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
//2、获取配置信息
Properties properties = new Properties();
properties.load(new FileInputStream("src\\properties"));
String user = properties.getProperty("user");
String password = properties.getProperty("password");
String url = properties.getProperty("url");
String driver = properties.getProperty("driver");
//3、给数据源comboPooledDataSource 设置相关参数
comboPooledDataSource.setDriverClass(driver);
comboPooledDataSource.setJdbcUrl(url);
comboPooledDataSource.setUser(user);
comboPooledDataSource.setPassword(password);
//4、设置初始化连接数
comboPooledDataSource.setInitialPoolSize(100);
//最大连接数
comboPooledDataSource.setMaxPoolSize(500);
long start = System.currentTimeMillis();
for(int i = 0; i < 5000; i ++) {
Connection connection = comboPooledDataSource.getConnection();
connection.close();
}
long end = System.currentTimeMillis();
System.out.println(end - start);
}
c3p0-config.xml配置文件
<c3p0-config>
<!--使用默认的配置读取数据库连接池对象 -->
<named-config name="abc">
<!-- 连接参数 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/db01</property>
<property name="user">root</property>
<property name="password">123456</property>
<!-- 连接池参数 -->
<!--每次增长的连接数量-->
<property name="acquireIncrement">5</property>
<!--初始化申请的连接数量-->
<property name="initialPoolSize">10</property>
<!--最小的连接数量-->
<property name="minPoolSize">5</property>
<!--最大的连接数量-->
<property name="maxPoolSize">50</property>
<!--可连接的最多的命令对象数-->
<property name="maxStatements">5</property>
<!--每个连接对象可连接的最多的命令对象数-->
<property name="maxStatementsPerConnection">2</property>
</named-config>
</c3p0-config>
public static void test() throws IOException, PropertyVetoException, SQLException {
//1、创建数据源对象
ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource("abc");
long start = System.currentTimeMillis();
for(int i = 0; i < 5000; i ++) {
Connection connection = comboPooledDataSource.getConnection();
connection.close();
}
long end = System.currentTimeMillis();
System.out.println(end - start);
}
druid
# druid.properties文件的配置
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/db01
username=root
password=123456
# 初始化连接数量
initialSize=10
# 最小连接数
minIdle=5
# 最大连接数
maxActive=50
# 最大超时时间
maxWait=3000
public void test() throws Exception {
Properties properties = new Properties();
properties.load(new FileInputStream("src\\druid.properties"));
//创建一个指定参数的数据库连接池
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
long start = System.currentTimeMillis();
for(int i = 0; i < 5000; i ++) {
Connection connection = dataSource.getConnection();
connection.close();
}
long end = System.currentTimeMillis();
System.out.println(end - start);
}
JDBCUtilsByDruid
public class JDBCUtilsByDruid {
private static DataSource ds;
static {
Properties properties = new Properties();
try {
properties.load(new FileInputStream("src//druid.properties"));
ds = DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
//把连接放回到连接池,并不是关闭
public static void close(ResultSet resultSet, Statement statement, Connection connection) {
try {
if(resultSet != null) {
resultSet.close();
}
if(statement != null) {
statement.close();
}
if(connection != null) {
connection.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
Apache-DBUtils
public void test() throws Exception {
Connection connection = JDBCUtilsByDruid.getConnection();
QueryRunner queryRunner = new QueryRunner();
String sql = "select * from actor where id > ?";
//new BeanListHandler<>(Actor.class) 将resultset -> Actor对象 -> 封装到ArrayList
//底层使用反射机制获取Actor类的属性,然后封装
//底层得到的resultset会在query关闭,PreparedStatment也会关闭
List<Actor> query =
queryRunner.query(connection, sql, new BeanListHandler<>(Actor.class), 1);
for (Actor actor : query) {
System.out.println(actor.getId() + actor.getName());
}
//返回单行记录
String sql1 = "select * from actor where id = ?";
Actor query1 =
queryRunner.query(connection, sql, new BeanHandler<>(Actor.class), 1);
//返回单行单列,返回的就是Object
String sql2 = "select name from actor where id = ?";
Object query2 = queryRunner.query(connection, sql, new ScalarHandler<>(), 1);
JDBCUtilsByDruid.close(null,null,connection);
}
DML
public void test() throws Exception {
Connection connection = JDBCUtilsByDruid.getConnection();
QueryRunner queryRunner = new QueryRunner();
String sql = "update actor set name = ? where id = ?";
//执行dml操作是 queryRunner.update()
//返回值是受影响的行数
int row = queryRunner.update(connection, sql, "aaa", 1);
System.out.println(row > 0 ? "执行成功" : "没有影响");
JDBCUtilsByDruid.close(null,null,connection);
}
BasicDAO
public class BasicDAO<T> {
private QueryRunner qr = new QueryRunner();
public int update(String sql, Object...parameters) throws SQLException {
Connection connection = null;
try {
connection = JDBCUtilsByDruid.getConnection();
int update = qr.update(connection, sql, parameters);
return update;
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
JDBCUtilsByDruid.close(null,null,connection);
}
}
public List<T> queryMulti(String sql, Class<T> clazz, Object...parameters) throws SQLException {
Connection connection = null;
try {
connection = JDBCUtilsByDruid.getConnection();
List<T> query = qr.query(connection, sql, new BeanListHandler<T>(clazz), parameters);
return query;
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
JDBCUtilsByDruid.close(null,null,connection);
}
}
public T querySingle(String sql, Class<T> clazz, Object...parameters) {
Connection connection = null;
try {
connection = JDBCUtilsByDruid.getConnection();
return qr.query(connection, sql, new BeanHandler<T>(clazz), parameters);
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
JDBCUtilsByDruid.close(null,null,connection);
}
}
public Object queryScalar(String sql, Object...parameters) {
Connection connection = null;
try {
connection = JDBCUtilsByDruid.getConnection();
return qr.query(connection, sql, new ScalarHandler(), parameters);
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
JDBCUtilsByDruid.close(null,null,connection);
}
}
}
ActorDAO、GoodsDAO可以继承BasicDAO,在Service类中使用