一、JDBC是什莫?
Java database connectivity(Java语言连接数据库)
1.1JDBC的本质是什莫?
是一套接口(在Java.sql.*下)
为什么要面向接口编程?
解耦合,降低程序的耦合度,提高程序扩展力!
1.2JDBC编程六步
第一步:加载驱动 (告诉程序使用的是哪个品牌的数据库)
第二步:建立连接 (打开与数据库的通道)
第三步:获取数据库对象 (使用数据库对象进行对数据的操作)
第四步:执行SQL语句 (DQL,DML)
第五步:对结果集进行处理 (只有当进行DQL才需要对结果集进行处理)
第六步:释放资源 (Java与数据库之间是进程的通信,需要关闭进程)
1.3 具体代码
第一步:加载驱动 (告诉程序使用的是哪个品牌的数据库)
java.sql.DriverManager 下有static registerDirver(Driver driver);用来注册驱动,参数是Driver类型的(Driver是Java.sql包下的接口类,需要其实现类)Driver的实现类是com.mysql.jdbc包下的Driver类 故有Driver driver= new com.mysql.jdbc.Driver();//多态 Java.sql.Driver父类引用指向子类com.mysql.jdbc.Driver对象
//第一种加载驱动的方式
Driver driver=new com.mysql.jdbc.Driver();
DriverManager.registerDriver(diver);
//第二种加载驱动的方式
//因为com.mysql.jdbc.Driver里有静态代码块,此代码块里的代码就是第一种加载驱动方式的代码,直接使用反射机制
Class.forName("com.mysql.jdbc.Driver");
第二步:建立连接 (打开与数据库的通道)
使用java.sql.DriverManager包下的getConnection(String url,String user,String password);进行数据库与jvm的连接。
url:统一资源定位符(网络中某个资源的绝对路径)
例如:https://www.baidu.com/就是url。
url包括几个部分:协议,ip,端口,资源名
https:// 协议;182.61.200.7 服务器ip地址 80 端口号 index.html 资源名
jdbc:mysql//协议 127.0.0.1 ip地址 3306 端口号 student 数据库实例名
说明:localhost 和 127.0.0.1都是本机地址
String url="jdbc:mysql//127.0.0.1:3306/student";
//String url=“jdbc:oracle:thin:@localhost:1521:”
String user="root";
String password="123";
Connection conn=DriverManager.getConnection(String url, String user, String password) ;
第三步:获取数据库对象 (使用数据库对象进行对数据的操作)
Statement state=conn.createStatement();
第四步:执行SQL语句 (DQL,DML)
String sql="insert into dept values(50,‘cehuabu‘,zhengzhou)";
int count=state.executeUpdate(sql);//专用于Dml 返回值是影响的行数
第五步:对结果集进行处理 (只有当进行DQL才需要对结果集进行处理)
ResultSet rs=state.executeQuery(sql);//sql必须是DQL
//遍历结果集1
/*while(rs.next()){
String sno=rs.getString(1);//getString的返回值一定是String类型,不管sno是什么类型都字符串返回。参数可以是下标
String sname-rs.getString(2);
}*/
//遍历结果集2
while(rs.next()){
int sno=rs.getInt(‘sno‘);//要是想要返回指定类型需要这样写(getDouble,getInt...)参数可以写字段名,但此字段名必须是得到的表的字段名(如:把sno as a)则此时获取值的字段名就为‘a’;
String sanme=rs.getString(‘sname‘);
}
第六步:释放资源 (Java与数据库之间是进程的通信,需要关闭进程)
需要使用在finally里try...catch....
关闭规则从小到大,先关闭state,再关闭conn
state.colse();
conn.close();
//用户登录代码
//主方法
public static void main(String[] args){
//用户登录界面
Map<String,String > uselogin=LoginUI();
//用户登录
boolean result= userJdbc(uselogin);
System.out.println(result?"登录成功":"登录失败");
}
//用户登录界面
private static Map<String, String> LoginUI() {
Scanner in =new Scanner(System.in);
System.out.println("请输入用户名:");
String username=in.next();
System.out.println("请输入密码:");
String password=in.next();
//封装用户名与密码
Map<String,String> users=new HashMap<>();
users.put("username",username);
users.put("password",password);
return users;
}
//用户信息连接数据库
private static boolean userJdbc(Map<String,String > uselogin) {
Connection conn=null;
Statement state=null;
ResultSet rs=null;
boolean b=false;
try {
//1、加载驱动
Class.forName("com.mysql.jdbc.Driver");
//2、建立连接
String url="jdbc:mysql://127.0.0.1:3306/uses?useUnicode=true&characterEncoding=gbk";
String user="root";
String password="root";
conn = DriverManager.getConnection(url, user, password);
//3、获取数据库操作对象
state = conn.createStatement();
//4、执行sql语句(DQL,DML)
String sql = "select * from user where username=‘"+uselogin.get("username")+"‘ and password=‘"+uselogin.get("password")+"‘";
rs=state.executeQuery(sql);
//5、处理结果集
if(rs.next()){
b=true;
}
?
} catch (SQLException throwables) {
throwables.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
//6、释放资源
if (rs != null) {
try {
rs.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if (state != null) {
try {
state.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
return b;
}
以上代码存在bug,如:
请输入用户名:
fds
请输入密码:
fsd‘or‘1‘=‘1
登录成功
select * from user where username=‘fds‘ and password=‘fsd‘ or ‘1‘=‘1‘;
二、解决SQL注入现象
SQL注入是因为非法信息与SQL语句进行了拼接并编译,这样会造成数据表结构遭到攻击破坏 解决方法是首先对SQL语句进行结构编译,在接收用户信息的时候不会对特殊字符进行编译了如:or,把用户注入的信息只当作普通数据,最后进行编译 使用java.sql.PreparedStatement 继承java.sql.Statement.进行预编译。
对以上代码中的用户连接数据库部分代码进行修改
Connection conn=null;
PreparedStatement ps=null;
ResultSet rs=null;
boolean b=false;
try {
//1、加载驱动
Class.forName("com.mysql.jdbc.Driver");
//2、建立连接
String url="jdbc:mysql://127.0.0.1:3306/uses?useUnicode=true&characterEncoding=gbk";
String user="root";
String password="root";
conn = DriverManager.getConnection(url, user, password);
//3、获取数据库预编译操作对象
String sql = "select * from user where username=? and password=?";
//?是占位符,第一个?下标是1,第二个是2
ps = conn.prepareStatement(sql);//先对SQL语句进行编译
ps.setString(1,uselogin.get("username"));
ps.setString(2,uselogin.get("password"));
//4、执行sql语句(DQL,DML)
rs=ps.executeQuery();
//5、处理结果集
if(rs.next()){
b=true;
}
2.1Statement和PreparedStatement的比较
Statement有SQL注入的问题,PreparedStatement没有
Statement 每次执行SQL语句都需要编译,PreparedStatement只需要编译一次,所以PreparedStatement 执行效率略高
PreparementStatement会做安全检查
2.2 Statement在什么时候使用?
需要SQL注入的时候使用。例如需要降序升序,添加关键字
例如按升序或降序进行输出代码:
//连接数据库的部分代码
//加载驱动
Class.forName("com.mysql.jdbc.Driver");
//连接数据库
conn= DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/uses?useUnicode&characterEncoding","root","root");
//获取数据库操作对象
state=conn.createStatement();
String sql="select * from user order by username "+da;//da是用户手动输入desc 或asc
//执行sql语句
rs=state.executeQuery(sql);
//处理结果集
while(rs.next()){
String usename = rs.getString("username");
String password=rs.getString("password");
System.out.println(usename+":"+password);
2.3封装JDBC
手动写JDBCUtil包,代码如下:
public class DBUtil {
//工具包里的方法都是静态的,直接类名点方法名调用
private DBUtil(){}//构造方法私有化,就不能new对象
//在类加载的时候执行
static{
try{
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Connection connection() throws SQLException {
return DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/bjpowernode?useUnicode=true&characterEncoding=gbk","root","root");
}
public static void close(Connection conn, PreparedStatement ps, ResultSet resultSet){
if(conn!=null){
try {
conn.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if(ps!=null){
try {
ps.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
if(resultSet!=null){
try {
resultSet.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
2.4JDBCUtil工具包的使用与模糊查询代码
public static void main(String[] args) {
Connection conn=null;
PreparedStatement ps=null;
ResultSet rs=null;
try{
//加载驱动和建立连接
conn=DBUtil.connection();
//获取数据库操作对象
String sql="select ename from emp where ename like ?";
ps=conn.prepareStatement(sql);
//赋值
ps.setString(1,"_A%");
//执行sql语句
rs=ps.executeQuery();
//处理结果集
while(rs.next()){
String ename = rs.getString("ename");
System.out.println(ename);
}
?
} catch (SQLException throwables) {
throwables.printStackTrace();
}finally{
DBUtil.close(conn,ps,rs);
}
}
运行结果:
WARD
MARTIN
JAMES
三、悲观锁模拟
悲观锁:在select 语句后面加 for update 意思是把这些查询出来的数据行锁住,其他事务无法修改这些数据 如:select ename,sal from emp where job=‘manager‘ for update 符合此条件的行都被锁上,其他事务无法修改数据。
代码:JdbcTestLock与JdbcTestLock01
四、单机事务
模拟转账系统
重要的三行代码
conn.setAutoCommit(false)//设置自动提交为关闭
conn.commit();//提交
conn.rollback();//回滚
部分代码:
public static void main(String[] args) {
Connection conn=null;
PreparedStatement ps=null;
try{
//加载驱动
Class.forName("com.mysql.jdbc.Driver");
//建立连接
conn= DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/uses?useUnicode&characterEncoding=gbk","root","root");
//关闭数据库自动提交设置
conn.setAutoCommit(false);
//获取数据库操作对象
String sql="update a_act set balance=? where actno=? ";
ps=conn.prepareStatement(sql);
ps.setDouble(1,10000);
ps.setInt(2,12);
int count =ps.executeUpdate();
?
ps.setDouble(1,10000);
ps.setInt(2,13);
count +=ps.executeUpdate();
//手动提交事务
conn.commit();
} catch (ClassNotFoundException | SQLException e) {
//如果事务执行半路出错,则进行回滚
if(conn!=null){
try {
conn.rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
e.printStackTrace();
}