JDBC
JDBC概述
JDBC:java database activity,由sun公司推出的用于规定java连接数据库的一种规范(API)。
应用程序发送请求访问数据库数据时,java代码不能直接对数据库进行操作,而需要通过数据库厂家提供的数据库驱动来进行操作,而为了统一不同数据库公司编写的数据库驱动编写规范,sun公司就推出了JDBC技术。
JDBC操作mysql数据库的步骤:
1 加载mysql数据库驱动
方式1:DriverManager调用registerDriver()方法注册mysql驱动
DriverManager.reigester(new Driver());
通过查看源码,new Driver时Driver类的静态代码块调用了DriverManager.registerDriver()方法。
相当于mysql驱动注册了两次,这样不好。因此有了方式2,通过加载Driver类来实现驱动的加载。
方式2:Class.forName()方法加载mysql驱动
Class.forName("com.mysql.jdbc.Driver");
2 创建连接对象Connection
DriverManager调用getConnection()方法创建连接对象
String url = "jdbc:mysql://localhost:3306/mydb1";
String user = "root";
String password = "root";
Connection connection = DriverManager.getConnection(url, user, password);
3 创建sql语句执行对象statement
用连接对象connection创建sql语句执行对象statement。
Statement statement = connection.createStatement();
但是这种方式有数据注入隐患。因此用PrepareStatement替代。
PreparedStatement statement = connection.PrepateStatement();
4 执行sql语句,获取结果
executeQuery(String sql):用于select语句的查询,返回结果集
executeUpdate(String sql):用于DML中队表中数据的操作,返回int类型,改变的行数
execute():用于sql任意语句,返回值是boolean,仅且仅当select语句并且有结果时返回true,其他都为false
5 对结果进行操作
next():判断获取到的结果集的下一行是否有数据,有则返回true,否则falsse
getObject():以Object类型接收
getInt():以int类型接收
getString():以String类型接收
......
6 释放资源
原则是:后执行的先释放(close()方法)。
例子:
//1.加载mysql驱动
//相当于驱动注册了两次,这样不好,改用class.forName()
//DriverManager.registerDriver(new Driver());
Class.forName("com.mysql.jdbc.Driver");
//2.创建连接对象
String url = "jdbc:mysql://localhost:3306/mydb1";
String user = "root";
String password = "root";
Connection connection = DriverManager.getConnection(url, user, password);
//3.创建sql语句执行对象statement
Statement statement = connection.createStatement();
//4.执行sql语句,得到结果集
String sql = "select * from star";
ResultSet resultSet = statement.executeQuery(sql);
//5.操作结果集
while(resultSet.next()) {
int id = resultSet.getInt("sid");
String name = resultSet.getString("name");
int age = resultSet.getInt("age");
String sex = resultSet.getString("sex");
int price = resultSet.getInt("price");
int type = resultSet.getInt("type");
System.out.println(id+"--"+name+"--"+age+"--"+sex+"--"+price+"--"+type);
System.out.println("==========================================");
}
//6.关闭资源
resultSet.close();
statement.close();
connection.close();
单元测试JUnit的用法
JUnit方便了我们对代码进行测试。
注意事项
- 必须有@Test注释,导入Junit4包
- 必须是无返回值,并且方法无参
这里也用上面的代码重写了一次单元测试案例,并对上面的数据库操作代码进行了改善
例:
@Test
public void test() {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
//1.加载mysql驱动
try {
Class.forName("com.mysql.jdbc.Driver");
//2.创建数据库连接对象
String url = "jdbc:mysql://localhost:3306/mydb1";
String user = "root";
String password = "root";
connection = DriverManager.getConnection(url,user,password);
//3.通过连接对象,创建sql语句执行对象
statement = connection.createStatement();
//4.执行sql语句,获取结果集
String sql = "select * from star";
resultSet = statement.executeQuery(sql);
//5.操作结果集
while(resultSet.next()) {
int id = resultSet.getInt("sid");
String name = resultSet.getString("name");
int age = resultSet.getInt("age");
String sex = resultSet.getString("sex");
int price = resultSet.getInt("price");
int type = resultSet.getInt("type");
System.out.println("["+id+"--"+name+"--"+age+"--"+sex+"--"+price+"--"+type+"]");
System.out.println("=========================================");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//6.释放资源(后执行的先释放)
if(resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
对操作数据库java代码的优化
通过上面的代码书写,我们可以发现代码的复用性很差,因此想要抽取方法从而对代码进行优化。
通过观察,我们可以将创建连接对象以及释放资源的代码进行方法抽取。
因此:
抽取后的JDBC_Utils工具类:
public class JDBC_Utils {
//静态代码块加载驱动
static {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws SQLException {
String url = "jdbc:mysql://localhost:3306/mydb1";
String user = "root";
String password = "root";
Connection connection = DriverManager.getConnection(url, user, password);
return connection;
}
public static void release(ResultSet resultSet, Statement statement, Connection connection) {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
抽取方法后,我们又发现,这种情况下,每次对数据库进行操作都要加载一次mysql驱动,这样不好。
因此我将驱动加载放到JDBC_Util类的静态代码块位置,这样每次调用JDBC_Utils类创建连接对象之前,
都会先加载静态代码块加载mysql驱动,而且保证了只加载一次。
优化后的数据库操作代码:
@Test
public void test() {
Connection connection = null;
Statement statement = null;
ResultSet resultSet = null;
try {
//1.加载mysql驱动
//2.创建数据库和驱动的连接对象
connection = JDBC_Utils.getConnection();
//3.创建sql语句的执行对象statement
statement = connection.createStatement();
//4.执行sql语句,获得结果集
String sql = "select * from star";
resultSet = statement.executeQuery(sql);
//5.操作结果集中的数据
while (resultSet.next()) {
int sid = resultSet.getInt("sid");
String name = resultSet.getString("name");
int age = resultSet.getInt("age");
String sex = resultSet.getString("sex");
int price = resultSet.getInt("price");
int type = resultSet.getInt("type");
System.out.println(sid+"--"+name+"--"+age+"--"+sex+"--"+price+"--"+type);
System.out.println("=========================================");
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
//6.关闭资源
JDBC_Utils.release(resultSet, statement, connection);
}
}
对JDBC_Utils类进一步优化
上面代码优化后,代码的复用性明显加强,也更符合java的单一设计思想。
但是,我们看到JDBC_Utils的getConnection中的代码还是不够好,为什么?
String url = "jdbc:mysql://localhost:3306/mydb1";
String user = "root";
String password = "root";
我们如果要更换数据库连接时,就要改动JDBC_Utils类下的url,user,和password,因此我们要进一步对齐进行改进。
我们可以将对数据库操作的代码中用到的常量提取出来写在配置文件中,在使用时再从配置文件中读取,这样我们在更换数据库连接时,只需要更改配置文件中的txt数据就行了,而不用对java代码进行改动。
那么,配置文件写好后,怎么在java中读取配置文件中的数据呢?
在JDBC_Utils类中,定义成员静态变量,使它们能被所有方法公用。
在静态代码块中,从配置文件中读取到对应的值。
因此:
创建配置文件,配置文件在src目录下,扩展名是properties
jdbc_configs.properties
配置文件前后都不能有空格,以key=value的形式保存数据
properties类介绍:
Properties 类表示了一个持久的属性集。
Properties 可保存在流中或从流中加载。属性列表中每个键及其对应值都是一个字符串。
public void load(InputStream inStream) throws IOException:
从输入流中读取属性列表(键和元素对)。此方法返回后,指定的流仍保持打开状态。
方式一:IO输入流读取配置文件数据
创建流对象,连接源配置文件
InputStream is = new FileInputStream(new File("src/jdbc_configs.properties"));
创建properties对象,properties对象可以从流中加载数据
Properties properties = new Properties();
properties.load(is);
读取配置文件中的数据
driverStr = properties.getProperty("driverStr");
url = properties.getProperty("url");
user = properties.getProperty("user");
password = properties.getProperty("password");
//方式二:(推荐使用,效率更高)
用这种方法,自动遍历工程找到对应的配置文件,给定的文件名不需properties后缀
ResourceBundle bundle = ResourceBundle.getBundle("jdbc_configs");
driverStr = bundle.getString("driverStr");
url = bundle.getString("url");
user = bundle.getString("user");
password = bundle.getString("password");
改进后的JDBC_Utils代码:
public class JDBC_Utils {
private static String driverStr;
private static String url;
private static String user;
private static String password;
//静态代码块加载驱动
static {
try {
//读取配置文件
//方式二:
ResourceBundle bundle = ResourceBundle.getBundle("jdbc_configs");
driverStr = bundle.getString("driverStr");
url = bundle.getString("url");
user = bundle.getString("user");
password = bundle.getString("password");
Class.forName(driverStr);
} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws SQLException {
Connection connection = DriverManager.getConnection(url, user, password);
return connection;
}
public static void release(ResultSet resultSet, Statement statement, Connection connection) {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
练习:实现一个用户登录功能
1.需求
在控制台输入用户名和密码,查询数据库,如果数据库存在当前用户,显示登录成功!
如果数据库不存在当前用户,显示登录失败!
分析:
1:首先创建一个数据库user用于存储用户信息(实现设置部分用户账号密码用于登陆)
2:键盘录入用户的账号和密码
3:接下来就是要从数据库中筛选查询和录入账户和密码一致的用户
3.1:加载数据库mysql驱动
3.2:创建连接对象
3.3:创建sql语句执行对象
3.4:执行sql语句,获得结果集
3.5:操作结果集
3.6:关闭资源
操作和之前的例子差不多
Client类
/**
* 这是用户的登陆类
* @author Administrator
*
* 客户端Client类,只提供用户登录操作的提示以及信息的反馈
*/
public class Client {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入账号:");
String username = sc.nextLine();
System.out.println("请输入密码:");
String password = sc.nextLine();
DoLogin service = new DoLogin();
User user = service.findUser(username, password);
if(user != null) {
System.out.println("登陆成功");
}else {
System.out.println("登录失败");
}
}
}
DoLogin登陆操作类
public class DoLogin {
public User findUser(String username, String password) {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
User user = null;
try {
// 3.1:加载驱动
// 3.2:获得连接
connection = Util.getConnection();
// 3.3:创建sql语句执行对象
// statement = connection.createStatement();
// String sql = "select * from user where username='"+username+"'
// and password='"+password+"'";
// sql语句拼接的方式会出现sql语句注入的隐患,因此应该采用较安全的执行对象,用PrepareStatement可以对sql语句进行预编译
String sql = "select * from user where username=? and password=?";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, username);
preparedStatement.setString(2, password);
// 3.4:执行sql语句,获得结果集
resultSet = preparedStatement.executeQuery();
// 3.5:操作结果集
while (resultSet.next()) {
user = new User();
String username2 = resultSet.getString("username");
String password2 = resultSet.getString("password");
String name = resultSet.getString("name");
int age = resultSet.getInt("age");
String sex = resultSet.getString("sex");
user.setUsername(username2);
user.setPassword(password2);
user.setName(name);
user.setAge(age);
user.setSex(sex);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 3.6:关闭资源
Util.release(connection, preparedStatement, resultSet);
}
return user;
}
}
Util工具类
public class Util {
private static String driverStr;
private static String url;
private static String user = null;
private static String password = null;
static {
// 加载驱动
try {
// 通过配置文件获得常量
ResourceBundle bundle = ResourceBundle.getBundle("jdbc_configs2");
driverStr = bundle.getString("driverStr");
url = bundle.getString("url");
user = bundle.getString("user");
password = bundle.getString("password");
Class.forName(driverStr);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Connection getConnection() {
// 3.2:创建连接对象
Connection connection = null;
try {
connection = DriverManager.getConnection(url, user, password);
} catch (SQLException e) {
e.printStackTrace();
}
return connection;
}
public static void release(Connection connection, Statement statement, ResultSet resultSet) {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
数据库注入问题
通过上面这个例子,可以看到用statement执行sql语句操作时,会有SQL语句数据库注入的隐患,因此应该用PreparedStatement来避免.
Prepared是一个接口,父接口是Statement,它用于表示预编译的 SQL 语句的对象。
通过connection对象创建
- prepareStatement(String sql) ;创建prepareStatement对象
- sql表示预编译的sql语句,如果sql语句有参数通过?来占位
过setxxx方法来指定参数
- setxxx(int i,Obj obj); i 指的就是问号的索引(指第几个问号,从1开始),xxx是类型(eg:int,String,Long)
实例
String sql = "select *from user where username =? and password =?";
//创建prepareStatement
PreparedStatement statement = connection.prepareStatement(sql);
//设置参数
statement.setString(1, username);
statement.setString(2, password);
ResultSet resultSet = statement.executeQuery();