Java基础
JDBC概述
什么是JDBC?为什么要学习JDBC?
JDBC( Java DataBase Connectivity ) 翻译过来就是Java数据库连接,其实就是通过Java语言操作数据库的一门技术。之前我们可以通过cmd或者navicat等工具连接并访问数据库,但是以后在企业级的开发中,我们更多的是使用JDBC(或者底层是JDBC的框架)来连接并访问数据库。使用Java语言连接数据库,就得通过JDBC这门技术!
如何通过JDBC程序访问数据库?
创建一个 jt_db 数据库,在库中创建一个Account表,并插入三条记录,然后利用Java程序查询出Account表中所有的记录,并打印在控制台上.
数据库的连接的原理图:
连接数据库驱动的步骤
//1.注册数据库驱动
Class.forName("com.mysql.jdbc.Driver");
//2.获取数据库连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jt_db?characterEncoding=utf-8", "root", "123456");
//3.获取传输器
Statement stat = conn.createStatement();
//4.利用传输器发送SQL到数据库执行,并返回执行结果
ResultSet rs = stat.executeQuery("select * from account");
//5.处理结果:将表中所有的记录输出在控制台
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
double money = rs.getDouble("money");
System.out.println(id+" : "+name+" : "+money);
}
//6.释放资源
rs.close();
stat.close();
conn.close();
连接数据库和进行查询遍历的具体的操作(自己书写的代码,介绍详细)
package com.tedu;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
/*
* 实现jdbc的快速的入门:
* 通过程序连接jt_db库,查询account的库
* 所有的记录,进行输入到控制台
*
*可以比较着运行的原理图
*/
public class JdbcDemo1 {
public static void main(String[] args) throws Exception {
//1.注册数据的驱动
/*
* 就是反射里面提供的一个方法,作用是让注册的驱动的代码执行,括号里面是类的全路径
* 前面写的是包路径+类名(类名的首字母一定是大写的)
*/
Class.forName("com.mysql.jdbc.Driver");
//2.获取数据库的连接(Connection)
/*
* 第一个参数是确定连接的数据的地址,连接那一个数据库,进入数据库的密码
* 最后的是选择数据库的名称,在此处书写的一定是localhost,与Navicat创建的名称无关
*/
String url="jdbc:mysql://localhost:3306/jt_db";
String user="root";
String password="123456";
//然后进行接受返回值,用父类的接口来接受数据
Connection conn= DriverManager.getConnection(url, user, password);
//3.获取传输器
/*
* 连接的对象引用一个方法,并把传输器进行接受
* 在这儿,同样需要导包,用父接口进行接受
*/
Statement stat= conn.createStatement();
//4.利用传输器进行发送sql到服务器进行执行,并返回执行的结果
/*
* 在这儿就有数据库常用的语句,同样需要进行导包,用父类进行接收
*/
String sql="select * from account";
//查询用的方法和更新,删除,添加使用都不相同
ResultSet rs = stat.executeQuery(sql);
//5.处理结果
/*
* rs.next(),就是进入下一次的循环,在此处代表的是行数
* 运行的条件,进行输出结果集
*/
while(rs.next()) {
//获取该行的id值
int id = rs.getInt("id");
//获取该行的name值
String name = rs.getString("name");
//获取该行的money值
double money = rs.getDouble("money");
//将他们输出到控制台
System.out.println(id+":"+name+":"+money);
}
//6.释放资源
/*
* 先获取的后关,后获取的先关闭,
* 如果代码写的标准关闭的方式就是从下往上创建出来的连接按照顺序关闭就可以
*/
rs.close();
stat.close();
conn.close();
//快捷的书写的内容时Syst,也是进行打印,但是区别是在括号里面有默认的内容
System.out.println("JdbcDemo1.main()");
}
}
1、注册数据库驱动
Class.forName("com.mysql.jdbc.Driver");
所谓的注册驱动,就是让JDBC程序加载mysql驱动程序,并管理驱动驱动程序实现了JDBC API定义的接口以及和数据库服务器交互的功能,加载驱动是为了方便使用这些功能。
2、获取连接之数据库URL
Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/jt_db?characterEncoding=utf-8",
//在这里如果是本机的数据库,加上端口号如果默认的是3306的前提下localhost和3360,可以进行省略
//characterEncoding=utf-8如果出现了乱码的问题,可以加上这一段代码
"root", "root"
);
DriverManager.getConnection() 用于获取数据连接, 返回的Connection连接对象是JDBC程序连接数据库至关重要的一个对象。
参数2和参数3分别是所连接数据库的用户名和密码。
参数1:"jdbc:mysql://localhost:3306/jt_db" 是连接数据库的URL,用于指定访问哪一个位置上的数据库服务器及服务器中的哪一个数据库,其写法为:
当连接本地数据库,并且端口为3306,可以简写为如下形式:jdbc:mysql:///jt_db
3、Statement传输器对象,就是一辆小车,进行输送数据的作用
Statement stat = conn.createStatement();
该方法返回用于向数据库服务器发送sql语句的Statement传输器对象,该对象上提供了发送sql的方法:
executeQuery(String sql) -- 用于向数据库发送查询类型的sql语句,返回一个ResultSet对象中
executeUpdate(String sql) -- 用于向数据库发送更新(增加、删除、修改)类型的sql语句,返回一个int值,表示影响的记录行数
executeQuery(String sql) -- 用于向数据库发送查询类型的sql语句,返回一个ResultSet对象中
executeUpdate(String sql) -- 用于向数据库发送更新(增加、删除、修改)类型的sql语句,返回一个int值,表示影响的记录行数
查询和更新为什么不合并一起的原因:就是另个返回的结果值是不同的,一个返回的是表格,一个返回的是改变数值的列
4、ResultSet结果集对象
ResultSet对象用于封装sql语句查询的结果,也是一个非常重要的对象。该对象上提供了遍历数据及获取数据的方法。
(1)遍历数据行的方法
next() – 使指向数据行的索引向下移动一行
(2)获取数据的方法
getInt(int columnIndex)
getInt(String columnLable)
getString(int columnIndex)
getString(String columnLable)
getDouble(int columnIndex)
getDouble(String columnLable)
getObject(int columnIndex)
getObject(String columnLable)
5、释放资源
rs.close();
stat.close();
conn.close();
此处释放资源必须按照一定的顺序释放,越晚获取的越先关闭。所以先关闭 rs对象,再关闭stat对象,最后关闭conn对象。另,为了避免上面的程序抛出异常,释放资源的代码不会执行,应该把释放资源的代码放在finally块中.
try{
...
}catch(Exception e){
...
}finally{
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace(); //就是显示错误的信息
} finally {
rs = null;
}
}
if (stat != null) {
try {
stat.close();
} catch (SQLException e) {
e.printStackTrace();
} finally {
stat = null;
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
} finally {
conn = null;
}
}
}
使用@test单元测试的方法:在方法的上面加上@test注解,可以在没有main函数的情况下,进行单元方法的测试
注意的情况:方法必须是公共的,方法必须是静态的 ,方法必须是没有返回值的,方法是没有参数的,上面的规则必须要同时满足才可以将单元测试的的代码运行起来
导入包的快捷键是ctrl+shift+o,是多个元素进行一起的导入包
三个重要的函数在数据的连接上面:Connection(连接的对象) Statement(传输器的对象) ResultSet(结果集的对象)
可以提前创建三个对象,然后赋值为null,对于优化后面的异常的处理
PerparedStatement对象:
在上面的增删改查询的操作中,使用的是Statement,传输器对象,而在开发中我们使用的更多的传输的对象是PerparedStatement,对于PerparedStatement是Statement的子接口,比Statement更加安全,并且能够提高代码的执行的效率
自己模拟用户登录的代码块,简单的用户名称和密码的校验
package com.tedu.ps;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Scanner;
import javax.swing.JButton;
import org.junit.Test;
import com.tedu.JDBCUtil;
/*
* 模拟用户的登录
*/
public class LoginUser {
public static void main(String[] args) {
//System.exit(0);
//提示用户登录
System.out.println("请登录");
//提示用户输入用户名称
System.out.println("请你输入登陆的用户名");
String name = new Scanner(System.in).nextLine();
//提示用户输入登录的密码
System.out.println("请你输入登陆密码");
String passWord = new Scanner(System.in).nextLine();
System.out.println(name+":"+passWord);
//根据用户输入的用户名称和密码进行校验
Login(name,passWord);
}
//书写用户名称和密码的校验的工具
public static void Login(String name, String passWord) {
Connection connection=null;
Statement statement=null;
ResultSet resultSet=null;
try {
Class.forName("com.mysql.jdbc.Driver");
String url="jdbc:mysql:///jt_db";
String userName="root";
String password="123456";
connection=DriverManager.getConnection(url, userName, password);
statement=connection.createStatement();
String sql="select * from user where username='"+name+"' and password='"+passWord+"'";
resultSet=statement.executeQuery(sql);
if (resultSet.next()) {
System.out.println("恭喜你登陆成功");
}else {
System.out.println("登陆失败,你的用户密码或者是用户名称有误");
}
} catch (Exception e) {
e.printStackTrace();
}finally {
JDBCUtil.close(connection, statement, resultSet);
}
}
}
在登陆的时候肯能会出现非法的注入问题:
非法的注入的问题:通过上面的案例,我们发现在执行时,不输入密码只输入用户名也可以登陆成功。这就是SQL注入攻击。SQL注入攻击:由于后台的SQL语句是拼接而来的。其中的参数是由用户提交的,如果用户在提交参数时,在其中掺杂了一些SQL关键字或者特殊符号,就可能会导致SQL语句的语意发生变化。从而执行一些意外的操作。
如何防止SQL注入攻击?
(1)使用正则表达式对用户提交的参数进行校验。
(2)使用PreparedStatement对象来替代Statement对象。
下面通过第二种方式解决SQL注入攻击:添加loginByPreparedSatement方法,在方法中,使用PreparedStatement来代替Statement作为传输器对象使用!
练习:知识点(PreparedStatement传输器的使用,有效的防止SQL的注入攻击)
有效的防止SQL的注入的攻击,服务器一旦编译了骨架,就不可以再随意的进行更改,以后再将sql中的参数也发送给服务器)
PreparedStatement preparedStatement=connection.PreparedStatement(sql);
public static void Login(String name, String passWord) {
Connection connection=null;
//在本身的代码上进行更换传输器
PreparedStatement preparedStatement=null;
ResultSet executeQuery=null;
try {
Class.forName("com.mysql.jdbc.Driver");
String url="jdbc:mysql:///jt_db";
String userName="root";
String password="123456";
connection=DriverManager.getConnection(url, userName, password);
//直接在对象的基础上进行调用方法
//在创建传输器的同时进行,也同样获取了数据传送的小车
String sql="select * from user where username=? and password=?;";
//传送sql语句的时候,目的就是获取对象,就是将sql语句和preparedStatement进行绑定在一起
preparedStatement=connection.prepareStatement(sql);
//进行设置参数的值,其中第一个参数代表的是占位符的位置,第二个参数代表的是占位符代表的值
preparedStatement.setString(1, name);
preparedStatement.setString(2, passWord);
//执行sql语句返回执行的结果,在此处调用的是无参的构造,原因是已经将SQL语句发送到服务器上了,避免重复发送
//preparedStatement.executeQuery(sql);这个是有参数的构造方法,会再一次将sql语句发送到服务器上面
executeQuery = preparedStatement.executeQuery();
if (executeQuery.next()) {
System.out.println("恭喜你登陆成功");
}else {
System.out.println("登陆失败,你的用户密码或者是用户名称有误");
}
} catch (Exception e) {
e.printStackTrace();
}finally {
JDBCUtil.close(connection, preparedStatement, executeQuery);
}
}
进行数据库表中的内容的更改操作:
public void testUpdate() {
//这样的定义可以在已知用户的id的时候随时的进行更改用户的密码
System.out.println("请你输入要更改的用户的密码");
String password = new Scanner(System.in).nextLine();
System.out.println("请你输入要更改的id号码");
String id = new Scanner(System.in).nextLine();
Connection connection=null;
PreparedStatement preparedStatement=null;
ResultSet excuteUpdate=null;
try {
Class.forName("com.mysql.jdbc.Driver");
connection=DriverManager.getConnection("jdbc:mysql://localhost:3306/jt_db", "root", "123456");
String sql="update user set password=? where id=?";
preparedStatement=connection.prepareStatement(sql);
//将数据传过来
preparedStatement.setString(1,password );
/*在这里设置的参数的额类型是无关紧要的,
* 主要的关键还是要保证SQL语句的正确,
* SQL语句的错误不会进行错误的提醒会直接返回异常
*/
preparedStatement.setString(2, id);
//preparedStatement.setInt(2, 2);
//返回一个更改行数
int executeUpdate = preparedStatement.executeUpdate();
System.out.println("本次操作影响的行数 "+executeUpdate);
} catch (Exception e) {
System.out.println("出现错误,错误的原因如下");
e.printStackTrace();
}finally {
JDBCUtil.close(connection, preparedStatement, excuteUpdate);
}
连接池:池就是一个容器,在内存中就是一片空间,用于存放数据。提供连接的共享
连接池:就是指将连接对象放在一个容器中,当需要连接时就直接从池中获取一个连接进行使用,用完后再将连接还回去。连接池用于在整个程序中共享连接,减少连接开关的次数,实现连接的复用,从而提高程序执行的效率.
提示:连接池还有另外一个名字,即DataSource,翻译为数据源,因此连接池也叫做数据源。为什么要使用数据库连接池
传统方式中(即不适用连接池),每次需要就创建一个连接进行使用,用完连接后直接将连接关闭。对于数据库来说,频繁的开关连接会非常的耗费资源,也会导致程序执行效率的低下。
1、传统方式操作数据库
我们可以在程序中创建一个池子,在程序启动时就初始化一批连接放在连接池中,当用户需要连接时,就直接从池子中拿一个连接使用,当用完连接后,也不要将连接关闭,而是将连接还回池中,下一个用户需要连接时也是如此。这样可以减少链接开关的次数,从而提供程序执行的效率.
2、使用连接池操作数据库
连接池的使用的方法:下面包含了连接池的具体的用法
public class JdbcDemo1 {
public static void main(String[] args) throws Exception {
Connection connection=null;
Statement statement=null;
ResultSet resultSet=null;
//使用连接池的方法进行连接(创建连接池的对象)
//就是创建一个对象,同样也是创建了一个连接池
ComboPooledDataSource comboPooledDataSource=new ComboPooledDataSource();
//在创建完连接池的时候要进行设置参数,告诉连接到的那一个数据库对象
//创建连接数据库的基本的信息
comboPooledDataSource.setDriverClass("com.mysql.jdbc.Driver");
comboPooledDataSource.setJdbcUrl("jdbc:mysql://localhost:3306/jt_db");
comboPooledDataSource.setUser("root");
comboPooledDataSource.setPassword("123456");
//从连接池里面取出一个连接,但是不知道连接那一个池
connection= comboPooledDataSource.getConnection();
注意的最后Close代码的含义:是将连接还回连接池中,而不是将连接关闭
//在这儿他的含义并不是关闭连接,而是连接还回到连接池
resultSet.close();
statement.close();
connection.close();
连接池的创建的方法(设置数据库连接的基本的信息)
(1)方式一:(不推荐)
cpds.setDriverClass("com.mysql.jdbc.Driver");
cpds.setJdbcUrl("jdbc:mysql:///jt_db?characterEncoding=utf-8");
cpds.setUser("root");
cpds.setPassword("root");
(2)方式二:(推荐)
文件的含义的解释
/*先写文档的声明*/
<?xml version="1.0" encoding="utf-8"?>
<c3p0-config.xml>
/*这个是默认的配置*/
<default-config>
/*这个是属性的配置 name代表的是配置的属性的名称*/
<property name="driverClass" >
com.mysql.jdbc.Driver
</property>
<property name="jdbcUrl" >
jdbc:mysql://localhost:3306/jt_db
</property>
<property name="user" >
root
</property>
<property name="password" >
123456
</property>
</default-config>
</c3p0-config.xml>>
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<default-config>
<property name="driverClass">
com.mysql.jdbc.Driver
</property>
<property name="jdbcUrl">
jdbc:mysql:///jt_db?characterEncoding=utf-8
</property>
<property name="user">
root
</property>
<property name="password">
root
</property>
</default-config>
</c3p0-config>
(3)方式三:(推荐)
在类目录下(开发时可以放在src或者类似的源码目录下), 添加一个c3p0.properties文件, 配置内容如下:
c3p0.driverClass=com.mysql.jdbc.Driver
c3p0.jdbcUrl=jdbc:mysql:///jt_db?characterEncoding=utf-8
c3p0.user=root
c3p0.password=root
将连接还回连接池
conn. close();
这行代码可以理解是关连接,也可以理解为还连接。具体要看Connection对象从哪儿来!如果没有使用连接池,连接是我们自己创建的(即通过DriverManager.getConnection获取的),这样的连接对象是原生的没有被修改过的。这时调用conn. close方法就是关闭连接!
但如果连接对象是从连接池中获取的(即 conn=pool. getConnection()),这样的连接对象已经被连接池给改造了,调用conn. close方法就是还连接到连接池中!
事务:将-堆的SQL语句绑定在一起执行, 要么全都执行成功, 要么全都执行失败
(1)原子性:事务中所有的操作(或者要执行的sql语句)都是一个整体,不可以被分割,也就是说,事务中所有的sql语句要么全部被执行,要么全部执行失败,并不存在一部分成功一部分失败
(2)一致性:事务前后数据的中和是保持不变的或者是保持一致的
以转账为例,AB账户各有1000,转账之后AB各有一千,或者是两个人的总和是2000,
(3)隔离性:事物之间完全的隔离开来,一个事物看不到另外的一个事物中进行的操作,能看到是另外一个未开始之前的状态或者是已经结束的状态
(4)持久性:在食物开启后,以及在食物结束之前,对数据的操作,并不会真正的修改数据库中的操作,而是以日止的形式进行保存,如果最后事务失败了,就进行清除日志,相当于并没有发生什么,如果最后数据的运行成功,就对日志里面的数据永久的保存到硬盘之中
MySQL中默认的是自动的开启事务,爱动的记性提交事务,默认一条sql语句就是一个事务,如果想要让多条的sql语句是一个事务,我们就需要手动的记性设置开启事务
在mysql中开启事务:start transaction
在mysql中结束事务:提交事务:commit 回滚事务:rollback
以转账为例,A转账100给B,假设A减去100执行成功,但是B加上100执行失败,最后要进行回滚事务
事务的并发读问题:
脏读:在一个事务中读取到另外一个事务中并进行提交数据
不可以重复读:在一个事物中读取到另外一个已经提交的数据
幻读:在一张表格中对同一张表的进行两次读取两次结果不一致,因为其中一个在中间的过程加入了相应的插入或者是删除的中间操作的步骤
MySQL查询当期的事务隔离级别:
Select @@tx_isolation
(1)set tx__ioslation="read-uncommitted"
安全性最差,容易出现脏读,不可重复读,幻觉读,但是性能最高
(2)set tx__ioslation="read-committed"
安全性一般,可以防止出现脏读,但是容易不可重复读,幻觉读
(3)set tx__ioslation="read-repeatable-read"
安全性较好,可以防止脏读,不可重复读,但是容易出现幻觉读
(3)set tx__ioslation="read-serialiable-read"
安全性最好,可以防止一切事务的错误的并发,但是他的性能是最差的
事务的创建和运行,进行手动的进行提交事务:
package com.tedu.c3p0;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import com.tedu.util.JDBCUtil;
public class testTransation {
public static void main(String[] args){
Connection connection=null;
Statement statement=null;
ResultSet resultSet=null;
ComboPooledDataSource comboPooledDataSource=new ComboPooledDataSource();
try {
connection= comboPooledDataSource.getConnection();
//设置取消自动提交事务(改为手动),里面有一个值就是选择手动的提交还是进行自动的提交
connection.setAutoCommit(false);
statement= connection.createStatement();
//转账的事务,A转给B 100,A变成900,B变成1100
String sql1="update acc set money=money-100 where name='A'";
statement.executeUpdate(sql1);
String sql2="update acc set money=money+100 where name='B'";
statement.executeUpdate(sql2);
//记性手动的提交事务
connection.commit();
System.out.println("事务正常进行,成功");
} catch (Exception e) {
e.printStackTrace();
//出现异常进行事务的回滚的操作
try {
connection.rollback();
} catch (SQLException e1) {
System.out.println("回滚事务执行出错");
e1.printStackTrace();
}
}finally {
JDBCUtil.close(connection, statement, resultSet);
}
}
}