技术的迭代紧跟着时代
基石是一切开始的开始
——Lungcen
目录
JDBC的简介
API的详解
DriveManger(驱动管理类)
Connection(数据库连接对象)
Statement(执行SQL语句方法)
ResultSet(结果集对象)
PreparedStatement (预编译执行)
数据库连接池
JDBC(Java DataBase Connectivity)是Java语言操作的数据库的一套API,也是一个接口
利用Java代码去操作数据库时,操作不同的关系型数据库,需要不同的对应数据库的代码,所以说,没有JDBC之前,同一套Java代码,不能操作不同的数据库。后来利用一套标准的接口(JDBC)来实现上述的不匹配问题。
怎么实现的呢?每个数据库的厂商都来实现JDBC接口(又称为驱动)。若使用mysql数据库那就导入mysql驱动,同理,若使用Oracle数据库那就导入Oracle驱动。改变数据库的时候就直接更换驱动就行,就不用更换java的代码。
JDBC的步骤:
第一步:导入对应数据库的驱动jar包
第二步:注册驱动 说明使用的驱动版本
第三步:获取数据库的连接
第四步:编写执行的SQL语句
第五步:获取执行SQL语句的对象
第六步:执行SQl语句
第七步:处理执行SQL语句所返回的结果
第八步:释放前面创建的资源
需要注意的是:
1、高版本的驱动必须设置useSSL和时区属性,低版本的不需要,可以写成(jdbc:mysql://localhost:3306/first)
2、执行SQL语句后返回的结果有两种,一种是返回受影响的行数(删除、修改、分页……),一种是返回所查询到结果(查询)
3、最后记得要释放资源
//注册驱动 Class.forName("com.mysql.jdbc.Driver"); //获取连接对象 String url = "jdbc:mysql://localhost:3306/first?useSSL=false &serverTimezone=Asia/Shanghai"; String username = "root"; String password = "12345"; Connection conn = DriverManager.getConnection(url, username, password); //编写SQL语句 String sql = "uptate student set age = 18 where id = 1212";
//获取执行sql语句对象 Statement stat = conn.createStatement(); //执行sql语句 int i = stat.executeUpdate(sql); //处理结果 System.out.println(i); //释放创建的资源 stat.close(); conn.close();
可以 注册驱动 和 获取数据库的连接
既然DriveManger可以注册驱动,那上面为啥使用这个呢? Class.forName("com.mysql.jdbc.Driver")。
这是因为在Driver中有一个静态代码块就在执行DriverManger.registerDriver(new Driver())
所以使用 Class.forName("com.mysql.jdbc.Driver")来注册驱动。
不过在5之后的版本中可以不在代码中写这句,在对应的jar包中有着对应的代码
获取数据库的连接,需要 连接的路径、数据库用户名、数据库密码
对于连接路径url = "jdbc:mysql://127.0.0.1:3306/first" 。要是是本地服务器,可以将ip地址替换为localhost。如下url = "jdbc:mysql://localhost:3306/first"
如果连接的是本机的mysql服务器,同时默认的端口是3306,可简写为url = "jdbc:mysql:///first",中间的ip地址和端口号省略
以目前这样的连接方式并不安全,所以idea会出现一个警告提示来想要你使用SSL的连接方式 。配置参数 useSSL = false 参数来禁用安全连接提示,解决这个警告提示
url = "jdbc:mysql://localhost:3306/first?useSSL=false"
可以 获取执行SQL的对象 和 管理事务
获取执行SQl的对象有三种方式:
1、普通执行SQl对象: Statement createStatement()
2、预编译SQL执行对象: PreparedStatement prepareStatement(sql)
3、执行存储过程的SQL执行对象: CallableStatement prepareCall(sql)
事务管理的作用:举个例子,假如 A 和 B 交易,A 扫微信给了 B 100块,那么 A 的微信余额就减少100块, B 的微信余额就加上100 。如果事务不出错,那么一切正常,如果在 B 的微信余额加上100块时出现错误,但是前一步 A 的微信余额已经减少100块了,这样运行结束后就会导致总金额出错,莫名其妙的少了100块。
进行事务管理后,只要运行时出错,那么就直接回滚事务,回到执行SQL语句之前。
MYSQL的事务管理有三个(MYSQL默认是自动提交事务的)
开启事务、BEGIN;
提交事务、COMMIT;
回滚事务、ROLLBACK;
我们使用JDBC就是利用Java来操作数据库,所以JDBC的Connection接口中同样定义了三个一样功能的事务管理,只不过在JDBC中别写成了方法
开启事务、setAutoCommit(boolean autoCommit)
提交事务、commit();
回滚事务、rollback();
在执行SQL语句之前 开启事务 ,在执行成功处理结果后 提交事务
那如何回滚事务?那就要理解为啥要回滚事务,肯定是因为SQL语句执行的时候出现问题,那么不就可以使用java自带的异常管理机制(try—catch)在catch中回滚事务
try { //开启事务 conn.setAutoCommit(false); int i1 = stat.executeUpdate(sql1); int i2 = stat.executeUpdate(sql2); System.out.println(i1); System.out.println(i2); //提交事务 conn.commit(); } catch (Exception e) { //回滚事务 conn.rollback(); e.printStackTrace(); }
可以 执行SQL语句
执行SQL 后会获得两种结果:
一种是:executeUptate(sql),执行DML、DDL语句,
DQL语句(数据的增删改操作)的返回值是DML语句影响的行数
DDL语句(表和库的增删改查操作)的返回值 有可能是0,但是语句是执行的(例如:删除一个数据库,语句成功执行对于的数据库被删除,但是返回的值是 0 )
一种是:executeQuery(sql),执行DQL语句,
DQL语句(数据的查操作)的返回值是ResultSet结果集对象(这个对象封装了DQL语句的结果)
封装DQL查询语句的结果,那如何从ResultSet中获取查询结果呢?
boolean next():
1、可以将光标从当前位置向前移动一位。
2、判断当前行是否有数据
xxx getXxx(参数):
1、获取数据
2、xxx:数据类型(字符串、整形……)
3、参数分两种:一种是列的编号,从1开始;一种是列的名称
//注册驱动
Class.forName("com.mysql.jdbc.Driver");
//获取连接对象
String url = "jdbc:mysql://localhost:3306/first?useSSL=false
&serverTimezone=Asia/Shanghai";
String username = "root";
String password = "12345";
Connection conn = DriverManager.getConnection(url, username, password);
//编写sql语句,平且执行
String sql = "select * from account";
Statement stat = conn.createStatement();
ResultSet rs = stat.executeQuery(sql);
//遍历结果集,输出内容
while (rs.next()){
int id = rs.getInt(1);//也可以是rs。getInt("id"),是你的数据库中表的列名
String name = rs.getString(2);
System.out.println(id);
System.out.println(name);
}
//释放资源
rs.close();
stat.close();
conn.close();
这种方式所获取的内容是散落的,不便于展示在页面上,所以利用Java对象存放数据,再将这些Java对象放到集合中。
创建一个用来存放数据的类,(Alt + Ins)组合键 可以快速添加set、 get、 toString、 构造方法(无参与有参)
需要注意的是,按照规范和便捷, 用来存放数据的类的对应属性名字应该和数据库的列名一致,这样更加容易识别编写的代码
public class Account{ private int id; private String name; public Account(int id, String name) { this.id = id; this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Account{" + "id=" + id + ", name='" + name + '\'' + '}'; } }
修改遍历结果集的代码,将获得数据存放到对象中,再将对象放到集合中,这样所获得的数据就不要是散乱的,而是整整齐齐的放到集合中
//创建集合用来放对象 List
list = new ArrayList<>(); //遍历结果集,输出内容 while (rs.next()){ //实例对象 Account account = new Account(); //获取数据 int id = rs.getInt(1); String name = rs.getString(2); //将数据存放到对象中 account.setId(id); account.setName(name); //将对象添加到集合中 list.add(account); }
可以 预编译执行SQL语句并执行,用来防止SQL注入问题的手段
SQL注入:通过操作输入来修改事先定义好的SQL语句,以此达到执行代码对服务器进行攻击的一种方法。
简单来说就是:在以前,拿到从前端界面获取的用户名和密码,是直接拼接字符串来组成SQL语句去执行的。例如(String sql = "select * from user where username =' " + name + " ' and password = ' " + pwd + " ' "),其中name 和 pwd 是所获取的用户名和密码。所以SQL注入就是,name随便填(假如填 iwruqeoi),pwd填 ' or '1' = '1
这样的话,所执行的 sql 就会变成:select * from user where username ='iwruqeoi' and password = ' ' or '1' = '1',这个SQL语句的最后一段是or ’1‘ = ’1‘,是恒为真,故返回值为真。
所以为了防止SQL注入,现在基本上都是使用预编译来执行SQL语句,它会先执行SQL语句,自动空出?位置的值,后面再把?对应的值给赋值进去。
这样就把用户名和密码当成一个值,会对一些特殊符号进行转义,而不是像拼接字符串那样,会把密码的字段当成SQL语句的一部分来执行。
Class.forName("com.mysql.jdbc.Driver"); String url = "jdbc:mysql://localhost:3306/first?useSSL=false &serverTimezone=Asia/Shanghai"; String password = "12345"; Connection conn = DriverManager.getConnection(url, username, password); //假设获取的用户名和密码 String name = "123123"; String pwd = "123123"; //编写sql语句,平且执行 String sql = "select * from account where username = ? and password = ?"; //获取pstmt对象 PreparedStatement pstmt = conn.prepareStatement(sql);
这里处理来处理SQL语句中的?号
//设置?的值 pstmt.setString(1, name); pstmt.setString(2, pwd); //返回的结果集 ResultSet rs = pstmt.executeQuery(); //遍历结果集,输出内容 while (rs.next()){ int id = rs.getInt(1); String names = rs.getString(2); } rs.close(); pstmt.close(); conn.close(); }
PreparedStatement (预编译执行),可以 预编译执行SQL语句, 那么上面的语句是否是通过预编译的方式来执行的呢?
我的答案是 “并没有”,因为一般情况下预编译是默认关闭的,所以需要再url后面再加上useServerPrepStmts = true。那问题来了,既然没有开启预编译那为啥上面的也可以防止SQl注入,那就需要了解预编译是啥了!
预编译:我们编写的java代码传入到mysql中去,需要 先检查SQL语法、再编译SQL,最后再执行SQL 。一般情况下都是传入一次就完整的执行一次,而预编译是将SQL语句运行到编译SQL这一步,只要将?的值重新赋值后就执行SQL,这就是预编译。
所以说预编译只是一个过程,解决SQL注入的是转义一些特殊字符!!
是一个容器,负责分配、管理数据库连接
在正常情况下,每一次与数据库进行连接就需要创建一个“连接”,当结束后就释放掉“连接”,无法做到资源复用,而且在创建和释放“连接”的过程是耗时间耗资源的。
于是乎就弄了一个容器,可以用来放“连接”,并且事先提前创建一些“连接“放到容器中,等到使用时就直接从容器中拿出来,不用的时候就放进去,而不是需要使用时创建、不用的时候就释放掉。
假如容器中只有三个”连接“,正好来了三个人拿走了容器中的”连接“,此时来了第四个人,那咋办呢?
数据库连接池就会对那三个人进行判断是否在用”连接“,不用的话就直接拿回”连接“给第四个人(所以有些界面,你长时间没响应就会自动退掉)
那么要是都在使用呢?那就让第四个人等待还是申请新的”连接“呢?对于这个问题需要看你设置的连接池最大值,如果没有达到最大值,就新建一个连接,如果已经达到最大值,就等待一定的时间。
数据库连接池的实现:官方标准接口——(DataSource【获取连接的功能】)利用getConnection()方法实现
常见的数据库连接池有:DBCP、C3P0、Druid
Druid:Druid连接池是阿里巴巴开源的数据库连接池、功能强大、性能优秀
使用步骤;
1、导入jar包(druid-1.1.12.jar)
2、定义配置文件(里面是一些数据库的信息和数据池的配置)
driverClassName=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/db1 username=Lungcen //数据库用户名 password=123456 //数据库密码 initialSize=5 //初始化连接数量 maxActive=10 //最大连接数 maxWait=3000 //最大等待时间
3、加载配置文件
4、获取连接池对象
5、获取连接
public static void main(String[] args) throws Exception { //获取配置文件的方法 Properties prop = new Properties(); //加载配置文件 prop.load(new FileInputStream("src/druid.properties")); //获取连接池对象 DataSource dataSource = DruidDataSourceFactory.createDataSource(prop); //获取连接 Connection conn = dataSource.getConnection(); //输出连接 System.out.println(conn); }
技术的迭代紧跟着时代
基石是一切开始的开始
——Lungcen