JDBC(一)
Java数据库连接,(Java Database Connectivity,简称JDBC)是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法。JDBC是面向关系型数据库的。
JDBC本质:官方(sun公司)定义的一套操作所有关系型数据库的规则,即接口。各个数据库厂商去实现这套接口,提供数据库驱动jar包。我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类。
(理解:Heart接口,Person实现类。Heart h = new Person()
(多态),执行h.beat,其实执行的是Person实现类中重写的beat方法)
JDBC入门
步骤:
1.导入jar包
在项目中创建一个libs文件夹,将jar包复制到其中。
注意要将复制的jar包导入驱动:
2.注册驱动
3.获取数据库连接对象
4.定义sql语句
5.执行sql的对象Statement
6.执行sql
7.处理结果
8.释放资源
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement;
public class Demo01 { public static void main(String[] args) throws ClassNotFoundException, SQLException { Class.forName("com.mysql.jdbc.Driver"); Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc_study", "root", "1234"); String sql = "update employees set age = 30 where name = 'jay';"; Statement statement = connection.createStatement(); int count = statement.executeUpdate(sql); System.out.println("一共执行了"+count+"条数据"); statement.close(); connection.close(); } }
|
JDBC各个类分析
- DriverManager:驱动管理对象
- Connection:数据库连接对象
- Statement:执行sql的对象
- ResultSet:结果集对象,封装查询结果
- PreparedStatement:执行sql的对象
DriverManager:驱动管理对象
功能:
1.注册驱动:告诉程序该使用哪一个数据库驱动(jar包)
static void registerDriver(Dirver driver)
:注册与给定的驱动DriverManager
使用时:Class.forName("com.mysql.jdbc.Driver");
源码:在com.mysql.jdbc.Driver类中的静态代码块如下:
1 2 3 4 5 6 7
| static { try { DriverManager.registerDriver(new Driver()); } catch (SQLException var1) { throw new RuntimeException("Can't register driver!"); } }
|
注意:mysql5之后的驱动jar包可以省略注册驱动步骤
原因:在META-INF文件夹下有以下文件:
2.获取数据库连接
方法:static Connection getConnection(String url,String user,String password)
三个参数:
- url:指定连接的路径
- 语法:
jdbc:mysql://ip地址(域名):端口号/数据库名称
- 例子:
jdbc:mysql://localhost:3306/jdbc_study
- 细节:如果连接的是本地的mysql服务并且服务器默认端口是3306,则url可以简写为:
jdbc:mysql:///数据库名称
- user:用户名
- password:密码
Connection:数据库连接对象
功能:
1.执行sql的对象
- Statement createStatement()
- PreparedStatement prepareStatement(String sql)
2.管理事务
- 开启事务:
setAutoCommit(boolean autoCommit)
,调用该方法设置参数为false,即开启事务
- 提交事务:
commit()
- 回滚事务:
rollback()
Statement:执行sql的对象
功能:
1.执行sql
boolean execute(String sql)
:可以执行任意的sql(了解)
int executeUpdate(String sql)
:执行DML、DDL语句。
- 返回值:影响的行数,即操作的记录数。可以通过这个影响行数判断DML语句是否成功。返回值>0则表示执行成功,反之则失败。
ResultSet executeQuery(String sql)
:执行DQL语句
ResultSet:结果集对象,封装查询结果
两个方法:
boolean next()
:游标向下移动一行,并判断当前行是否是最后一行,如果是返回false
,如果不是则返回true
getXxx(参数)
:获取数据
- Xxx表示数据类型,如:
int getInt()
,String getString()
- 参数:
- 参数为int:表示列的编号,从1开始,如:
getString(1)
- 参数为string:表示列的名称。如:
getInt("age")
例子:使用jdbc查询employees表中的id=1和id=4的数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| package day01;
import java.sql.*;
public class Demo03 { public static void main(String[] args) throws ClassNotFoundException, SQLException { Class.forName("com.mysql.jdbc.Driver"); Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc_study", "root", "1234"); String sql = "select * from employees where id = 1 or id = 4;"; Statement statement = connection.createStatement(); ResultSet resultSet = statement.executeQuery(sql); System.out.println("查询结果为:"); while (resultSet.next()){ int id = resultSet.getInt(1); String name = resultSet.getString("name"); int age = resultSet.getInt("age"); System.out.println(id+" "+name+" "+age); } statement.close(); connection.close(); resultSet.close(); } }
|
执行结果为:
PreparedStatement:执行sql的对象
sql注入基础原理(超详细)
史上最完整的MySQL注入
sql注入问题:在拼接sql时,有一些sql的特殊关键字符参与字符串的拼接。会造成安全性问题
解决sql注入问题:使用PreparedStatement对象。
预编译的sql:sql的参数使用?
作为占位符,如:
1
| select * from user where username = ? and password = ?;
|
使用步骤:
- 导入jar包
- 注册驱动
- 获取数据库连接Connection对象
- 定义sql(注意sql参数使用
?
作为占位符。如select * from user where username = ? and password = ?
;)
- 获取执行sql的PreparedStatement对象:
connection.preparedStatement(String sql)
- 给
?
赋值:
- 方法:setXxx(参数1,参数2) (Xxx为数据类型,如getInt)
- 执行sql,接受返回结果,不需要传递sql语句
- 处理结果
- 释放资源
注意:后面我们都会使用PreparedStatement来完成增删改查:
JDBC增删改练习
针对我们现有的employees表,我们通过JDBC进行以下操作(DML):
添加一条记录
符合规范的jdbc代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| package day01;
import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement;
public class Demo02 { public static void main(String[] args) { Connection connection = null; Statement statement = null; try { Class.forName("com.mysql.jdbc.Driver"); connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc_study", "root", "1234"); String sql = " insert into employees values (4,'tom',18);"; statement = connection.createStatement(); int count = statement.executeUpdate(sql); System.out.println("一共执行了"+count+"条记录"); if (count>0){ System.out.println("sql执行成功!"); }else { System.out.println("sql执行失败!"); } } catch (ClassNotFoundException | SQLException e) { e.printStackTrace(); } finally { if (statement!=null){ try { statement.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } if (connection!=null){ try { connection.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } } } }
|
修改记录
在上述代码的基础上,将String sql = " insert into employees values (4,'tom',18);";
修改为:
1
| String sql = " update employees set age = 20 where name = 'tom';";
|
删除一条记录
在上述代码的基础上,将String sql = " update employees set age = 20 where name = 'tom';";
修改为:
1
| String sql = " delete from employees where id = 2;";
|
JDBC查询数据(JDBC select语句)
查询emp表中所有数据并将其封装为对象,然后装载集合。将查询结果打印在控制台上。
其中emp表的数据和结构如下:
- 定义emp类
- 定义方法
public List<Emp> findAll(){}
- 实现功能
select * from emp;
我们在domain包下创建Emp.java类,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| package day01.domain;
import java.math.BigDecimal; import java.util.Date;
public class Emp { private Integer id; private String ename; private Integer job_id; private Integer mgr; private Date joindate; private BigDecimal salary; private BigDecimal bonus; private Integer dept_id;
@Override public String toString() { return "Emp{" + "id=" + id + ", ename='" + ename + '\'' + ", job_id=" + job_id + ", mgr=" + mgr + ", joindate=" + joindate + ", salary=" + salary + ", bonus=" + bonus + ", dept_id=" + dept_id + '}'; } }
|
(省略Alt+Insert
快速生成的Getter和Setter方法)
然后创建主启动类Demo05.java(含findAll方法),代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
| package day01;
import day01.domain.Emp;
import java.math.BigDecimal; import java.sql.*; import java.util.ArrayList; import java.util.List;
public class Demo05 { public static void main(String[] args) { List<Emp> list = findAll(); for (Emp emp : list) { System.out.println(emp); } } public static List<Emp> findAll(){ Connection connection = null; Statement statement = null; ResultSet resultSet = null; List<Emp> empList = null; try { Class.forName("com.mysql.jdbc.Driver"); connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc_study", "root", "1234"); String sql = "select * from emp;"; statement = connection.createStatement(); resultSet = statement.executeQuery(sql); empList = new ArrayList<>(); Emp emp = null; while (resultSet.next()){ int id = resultSet.getInt("id"); String ename = resultSet.getString("ename"); int job_id = resultSet.getInt("job_id"); int mgr = resultSet.getInt("mgr"); Date joindate = resultSet.getDate("joindate"); BigDecimal salary = resultSet.getBigDecimal("salary"); BigDecimal bonus = resultSet.getBigDecimal("bonus"); int dept_id = resultSet.getInt("dept_id"); 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); empList.add(emp); }
} catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException throwables) { throwables.printStackTrace(); } finally { if (statement!=null){ try { statement.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } if (connection!=null){ try { connection.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } if (resultSet!=null){ try { resultSet.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } } return empList; } }
|
执行结果如下:
这里给出数据库类型和Java数据类型对应如下:
类型名称 |
显示长度 |
数据库类型 |
JAVA类型 |
JDBC类型索引(int) |
VARCHAR |
L+N |
VARCHAR |
java.lang.String |
12 |
CHAR |
N |
CHAR |
java.lang.String |
1 |
BLOB |
L+N |
BLOB |
java.lang.byte[] |
-4 |
TEXT |
65535 |
VARCHAR |
java.lang.String |
-1 |
INTEGER |
4 |
INTEGER UNSIGNED |
java.lang.Long |
4 |
TINYINT |
3 |
TINYINT UNSIGNED |
java.lang.Integer |
-6 |
SMALLINT |
5 |
SMALLINT UNSIGNED |
java.lang.Integer |
5 |
MEDIUMINT |
8 |
MEDIUMINT UNSIGNED |
java.lang.Integer |
4 |
BIT |
1 |
BIT |
java.lang.Boolean |
-7 |
BIGINT |
20 |
BIGINT UNSIGNED |
java.math.BigInteger |
-5 |
FLOAT |
4+8 |
FLOAT |
java.lang.Float |
7 |
DOUBLE |
22 |
DOUBLE |
java.lang.Double |
8 |
DECIMAL |
11 |
DECIMAL |
java.math.BigDecimal |
3 |
BOOLEAN |
1 |
同TINYINT |
|
|
ID |
11 |
PK (INTEGER UNSIGNED) |
java.lang.Long |
4 |
DATE |
10 |
DATE |
java.sql.Date |
91 |
TIME |
8 |
TIME |
java.sql.Time |
92 |
DATETIME |
19 |
DATETIME |
java.sql.Timestamp |
93 |
TIMESTAMP |
19 |
TIMESTAMP |
java.sql.Timestamp |
93 |
YEAR |
4 |
YEAR |
java.sql.Date |
91 |
JDBC工具类
JDBC工具类(可以体会到配置文件的优点)
从上述的[查询数据](#JDBC查询数据(JDBC select语句))例子中可以发现,有很多重复的代码。
所以我们试着抽取JDBC工具类:
目的:简化代码
分析:
-
抽取注册驱动
-
抽取一个方法来获取数据库连接对象
-
抽取一个方法来释放资源
例子:我们在创建util包下创建JDBCUtils.java类,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
| package day01.util;
import java.io.FileReader; import java.io.IOException; import java.sql.*; import java.util.Properties;
public class JDBCUtils { private static String url; private static String user; private static String password; private static String driver;
static { try { Properties properties = new Properties();
properties.load(new FileReader("src/jdbc.properties"));
url = properties.getProperty("url"); user = properties.getProperty("user"); password = properties.getProperty("password"); driver = properties.getProperty("driver");
Class.forName(driver); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } }
public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url,user,password); }
public static void close(Statement statement, Connection connection){ if (statement != null){ try { statement.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } if (connection != null){ try { connection.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } } public static void close(Statement statement, Connection connection, ResultSet resultSet){ if (statement != null){ try { statement.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } if (connection != null){ try { connection.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } if (resultSet != null){ try { resultSet.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } } }
|
在src文件夹下创建配置文件jdbc.properties文件,代码如下:
1 2 3 4
| url=jdbc:mysql://localhost:3306/jdbc_study user=root password=1234 driver=com.mysql.jdbc.Driver
|
然后我们创建主启动类Demo04.java,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| package day01;
import day01.domain.Emp; import day01.util.JDBCUtils;
import java.math.BigDecimal; import java.sql.*; import java.util.ArrayList; import java.util.List;
public class Demo04 { public static void main(String[] args) { List<Emp> list = findAll(); for (Emp emp : list) { System.out.println(emp); } }
public static List<Emp> findAll(){ Connection connection = null; Statement statement = null; ResultSet resultSet = null; List<Emp> empList = null; try { connection = JDBCUtils.getConnection(); String sql = "select * from emp;"; statement = connection.createStatement(); resultSet = statement.executeQuery(sql); empList = new ArrayList<>(); Emp emp = null; while (resultSet.next()){ int id = resultSet.getInt("id"); String ename = resultSet.getString("ename"); int job_id = resultSet.getInt("job_id"); int mgr = resultSet.getInt("mgr"); Date joindate = resultSet.getDate("joindate"); BigDecimal salary = resultSet.getBigDecimal("salary"); BigDecimal bonus = resultSet.getBigDecimal("bonus"); int dept_id = resultSet.getInt("dept_id"); 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); empList.add(emp); } } catch (SQLException throwables) { throwables.printStackTrace(); } finally { JDBCUtils.close(statement,connection,resultSet); } return empList; } }
|
执行结果和上述的[查询数据](#JDBC查询数据(JDBC select语句))例子的结果是一样的
JDBC登录案例
需求:
- 通过键盘录入用户名和密码
- 判断用户是否登录成功(用户名和密码存储在user表中)
现有user表如下:
JDBC工具类JDBCUtils.java和jdbc.properties配置文件同上JDBC工具类。
使用createStatement()来执行sql
创建主启动类Demo06.java代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| package day01.util;
import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Scanner;
public class Demo06 { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.print("请输入用户名:"); String username = scanner.nextLine(); System.out.print("请输入密码:"); String password = scanner.nextLine(); boolean flag = login(username, password); if (flag){ System.out.println("登陆成功!"); }else { System.out.println("登录失败!用户名或密码错误"); } }
public static boolean login(String username, String password){ Connection connection = null; Statement statement = null; ResultSet resultSet = null; if (username==null || password==null){ System.out.println("用户名或密码不能为空!"); return false; }else { try { connection = JDBCUtils.getConnection(); String sql = "select * from user where username = '"+username+"' and password = '"+password+"';"; statement = connection.createStatement(); resultSet = statement.executeQuery(sql); return resultSet.next(); } catch (SQLException throwables) { throwables.printStackTrace(); } finally { JDBCUtils.close(statement,connection,resultSet); } } return false; } }
|
这里会出现一个问题,那就是sql注入。关于sql注入在上文已经给出说明了。直接来看一个例子:
为了解决sql注入的问题,我们使用prepareStatement(sql)来执行sql
使用prepareStatement(sql)来执行sql
创建主启动类Demo07.java代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| package day01;
import day01.util.JDBCUtils;
import java.sql.*; import java.util.Scanner;
public class Demo07 { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.print("请输入用户名:"); String username = scanner.nextLine(); System.out.print("请输入密码:"); String password = scanner.nextLine(); boolean flag = login(username, password); if (flag){ System.out.println("登陆成功!"); }else { System.out.println("登录失败!用户名或密码错误"); } }
public static boolean login(String username, String password){ Connection connection = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null; if (username==null || password==null){ System.out.println("用户名或密码不能为空!"); return false; }else { try { connection = JDBCUtils.getConnection(); String sql = "select * from user where username = ? and password = ?;"; preparedStatement = connection.prepareStatement(sql); preparedStatement.setString(1,username); preparedStatement.setString(2,password); System.out.println("执行的sql为:"+sql); resultSet = preparedStatement.executeQuery(); return resultSet.next(); } catch (SQLException throwables) { throwables.printStackTrace(); } finally { JDBCUtils.close(preparedStatement,connection,resultSet); } } return false; } }
|
执行效果为:
在后续开发中我们都会使用PreparedStatement来完成增删改查
JDBC管理事务
关于事务的讲解,之前的笔记有讲到过。这里再简单说一下:
事务:一个包含多个步骤的业务操作。如果这个业务操作被事务管理,则这多个步骤要么同时成功,要么同时失败。
相关操作:
在JDBC中,我们使用Connection对象来管理事务:
- 开启事务:
setAutoCommit(boolean autoCommit)
:要调用该方法则设置参数为false,即开启事务
- 提交事务:
commit()
- 回滚事务:
rollback()
例子:
我们有account表如下:
JDBC工具类中添加close的重载方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public static void close(Statement statement1, Statement statement2, Connection connection) { if (statement1 != null) { try { statement1.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } if (statement2 != null) { try { statement2.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } if (connection != null) { try { connection.close(); } catch (SQLException throwables) { throwables.printStackTrace(); } } }
|
配置文件同上
创建主启动类Demo09.java,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| package day01;
import day01.util.JDBCUtils;
import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException;
public class Demo08 { public static void main(String[] args) { Connection connection = null; PreparedStatement preparedStatement1 = null; PreparedStatement preparedStatement2 = null; ResultSet resultSet = null; try { connection = JDBCUtils.getConnection(); connection.setAutoCommit(false); String sql1 = "update account set balance = balance - ? where NAME = ?"; String sql2 = "update account set balance = balance + ? where NAME = ?"; preparedStatement1 = connection.prepareStatement(sql1); preparedStatement2 = connection.prepareStatement(sql2); preparedStatement1.setDouble(1,500); preparedStatement1.setString(2,"zhangsan");
preparedStatement2.setDouble(1,500); preparedStatement2.setString(2,"lisi"); preparedStatement1.executeUpdate(); int i = 1/0; preparedStatement2.executeUpdate(); connection.commit(); } catch (Exception exception) { try { if (connection!=null){ connection.rollback(); } } catch (SQLException e) { e.printStackTrace(); } exception.printStackTrace(); } finally { JDBCUtils.close(preparedStatement1,preparedStatement2,connection); } } }
|