1.JDBC是什么?
Java DataBase Connectivity
2.JDBC本质
SUN公司的一套接口,面向接口编程。
java.sql.*包下的
注: 各大厂家对JDBC接口的实现类被称为驱动。
java程序员不需要知道JDBC接口是怎么实现的,只需要面向接口编程。
SUN公司负责定义JDBC接口,
各大数据库公司负责接口的实现。
程序员程序:
package com.guigu.JDBC;
import java.util.ResourceBundle;
public class JavaProgrammer {
public static void main(String[] args) throws Exception{
ResourceBundle bundle=ResourceBundle.getBundle("com.guigu.JDBC.prop");
String className=bundle.getString("ClassName");
Class c=Class.forName("com.guigu.JDBC."+className);
JDBC jdbc=(JDBC)c.newInstance();
jdbc.getConnection();
}
}
SUN公司写的JDBC接口:
package com.guigu.JDBC;
public interface JDBC {
public void getConnection();
}
MySQL公司写的实现类:
package com.guigu.JDBC;
import javax.xml.bind.SchemaOutputResolver;
public class MySQL implements JDBC{
@Override
public void getConnection() {
System.out.println("开始运行MySQL");
}
}
准备:官网下载驱动jar包,配置路径到classpath中。
变量名:classpath 配置时,前面要加 .; 表示在原路径上添加。
classpath=.;F:\工作\cs自学资料\mysql\JDBC\mysql-connector-java-5.1.23-bin.jar
3.JDBC编程六步:
url:
各部分意义:
IP是计算机的代号,端口代表某个软件
注: 要从小到大依次释放。JDBC中的sql语句不需要写分号。
完整:
注册驱动的第二种方法:
.executequarry()方法:
查询结果保存在ResaultSet类中:
ResaultSet中取数据:
上述用列名也可以(推荐):
注:列名为查寻结果的列名,不是原表中的列名。
除了以getString()取出,还可以getInt(),getDouble()。
导入数据库驱动:
先apply再ok。
4.登录程序实例:
-
当前程序存在的问题:
用户名:fdsa
密码:fdsa’ or ‘1’='1
登录成功
这种现象被称为SQL注入(黑客经常使用) -
原因:
由于输入的非法字符有sql关键字,并且这些关键字参与了DBMS编译,导致sql语句被扭曲(where语句中的值恒为1, 默认查找所有数据),进而达到sql注入。 -
如何解决sql注入:
用户提供的sql信息不参与SQL编译,问题就解决了。
要实现该功能必须使用java.sql.PreparedStatement。
PreparedStatement继承了java.sql.Statement。
PreparedStatement原理:预编译SQL框架,之后再给SQL语句传“值”。
注意PreparedStatement和Statement的区别:
第三步:
String sql=“select * from t_log where username=? and password=?”;
ps=conn.prepareStatement(sql);
ps.setString(1,username);
ps.setString(2,password);
rs=ps.executeQuery();
package com.bjpowernode.jdbc;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
public class JdbcTest02 {
public static void main(String[] args) {
Map<String,String> map=input();
login(map);
}
public static Map<String,String> input(){
Scanner s=new Scanner(System.in);
System.out.println("请输入账户名:");
String username=s.nextLine();
System.out.println("请输入密码:");
String password=s.nextLine();
Map<String,String> map=new HashMap<String, String>();
map.put("username",username);
map.put("password",password);
return map;
}
public static void login(Map<String,String> map){
String username=map.get("username");
String password=map.get("password");
String url="jdbc:mysql://localhost:3306/bjpowernode";
String usernamedb="root";
String passworddb="******";
boolean log=false;
Connection conn=null;
PreparedStatement ps=null;
ResultSet rs=null;
try {
Class.forName("com.mysql.jdbc.Driver");
conn=DriverManager.getConnection(url,usernamedb,passworddb);
String sql="select * from t_log where username=? and password=?";
ps=conn.prepareStatement(sql);
ps.setString(1,username);
ps.setString(2,password);
rs=ps.executeQuery();
if(rs.next()){
log=true;
}
System.out.println(log ? "登陆成功":"登录失败");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
if (rs != null) {
try {
rs.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (ps != null) {
try {
ps.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
}
- 对比PreparedStatement和Statement:
PreparedStatement使用较多。 - 什么时候需要使用Statement:
业务要求必须使用SQL注入情况下。(Statement支持SQL注入)
5.JDBC事务机制
默认自动提交。
java.sql.Connection包中方法:开启事务—手动提交—回滚事务
conn.setAutoCommit(false);//开启事务
...
...
...
conn.commit();//手动提交
最后,有异常就回滚:
try{}
catch{
if(conn!=null){
conn.rollback();//回滚事务
}
}
6.JDBC工具类封装
1.私有化构造方法(防止别人new对象)
2.构造 获取连接和释放资源方法:
package com.bjpowernode.jdbc;
import java.sql.*;
public class DBUtil {
static {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
private DBUtil(){}
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection("jdbc:mysql://localhost:3306/bjpowernode", "root","****");
}
public static void close(Connection conn, Statement stmt, ResultSet rs){
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();
}
}
}
}
执行测试:
package com.bjpowernode.jdbc;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class JdbcTest03 {
public static void main(String[] args) {
Connection conn=null;
PreparedStatement ps=null;
ResultSet rs=null;
try {
conn=DBUtil.getConnection();
String sql="select * from emp where ename like ?";
ps=conn.prepareStatement(sql);
ps.setString(1,"_A%");
rs=ps.executeQuery();
while (rs.next()){
System.out.println(rs.getString("ename"));
}
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally {
DBUtil.close(conn,ps,rs);
}
}
}
7.行级锁(悲观锁)
select 语句后边+for update;
select ename,job,deptno from emp where job = 'manager' for update;
+-------+---------+--------+
| ename | job | deptno |
+-------+---------+--------+
| JONES | MANAGER | 20 |
| BLAKE | MANAGER | 30 |
| CLARK | MANAGER | 10 |
+-------+---------+--------+
锁住原表中被选中三行的所有字段,只要当前事务未结束,就无法被修改:
+-------+-------+---------+------+------------+---------+------+--------+
| EMPNO | ENAME | JOB | MGR | HIREDATE | SAL | COMM | DEPTNO |
+-------+-------+---------+------+------------+---------+------+--------+
| 7566 | JONES | MANAGER | 7839 | 1981-04-02 | 2975.00 | NULL | 20 |
| 7698 | BLAKE | MANAGER | 7839 | 1981-05-01 | 2850.00 | NULL | 30 |
| 7782 | CLARK | MANAGER | 7839 | 1981-06-09 | 2450.00 | NULL | 10 |
+-------+-------+---------+------+------------+---------+------+--------+
悲观锁: 事务必须排队执行,数据锁住了,不允许多线程并发。(行级锁:for update;)
乐观锁: 允许多线程并发,事务不需要排队,只不过需要一个版本号, 每个线程操作完成之后版本号会变化,如果一个线程执行完之后发现版本号与线程开始时不同,意味着这期间其他线程对该数据进行过修改,则原线程执行回滚。
悲观锁效果演示,第一个例子:事务查询,存在行级锁,断点处等待,不提交。
第二个例子:在第一个事务执行期间,进行数据修改。
效果:第一个例子调试一结束,第二个例子马上可以输出3。
package com.bjpowernode.jdbc;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class JDBCTest04 {
public static void main(String[] args) {
Connection conn=null;
PreparedStatement ps=null;
ResultSet rs=null;
try {
conn=DBUtil.getConnection();
conn.setAutoCommit(false);
String sql="select * from emp where job=? for update";
ps=conn.prepareStatement(sql);
ps.setString(1,"manager");
rs=ps.executeQuery();
while(rs.next()){
System.out.println(rs.getString("ename")+","+
rs.getString("job")+","+rs.getString("sal"));
}
conn.commit();
} catch (SQLException throwables) {
try {
conn.rollback();
} catch (SQLException e) {
e.printStackTrace();
}
throwables.printStackTrace();
}finally {
DBUtil.close(conn,ps,rs);
}
}
}
package com.bjpowernode.jdbc;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class JDBCTest05 {
public static void main(String[] args) {
Connection conn=null;
PreparedStatement ps=null;
try {
conn=DBUtil.getConnection();
conn.setAutoCommit(false);
String sql ="update emp set sal=sal*1.1 where job=?";
ps=conn.prepareStatement(sql);
ps.setString(1,"manager");
int n=ps.executeUpdate();
conn.commit();
System.out.println(n);
} catch (SQLException throwables) {
try {
conn.rollback();
} catch (SQLException e) {
e.printStackTrace();
}
throwables.printStackTrace();
}
}
}