赞
踩
由于每一个数据库的底层实现都是不同的,有不同的实现方法,使用起来会比较麻烦。于是就有了SUN公司开发的这一套jdbc的问世。
简而言之,jbdc(Java连接数据库)的本质就是接口,让程序员不用关心数据库的具体实现类,只需要针对该接口进行开发即可,接口存在的目的就是解耦合,提高程序的可拓展性。这里就是面向抽象的编程方法了。
入门真就这六步(这里以MySQL数据库为例)
我们先从官网下载相应的驱动文件,笔者使用的是mysql-connector-j-8.4.0的版本 MySQL :: MySQL Community Downloadshttps://dev.mysql.com/downloads/
里面最核心的就是jar包中的.class文件
下载好后将其目录添加到系统环境变量classpath中
或
使用IDEA,在项目结构中添加JAR或目录,选择文件并将其导入即可
1、注册驱动
- Driver driver = new com.mysql.cj.jdbc.Driver();
- DriverManager.registerDriver(driver);
2、获取连接
- String url = "jdbc:mysql://127.0.0.1:3306/abc";
- String user = "username";
- String password = "123456";
- connection = DriverManager.getConnection(url,user,password);
3、创建数据库操作对象
statement = connection.createStatement();
4、执行SQL
- String sql = "insert into test(name) values('root')";
- int count = statement.executeUpdate(sql);
statement.executeUpdate(sql)方法会返回一个int类型的数据,表示受该次SQL语句影响的行数,若返回为-1则表示SQL出错了。
5、处理对象结果集
- String sql = "select name from test";
- rs = statement.executeQuery(sql);
- //处理查询结果集
- boolean hasNext = rs.next();
- while (hasNext){
- //取出数据库中的内容
- System.out.println(rs.getString(1));
- hasNext = rs.next();
- }
6、释放资源
注意释放资源的代码要写在finally代码块中,以保证资源的释放。然后还是老规矩,由内到外向外释放,即先创建的后释放。
另外,为了避免释放过程出现异常导致其他资源释放不了的情况出现,将资源释放的代码分开try—catch。
- finally {
- //释放资源,保证资源释放,老规矩,从里到外
- try{
- if (statement!=null){
- statement.close();
- }
- }catch (Exception e){
- e.printStackTrace();
- }
- try {
- if (connection!=null){
- connection.close();
- }
- }catch (Exception e){
- e.printStackTrace();
- }
- }

诶,这样写太麻烦了,有没有改进的办法?
查看到需要释放的Connection和Statement两个类都实现了AutoCloseable接口!!!
那我们就可以使用try—with—resource语句让系统自动释放了
修改后代码如下
- public class ConnectionTest {
- public static void main(String[] args) throws Exception {
- String url = "jdbc:mysql://127.0.0.1:3306/abc";
- String user = "username";
- String password = "123456";
- try (
- Connection connection = DriverManager.getConnection(url, user, password);
- Statement statement = connection.createStatement();
- ) {
- //注册驱动
- Driver driver = new com.mysql.cj.jdbc.Driver();
- DriverManager.registerDriver(driver);
- //获取连接
- //url:统一资源定位符 (网络中某个资源的绝对路径)
- //url包括协议、ip、端口号、资源名
- //jdbc:mysql是协议,127.0.0.1是ip,3306是端口号,rental是资源名
- //什么是通信协议?是在通信前定好的数据传送格式。数据包具体怎么传数据,格式提前定好。
- System.out.println("数据库连接对象:" + connection);
- //获取数据库操作对象,创建该对象来发送SQL语句
- //执行SQL
- String sql = "insert into test(name) values('root')";
- int count = statement.executeUpdate(sql);
- System.out.println(count);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }

1、用户输入的数据中含有符合SQL语法的语句,会导致原SQL语句的语义被歪曲
2、采用了字符串拼接的方式来构造SQL语句
3、拼接完SQL语句之后再一起编译发送
下面是会发生SQL注入现象的关于用户登录的例子:
- Scanner scanner = new Scanner(System.in);
- System.out.println("please input your username:");
- String name = scanner.nextLine();
- System.out.println("please input your password:");
- String pwd = scanner.nextLine();
-
- String sql = "select * from login_test where name='" + name + "'and pwd ='" + pwd + "'";
- try {
- if (DUtil.select_D(sql).next()){
- System.out.println("login!!!");
- }
- else System.out.println("error");
- } catch (SQLException e) {
- throw new RuntimeException(e);
- }
上述的例子中可以看到,用户通过SQL注入成功破除了登录的验证
使用占位符,利用PreparedStatement来对SQL语句进行预编译,就算用户输入了能够扭曲SQL语义的数据也不影响编译过后的SQL语句的语义。
举例:
- public class Login {
- public static void main(String[] args) throws ClassNotFoundException {
- ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
- String url = bundle.getString("url");
- String username = bundle.getString("username");
- String password = bundle.getString("password");
- Class.forName("com.mysql.cj.jdbc.Driver");
- PreparedStatement ps = null;
- ResultSet rs = null;
- try (
- Connection connection = DriverManager.getConnection(url, username, password);
- ) {
- Scanner scanner = new Scanner(System.in);
- System.out.println("please input your username:");
- String name = scanner.nextLine();
- System.out.println("please input your password:");
- String pwd = scanner.nextLine();
-
- String sql = "select * from login_test where name= ? and pwd = ?";
- ps = connection.prepareStatement(sql);
- ps.setString(1,name);
- ps.setString(2,pwd);
- rs = ps.executeQuery();
- if (rs.next()){
- System.out.println("login!!!");
- }
- else System.out.println("error");
- } catch (Exception e) {
- e.printStackTrace();
- }finally {
- if (rs!=null){
- try {
- rs.close();
- } catch (SQLException e) {
- e.printStackTrace();
- }
- }
- if (ps!=null){
- try {
- ps.close();
- }catch (Exception e){
- e.printStackTrace();
- }
- }
- }

修改代码后,SQL注入失效。最关键的一点就是SQL语句预编译了,没有给注入的机会。
核心公式: 第pageNo页:limit(pageNo-1)*pageSize, pageSize
从第X条数据开始,往后显示Y条数据
实例代码:
- String sql = "select name from test limit ?,?";
- ps = conn.prepareStatement(sql);
- ps.setInt(1,(pageNo-1)*pageSize);
- ps.setInt(2,pageSize);
- rs = ps.executeQuery();
核心:like……
直接看示例:
- String sql = "select name from test where name like ?";
- ps = conn.prepareStatement(sql);
- ps.setString(1,Ename);
使用批处理可以降低处理时间,把指令打包到一起然后执行一次(磁盘IO一次),减少java程序与系统数据库的交互次数
如何启用?
在URL后面添加参数:rewriteBatchedStatements=true
使用示例如下:
- public static void main(String[] args) throws ClassNotFoundException {
- long start = System.currentTimeMillis();
- ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
- String url = bundle.getString("url");
- String username = bundle.getString("username");
- String password = bundle.getString("password");
- Class.forName("com.mysql.cj.jdbc.Driver");
- Connection connection = null;
- PreparedStatement ps = null;
- try {
- int count = 0;
- connection = DriverManager.getConnection(url, username, password);
- String name = "kk";
- String pwd = "123456";
- String sql = "insert into login_test(name,pwd) values(?,?)";
- ps = connection.prepareStatement(sql);
- for (int i = 0; i < 3000; i++) {
- ps.setString(1, name);
- ps.setString(2, pwd);
- //将指令打包!!!
- ps.addBatch();
- if (i % 500 == 0) {
- count += ps.executeBatch().length;
- }
- }
- count += ps.executeBatch().length;
- long end = System.currentTimeMillis();
- System.out.println("exe " + count + "orders used time:" + (end-start));

打包后可以执行3000条insert语句时间缩短至500毫秒左右
未打包为:
效果拔群!!!
JDBC事务默认是自动提交的,只要执行一条DML语句则自动提交一次。(很危险的喔)
若两条DML语句有关联,如果执行过程当中出了问题就很容易导致数据出错。
一般开发中,我们都是需要用多条DML语句来实现一个业务的。
所以要添加事务控制来保障业务安全。
三步走:1、开启事务 2、手动提交事务 3、手动回滚事务
1、开启事务,即将JDBC的自动提交事务关闭 setAutoCommit
- //开启事务,将JDBC的自动事务提交修改为false
- DUtil.connection.setAutoCommit(false);
2、手动提交事务,提交事务的时机在需要一同提交的事务之后 commit
- //事务一
- String sql1 = "update login_test set name = ? where id = 1";
- PreparedStatement ps1 = DUtil.connection.prepareStatement(sql1);
- ps1.setString(1,"abc");
- ps1.executeUpdate();
-
- //模拟异常发生
- String s = null;
- s.toString();
-
- //事务二
- String sql2 = "update login_test set name = ? where id = 1";
- PreparedStatement ps2 = DUtil.connection.prepareStatement(sql2);
- ps1.setString(1,"kevin");
- ps1.executeUpdate();
-
- //提交事务,事务结束
- DUtil.connection.commit();

3、手动回滚业务,这里用使用到异常处理机制,把回滚的代码写到catch代码块中。当其中有事务发生异常,执行回滚,结束事务。
- catch (Exception e){
- //若有异常发生,回滚事务。然后事务结束
- try {
- DUtil.connection.rollback();
- } catch (SQLException ex) {
- throw new RuntimeException(ex);
- }
- e.printStackTrace();
事务有四大特性: 原子性、一致性、隔离性、持久性
在jdbc中使用java代码设置事务的隔离级别:
DUtil.connection.setTransactionIsolation(int类型的隔离级别);
隔离级别有:读未提交、读提交、可重复读、串行化
三大现象:脏读、不可重复读、幻读
隔离级别从低到高:读未提交<读提交<可重复读<串行化(不支持并发啦)
脏读指当一个事务正在修改某些数据时,另一个事务在未提交的情况下读取这些数据。这就可能导致读取到还未提交的脏数据。
两个事务读取同一数据,但第二个事务在第一个事务读取之后修改了该数据,导致第一个事务再次读取该数据时获取到的数据与第一次读取的数据不一致。
指一个正在执行的事务读取到了其他事务提交后新增的数据。
悲观锁是行级锁,将数据行锁住,事务必须排队执行,不允许并发。乐观锁是多线程并发,实现乐观锁的关键是加入版本号,各事务操作后要修改资源的版本号,如果读取到的版本号和执行后读到版本号一致即可提交,不一致则回滚。
for update开启行级锁
本质是一种缓存技术,池子嘛,拿来存数据的。所有连接池都实现了javax.sql.DataSource这个接口。
如果不创建连接池的话?
Connection是一个重量级的对象,建立两个进程之间的通信消耗的资源比较大,数据库操作大部分时间都是耗费在连接对象创建上。1、每次请求都创建对象,太慢了,且浪费资源 ;2、如果请求太多,不做连接数量上的限制的话数据库服务器会崩掉。
1、初始化连接数 initialSize
2、最大连接数 maxActive
3、最小空闲连接数量 minIdle
4、最大空闲连接数量 maxIdle
5、最大等待时间 maxWait
6、连接有效性检查 testOnBorrow、testOnReturn
7、连接的driver、url、user、password
Druid、HikariCP
Druid是阿里巴巴开源的一个数据库连接池实现,基于JDBC规范,为数据库连接提供高性能、可伸缩性的解决方案。
优点:
HikariCP是一个高性能的JDBC连接池,相较于其他连接池,在速度、内存使用和特性方面都有显著的优势。
优点:
感谢大家阅读
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。