今日内容–jdbc
- jdbc基本概念
- 入门小案例
- 四大核心类介绍
- sql注入
- 登录案例完善
一.jdbc基础
1.概念
- 两个层面去理解jdbc
- 干什么的?
- 如何实现的?
1.全称:jdbc: java database connectivity 直译:java 数据库连接
2.表面意思:java连接并操作数据库 -- 解释jdbc是干什么的
3.本质含义:jdbc是sun公司制定的一套连接关系型数据库需要遵循的规范,即接口。而各个关系型数据库厂商自己来实现这个接口。程序员只需要面向接口编程。 -- 解释jdbc如何实现 连接并操作数据库 的
即:jdbc通过定义了一些接口,借助接口中定义的抽象方法,来规定java要如何连接数据库,如何操作数据库。
而接口中的抽象方法的具体实现,会因为数据库的不同实现方式也不一样,但是这个不是我们关心的,因为这些实现类是数据库厂商提供的,我们只需会调用接口中的方法就可以了。
所以:我们今天学习的内容并不是研究java到底如何连上数据库服务器的,这些底层也不是我们研究的范围,我们要学的就是一套操作步骤,学习在什么时候调用什么接口中的什么方法。
2.快速入门
- 入门小案例
//1.导入jar包
//2.注册驱动
Class.forName("com.mysql.jdbc.Driver");
//3.创建连接
Connection con=DriverManager.getConnection("jdbc:mysql://localhost:3306/db1","root","root");
System.out.println(con);
//4.定义sql
String sql="update account set balance=balance+500 where id=1";
//5.获取执行sql的对象 stmt
Statement stmt = con.createStatement();
//6.执行sql
int count = stmt.executeUpdate(sql);
//7.结果处理
System.out.println(count);
//8.释放资源
stmt.close();
con.close();
- 注意:
1.得到了连接对象con,即意味着java程序和数据库服务器已经连通了。
2.stmt对象看似是执行sql,其实是发送sql给数据库服务器去执行
- 总结流程:
1. 导入jar包
2. 加载驱动
3. 构建连接对象
4. 生成statement对象
5. 执行sql
6. 处理结果
7. 释放资源
3.核心类详解
3.1 DriverManager
-
两个功能
- 管理和注册驱动:查看源码解释
- 获取连接对象
-
获取连接对象的方法
//1.通过 连接协议,用户名,密码 得到数据库连接对象 Connection getConnection(String url,String user,String password) //2.通过 url,Properties 对象得到连接对象 Connection getConnection(String url,Properties p)
-
连接数据库四大参数
1.驱动类的字符串名:com.mysq.jdbc.Driver -- 在jar文件的META-INF/services文件夹下 2.连接协议路径字符串url:不同的数据库url是不同的,mysql的写法 jdbc:mysql://localhost:3306/数据库名[?参数名=参数值] 3.数据库用户名 :root 4.数据库登录密码:root
-
url 是什么?
-
是一个连接数据库的路径,用于标识数据库的位置
-
包含:jdbc协议,mysql子协议,数据库所在主机名,数据库服务器的端口号,数据库名
-
如果是本地服务器,端口号是3306,可简写:
jdbc:mysql:///数据库名
-
-
乱码处理
-
如果使用jdbc对数据库进行操作时出现中文乱码,可以在url后指定字符集编码参数,表示让数据库以UTF-8编码来处理数据
jdbc:mysql:///数据库?characterEncoding=utf8
-
mysql8还需要指定时区
jdbc:mysql:///数据库名?useSSL=false&serverTimezone=UTC&characterEncoding=utf8
-
-
案例
//1.使用用户名,密码,url得到连接对象 public static void main(String [] args){ String url="jdbc:mysql:///db1"; Connection con=DriverManager.getConnection(url,"root","root"); System.out.println(con); } //2.使用properties对象和url得到连接对象 public static void main(String [] args){ String url="jdbc:mysql:///db1"; //将用户名和密码存进Properties对象中 Properties info=new Properties(); info.setProperty("user","root"); info.setProperty("password","root"); //使用url和Properties对象得到连接对象 Connection con=DriverManager.getConnection(url,info); System.out.println(con); } //3.使用properties文件和url得到连接对象
@Test
public void test1(){
try {
//注册驱动 Driver
Class.forName("com.mysql.cj.jdbc.Driver");
//获取连接数据库对象 DriverManager
String url="jdbc:mysql://localhost:3306/db3 ?useSSL=false&serverTimezone=UTC";
Connection con = DriverManager.getConnection(url, "root", "123456");
System.out.println(con);
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
public void test2(){
try {
Class.forName("com.mysql.cj.jdbc.Driver");
String url="jdbc:mysql://localhost:3306/db3 ?useSSL=false&serverTimezone=UTC&characterEncoding=utf8";
Properties p = new Properties();
p.setProperty("user","root");
p.setProperty("password","123456");
Connection con = DriverManager.getConnection(url,p);
System.out.println(con);
} catch (Exception e) {
e.printStackTrace();
}
}
@Test
public void test3(){
try {
Class.forName("com.mysql.cj.jdbc.Driver");
String url="jdbc:mysql://localhost:3306/db3 ?useSSL=false&serverTimezone=UTC";
InputStream is = Hello1.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties p = new Properties();
p.load(is);
Connection con = DriverManager.getConnection(url, p);
System.out.println(con);
} catch (Exception e) {
e.printStackTrace();
}
}
3.2 Connection
-
Connection是一个接口,具体的实现类由数据库厂商提供实现,代表一个连接对象
-
功能:
- 获取执行sql 的对象
Statement createStatement() PreparedStatement prepareStatement(String sql) //预编译对象,效率更高
- 管理事务:
- 开启事务:setAutoCommit(boolean autoCommit) :调用该方法设置参数为false,即开启事务 - 提交事务:commit() - 回滚事务:rollback()
3.3 Statement
-
功能:用于发送sql语句给服务器,用于执行静态sql语句并返回结果
-
方法
//用于发送dml语句,执行增删改的操作,返回对数据库影响的行数,还可以执行ddl语句 int executeUpdate(String sql) //用于发送dql语句,执行查询的操作,返回结果集对象 ResultSet executeQuery(String sql)
3.4 练习–dml和ddl
-
演示代码
- 增删改 – 返回受影响的行数
- 创建student表 – 返回0
public class Hello2 {
private Connection con;
@Before //执行@Test前,先执行@Before
public void init(){
try {
//注册驱动 Driver
Class.forName("com.mysql.cj.jdbc.Driver");
//获取连接数据库对象 DriverManager
String url="jdbc:mysql://localhost:3306/db4 ?useSSL=false&serverTimezone=UTC";
con = DriverManager.getConnection(url, "root", "123456");
// System.out.println(con);
} catch (Exception e) {
e.printStackTrace();
}
}
@Test//dml 新增
public void test1(){
try {
String sql="insert into dept values(50,'信息部','武汉')";
Statement state = con.createStatement();
// PreparedStatement ps = con.prepareStatement("insert into dept values(60,'信息部','武汉')");
int i=state.executeUpdate(sql);
System.out.println(i);//返回表格影响的行数 1
} catch (SQLException e) {
e.printStackTrace();
}
}
@Test//dml 删除
public void test2(){
try {
String sql="delete from dept where id='50'";
Statement state = con.createStatement();
int i=state.executeUpdate(sql);
System.out.println(i);//返回表格影响的行数 1
} catch (SQLException e) {
e.printStackTrace();
}
}
@Test//dml 修改
public void test3(){
try {
String sql="update dept set loc='武汉' where id='40'";
Statement state = con.createStatement();
int i=state.executeUpdate(sql);
System.out.println(i);//返回表格影响的行数 1
} catch (SQLException e) {
e.printStackTrace();
}
}
@Test//ddl 修改表名
public void test4(){
try {
String sql="alter table dept rename to dept1";
Statement state = con.createStatement();
int i=state.executeUpdate(sql);
System.out.println(i);// 返回0
} catch (SQLException e) {
e.printStackTrace();
}
}
}
-
注意:
- 释放资源
1.需要释放的对象 ResultSet Statement Connection 2.释放原则:先开的后关,后开的先关 ResultSet --> Statement --> Connection 3.放在finally代码块中
finally { //6. 释放资源 //关闭之前要先判断 if (statement != null) { try { statement.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }
3.5 ResultSet
-
作用:封装数据库查询的结果集,并提供了遍历结果集的方法,取出每一条记录
-
方法
boolean next() -- 每调用一次,行光标向下移动1行,返回true表示还有下一条记录,否则返回false 数据类型 getXxx() 可以通过列名或列号获取某一行的某一列的数据
- 画图理解行光标和遍历数据的步骤
-
演示
- 查询一行数据输出 if(rs.next())
- 查询所有数据输出 while(rs.next())
3.6 练习–查询封装结果集
-
代码演示–查询员工表,并封装结果集到list集合中
/** * 查询所有emp对象 * @return */ public List<Emp> findAll(){ Connection conn = null; Statement stmt = null; ResultSet rs = null; List<Emp> list = null; try { //1.注册驱动 Class.forName("com.mysql.jdbc.Driver"); //2.获取连接 conn = DriverManager.getConnection("jdbc:mysql:///db3", "root", "root"); //3.定义sql String sql = "select * from emp"; //4.获取执行sql的对象 stmt = conn.createStatement(); //5.执行sql rs = stmt.executeQuery(sql); //6.遍历结果集,封装对象,装载集合 Emp emp = null; list = new ArrayList<Emp>(); while(rs.next()){ //获取数据 int id = rs.getInt("id"); String ename = rs.getString("ename"); int job_id = rs.getInt("job_id"); int mgr = rs.getInt("mgr"); Date joindate = rs.getDate("joindate"); double salary = rs.getDouble("salary"); double bonus = rs.getDouble("bonus"); int dept_id = rs.getInt("dept_id"); // 创建emp对象,并赋值 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); //装载集合 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(stmt != null){ try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } } if(conn != null){ try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } return list; }
-
注意事项
- emp对象的创建要放在while循环的里面,因为每循环一次,相当于取出一行记录,封装到一个对象中。
- 把emp对象存进list集合也是放在while循环里面
-
核心步骤:
- 获得连接对象
- 执行查询
- 封装查询结果集
二.jdbc使用
1. 工具类
1.为什么要写工具类?
工具类的作用:
1.实现注册驱动 和 连接对象的获取
2.释放资源
-
代码演示
/** * 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(); // System.out.println(path);///D:/IdeaProjects/itcast/out/production/day04_jdbc/jdbc.properties //2. 加载文件 // pro.load(new FileReader("D:\\IdeaProjects\\itcast\\day04_jdbc\\src\\jdbc.properties")); pro.load(new FileReader(path)); //3. 获取数据,赋值 url = pro.getProperty("url"); user = pro.getProperty("user"); password = pro.getProperty("password"); driver = pro.getProperty("driver"); //4. 注册驱动 Class.forName(driver); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } /** * 获取连接 * @return 连接对象 */ public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, user, password); } /** * 释放资源 * @param stmt * @param conn */ public static void close(Statement stmt,Connection conn){ close(null,stmt,conn);//直接调用重载的close方法 } /** * 释放资源 * @param stmt * @param 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(); } } } }
-
注意事项
- 资源用完后一定要释放
-
可能出现的异常
- 读取properties文件可能会出现异常
2. 登录案例
-
代码演示
/** * 登录方法 */ public boolean login(String username ,String password){ if(username == null || password == null){ return false; } //连接数据库判断是否登录成功 Connection conn = null; Statement stmt = null; ResultSet rs = null; //1.获取连接 try { conn = JDBCUtils.getConnection(); //2.定义sql 获取username 和password两个数据 String sql = "select * from user where username = '"+username+"' and password = '"+password+"' "; System.out.println(sql); //3.获取执行sql的对象 stmt = conn.createStatement(); //4.执行查询 rs = stmt.executeQuery(sql); return rs.next();//如果有下一行,则返回true } catch (SQLException e) { e.printStackTrace(); }finally { JDBCUtils.close(rs,stmt,conn); } return false; }
-
注意事项
- 在sql语句拼接用户名和密码时注意单引号和双引号
-
可能出现的异常
- sql语句拼接不正确会导致sql语法异常
3. 预编译对象优化登录案例
-
代码演示
/** * 登录方法,使用PreparedStatement实现 */ public boolean login2(String username ,String password){ if(username == null || password == null){ return false; } //连接数据库判断是否登录成功 Connection conn = null; PreparedStatement pstmt = null; ResultSet rs = null; //1.获取连接 try { conn = JDBCUtils.getConnection(); //2.定义sql String sql = "select * from user where username = ? and password = ?"; //3.获取执行sql的对象 pstmt = conn.prepareStatement(sql); //给?赋值 pstmt.setString(1,username); pstmt.setString(2,password); //4.执行查询,不需要传递sql rs = pstmt.executeQuery(); //5.判断 return rs.next();//如果有下一行,则返回true } catch (SQLException e) { e.printStackTrace(); }finally { JDBCUtils.close(rs,pstmt,conn); } return false; }
-
注意事项
- 记得给占位符?赋值
-
可能出现的异常
- sql语句不要写错
-
两种实现方式的差异:
1.sql语句不再是拼接参数 ,使用?代替参数
2.在创建stmt对象时,空参 ; 在创建pstm对象时,传入sql模板 --预编译
3.pstm对象还需要为?填充参数值
4.stmt对象执行sql时,需要传入sql ; pstm对象执行sql时,不用再传sql
优点:
1.PreparedStatement对象可以防止sql注入,Statement对象不可以
2.PreparedStatement对象是预编译方式执行sql,会提前检查sql是否有语法错误,因此执行效率更高
4. 管理事务
- 事务的操作在jdbc流程中的位置
1.开启 con.setAutoCommit(false); 放在con对象创建后
2.回滚 con.rollback(); 放在catch代码块
3.提交 con.commit(); try的最后一行
-
代码演示
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); } }
-
注意事项
- 要执行两个sql,所有需要创建两个prepareStatement对象,但只需一个连接对象即可
- 两个prepareStatement对象最后都需要关闭
-
可能出现的异常
- sql语句语法错误会导致异常
- 给两个prepareStatement对象中的sql模板的占位符赋值不要错乱
作业:
-
对一个表进行简单的增删改查
-
查询一个表中所有的数据,得到一个结果集,并在控制台全部输出
-
查询一个表中所有的数据,得到一个结果集,封装结果集到list集合,并在控制台输出
-
工具类的编写–读取外部的properties配置文件:1.获取连接对象 2.释放资源
-
用statement去实现登录案例
-
用子接口preparedstatement去优化登录案例
-
事务–转账案例
-
在控制台实现一个百里半用户管理系统,包含3个功能:
1.用户登录 2.注册 3.用户查询。
启动程序后,进入主菜单选项:
输出:“请选择您要操作的功能:1.用户登录 2.新用户注册 3所有用户查询: ”
功能说明:
1.用户登录:
请用户输入用户名和密码,接收后,去数据库的users表中查询是否存在该用户名。并输出合理的提示,例如:登录成功! 用户名不存在! 密码错误!
不论登录是否成功,都返回主菜单界面。
2.新用户注册 :
请用户输入用户名和密码,接收后,去数据库的users表中执行新增操作。并输出合理的提示,例如:注册成功! 用户名已存在!
3.所有用户查询:
该功能必须在用户登录后方可查看,如果用户已经登录成功,即可在控制台查看所有用户的所有信息。如果没有登录,提示:您还没有登录呢,无权查看用户信息!
说明:
users表中只需要3列即可,id,name,password.