目录
JDBC入门
JDBC各个类详解
DriverManager:驱动管理对象
Connection:数据库连接对象
Statement:执行sql的对象
PreparedStatement:执行sql的对象
ResultSet:结果集对象,封装查询结果
抽取JDBC工具类
JDBC 登录案例
JDBC控制事务
* JDBC本质:其实是官方(sun公司)定义的一套操作所有关系型数据库的规则,即接口。各个数据库厂商去实现这套接口,提供数据库驱动jar包。我们可以使用这套接口(JDBC)编程,真正执行的代码是驱动jar包中的实现类。
2. 快速入门:基本操作步骤在代码部分。这只是一段了解JDBC的代码,并不完善,完善代码请看后面。
package JDBC; import java.sql.Connection; import java.sql.DriverManager; import java.sql.Statement; /** * 导包:导入驱动jar包 mysql-connector-java-5.1.37-bin.jar * 1.复制mysql-connector-java-5.1.37-bin.jar到项目的libs目录下 * 2.右键-->Add As Library * <p> * 1. 导入驱动jar包 mysql-connector-java-5.1.37-bin.jar * 2. 注册驱动 * 3. 获取数据库连接对象 Connection * 4. 定义sql * 5. 获取执行sql语句的对象 Statement * 6. 执行sql,接受返回结果 * 7. 处理结果 * 8. 释放资源 */ public class jdbcConnect { public static void main(String[] args) throws Exception { // 1. 加载驱动 Class.forName( "com.mysql.jdbc.Driver" ); // 2. 获取数据连接对象 Connection conn = DriverManager.getConnection( "jdbc:mysql://localhost:3306/school", "root", "root" ); // 3. 定义sql String sql = "UPDATE student SET id =101 WHERE id=102"; // 4. 获取执行sql语句的对象 Statement Statement stat = conn.createStatement(); // 5. 执行sql // 返回值是受影响的行数 int i = stat.executeUpdate( sql ); // 6. 处理结果 System.out.println( i ); // 6. 释放资源 stat.close(); conn.close(); } } ==================================================== 1 Process finished with exit code 0功能: (1)注册驱动:告诉程序该使用哪一个数据库驱动jar static void registerDriver(Driver driver) :注册与给定的驱动程序 DriverManager 。 写代码使用: Class.forName("com.mysql.jdbc.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包可以省略注册驱动的步骤。 (2) 获取数据库连接: * 方法:static Connection getConnection(String url, String user, String password) * 参数: * url:指定连接的路径 * 语法:jdbc:mysql://ip地址(域名):端口号/数据库名称 * 例子:jdbc:mysql://localhost:3306/db3 * 细节:如果连接的是本机mysql服务器,并且mysql服务默认端口是3306,则url可以简写为:jdbc:mysql:///数据库名称 * user:用户名 * password:密码
功能: 1. 获取执行sql 的对象 * Statement createStatement() * PreparedStatement prepareStatement(String sql) 2. 管理事务: * 开启事务:setAutoCommit(boolean autoCommit) :调用该方法设置参数为false,即开启事务 * 提交事务:commit() * 回滚事务:rollback()
功能: 1. boolean execute(String sql) :可以执行任意的sql 了解 2. int executeUpdate(String sql) :执行DML(insert、update、delete)语句、DDL(create,alter、drop)语句 * 返回值:影响的行数,可以通过这个影响的行数判断DML语句是否执行成功 返回值>0的则执行成功,反之,则失败。 3. ResultSet executeQuery(String sql) :执行DQL(select)语句 ,返回一个结果集 案例:向employee表添加一条数据。
package JDBC; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; /** * student表 添加一条语句 insert */ public class JDBCDemo1 { public static void main(String[] args) { Statement stat = null; Connection conn = null; try { // 1.加载驱动 Class.forName( "com.mysql.jdbc.Driver" ); // 2.定义sql String sql = "insert into employee values(7,'赵虎',19,2)"; // 3.获取Connection对象 conn = DriverManager.getConnection( "jdbc:mysql://localhost:3306/school", "root", "root" ); // 4.获取sql对象 stat = conn.createStatement(); // 5.执行sql int count = stat.executeUpdate( sql ); // 6.操作结果 if (count > 0) { System.out.println( "插入数据成功" ); } else { System.out.println( "插入数据失败" ); } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } finally { // 7.释放资源 // 防止空指针异常 if (stat != null) { try { stat.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn != null) { try { stat.close(); } catch (SQLException e) { e.printStackTrace(); } } } } } ================================================= 插入数据成功 Process finished with exit code 0(1) SQL注入问题:在拼接sql时,有一些sql的特殊关键字参与字符串的拼接。会造成安全性问题 1. 输入用户随便,输入密码:a' or 'a' = 'a 2. sql:select * from user where username = 'fhdsjkf' and password = 'a' or 'a' = 'a'
(2)解决sql注入问题:使用PreparedStatement对象来解决 预编译的SQL:参数使用?作为占位符 步骤: 1. 导入驱动jar包 mysql-connector-java-5.1.37-bin.jar 2. 注册驱动 3. 获取数据库连接对象 Connection 4. 定义sql * 注意:sql的参数使用?作为占位符。 如:select * from user where username = ? and password = ?; 5. 获取执行sql语句的对象 PreparedStatement Connection.prepareStatement(String sql) 6. 给?赋值: * 方法: setXxx(参数1,参数2) * 参数1:?的位置编号 从1 开始 * 参数2:?的值 7. 执行sql,接受返回结果,不需要传递sql语句 8. 处理结果 9. 释放资源
(3) 注意:后期都会使用PreparedStatement来完成增删改查的所有操作 1. 可以防止SQL注入 2. 效率更高
功能:
* boolean next(): 游标向下移动一行,判断当前行是否是最后一行末尾(是否有数据),如果是,则返回false,如果不是则返回true * getXxx(参数):获取数据 * Xxx:代表数据类型 如: int getInt() , String getString() * 参数: 1. int:代表列的编号,从1开始 如: getString(1) 2. String:代表列名称。 如: getDouble("id") 表示获取employee表里id列的值
* 使用步骤: 1. 游标向下移动一行 2. 判断是否有数据 3. 获取数据 案例:定义一个方法,查询emp表的数据将其封装为对象,然后装载集合,返回。(省略了定义employee实体类的过程)
package JDBC; import domain.employee; import java.sql.*; import java.util.ArrayList; import java.util.List; /** * * 定义一个方法,查询emp表的数据将其封装为对象,然后装载集合,返回。 * 1. 定义Emp类 * 2. 定义方法 public List<Emp> findAll(){} * 3. 实现方法 select * from emp; */ public class JDBCDemo4 { public static void main(String[] args) { //调用findAll方法 List<employee> list = new JDBCDemo4().findAll(); for (employee employee : list) { System.out.println( employee ); } } public List<employee> findAll() { Connection conn = null; Statement stat = null; ResultSet rs = null; List<employee> list = null; try { // 1.加载驱动 Class.forName( "com.mysql.jdbc.Driver" ); // 2.定义sql String sql = "select * from employee"; // 3.获取Connection对象 conn = DriverManager.getConnection( "jdbc:mysql://localhost:3306/school", "root", "root" ); // 4.获取执行sql的对象 stat = conn.createStatement(); // 5.执行sql rs = stat.executeQuery( sql ); // 6.处理结果,遍历结果集,封装对象,装载到集合中 list = new ArrayList<employee>(); employee emp = null; while (rs.next()) { //创建employee对象 emp = new employee(); emp.setId( rs.getInt( "id" ) ); emp.setName( rs.getString( "name" ) ); emp.setAge( rs.getInt( "age" ) ); emp.setDep_id( rs.getInt( "dep_id" ) ); //添加到集合中 list.add( emp ); } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } finally { // 释放资源 if (rs != null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (stat != null) { try { stat.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } return list; } } ============================================================ employee{id=1, name='张三', age=20, dep_id=3} employee{id=2, name='李四', age=21, dep_id=3} employee{id=3, name='王五', age=20, dep_id=3} employee{id=4, name='老王', age=20, dep_id=2} employee{id=5, name='大王', age=22, dep_id=2} employee{id=6, name='小王', age=18, dep_id=2} Process finished with exit code 0目的:简化书写
分析: 1. 注册驱动抽取 2. 抽取一个方法获取连接对象 ** 不传递参数,必须保证工具类的通用性能,需要创建一个配置文件,配置文件里存放数据库地址,用户名,密码,驱动 3. 抽取一个方法释放资源
配置文件:
url=jdbc:mysql://localhost:3306/school user=root password=root driver=com.mysql.jdbc.DriverJDBC工具类:
package tool; import java.io.FileReader; import java.io.IOException; import java.net.URL; import java.sql.*; import java.util.Properties; /** * JDBC工具类 */ public class JDBCUtils { /** * 静态变量才能被静态代码块和静态方法访问 */ private static String url; private static String user; private static String password; private static String driver; /** *文件的读取,只需要读取一次就可以拿到这些值。可以使用静态代码块,随着类的加载而执行一次 * 静态代码块中的异常只能处理,不能抛出 */ static { //读取资源文件,获取值 try { // 1.创建properties集合类 Properties pro = new Properties(); //获取src路径下的文件的方式--->ClassLoader 类加载器 ClassLoader classLoader = JDBCUtils.class.getClassLoader(); URL res = classLoader.getResource( "jdbc.properties" ); String path = res.getPath(); // 2.加载文件 // pro.load( new FileReader( "E:\\IdeaProject\\ShuangYuan\\day15\\jdbc.properties" ) ); pro.load( new FileReader( path ) ); // 3.获取数据,赋值 url = pro.getProperty( "url" ); user = pro.getProperty( "user" ); password = pro.getProperty( "password" ); driver=pro.getProperty( "driver" ); Class.forName( driver ); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } /** * 获取连接 * * @return Connection连接对象 */ public static Connection getConnection() throws SQLException { return DriverManager.getConnection( url,user,password ); } /** * 释放资源 * * @param stat * @param conn */ public static void close(ResultSet rs, Statement stat, Connection conn) { if (rs != null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (stat != null) { try { stat.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }需求: 1. 通过键盘录入用户名和密码 2. 判断用户登录是否成功,反馈信息打印到控制台
创建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','456');登录代码:
package JDBC; import tool.JDBCUtils; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Scanner; /** * 需求: * 1. 通过键盘录入用户名和密码 * 2. 判断用户是否登录成功 */ public class JDBCDemo5 { 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(); boolean login = new JDBCDemo5().login( username, password ); if(login){ System.out.println("登录成功"); }else{ System.out.println("登录失败"); } } /** * 登录方法 * @param username * @param password * @return */ public boolean login(String username, String password) { // 若用户名和密码为空,返回false if (username == null || password == null) { return false; } Connection conn = null; Statement stat = null; ResultSet rs = null; try { // 1.获取连接 conn = JDBCUtils.getConnection(); // 2.定义sql String sql = "select * from user where username = '" + username + "' and password = '" + password + "' "; // 3.获取执行sql的对象 stat = conn.createStatement(); // 4.执行SQL语句 rs = stat.executeQuery( sql ); return rs.next(); } catch (SQLException e) { e.printStackTrace(); } finally { // 5.释放资源 JDBCUtils.close( rs, stat, conn ); } return false; } }测试结果:
请输入用户名: zhangsan 请输入密码: 123 登录成功 Process finished with exit code 0 ========================================== 请输入用户名: zhangsan 请输入密码: 32424324 登录失败 Process finished with exit code 0 ========================================== 请输入用户名: lisi 请输入密码: 456 登录成功 Process finished with exit code 0注意:上述代码是有问题的,如下用户名和密码在数据库中都不存在,但是却登录成功了,这是为什么呢?其实我们看下它的sql语句就可以看出来,最后的字符串拼接,or 'a' = 'a' ,or后面是一个恒等式,所以自然就成立,这就是SQL注入问题--(SQL注入,就是通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令)。 sql:select * from user where username = 'fhdsjkf' and password = 'a' or 'a' = 'a'
请输入用户名: dasd sa 请输入密码: a' or 'a' = 'a 登录成功 Process finished with exit code 0解决sql注入问题的方案:使用PreparedStatement对象来解决
预编译的SQL:参数使用 ? 作为占位符
// 1.获取连接 conn = JDBCUtils.getConnection(); // 2.定义sql String sql = "SELECT * from user WHERE username=? and password=?"; // 3.获取执行sql的对象 ps = conn.prepareStatement( sql ); // 4.给?赋值 ps.setString(1,username); ps.setString( 2,password ); // 5.执行查询,不需要传递sql rs = ps.executeQuery();(1)事务:一个包含多个步骤的业务操作。如果这个业务操作被事务管理,则这多个步骤要么同时成功,要么同时失败。 (2 ) 操作: 1. 开启事务 2. 提交事务 3. 回滚事务 (3)使用Connection对象来管理事务 * 开启事务:setAutoCommit(boolean autoCommit) :调用该方法设置参数为false,即开启事务 * 在执行sql之前开启事务 * 提交事务:commit() * 当所有sql都执行完提交事务 * 回滚事务:rollback() * 在catch中回滚事务
public class JDBCDemo10 { public static void main(String[] args) { 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 (Exception e) { //事务回滚 try { if(conn != null) { conn.rollback(); } } catch (SQLException e1) { e1.printStackTrace(); } e.printStackTrace(); }finally { JDBCUtils.close(pstmt1,conn); JDBCUtils.close(pstmt2,null); }